fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / net / ipv4 / netfilter / ip_conntrack_helper_pptp.c
index d716bba..4d19373 100644 (file)
  *      - We can only support one single call within each session
  *
  * TODO:
- *      - testing of incoming PPTP calls 
+ *      - testing of incoming PPTP calls
  *
- * Changes: 
+ * Changes:
  *     2002-02-05 - Version 1.3
- *       - Call ip_conntrack_unexpect_related() from 
+ *       - Call ip_conntrack_unexpect_related() from
  *         pptp_destroy_siblings() to destroy expectations in case
  *         CALL_DISCONNECT_NOTIFY or tcp fin packet was seen
  *         (Philip Craig <philipc@snapgear.com>)
@@ -46,7 +46,6 @@
  *
  */
 
-#include <linux/config.h>
 #include <linux/module.h>
 #include <linux/netfilter.h>
 #include <linux/ip.h>
@@ -81,7 +80,7 @@ int
                          struct PptpControlHeader *ctlh,
                          union pptp_ctrl_union *pptpReq);
 
-int
+void
 (*ip_nat_pptp_hook_exp_gre)(struct ip_conntrack_expect *expect_orig,
                            struct ip_conntrack_expect *expect_reply);
 
@@ -125,6 +124,8 @@ EXPORT_SYMBOL(pptp_msg_name);
 static void pptp_expectfn(struct ip_conntrack *ct,
                         struct ip_conntrack_expect *exp)
 {
+       typeof(ip_nat_pptp_hook_expectfn) ip_nat_pptp_expectfn;
+
        DEBUGP("increasing timeouts\n");
 
        /* increase timeout of GRE data channel conntrack entry */
@@ -134,7 +135,9 @@ static void pptp_expectfn(struct ip_conntrack *ct,
        /* Can you see how rusty this code is, compared with the pre-2.6.11
         * one? That's what happened to my shiny newnat of 2002 ;( -HW */
 
-       if (!ip_nat_pptp_hook_expectfn) {
+       rcu_read_lock();
+       ip_nat_pptp_expectfn = rcu_dereference(ip_nat_pptp_hook_expectfn);
+       if (!ip_nat_pptp_expectfn) {
                struct ip_conntrack_tuple inv_t;
                struct ip_conntrack_expect *exp_other;
 
@@ -142,8 +145,8 @@ static void pptp_expectfn(struct ip_conntrack *ct,
                invert_tuplepr(&inv_t, &exp->tuple);
                DEBUGP("trying to unexpect other dir: ");
                DUMP_TUPLE(&inv_t);
-       
-               exp_other = ip_conntrack_expect_find(&inv_t);
+
+               exp_other = ip_conntrack_expect_find_get(&inv_t);
                if (exp_other) {
                        /* delete other expectation.  */
                        DEBUGP("found\n");
@@ -154,8 +157,9 @@ static void pptp_expectfn(struct ip_conntrack *ct,
                }
        } else {
                /* we need more than simple inversion */
-               ip_nat_pptp_hook_expectfn(ct, exp);
+               ip_nat_pptp_expectfn(ct, exp);
        }
+       rcu_read_unlock();
 }
 
 static int destroy_sibling_or_exp(const struct ip_conntrack_tuple *t)
@@ -177,7 +181,7 @@ static int destroy_sibling_or_exp(const struct ip_conntrack_tuple *t)
                ip_conntrack_put(sibling);
                return 1;
        } else {
-               exp = ip_conntrack_expect_find(t);
+               exp = ip_conntrack_expect_find_get(t);
                if (exp) {
                        DEBUGP("unexpect_related of expect %p\n", exp);
                        ip_conntrack_unexpect_related(exp);
@@ -195,15 +199,16 @@ static void pptp_destroy_siblings(struct ip_conntrack *ct)
 {
        struct ip_conntrack_tuple t;
 
-       /* Since ct->sibling_list has literally rusted away in 2.6.11, 
+       ip_ct_gre_keymap_destroy(ct);
+       /* Since ct->sibling_list has literally rusted away in 2.6.11,
         * we now need another way to find out about our sibling
         * contrack and expects... -HW */
 
        /* try original (pns->pac) tuple */
        memcpy(&t, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, sizeof(t));
        t.dst.protonum = IPPROTO_GRE;
-       t.src.u.gre.key = htons(ct->help.ct_pptp_info.pns_call_id);
-       t.dst.u.gre.key = htons(ct->help.ct_pptp_info.pac_call_id);
+       t.src.u.gre.key = ct->help.ct_pptp_info.pns_call_id;
+       t.dst.u.gre.key = ct->help.ct_pptp_info.pac_call_id;
 
        if (!destroy_sibling_or_exp(&t))
                DEBUGP("failed to timeout original pns->pac ct/exp\n");
@@ -211,8 +216,8 @@ static void pptp_destroy_siblings(struct ip_conntrack *ct)
        /* try reply (pac->pns) tuple */
        memcpy(&t, &ct->tuplehash[IP_CT_DIR_REPLY].tuple, sizeof(t));
        t.dst.protonum = IPPROTO_GRE;
-       t.src.u.gre.key = htons(ct->help.ct_pptp_info.pac_call_id);
-       t.dst.u.gre.key = htons(ct->help.ct_pptp_info.pns_call_id);
+       t.src.u.gre.key = ct->help.ct_pptp_info.pac_call_id;
+       t.dst.u.gre.key = ct->help.ct_pptp_info.pns_call_id;
 
        if (!destroy_sibling_or_exp(&t))
                DEBUGP("failed to timeout reply pac->pns ct/exp\n");
@@ -220,94 +225,65 @@ static void pptp_destroy_siblings(struct ip_conntrack *ct)
 
 /* expect GRE connections (PNS->PAC and PAC->PNS direction) */
 static inline int
-exp_gre(struct ip_conntrack *master,
-       u_int32_t seq,
+exp_gre(struct ip_conntrack *ct,
        __be16 callid,
        __be16 peer_callid)
 {
-       struct ip_conntrack_tuple inv_tuple;
-       struct ip_conntrack_tuple exp_tuples[] = {
-               /* tuple in original direction, PNS->PAC */
-               { .src = { .ip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip,
-                          .u = { .gre = { .key = peer_callid } }
-                        },
-                 .dst = { .ip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip,
-                          .u = { .gre = { .key = callid } },
-                          .protonum = IPPROTO_GRE
-                        },
-                },
-               /* tuple in reply direction, PAC->PNS */
-               { .src = { .ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip,
-                          .u = { .gre = { .key = callid } }
-                        },
-                 .dst = { .ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip,
-                          .u = { .gre = { .key = peer_callid } },
-                          .protonum = IPPROTO_GRE
-                        },
-                }
-       };
        struct ip_conntrack_expect *exp_orig, *exp_reply;
        int ret = 1;
+       typeof(ip_nat_pptp_hook_exp_gre) ip_nat_pptp_exp_gre;
 
-       exp_orig = ip_conntrack_expect_alloc(master);
+       exp_orig = ip_conntrack_expect_alloc(ct);
        if (exp_orig == NULL)
                goto out;
 
-       exp_reply = ip_conntrack_expect_alloc(master);
+       exp_reply = ip_conntrack_expect_alloc(ct);
        if (exp_reply == NULL)
                goto out_put_orig;
 
-       memcpy(&exp_orig->tuple, &exp_tuples[0], sizeof(exp_orig->tuple));
+       /* original direction, PNS->PAC */
+       exp_orig->tuple.src.ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
+       exp_orig->tuple.src.u.gre.key = peer_callid;
+       exp_orig->tuple.dst.ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
+       exp_orig->tuple.dst.u.gre.key = callid;
+       exp_orig->tuple.dst.protonum = IPPROTO_GRE;
 
-       exp_orig->mask.src.ip = 0xffffffff;
+       exp_orig->mask.src.ip = htonl(0xffffffff);
        exp_orig->mask.src.u.all = 0;
-       exp_orig->mask.dst.u.all = 0;
        exp_orig->mask.dst.u.gre.key = htons(0xffff);
-       exp_orig->mask.dst.ip = 0xffffffff;
+       exp_orig->mask.dst.ip = htonl(0xffffffff);
        exp_orig->mask.dst.protonum = 0xff;
-               
-       exp_orig->master = master;
+
+       exp_orig->master = ct;
        exp_orig->expectfn = pptp_expectfn;
        exp_orig->flags = 0;
 
        /* both expectations are identical apart from tuple */
        memcpy(exp_reply, exp_orig, sizeof(*exp_reply));
-       memcpy(&exp_reply->tuple, &exp_tuples[1], sizeof(exp_reply->tuple));
-
-       if (ip_nat_pptp_hook_exp_gre)
-               ret = ip_nat_pptp_hook_exp_gre(exp_orig, exp_reply);
-       else {
-
-               DEBUGP("calling expect_related PNS->PAC");
-               DUMP_TUPLE(&exp_orig->tuple);
-
-               if (ip_conntrack_expect_related(exp_orig) != 0) {
-                       DEBUGP("cannot expect_related()\n");
-                       goto out_put_both;
-               }
-
-               DEBUGP("calling expect_related PAC->PNS");
-               DUMP_TUPLE(&exp_reply->tuple);
-
-               if (ip_conntrack_expect_related(exp_reply) != 0) {
-                       DEBUGP("cannot expect_related()\n");
-                       goto out_unexpect_orig;
-               }
 
-               /* Add GRE keymap entries */
-               if (ip_ct_gre_keymap_add(master, &exp_reply->tuple, 0) != 0) {
-                       DEBUGP("cannot keymap_add() exp\n");
-                       goto out_unexpect_both;
-               }
-
-               invert_tuplepr(&inv_tuple, &exp_reply->tuple);
-               if (ip_ct_gre_keymap_add(master, &inv_tuple, 1) != 0) {
-                       ip_ct_gre_keymap_destroy(master);
-                       DEBUGP("cannot keymap_add() exp_inv\n");
-                       goto out_unexpect_both;
-               }
-               ret = 0;
+       /* reply direction, PAC->PNS */
+       exp_reply->tuple.src.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
+       exp_reply->tuple.src.u.gre.key = callid;
+       exp_reply->tuple.dst.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+       exp_reply->tuple.dst.u.gre.key = peer_callid;
+       exp_reply->tuple.dst.protonum = IPPROTO_GRE;
+
+       ip_nat_pptp_exp_gre = rcu_dereference(ip_nat_pptp_hook_exp_gre);
+       if (ip_nat_pptp_exp_gre)
+               ip_nat_pptp_exp_gre(exp_orig, exp_reply);
+       if (ip_conntrack_expect_related(exp_orig) != 0)
+               goto out_put_both;
+       if (ip_conntrack_expect_related(exp_reply) != 0)
+               goto out_unexpect_orig;
+
+       /* Add GRE keymap entries */
+       if (ip_ct_gre_keymap_add(ct, &exp_orig->tuple, 0) != 0)
+               goto out_unexpect_both;
+       if (ip_ct_gre_keymap_add(ct, &exp_reply->tuple, 1) != 0) {
+               ip_ct_gre_keymap_destroy(ct);
+               goto out_unexpect_both;
        }
+       ret = 0;
 
 out_put_both:
        ip_conntrack_expect_put(exp_reply);
@@ -323,73 +299,37 @@ out_unexpect_orig:
        goto out_put_both;
 }
 
-static inline int 
+static inline int
 pptp_inbound_pkt(struct sk_buff **pskb,
-                struct tcphdr *tcph,
-                unsigned int nexthdr_off,
-                unsigned int datalen,
+                struct PptpControlHeader *ctlh,
+                union pptp_ctrl_union *pptpReq,
+                unsigned int reqlen,
                 struct ip_conntrack *ct,
                 enum ip_conntrack_info ctinfo)
 {
-       struct PptpControlHeader _ctlh, *ctlh;
-       unsigned int reqlen;
-       union pptp_ctrl_union _pptpReq, *pptpReq;
        struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info;
        u_int16_t msg;
-       __be16 *cid, *pcid;
-       u_int32_t seq;  
-
-       ctlh = skb_header_pointer(*pskb, nexthdr_off, sizeof(_ctlh), &_ctlh);
-       if (!ctlh) {
-               DEBUGP("error during skb_header_pointer\n");
-               return NF_ACCEPT;
-       }
-       nexthdr_off += sizeof(_ctlh);
-       datalen -= sizeof(_ctlh);
-
-       reqlen = datalen;
-       if (reqlen > sizeof(*pptpReq))
-               reqlen = sizeof(*pptpReq);
-       pptpReq = skb_header_pointer(*pskb, nexthdr_off, reqlen, &_pptpReq);
-       if (!pptpReq) {
-               DEBUGP("error during skb_header_pointer\n");
-               return NF_ACCEPT;
-       }
+       __be16 cid = 0, pcid = 0;
+       typeof(ip_nat_pptp_hook_inbound) ip_nat_pptp_inbound;
 
        msg = ntohs(ctlh->messageType);
        DEBUGP("inbound control message %s\n", pptp_msg_name[msg]);
 
        switch (msg) {
        case PPTP_START_SESSION_REPLY:
-               if (reqlen < sizeof(_pptpReq.srep)) {
-                       DEBUGP("%s: short packet\n", pptp_msg_name[msg]);
-                       break;
-               }
-
                /* server confirms new control session */
-               if (info->sstate < PPTP_SESSION_REQUESTED) {
-                       DEBUGP("%s without START_SESS_REQUEST\n",
-                               pptp_msg_name[msg]);
-                       break;
-               }
+               if (info->sstate < PPTP_SESSION_REQUESTED)
+                       goto invalid;
                if (pptpReq->srep.resultCode == PPTP_START_OK)
                        info->sstate = PPTP_SESSION_CONFIRMED;
-               else 
+               else
                        info->sstate = PPTP_SESSION_ERROR;
                break;
 
        case PPTP_STOP_SESSION_REPLY:
-               if (reqlen < sizeof(_pptpReq.strep)) {
-                       DEBUGP("%s: short packet\n", pptp_msg_name[msg]);
-                       break;
-               }
-
                /* server confirms end of control session */
-               if (info->sstate > PPTP_SESSION_STOPREQ) {
-                       DEBUGP("%s without STOP_SESS_REQUEST\n",
-                               pptp_msg_name[msg]);
-                       break;
-               }
+               if (info->sstate > PPTP_SESSION_STOPREQ)
+                       goto invalid;
                if (pptpReq->strep.resultCode == PPTP_STOP_OK)
                        info->sstate = PPTP_SESSION_NONE;
                else
@@ -397,116 +337,64 @@ pptp_inbound_pkt(struct sk_buff **pskb,
                break;
 
        case PPTP_OUT_CALL_REPLY:
-               if (reqlen < sizeof(_pptpReq.ocack)) {
-                       DEBUGP("%s: short packet\n", pptp_msg_name[msg]);
-                       break;
-               }
-
                /* server accepted call, we now expect GRE frames */
-               if (info->sstate != PPTP_SESSION_CONFIRMED) {
-                       DEBUGP("%s but no session\n", pptp_msg_name[msg]);
-                       break;
-               }
+               if (info->sstate != PPTP_SESSION_CONFIRMED)
+                       goto invalid;
                if (info->cstate != PPTP_CALL_OUT_REQ &&
-                   info->cstate != PPTP_CALL_OUT_CONF) {
-                       DEBUGP("%s without OUTCALL_REQ\n", pptp_msg_name[msg]);
-                       break;
-               }
-               if (pptpReq->ocack.resultCode != PPTP_OUTCALL_CONNECT) {
+                   info->cstate != PPTP_CALL_OUT_CONF)
+                       goto invalid;
+
+               cid = pptpReq->ocack.callID;
+               pcid = pptpReq->ocack.peersCallID;
+               if (info->pns_call_id != pcid)
+                       goto invalid;
+               DEBUGP("%s, CID=%X, PCID=%X\n", pptp_msg_name[msg],
+                       ntohs(cid), ntohs(pcid));
+
+               if (pptpReq->ocack.resultCode == PPTP_OUTCALL_CONNECT) {
+                       info->cstate = PPTP_CALL_OUT_CONF;
+                       info->pac_call_id = cid;
+                       exp_gre(ct, cid, pcid);
+               } else
                        info->cstate = PPTP_CALL_NONE;
-                       break;
-               }
-
-               cid = &pptpReq->ocack.callID;
-               pcid = &pptpReq->ocack.peersCallID;
-
-               info->pac_call_id = ntohs(*cid);
-               
-               if (htons(info->pns_call_id) != *pcid) {
-                       DEBUGP("%s for unknown callid %u\n",
-                               pptp_msg_name[msg], ntohs(*pcid));
-                       break;
-               }
-
-               DEBUGP("%s, CID=%X, PCID=%X\n", pptp_msg_name[msg], 
-                       ntohs(*cid), ntohs(*pcid));
-               
-               info->cstate = PPTP_CALL_OUT_CONF;
-
-               seq = ntohl(tcph->seq) + sizeof(struct pptp_pkt_hdr)
-                                      + sizeof(struct PptpControlHeader)
-                                      + ((void *)pcid - (void *)pptpReq);
-                       
-               if (exp_gre(ct, seq, *cid, *pcid) != 0)
-                       printk("ip_conntrack_pptp: error during exp_gre\n");
                break;
 
        case PPTP_IN_CALL_REQUEST:
-               if (reqlen < sizeof(_pptpReq.icack)) {
-                       DEBUGP("%s: short packet\n", pptp_msg_name[msg]);
-                       break;
-               }
-
                /* server tells us about incoming call request */
-               if (info->sstate != PPTP_SESSION_CONFIRMED) {
-                       DEBUGP("%s but no session\n", pptp_msg_name[msg]);
-                       break;
-               }
-               pcid = &pptpReq->icack.peersCallID;
-               DEBUGP("%s, PCID=%X\n", pptp_msg_name[msg], ntohs(*pcid));
+               if (info->sstate != PPTP_SESSION_CONFIRMED)
+                       goto invalid;
+
+               cid = pptpReq->icreq.callID;
+               DEBUGP("%s, CID=%X\n", pptp_msg_name[msg], ntohs(cid));
                info->cstate = PPTP_CALL_IN_REQ;
-               info->pac_call_id = ntohs(*pcid);
+               info->pac_call_id = cid;
                break;
 
        case PPTP_IN_CALL_CONNECT:
-               if (reqlen < sizeof(_pptpReq.iccon)) {
-                       DEBUGP("%s: short packet\n", pptp_msg_name[msg]);
-                       break;
-               }
-
                /* server tells us about incoming call established */
-               if (info->sstate != PPTP_SESSION_CONFIRMED) {
-                       DEBUGP("%s but no session\n", pptp_msg_name[msg]);
-                       break;
-               }
-               if (info->sstate != PPTP_CALL_IN_REP
-                   && info->sstate != PPTP_CALL_IN_CONF) {
-                       DEBUGP("%s but never sent IN_CALL_REPLY\n",
-                               pptp_msg_name[msg]);
-                       break;
-               }
+               if (info->sstate != PPTP_SESSION_CONFIRMED)
+                       goto invalid;
+               if (info->cstate != PPTP_CALL_IN_REP &&
+                   info->cstate != PPTP_CALL_IN_CONF)
+                       goto invalid;
 
-               pcid = &pptpReq->iccon.peersCallID;
-               cid = &info->pac_call_id;
+               pcid = pptpReq->iccon.peersCallID;
+               cid = info->pac_call_id;
 
-               if (info->pns_call_id != ntohs(*pcid)) {
-                       DEBUGP("%s for unknown CallID %u\n", 
-                               pptp_msg_name[msg], ntohs(*pcid));
-                       break;
-               }
+               if (info->pns_call_id != pcid)
+                       goto invalid;
 
-               DEBUGP("%s, PCID=%X\n", pptp_msg_name[msg], ntohs(*pcid));
+               DEBUGP("%s, PCID=%X\n", pptp_msg_name[msg], ntohs(pcid));
                info->cstate = PPTP_CALL_IN_CONF;
 
                /* we expect a GRE connection from PAC to PNS */
-               seq = ntohl(tcph->seq) + sizeof(struct pptp_pkt_hdr)
-                                      + sizeof(struct PptpControlHeader)
-                                      + ((void *)pcid - (void *)pptpReq);
-                       
-               if (exp_gre(ct, seq, *cid, *pcid) != 0)
-                       printk("ip_conntrack_pptp: error during exp_gre\n");
-
+               exp_gre(ct, cid, pcid);
                break;
 
        case PPTP_CALL_DISCONNECT_NOTIFY:
-               if (reqlen < sizeof(_pptpReq.disc)) {
-                       DEBUGP("%s: short packet\n", pptp_msg_name[msg]);
-                       break;
-               }
-
                /* server confirms disconnect */
-               cid = &pptpReq->disc.callID;
-               DEBUGP("%s, CID=%X\n", pptp_msg_name[msg], ntohs(*cid));
+               cid = pptpReq->disc.callID;
+               DEBUGP("%s, CID=%X\n", pptp_msg_name[msg], ntohs(cid));
                info->cstate = PPTP_CALL_NONE;
 
                /* untrack this call id, unexpect GRE packets */
@@ -514,54 +402,40 @@ pptp_inbound_pkt(struct sk_buff **pskb,
                break;
 
        case PPTP_WAN_ERROR_NOTIFY:
-               break;
-
        case PPTP_ECHO_REQUEST:
        case PPTP_ECHO_REPLY:
                /* I don't have to explain these ;) */
                break;
        default:
-               DEBUGP("invalid %s (TY=%d)\n", (msg <= PPTP_MSG_MAX)
-                       ? pptp_msg_name[msg]:pptp_msg_name[0], msg);
-               break;
+               goto invalid;
        }
 
-
-       if (ip_nat_pptp_hook_inbound)
-               return ip_nat_pptp_hook_inbound(pskb, ct, ctinfo, ctlh,
-                                               pptpReq);
-
+       ip_nat_pptp_inbound = rcu_dereference(ip_nat_pptp_hook_inbound);
+       if (ip_nat_pptp_inbound)
+               return ip_nat_pptp_inbound(pskb, ct, ctinfo, ctlh, pptpReq);
        return NF_ACCEPT;
 
+invalid:
+       DEBUGP("invalid %s: type=%d cid=%u pcid=%u "
+              "cstate=%d sstate=%d pns_cid=%u pac_cid=%u\n",
+              msg <= PPTP_MSG_MAX ? pptp_msg_name[msg] : pptp_msg_name[0],
+              msg, ntohs(cid), ntohs(pcid),  info->cstate, info->sstate,
+              ntohs(info->pns_call_id), ntohs(info->pac_call_id));
+       return NF_ACCEPT;
 }
 
 static inline int
 pptp_outbound_pkt(struct sk_buff **pskb,
-                 struct tcphdr *tcph,
-                 unsigned int nexthdr_off,
-                 unsigned int datalen,
+                 struct PptpControlHeader *ctlh,
+                 union pptp_ctrl_union *pptpReq,
+                 unsigned int reqlen,
                  struct ip_conntrack *ct,
                  enum ip_conntrack_info ctinfo)
 {
-       struct PptpControlHeader _ctlh, *ctlh;
-       unsigned int reqlen;
-       union pptp_ctrl_union _pptpReq, *pptpReq;
        struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info;
        u_int16_t msg;
-       __be16 *cid, *pcid;
-
-       ctlh = skb_header_pointer(*pskb, nexthdr_off, sizeof(_ctlh), &_ctlh);
-       if (!ctlh)
-               return NF_ACCEPT;
-       nexthdr_off += sizeof(_ctlh);
-       datalen -= sizeof(_ctlh);
-       
-       reqlen = datalen;
-       if (reqlen > sizeof(*pptpReq))
-               reqlen = sizeof(*pptpReq);
-       pptpReq = skb_header_pointer(*pskb, nexthdr_off, reqlen, &_pptpReq);
-       if (!pptpReq)
-               return NF_ACCEPT;
+       __be16 cid = 0, pcid = 0;
+       typeof(ip_nat_pptp_hook_outbound) ip_nat_pptp_outbound;
 
        msg = ntohs(ctlh->messageType);
        DEBUGP("outbound control message %s\n", pptp_msg_name[msg]);
@@ -569,10 +443,8 @@ pptp_outbound_pkt(struct sk_buff **pskb,
        switch (msg) {
        case PPTP_START_SESSION_REQUEST:
                /* client requests for new control session */
-               if (info->sstate != PPTP_SESSION_NONE) {
-                       DEBUGP("%s but we already have one",
-                               pptp_msg_name[msg]);
-               }
+               if (info->sstate != PPTP_SESSION_NONE)
+                       goto invalid;
                info->sstate = PPTP_SESSION_REQUESTED;
                break;
        case PPTP_STOP_SESSION_REQUEST:
@@ -581,123 +453,115 @@ pptp_outbound_pkt(struct sk_buff **pskb,
                break;
 
        case PPTP_OUT_CALL_REQUEST:
-               if (reqlen < sizeof(_pptpReq.ocreq)) {
-                       DEBUGP("%s: short packet\n", pptp_msg_name[msg]);
-                       /* FIXME: break; */
-               }
-
                /* client initiating connection to server */
-               if (info->sstate != PPTP_SESSION_CONFIRMED) {
-                       DEBUGP("%s but no session\n",
-                               pptp_msg_name[msg]);
-                       break;
-               }
+               if (info->sstate != PPTP_SESSION_CONFIRMED)
+                       goto invalid;
                info->cstate = PPTP_CALL_OUT_REQ;
                /* track PNS call id */
-               cid = &pptpReq->ocreq.callID;
-               DEBUGP("%s, CID=%X\n", pptp_msg_name[msg], ntohs(*cid));
-               info->pns_call_id = ntohs(*cid);
+               cid = pptpReq->ocreq.callID;
+               DEBUGP("%s, CID=%X\n", pptp_msg_name[msg], ntohs(cid));
+               info->pns_call_id = cid;
                break;
        case PPTP_IN_CALL_REPLY:
-               if (reqlen < sizeof(_pptpReq.icack)) {
-                       DEBUGP("%s: short packet\n", pptp_msg_name[msg]);
-                       break;
-               }
-
                /* client answers incoming call */
-               if (info->cstate != PPTP_CALL_IN_REQ
-                   && info->cstate != PPTP_CALL_IN_REP) {
-                       DEBUGP("%s without incall_req\n", 
-                               pptp_msg_name[msg]);
-                       break;
-               }
-               if (pptpReq->icack.resultCode != PPTP_INCALL_ACCEPT) {
+               if (info->cstate != PPTP_CALL_IN_REQ &&
+                   info->cstate != PPTP_CALL_IN_REP)
+                       goto invalid;
+
+               cid = pptpReq->icack.callID;
+               pcid = pptpReq->icack.peersCallID;
+               if (info->pac_call_id != pcid)
+                       goto invalid;
+               DEBUGP("%s, CID=%X PCID=%X\n", pptp_msg_name[msg],
+                      ntohs(cid), ntohs(pcid));
+
+               if (pptpReq->icack.resultCode == PPTP_INCALL_ACCEPT) {
+                       /* part two of the three-way handshake */
+                       info->cstate = PPTP_CALL_IN_REP;
+                       info->pns_call_id = cid;
+               } else
                        info->cstate = PPTP_CALL_NONE;
-                       break;
-               }
-               pcid = &pptpReq->icack.peersCallID;
-               if (info->pac_call_id != ntohs(*pcid)) {
-                       DEBUGP("%s for unknown call %u\n", 
-                               pptp_msg_name[msg], ntohs(*pcid));
-                       break;
-               }
-               DEBUGP("%s, CID=%X\n", pptp_msg_name[msg], ntohs(*pcid));
-               /* part two of the three-way handshake */
-               info->cstate = PPTP_CALL_IN_REP;
-               info->pns_call_id = ntohs(pptpReq->icack.callID);
                break;
 
        case PPTP_CALL_CLEAR_REQUEST:
                /* client requests hangup of call */
-               if (info->sstate != PPTP_SESSION_CONFIRMED) {
-                       DEBUGP("CLEAR_CALL but no session\n");
-                       break;
-               }
+               if (info->sstate != PPTP_SESSION_CONFIRMED)
+                       goto invalid;
                /* FUTURE: iterate over all calls and check if
                 * call ID is valid.  We don't do this without newnat,
                 * because we only know about last call */
                info->cstate = PPTP_CALL_CLEAR_REQ;
                break;
        case PPTP_SET_LINK_INFO:
-               break;
        case PPTP_ECHO_REQUEST:
        case PPTP_ECHO_REPLY:
                /* I don't have to explain these ;) */
                break;
        default:
-               DEBUGP("invalid %s (TY=%d)\n", (msg <= PPTP_MSG_MAX)? 
-                       pptp_msg_name[msg]:pptp_msg_name[0], msg);
-               /* unknown: no need to create GRE masq table entry */
-               break;
+               goto invalid;
        }
-       
-       if (ip_nat_pptp_hook_outbound)
-               return ip_nat_pptp_hook_outbound(pskb, ct, ctinfo, ctlh,
-                                                pptpReq);
 
+       ip_nat_pptp_outbound = rcu_dereference(ip_nat_pptp_hook_outbound);
+       if (ip_nat_pptp_outbound)
+               return ip_nat_pptp_outbound(pskb, ct, ctinfo, ctlh, pptpReq);
+       return NF_ACCEPT;
+
+invalid:
+       DEBUGP("invalid %s: type=%d cid=%u pcid=%u "
+              "cstate=%d sstate=%d pns_cid=%u pac_cid=%u\n",
+              msg <= PPTP_MSG_MAX ? pptp_msg_name[msg] : pptp_msg_name[0],
+              msg, ntohs(cid), ntohs(pcid),  info->cstate, info->sstate,
+              ntohs(info->pns_call_id), ntohs(info->pac_call_id));
        return NF_ACCEPT;
 }
 
+static const unsigned int pptp_msg_size[] = {
+       [PPTP_START_SESSION_REQUEST]  = sizeof(struct PptpStartSessionRequest),
+       [PPTP_START_SESSION_REPLY]    = sizeof(struct PptpStartSessionReply),
+       [PPTP_STOP_SESSION_REQUEST]   = sizeof(struct PptpStopSessionRequest),
+       [PPTP_STOP_SESSION_REPLY]     = sizeof(struct PptpStopSessionReply),
+       [PPTP_OUT_CALL_REQUEST]       = sizeof(struct PptpOutCallRequest),
+       [PPTP_OUT_CALL_REPLY]         = sizeof(struct PptpOutCallReply),
+       [PPTP_IN_CALL_REQUEST]        = sizeof(struct PptpInCallRequest),
+       [PPTP_IN_CALL_REPLY]          = sizeof(struct PptpInCallReply),
+       [PPTP_IN_CALL_CONNECT]        = sizeof(struct PptpInCallConnected),
+       [PPTP_CALL_CLEAR_REQUEST]     = sizeof(struct PptpClearCallRequest),
+       [PPTP_CALL_DISCONNECT_NOTIFY] = sizeof(struct PptpCallDisconnectNotify),
+       [PPTP_WAN_ERROR_NOTIFY]       = sizeof(struct PptpWanErrorNotify),
+       [PPTP_SET_LINK_INFO]          = sizeof(struct PptpSetLinkInfo),
+};
 
 /* track caller id inside control connection, call expect_related */
-static int 
+static int
 conntrack_pptp_help(struct sk_buff **pskb,
                    struct ip_conntrack *ct, enum ip_conntrack_info ctinfo)
 
 {
-       struct pptp_pkt_hdr _pptph, *pptph;
-       struct tcphdr _tcph, *tcph;
-       u_int32_t tcplen = (*pskb)->len - (*pskb)->nh.iph->ihl * 4;
-       u_int32_t datalen;
        int dir = CTINFO2DIR(ctinfo);
        struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info;
-       unsigned int nexthdr_off;
-
+       struct tcphdr _tcph, *tcph;
+       struct pptp_pkt_hdr _pptph, *pptph;
+       struct PptpControlHeader _ctlh, *ctlh;
+       union pptp_ctrl_union _pptpReq, *pptpReq;
+       unsigned int tcplen = (*pskb)->len - (*pskb)->nh.iph->ihl * 4;
+       unsigned int datalen, reqlen, nexthdr_off;
        int oldsstate, oldcstate;
        int ret;
+       u_int16_t msg;
 
        /* don't do any tracking before tcp handshake complete */
-       if (ctinfo != IP_CT_ESTABLISHED 
+       if (ctinfo != IP_CT_ESTABLISHED
            && ctinfo != IP_CT_ESTABLISHED+IP_CT_IS_REPLY) {
                DEBUGP("ctinfo = %u, skipping\n", ctinfo);
                return NF_ACCEPT;
        }
-       
+
        nexthdr_off = (*pskb)->nh.iph->ihl*4;
        tcph = skb_header_pointer(*pskb, nexthdr_off, sizeof(_tcph), &_tcph);
        BUG_ON(!tcph);
        nexthdr_off += tcph->doff * 4;
        datalen = tcplen - tcph->doff * 4;
 
-       if (tcph->fin || tcph->rst) {
-               DEBUGP("RST/FIN received, timeouting GRE\n");
-               /* can't do this after real newnat */
-               info->cstate = PPTP_CALL_NONE;
-
-               /* untrack this call id, unexpect GRE packets */
-               pptp_destroy_siblings(ct);
-       }
-
        pptph = skb_header_pointer(*pskb, nexthdr_off, sizeof(_pptph), &_pptph);
        if (!pptph) {
                DEBUGP("no full PPTP header, can't track\n");
@@ -713,6 +577,23 @@ conntrack_pptp_help(struct sk_buff **pskb,
                return NF_ACCEPT;
        }
 
+       ctlh = skb_header_pointer(*pskb, nexthdr_off, sizeof(_ctlh), &_ctlh);
+       if (!ctlh)
+               return NF_ACCEPT;
+       nexthdr_off += sizeof(_ctlh);
+       datalen -= sizeof(_ctlh);
+
+       reqlen = datalen;
+       msg = ntohs(ctlh->messageType);
+       if (msg > 0 && msg <= PPTP_MSG_MAX && reqlen < pptp_msg_size[msg])
+               return NF_ACCEPT;
+       if (reqlen > sizeof(*pptpReq))
+               reqlen = sizeof(*pptpReq);
+
+       pptpReq = skb_header_pointer(*pskb, nexthdr_off, reqlen, &_pptpReq);
+       if (!pptpReq)
+               return NF_ACCEPT;
+
        oldsstate = info->sstate;
        oldcstate = info->cstate;
 
@@ -722,11 +603,11 @@ conntrack_pptp_help(struct sk_buff **pskb,
         * established from PNS->PAC.  However, RFC makes no guarantee */
        if (dir == IP_CT_DIR_ORIGINAL)
                /* client -> server (PNS -> PAC) */
-               ret = pptp_outbound_pkt(pskb, tcph, nexthdr_off, datalen, ct,
+               ret = pptp_outbound_pkt(pskb, ctlh, pptpReq, reqlen, ct,
                                        ctinfo);
        else
                /* server -> client (PAC -> PNS) */
-               ret = pptp_inbound_pkt(pskb, tcph, nexthdr_off, datalen, ct,
+               ret = pptp_inbound_pkt(pskb, ctlh, pptpReq, reqlen, ct,
                                       ctinfo);
        DEBUGP("sstate: %d->%d, cstate: %d->%d\n",
                oldsstate, info->sstate, oldcstate, info->cstate);
@@ -736,40 +617,41 @@ conntrack_pptp_help(struct sk_buff **pskb,
 }
 
 /* control protocol helper */
-static struct ip_conntrack_helper pptp = { 
+static struct ip_conntrack_helper pptp = {
        .list = { NULL, NULL },
-       .name = "pptp", 
+       .name = "pptp",
        .me = THIS_MODULE,
        .max_expected = 2,
        .timeout = 5 * 60,
-       .tuple = { .src = { .ip = 0, 
-                           .u = { .tcp = { .port =  
-                                   __constant_htons(PPTP_CONTROL_PORT) } } 
-                         }, 
-                  .dst = { .ip = 0, 
+       .tuple = { .src = { .ip = 0,
+                           .u = { .tcp = { .port =
+                                   __constant_htons(PPTP_CONTROL_PORT) } }
+                         },
+                  .dst = { .ip = 0,
                            .u = { .all = 0 },
                            .protonum = IPPROTO_TCP
-                         } 
+                         }
                 },
-       .mask = { .src = { .ip = 0, 
-                          .u = { .tcp = { .port = __constant_htons(0xffff) } } 
-                        }, 
-                 .dst = { .ip = 0, 
+       .mask = { .src = { .ip = 0,
+                          .u = { .tcp = { .port = __constant_htons(0xffff) } }
+                        },
+                 .dst = { .ip = 0,
                           .u = { .all = 0 },
-                          .protonum = 0xff 
-                        } 
+                          .protonum = 0xff
+                        }
                },
-       .help = conntrack_pptp_help
+       .help = conntrack_pptp_help,
+       .destroy = pptp_destroy_siblings,
 };
 
 extern void ip_ct_proto_gre_fini(void);
 extern int __init ip_ct_proto_gre_init(void);
 
 /* ip_conntrack_pptp initialization */
-static int __init init(void)
+static int __init ip_conntrack_helper_pptp_init(void)
 {
        int retcode;
+
        retcode = ip_ct_proto_gre_init();
        if (retcode < 0)
                return retcode;
@@ -786,15 +668,15 @@ static int __init init(void)
        return 0;
 }
 
-static void __exit fini(void)
+static void __exit ip_conntrack_helper_pptp_fini(void)
 {
        ip_conntrack_helper_unregister(&pptp);
        ip_ct_proto_gre_fini();
        printk("ip_conntrack_pptp version %s unloaded\n", IP_CT_PPTP_VERSION);
 }
 
-module_init(init);
-module_exit(fini);
+module_init(ip_conntrack_helper_pptp_init);
+module_exit(ip_conntrack_helper_pptp_fini);
 
 EXPORT_SYMBOL(ip_nat_pptp_hook_outbound);
 EXPORT_SYMBOL(ip_nat_pptp_hook_inbound);