1/* 2 * ip_conntrack_pptp.c - Version 1.9 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-2003 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 * 21 * TODO: - finish support for multiple calls within one session 22 * (needs expect reservations in newnat) 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_timeout_related() 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 * 36 */ 37 38#include <linux/config.h> 39#include <linux/module.h> 40#include <linux/netfilter.h> 41#include <linux/ip.h> 42#include <net/checksum.h> 43#include <net/tcp.h> 44 45#include <linux/netfilter_ipv4/lockhelp.h> 46#include <linux/netfilter_ipv4/ip_conntrack_helper.h> 47#include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h> 48#include <linux/netfilter_ipv4/ip_conntrack_pptp.h> 49 50#define IP_CT_PPTP_VERSION "1.9" 51 52MODULE_LICENSE("GPL"); 53MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>"); 54MODULE_DESCRIPTION("Netfilter connection tracking helper module for PPTP"); 55 56DECLARE_LOCK(ip_pptp_lock); 57 58#if 0 59#include "ip_conntrack_pptp_priv.h" 60#define DEBUGP(format, args...) printk(KERN_DEBUG __FILE__ ":" __FUNCTION__ \ 61 ": " format, ## args) 62#else 63#define DEBUGP(format, args...) 64#endif 65 66#define SECS *HZ 67#define MINS * 60 SECS 68#define HOURS * 60 MINS 69#define DAYS * 24 HOURS 70 71#define PPTP_GRE_TIMEOUT (10 MINS) 72#define PPTP_GRE_STREAM_TIMEOUT (5 DAYS) 73 74static int pptp_expectfn(struct ip_conntrack *ct) 75{ 76 struct ip_conntrack *master; 77 struct ip_conntrack_expect *exp; 78 79 DEBUGP("increasing timeouts\n"); 80 /* increase timeout of GRE data channel conntrack entry */ 81 ct->proto.gre.timeout = PPTP_GRE_TIMEOUT; 82 ct->proto.gre.stream_timeout = PPTP_GRE_STREAM_TIMEOUT; 83 84 master = master_ct(ct); 85 if (!master) { 86 DEBUGP(" no master!!!\n"); 87 return 0; 88 } 89 90 exp = ct->master; 91 if (!exp) { 92 DEBUGP("no expectation!!\n"); 93 return 0; 94 } 95 96 DEBUGP("completing tuples with ct info\n"); 97 /* we can do this, since we're unconfirmed */ 98 if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.gre.key == 99 htonl(master->help.ct_pptp_info.pac_call_id)) { 100 /* assume PNS->PAC */ 101 ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key = 102 htonl(master->help.ct_pptp_info.pns_call_id); 103 ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key = 104 htonl(master->help.ct_pptp_info.pns_call_id); 105 } else { 106 /* assume PAC->PNS */ 107 ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key = 108 htonl(master->help.ct_pptp_info.pac_call_id); 109 ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key = 110 htonl(master->help.ct_pptp_info.pac_call_id); 111 } 112 113 /* delete other expectation */ 114 if (exp->expected_list.next != &exp->expected_list) { 115 struct ip_conntrack_expect *other_exp; 116 struct list_head *cur_item, *next; 117 118 for (cur_item = master->sibling_list.next; 119 cur_item != &master->sibling_list; cur_item = next) { 120 next = cur_item->next; 121 other_exp = list_entry(cur_item, 122 struct ip_conntrack_expect, 123 expected_list); 124 /* remove only if occurred at same sequence number */ 125 if (other_exp != exp && other_exp->seq == exp->seq) { 126 DEBUGP("unexpecting other direction\n"); 127 ip_ct_gre_keymap_destroy(other_exp); 128 ip_conntrack_unexpect_related(other_exp); 129 } 130 } 131 } 132 133 return 0; 134} 135 136/* timeout GRE data connections */ 137static int pptp_timeout_related(struct ip_conntrack *ct) 138{ 139 struct list_head *cur_item, *next; 140 struct ip_conntrack_expect *exp; 141 142 /* FIXME: do we have to lock something ? */ 143 for (cur_item = ct->sibling_list.next; 144 cur_item != &ct->sibling_list; cur_item = next) { 145 next = cur_item->next; 146 exp = list_entry(cur_item, struct ip_conntrack_expect, 147 expected_list); 148 149 ip_ct_gre_keymap_destroy(exp); 150 if (!exp->sibling) { 151 ip_conntrack_unexpect_related(exp); 152 continue; 153 } 154 155 DEBUGP("setting timeout of conntrack %p to 0\n", 156 exp->sibling); 157 exp->sibling->proto.gre.timeout = 0; 158 exp->sibling->proto.gre.stream_timeout = 0; 159 ip_ct_refresh(exp->sibling, 0); 160 } 161 162 return 0; 163} 164 165/* expect GRE connections (PNS->PAC and PAC->PNS direction) */ 166static inline int 167exp_gre(struct ip_conntrack *master, 168 u_int32_t seq, 169 u_int16_t callid, 170 u_int16_t peer_callid) 171{ 172 struct ip_conntrack_expect exp; 173 struct ip_conntrack_tuple inv_tuple; 174 175 memset(&exp, 0, sizeof(exp)); 176 /* tuple in original direction, PNS->PAC */ 177 exp.tuple.src.ip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip; 178 exp.tuple.src.u.gre.key = htonl(ntohs(peer_callid)); 179 exp.tuple.dst.ip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip; 180 exp.tuple.dst.u.gre.key = htonl(ntohs(callid)); 181 exp.tuple.dst.u.gre.protocol = __constant_htons(GRE_PROTOCOL_PPTP); 182 exp.tuple.dst.u.gre.version = GRE_VERSION_PPTP; 183 exp.tuple.dst.protonum = IPPROTO_GRE; 184 185 exp.mask.src.ip = 0xffffffff; 186 exp.mask.src.u.all = 0; 187 exp.mask.dst.u.all = 0; 188 exp.mask.dst.u.gre.key = 0xffffffff; 189 exp.mask.dst.u.gre.version = 0xff; 190 exp.mask.dst.u.gre.protocol = 0xffff; 191 exp.mask.dst.ip = 0xffffffff; 192 exp.mask.dst.protonum = 0xffff; 193 194 exp.seq = seq; 195 exp.expectfn = pptp_expectfn; 196 197 exp.help.exp_pptp_info.pac_call_id = ntohs(callid); 198 exp.help.exp_pptp_info.pns_call_id = ntohs(peer_callid); 199 200 DEBUGP("calling expect_related "); 201 DUMP_TUPLE_RAW(&exp.tuple); 202 203 /* Add GRE keymap entries */ 204 if (ip_ct_gre_keymap_add(&exp, &exp.tuple, 0) != 0) 205 return 1; 206 207 invert_tuplepr(&inv_tuple, &exp.tuple); 208 if (ip_ct_gre_keymap_add(&exp, &inv_tuple, 1) != 0) { 209 ip_ct_gre_keymap_destroy(&exp); 210 return 1; 211 } 212 213 if (ip_conntrack_expect_related(master, &exp) != 0) { 214 ip_ct_gre_keymap_destroy(&exp); 215 DEBUGP("cannot expect_related()\n"); 216 return 1; 217 } 218 219 /* tuple in reply direction, PAC->PNS */ 220 exp.tuple.src.ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip; 221 exp.tuple.src.u.gre.key = htonl(ntohs(callid)); 222 exp.tuple.dst.ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip; 223 exp.tuple.dst.u.gre.key = htonl(ntohs(peer_callid)); 224 225 DEBUGP("calling expect_related "); 226 DUMP_TUPLE_RAW(&exp.tuple); 227 228 /* Add GRE keymap entries */ 229 ip_ct_gre_keymap_add(&exp, &exp.tuple, 0); 230 invert_tuplepr(&inv_tuple, &exp.tuple); 231 ip_ct_gre_keymap_add(&exp, &inv_tuple, 1); 232 /* FIXME: cannot handle error correctly, since we need to free 233 * the above keymap :( */ 234 235 if (ip_conntrack_expect_related(master, &exp) != 0) { 236 /* free the second pair of keypmaps */ 237 ip_ct_gre_keymap_destroy(&exp); 238 DEBUGP("cannot expect_related():\n"); 239 return 1; 240 } 241 242 return 0; 243} 244 245static inline int 246pptp_inbound_pkt(struct tcphdr *tcph, 247 struct pptp_pkt_hdr *pptph, 248 size_t datalen, 249 struct ip_conntrack *ct, 250 enum ip_conntrack_info ctinfo) 251{ 252 struct PptpControlHeader *ctlh; 253 union pptp_ctrl_union pptpReq; 254 255 struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info; 256 u_int16_t msg, *cid, *pcid; 257 u_int32_t seq; 258 259 ctlh = (struct PptpControlHeader *) 260 ((char *) pptph + sizeof(struct pptp_pkt_hdr)); 261 pptpReq.rawreq = (void *) 262 ((char *) ctlh + sizeof(struct PptpControlHeader)); 263 264 msg = ntohs(ctlh->messageType); 265 DEBUGP("inbound control message %s\n", strMName[msg]); 266 267 switch (msg) { 268 case PPTP_START_SESSION_REPLY: 269 /* server confirms new control session */ 270 if (info->sstate < PPTP_SESSION_REQUESTED) { 271 DEBUGP("%s without START_SESS_REQUEST\n", 272 strMName[msg]); 273 break; 274 } 275 if (pptpReq.srep->resultCode == PPTP_START_OK) 276 info->sstate = PPTP_SESSION_CONFIRMED; 277 else 278 info->sstate = PPTP_SESSION_ERROR; 279 break; 280 281 case PPTP_STOP_SESSION_REPLY: 282 /* server confirms end of control session */ 283 if (info->sstate > PPTP_SESSION_STOPREQ) { 284 DEBUGP("%s without STOP_SESS_REQUEST\n", 285 strMName[msg]); 286 break; 287 } 288 if (pptpReq.strep->resultCode == PPTP_STOP_OK) 289 info->sstate = PPTP_SESSION_NONE; 290 else 291 info->sstate = PPTP_SESSION_ERROR; 292 break; 293 294 case PPTP_OUT_CALL_REPLY: 295 /* server accepted call, we now expect GRE frames */ 296 if (info->sstate != PPTP_SESSION_CONFIRMED) { 297 DEBUGP("%s but no session\n", strMName[msg]); 298 break; 299 } 300 if (info->cstate != PPTP_CALL_OUT_REQ && 301 info->cstate != PPTP_CALL_OUT_CONF) { 302 DEBUGP("%s without OUTCALL_REQ\n", strMName[msg]); 303 break; 304 } 305 if (pptpReq.ocack->resultCode != PPTP_OUTCALL_CONNECT) { 306 info->cstate = PPTP_CALL_NONE; 307 break; 308 } 309 310 cid = &pptpReq.ocack->callID; 311 pcid = &pptpReq.ocack->peersCallID; 312 313 info->pac_call_id = ntohs(*cid); 314 315 if (htons(info->pns_call_id) != *pcid) { 316 DEBUGP("%s for unknown callid %u\n", 317 strMName[msg], ntohs(*pcid)); 318 break; 319 } 320 321 DEBUGP("%s, CID=%X, PCID=%X\n", strMName[msg], 322 ntohs(*cid), ntohs(*pcid)); 323 324 info->cstate = PPTP_CALL_OUT_CONF; 325 326 seq = ntohl(tcph->seq) + ((void *)pcid - (void *)pptph); 327 if (exp_gre(ct, seq, *cid, *pcid) != 0) 328 printk("ip_conntrack_pptp: error during exp_gre\n"); 329 break; 330 331 case PPTP_IN_CALL_REQUEST: 332 /* server tells us about incoming call request */ 333 if (info->sstate != PPTP_SESSION_CONFIRMED) { 334 DEBUGP("%s but no session\n", strMName[msg]); 335 break; 336 } 337 pcid = &pptpReq.icack->peersCallID; 338 DEBUGP("%s, PCID=%X\n", strMName[msg], ntohs(*pcid)); 339 info->cstate = PPTP_CALL_IN_REQ; 340 info->pac_call_id= ntohs(*pcid); 341 break; 342 343 case PPTP_IN_CALL_CONNECT: 344 /* server tells us about incoming call established */ 345 if (info->sstate != PPTP_SESSION_CONFIRMED) { 346 DEBUGP("%s but no session\n", strMName[msg]); 347 break; 348 } 349 if (info->sstate != PPTP_CALL_IN_REP 350 && info->sstate != PPTP_CALL_IN_CONF) { 351 DEBUGP("%s but never sent IN_CALL_REPLY\n", 352 strMName[msg]); 353 break; 354 } 355 356 pcid = &pptpReq.iccon->peersCallID; 357 cid = &info->pac_call_id; 358 359 if (info->pns_call_id != ntohs(*pcid)) { 360 DEBUGP("%s for unknown CallID %u\n", 361 strMName[msg], ntohs(*cid)); 362 break; 363 } 364 365 DEBUGP("%s, PCID=%X\n", strMName[msg], ntohs(*pcid)); 366 info->cstate = PPTP_CALL_IN_CONF; 367 368 /* we expect a GRE connection from PAC to PNS */ 369 seq = ntohl(tcph->seq) + ((void *)pcid - (void *)pptph); 370 if (exp_gre(ct, seq, *cid, *pcid) != 0) 371 printk("ip_conntrack_pptp: error during exp_gre\n"); 372 373 break; 374 375 case PPTP_CALL_DISCONNECT_NOTIFY: 376 /* server confirms disconnect */ 377 cid = &pptpReq.disc->callID; 378 DEBUGP("%s, CID=%X\n", strMName[msg], ntohs(*cid)); 379 info->cstate = PPTP_CALL_NONE; 380 381 /* untrack this call id, unexpect GRE packets */ 382 pptp_timeout_related(ct); 383 break; 384 385 case PPTP_WAN_ERROR_NOTIFY: 386 break; 387 388 case PPTP_ECHO_REQUEST: 389 case PPTP_ECHO_REPLY: 390 /* I don't have to explain these ;) */ 391 break; 392 default: 393 DEBUGP("invalid %s (TY=%d)\n", (msg <= PPTP_MSG_MAX) 394 ? strMName[msg]:strMName[0], msg); 395 break; 396 } 397 398 return NF_ACCEPT; 399 400} 401 402static inline int 403pptp_outbound_pkt(struct tcphdr *tcph, 404 struct pptp_pkt_hdr *pptph, 405 size_t datalen, 406 struct ip_conntrack *ct, 407 enum ip_conntrack_info ctinfo) 408{ 409 struct PptpControlHeader *ctlh; 410 union pptp_ctrl_union pptpReq; 411 struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info; 412 u_int16_t msg, *cid, *pcid; 413 414 ctlh = (struct PptpControlHeader *) ((void *) pptph + sizeof(*pptph)); 415 pptpReq.rawreq = (void *) ((void *) ctlh + sizeof(*ctlh)); 416 417 msg = ntohs(ctlh->messageType); 418 DEBUGP("outbound control message %s\n", strMName[msg]); 419 420 switch (msg) { 421 case PPTP_START_SESSION_REQUEST: 422 /* client requests for new control session */ 423 if (info->sstate != PPTP_SESSION_NONE) { 424 DEBUGP("%s but we already have one", 425 strMName[msg]); 426 } 427 info->sstate = PPTP_SESSION_REQUESTED; 428 break; 429 case PPTP_STOP_SESSION_REQUEST: 430 /* client requests end of control session */ 431 info->sstate = PPTP_SESSION_STOPREQ; 432 break; 433 434 case PPTP_OUT_CALL_REQUEST: 435 /* client initiating connection to server */ 436 if (info->sstate != PPTP_SESSION_CONFIRMED) { 437 DEBUGP("%s but no session\n", 438 strMName[msg]); 439 break; 440 } 441 info->cstate = PPTP_CALL_OUT_REQ; 442 /* track PNS call id */ 443 cid = &pptpReq.ocreq->callID; 444 DEBUGP("%s, CID=%X\n", strMName[msg], ntohs(*cid)); 445 info->pns_call_id = ntohs(*cid); 446 break; 447 case PPTP_IN_CALL_REPLY: 448 /* client answers incoming call */ 449 if (info->cstate != PPTP_CALL_IN_REQ 450 && info->cstate != PPTP_CALL_IN_REP) { 451 DEBUGP("%s without incall_req\n", 452 strMName[msg]); 453 break; 454 } 455 if (pptpReq.icack->resultCode != PPTP_INCALL_ACCEPT) { 456 info->cstate = PPTP_CALL_NONE; 457 break; 458 } 459 pcid = &pptpReq.icack->peersCallID; 460 if (info->pac_call_id != ntohs(*pcid)) { 461 DEBUGP("%s for unknown call %u\n", 462 strMName[msg], ntohs(*pcid)); 463 break; 464 } 465 DEBUGP("%s, CID=%X\n", strMName[msg], ntohs(*pcid)); 466 /* part two of the three-way handshake */ 467 info->cstate = PPTP_CALL_IN_REP; 468 info->pns_call_id = ntohs(pptpReq.icack->callID); 469 break; 470 471 case PPTP_CALL_CLEAR_REQUEST: 472 /* client requests hangup of call */ 473 if (info->sstate != PPTP_SESSION_CONFIRMED) { 474 DEBUGP("CLEAR_CALL but no session\n"); 475 break; 476 } 477 /* FUTURE: iterate over all calls and check if 478 * call ID is valid. We don't do this without newnat, 479 * because we only know about last call */ 480 info->cstate = PPTP_CALL_CLEAR_REQ; 481 break; 482 case PPTP_SET_LINK_INFO: 483 break; 484 case PPTP_ECHO_REQUEST: 485 case PPTP_ECHO_REPLY: 486 /* I don't have to explain these ;) */ 487 break; 488 default: 489 DEBUGP("invalid %s (TY=%d)\n", (msg <= PPTP_MSG_MAX)? 490 strMName[msg]:strMName[0], msg); 491 /* unknown: no need to create GRE masq table entry */ 492 break; 493 } 494 495 return NF_ACCEPT; 496} 497 498 499/* track caller id inside control connection, call expect_related */ 500static int 501conntrack_pptp_help(const struct iphdr *iph, size_t len, 502 struct ip_conntrack *ct, enum ip_conntrack_info ctinfo) 503 504{ 505 struct pptp_pkt_hdr *pptph; 506 507 struct tcphdr *tcph = (void *) iph + iph->ihl * 4; 508 u_int32_t tcplen = len - iph->ihl * 4; 509 u_int32_t datalen = tcplen - tcph->doff * 4; 510 void *datalimit; 511 int dir = CTINFO2DIR(ctinfo); 512 struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info; 513 514 int oldsstate, oldcstate; 515 int ret; 516 517 /* don't do any tracking before tcp handshake complete */ 518 if (ctinfo != IP_CT_ESTABLISHED 519 && ctinfo != IP_CT_ESTABLISHED+IP_CT_IS_REPLY) { 520 DEBUGP("ctinfo = %u, skipping\n", ctinfo); 521 return NF_ACCEPT; 522 } 523 524 /* not a complete TCP header? */ 525 if (tcplen < sizeof(struct tcphdr) || tcplen < tcph->doff * 4) { 526 DEBUGP("tcplen = %u\n", tcplen); 527 return NF_ACCEPT; 528 } 529 530 /* checksum invalid? */ 531 if (tcp_v4_check(tcph, tcplen, iph->saddr, iph->daddr, 532 csum_partial((char *) tcph, tcplen, 0))) { 533 printk(KERN_NOTICE __FILE__ ": bad csum\n"); 534 /* W2K PPTP server sends TCP packets with wrong checksum :(( */ 535 //return NF_ACCEPT; 536 } 537 538 if (tcph->fin || tcph->rst) { 539 DEBUGP("RST/FIN received, timeouting GRE\n"); 540 /* can't do this after real newnat */ 541 info->cstate = PPTP_CALL_NONE; 542 543 /* untrack this call id, unexpect GRE packets */ 544 pptp_timeout_related(ct); 545 } 546 547 548 pptph = (struct pptp_pkt_hdr *) ((void *) tcph + tcph->doff * 4); 549 datalimit = (void *) pptph + datalen; 550 551 /* not a full pptp packet header? */ 552 if ((void *) pptph+sizeof(*pptph) >= datalimit) { 553 DEBUGP("no full PPTP header, can't track\n"); 554 return NF_ACCEPT; 555 } 556 557 /* if it's not a control message we can't do anything with it */ 558 if (ntohs(pptph->packetType) != PPTP_PACKET_CONTROL || 559 ntohl(pptph->magicCookie) != PPTP_MAGIC_COOKIE) { 560 DEBUGP("not a control packet\n"); 561 return NF_ACCEPT; 562 } 563 564 oldsstate = info->sstate; 565 oldcstate = info->cstate; 566 567 LOCK_BH(&ip_pptp_lock); 568 569 /* FIXME: We just blindly assume that the control connection is always 570 * established from PNS->PAC. However, RFC makes no guarantee */ 571 if (dir == IP_CT_DIR_ORIGINAL) 572 /* client -> server (PNS -> PAC) */ 573 ret = pptp_outbound_pkt(tcph, pptph, datalen, ct, ctinfo); 574 else 575 /* server -> client (PAC -> PNS) */ 576 ret = pptp_inbound_pkt(tcph, pptph, datalen, ct, ctinfo); 577 DEBUGP("sstate: %d->%d, cstate: %d->%d\n", 578 oldsstate, info->sstate, oldcstate, info->cstate); 579 UNLOCK_BH(&ip_pptp_lock); 580 581 return ret; 582} 583 584/* control protocol helper */ 585static struct ip_conntrack_helper pptp = { 586 .list = { NULL, NULL }, 587 .name = "pptp", 588 .flags = IP_CT_HELPER_F_REUSE_EXPECT, 589 .me = THIS_MODULE, 590 .max_expected = 2, 591 .timeout = 0, 592 .tuple = { .src = { .ip = 0, 593 .u = { .tcp = { .port = 594 __constant_htons(PPTP_CONTROL_PORT) } } 595 }, 596 .dst = { .ip = 0, 597 .u = { .all = 0 }, 598 .protonum = IPPROTO_TCP 599 } 600 }, 601 .mask = { .src = { .ip = 0, 602 .u = { .tcp = { .port = 0xffff } } 603 }, 604 .dst = { .ip = 0, 605 .u = { .all = 0 }, 606 .protonum = 0xffff 607 } 608 }, 609 .help = conntrack_pptp_help 610}; 611 612/* ip_conntrack_pptp initialization */ 613static int __init init(void) 614{ 615 int retcode; 616 617 DEBUGP(__FILE__ ": registering helper\n"); 618 if ((retcode = ip_conntrack_helper_register(&pptp))) { 619 printk(KERN_ERR "Unable to register conntrack application " 620 "helper for pptp: %d\n", retcode); 621 return -EIO; 622 } 623 624 printk("ip_conntrack_pptp version %s loaded\n", IP_CT_PPTP_VERSION); 625 return 0; 626} 627 628static void __exit fini(void) 629{ 630 ip_conntrack_helper_unregister(&pptp); 631 printk("ip_conntrack_pptp version %s unloaded\n", IP_CT_PPTP_VERSION); 632} 633 634module_init(init); 635module_exit(fini); 636 637EXPORT_SYMBOL(ip_pptp_lock); 638