/*
- * ip_conntrack_pptp.c - Version 1.9
+ * ip_conntrack_pptp.c - Version 2.0
*
* Connection tracking support for PPTP (Point to Point Tunneling Protocol).
* PPTP is a a protocol for creating virtual private networks.
* GRE is defined in RFC 1701 and RFC 1702. Documentation of
* PPTP can be found in RFC 2637
*
- * (C) 2000-2003 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2000-2005 by Harald Welte <laforge@gnumonks.org>
*
* Development of this code funded by Astaro AG (http://www.astaro.com/)
*
* 2002-02-10 - Version 1.6
* - move to C99 style initializers
* - remove second expectation if first arrives
+ * 2004-10-22 - Version 2.0
+ * - merge Mandrake's 2.6.x port with recent 2.6.x API changes
+ * - fix lots of linear skb assumptions from Mandrake's port
+ * 2005-06-10 - Version 2.1
+ * - use ip_conntrack_expect_free() instead of kfree() on the
+ * expect's (which are from the slab for quite some time)
*
*/
#include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h>
#include <linux/netfilter_ipv4/ip_conntrack_pptp.h>
-#define IP_CT_PPTP_VERSION "1.9"
+#define IP_CT_PPTP_VERSION "2.1"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
#if 0
#include "ip_conntrack_pptp_priv.h"
-#define DEBUGP(format, args...) printk(KERN_DEBUG __FILE__ ":" __FUNCTION__ \
- ": " format, ## args)
+#define DEBUGP(format, args...) printk(KERN_DEBUG "%s:%s: " format, __FILE__, __FUNCTION__, ## args)
#else
#define DEBUGP(format, args...)
#endif
exp->sibling);
exp->sibling->proto.gre.timeout = 0;
exp->sibling->proto.gre.stream_timeout = 0;
+ /* refresh_acct will not modify counters if skb == NULL */
+ //ip_ct_refresh_acct(exp->sibling, 0, NULL, 0);
ip_ct_refresh(exp->sibling, 0);
}
u_int16_t callid,
u_int16_t peer_callid)
{
- struct ip_conntrack_expect exp;
struct ip_conntrack_tuple inv_tuple;
-
- memset(&exp, 0, sizeof(exp));
- /* tuple in original direction, PNS->PAC */
- exp.tuple.src.ip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
- exp.tuple.src.u.gre.key = htonl(ntohs(peer_callid));
- exp.tuple.dst.ip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
- exp.tuple.dst.u.gre.key = htonl(ntohs(callid));
- exp.tuple.dst.u.gre.protocol = __constant_htons(GRE_PROTOCOL_PPTP);
- exp.tuple.dst.u.gre.version = GRE_VERSION_PPTP;
- exp.tuple.dst.protonum = IPPROTO_GRE;
-
- exp.mask.src.ip = 0xffffffff;
- exp.mask.src.u.all = 0;
- exp.mask.dst.u.all = 0;
- exp.mask.dst.u.gre.key = 0xffffffff;
- exp.mask.dst.u.gre.version = 0xff;
- exp.mask.dst.u.gre.protocol = 0xffff;
- exp.mask.dst.ip = 0xffffffff;
- exp.mask.dst.protonum = 0xffff;
+ 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 = htonl(ntohs(peer_callid)) } }
+ },
+ .dst = { .ip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip,
+ .u = { .gre = { .key = htonl(ntohs(callid)) } },
+ .protonum = IPPROTO_GRE
+ },
+ },
+ /* tuple in reply direction, PAC->PNS */
+ { .src = { .ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip,
+ .u = { .gre = { .key = htonl(ntohs(callid)) } }
+ },
+ .dst = { .ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip,
+ .u = { .gre = { .key = htonl(ntohs(peer_callid)) } },
+ .protonum = IPPROTO_GRE
+ },
+ }
+ }, *exp_tuple;
+
+ for (exp_tuple = exp_tuples; exp_tuple < &exp_tuples[2]; exp_tuple++) {
+ struct ip_conntrack_expect *exp;
+
+ exp = ip_conntrack_expect_alloc();
+ if (exp == NULL)
+ return 1;
+
+ memcpy(&exp->tuple, exp_tuple, sizeof(exp->tuple));
+
+ exp->mask.src.ip = 0xffffffff;
+ exp->mask.src.u.all = 0;
+ exp->mask.dst.u.all = 0;
+ exp->mask.dst.u.gre.key = 0xffffffff;
+ exp->mask.dst.ip = 0xffffffff;
+ exp->mask.dst.protonum = 0xffff;
- exp.seq = seq;
- exp.expectfn = pptp_expectfn;
+ exp->seq = seq;
+ exp->expectfn = pptp_expectfn;
- exp.help.exp_pptp_info.pac_call_id = ntohs(callid);
- exp.help.exp_pptp_info.pns_call_id = ntohs(peer_callid);
+ exp->help.exp_pptp_info.pac_call_id = ntohs(callid);
+ exp->help.exp_pptp_info.pns_call_id = ntohs(peer_callid);
- DEBUGP("calling expect_related ");
- DUMP_TUPLE_RAW(&exp.tuple);
-
- /* Add GRE keymap entries */
- if (ip_ct_gre_keymap_add(&exp, &exp.tuple, 0) != 0)
- return 1;
-
- invert_tuplepr(&inv_tuple, &exp.tuple);
- if (ip_ct_gre_keymap_add(&exp, &inv_tuple, 1) != 0) {
- ip_ct_gre_keymap_destroy(&exp);
- return 1;
- }
+ DEBUGP("calling expect_related ");
+ DUMP_TUPLE_RAW(&exp->tuple);
- if (ip_conntrack_expect_related(&exp, master) != 0) {
- ip_ct_gre_keymap_destroy(&exp);
- DEBUGP("cannot expect_related()\n");
- return 1;
- }
-
- /* tuple in reply direction, PAC->PNS */
- exp.tuple.src.ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
- exp.tuple.src.u.gre.key = htonl(ntohs(callid));
- exp.tuple.dst.ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
- exp.tuple.dst.u.gre.key = htonl(ntohs(peer_callid));
+ /* Add GRE keymap entries */
+ if (ip_ct_gre_keymap_add(exp, &exp->tuple, 0) != 0) {
+ //ip_conntrack_expect_free(exp);
+ ip_conntrack_expect_put(exp);
+ return 1;
+ }
- DEBUGP("calling expect_related ");
- DUMP_TUPLE_RAW(&exp.tuple);
-
- /* Add GRE keymap entries */
- ip_ct_gre_keymap_add(&exp, &exp.tuple, 0);
- invert_tuplepr(&inv_tuple, &exp.tuple);
- ip_ct_gre_keymap_add(&exp, &inv_tuple, 1);
- /* FIXME: cannot handle error correctly, since we need to free
- * the above keymap :( */
+ invert_tuplepr(&inv_tuple, &exp->tuple);
+ if (ip_ct_gre_keymap_add(exp, &inv_tuple, 1) != 0) {
+ ip_ct_gre_keymap_destroy(exp);
+ //ip_conntrack_expect_free(exp);
+ ip_conntrack_expect_put(exp);
+ return 1;
+ }
- if (ip_conntrack_expect_related(&exp, master) != 0) {
- /* free the second pair of keypmaps */
- ip_ct_gre_keymap_destroy(&exp);
- DEBUGP("cannot expect_related():\n");
- return 1;
+ if (ip_conntrack_expect_related(exp, master) != 0) {
+ ip_ct_gre_keymap_destroy(exp);
+ //ip_conntrack_expect_free(exp);
+ ip_conntrack_expect_put(exp);
+ DEBUGP("cannot expect_related()\n");
+ return 1;
+ }
}
return 0;
}
static inline int
-pptp_inbound_pkt(struct tcphdr *tcph,
- struct pptp_pkt_hdr *pptph,
+pptp_inbound_pkt(struct sk_buff *skb,
+ struct tcphdr *tcph,
+ unsigned int ctlhoff,
size_t datalen,
- struct ip_conntrack *ct,
- enum ip_conntrack_info ctinfo)
+ struct ip_conntrack *ct)
{
- struct PptpControlHeader *ctlh;
- union pptp_ctrl_union pptpReq;
-
+ 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, *cid, *pcid;
u_int32_t seq;
- ctlh = (struct PptpControlHeader *)
- ((char *) pptph + sizeof(struct pptp_pkt_hdr));
- pptpReq.rawreq = (void *)
- ((char *) ctlh + sizeof(struct PptpControlHeader));
+ ctlh = skb_header_pointer(skb, ctlhoff, sizeof(_ctlh), &_ctlh);
+ if (unlikely(!ctlh)) {
+ DEBUGP("error during skb_header_pointer\n");
+ return NF_ACCEPT;
+ }
+
+ reqlen = datalen - sizeof(struct pptp_pkt_hdr) - sizeof(_ctlh);
+ pptpReq = skb_header_pointer(skb, ctlhoff+sizeof(_ctlh),
+ reqlen, &_pptpReq);
+ if (unlikely(!pptpReq)) {
+ DEBUGP("error during skb_header_pointer\n");
+ return NF_ACCEPT;
+ }
msg = ntohs(ctlh->messageType);
DEBUGP("inbound control message %s\n", strMName[msg]);
switch (msg) {
case PPTP_START_SESSION_REPLY:
+ if (reqlen < sizeof(_pptpReq.srep)) {
+ DEBUGP("%s: short packet\n", strMName[msg]);
+ break;
+ }
+
/* server confirms new control session */
if (info->sstate < PPTP_SESSION_REQUESTED) {
DEBUGP("%s without START_SESS_REQUEST\n",
strMName[msg]);
break;
}
- if (pptpReq.srep->resultCode == PPTP_START_OK)
+ if (pptpReq->srep.resultCode == PPTP_START_OK)
info->sstate = PPTP_SESSION_CONFIRMED;
else
info->sstate = PPTP_SESSION_ERROR;
break;
case PPTP_STOP_SESSION_REPLY:
+ if (reqlen < sizeof(_pptpReq.strep)) {
+ DEBUGP("%s: short packet\n", strMName[msg]);
+ break;
+ }
+
/* server confirms end of control session */
if (info->sstate > PPTP_SESSION_STOPREQ) {
DEBUGP("%s without STOP_SESS_REQUEST\n",
strMName[msg]);
break;
}
- if (pptpReq.strep->resultCode == PPTP_STOP_OK)
+ if (pptpReq->strep.resultCode == PPTP_STOP_OK)
info->sstate = PPTP_SESSION_NONE;
else
info->sstate = PPTP_SESSION_ERROR;
break;
case PPTP_OUT_CALL_REPLY:
+ if (reqlen < sizeof(_pptpReq.ocack)) {
+ DEBUGP("%s: short packet\n", strMName[msg]);
+ break;
+ }
+
/* server accepted call, we now expect GRE frames */
if (info->sstate != PPTP_SESSION_CONFIRMED) {
DEBUGP("%s but no session\n", strMName[msg]);
DEBUGP("%s without OUTCALL_REQ\n", strMName[msg]);
break;
}
- if (pptpReq.ocack->resultCode != PPTP_OUTCALL_CONNECT) {
+ if (pptpReq->ocack.resultCode != PPTP_OUTCALL_CONNECT) {
info->cstate = PPTP_CALL_NONE;
break;
}
- cid = &pptpReq.ocack->callID;
- pcid = &pptpReq.ocack->peersCallID;
+ cid = &pptpReq->ocack.callID;
+ pcid = &pptpReq->ocack.peersCallID;
info->pac_call_id = ntohs(*cid);
info->cstate = PPTP_CALL_OUT_CONF;
- seq = ntohl(tcph->seq) + ((void *)pcid - (void *)pptph);
+ 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", strMName[msg]);
+ break;
+ }
+
/* server tells us about incoming call request */
if (info->sstate != PPTP_SESSION_CONFIRMED) {
DEBUGP("%s but no session\n", strMName[msg]);
break;
}
- pcid = &pptpReq.icack->peersCallID;
+ pcid = &pptpReq->icack.peersCallID;
DEBUGP("%s, PCID=%X\n", strMName[msg], ntohs(*pcid));
info->cstate = PPTP_CALL_IN_REQ;
- info->pac_call_id= ntohs(*pcid);
+ info->pac_call_id = ntohs(*pcid);
break;
case PPTP_IN_CALL_CONNECT:
+ if (reqlen < sizeof(_pptpReq.iccon)) {
+ DEBUGP("%s: short packet\n", strMName[msg]);
+ break;
+ }
+
/* server tells us about incoming call established */
if (info->sstate != PPTP_SESSION_CONFIRMED) {
DEBUGP("%s but no session\n", strMName[msg]);
break;
}
- pcid = &pptpReq.iccon->peersCallID;
+ pcid = &pptpReq->iccon.peersCallID;
cid = &info->pac_call_id;
if (info->pns_call_id != ntohs(*pcid)) {
info->cstate = PPTP_CALL_IN_CONF;
/* we expect a GRE connection from PAC to PNS */
- seq = ntohl(tcph->seq) + ((void *)pcid - (void *)pptph);
+ 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_CALL_DISCONNECT_NOTIFY:
+ if (reqlen < sizeof(_pptpReq.disc)) {
+ DEBUGP("%s: short packet\n", strMName[msg]);
+ break;
+ }
+
/* server confirms disconnect */
- cid = &pptpReq.disc->callID;
+ cid = &pptpReq->disc.callID;
DEBUGP("%s, CID=%X\n", strMName[msg], ntohs(*cid));
info->cstate = PPTP_CALL_NONE;
}
static inline int
-pptp_outbound_pkt(struct tcphdr *tcph,
- struct pptp_pkt_hdr *pptph,
+pptp_outbound_pkt(struct sk_buff *skb,
+ struct tcphdr *tcph,
+ unsigned int ctlhoff,
size_t datalen,
- struct ip_conntrack *ct,
- enum ip_conntrack_info ctinfo)
+ struct ip_conntrack *ct)
{
- struct PptpControlHeader *ctlh;
- union pptp_ctrl_union pptpReq;
+ 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, *cid, *pcid;
- ctlh = (struct PptpControlHeader *) ((void *) pptph + sizeof(*pptph));
- pptpReq.rawreq = (void *) ((void *) ctlh + sizeof(*ctlh));
+ ctlh = skb_header_pointer(skb, ctlhoff, sizeof(_ctlh), &_ctlh);
+ if (!ctlh)
+ return NF_ACCEPT;
+
+ reqlen = datalen - sizeof(struct pptp_pkt_hdr) - sizeof(_ctlh);
+ pptpReq = skb_header_pointer(skb, ctlhoff+sizeof(_ctlh), reqlen,
+ &_pptpReq);
+ if (!pptpReq)
+ return NF_ACCEPT;
msg = ntohs(ctlh->messageType);
DEBUGP("outbound control message %s\n", strMName[msg]);
break;
case PPTP_OUT_CALL_REQUEST:
+ if (reqlen < sizeof(_pptpReq.ocreq)) {
+ DEBUGP("%s: short packet\n", strMName[msg]);
+ /* FIXME: break; */
+ }
+
/* client initiating connection to server */
if (info->sstate != PPTP_SESSION_CONFIRMED) {
DEBUGP("%s but no session\n",
}
info->cstate = PPTP_CALL_OUT_REQ;
/* track PNS call id */
- cid = &pptpReq.ocreq->callID;
+ cid = &pptpReq->ocreq.callID;
DEBUGP("%s, CID=%X\n", strMName[msg], ntohs(*cid));
info->pns_call_id = ntohs(*cid);
break;
case PPTP_IN_CALL_REPLY:
+ if (reqlen < sizeof(_pptpReq.icack)) {
+ DEBUGP("%s: short packet\n", strMName[msg]);
+ break;
+ }
+
/* client answers incoming call */
if (info->cstate != PPTP_CALL_IN_REQ
&& info->cstate != PPTP_CALL_IN_REP) {
strMName[msg]);
break;
}
- if (pptpReq.icack->resultCode != PPTP_INCALL_ACCEPT) {
+ if (pptpReq->icack.resultCode != PPTP_INCALL_ACCEPT) {
info->cstate = PPTP_CALL_NONE;
break;
}
- pcid = &pptpReq.icack->peersCallID;
+ pcid = &pptpReq->icack.peersCallID;
if (info->pac_call_id != ntohs(*pcid)) {
DEBUGP("%s for unknown call %u\n",
strMName[msg], ntohs(*pcid));
DEBUGP("%s, CID=%X\n", strMName[msg], ntohs(*pcid));
/* part two of the three-way handshake */
info->cstate = PPTP_CALL_IN_REP;
- info->pns_call_id = ntohs(pptpReq.icack->callID);
+ info->pns_call_id = ntohs(pptpReq->icack.callID);
break;
case PPTP_CALL_CLEAR_REQUEST:
struct ip_conntrack *ct, enum ip_conntrack_info ctinfo)
{
- struct pptp_pkt_hdr *pptph;
+ struct pptp_pkt_hdr _pptph, *pptph;
- struct tcphdr *tcph = (void *) skb->nh.iph + skb->nh.iph->ihl * 4;
+ struct tcphdr _tcph, *tcph;
u_int32_t tcplen = skb->len - skb->nh.iph->ihl * 4;
- u_int32_t datalen = tcplen - tcph->doff * 4;
+ u_int32_t datalen;
void *datalimit;
int dir = CTINFO2DIR(ctinfo);
struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info;
+ unsigned int nexthdr_off;
int oldsstate, oldcstate;
int ret;
return NF_ACCEPT;
}
+ nexthdr_off = skb->nh.iph->ihl*4;
+ tcph = skb_header_pointer(skb, skb->nh.iph->ihl*4, sizeof(_tcph),
+ &_tcph);
+ if (!tcph)
+ return NF_ACCEPT;
+
/* not a complete TCP header? */
if (tcplen < sizeof(struct tcphdr) || tcplen < tcph->doff * 4) {
DEBUGP("tcplen = %u\n", tcplen);
return NF_ACCEPT;
}
+
+ datalen = tcplen - tcph->doff * 4;
+
/* checksum invalid? */
if (tcp_v4_check(tcph, tcplen, skb->nh.iph->saddr, skb->nh.iph->daddr,
csum_partial((char *) tcph, tcplen, 0))) {
pptp_timeout_related(ct);
}
-
- pptph = (struct pptp_pkt_hdr *) ((void *) tcph + tcph->doff * 4);
- datalimit = (void *) pptph + datalen;
-
- /* not a full pptp packet header? */
- if ((void *) pptph+sizeof(*pptph) >= datalimit) {
+ nexthdr_off += tcph->doff*4;
+ pptph = skb_header_pointer(skb, skb->nh.iph->ihl*4 + tcph->doff*4,
+ sizeof(_pptph), &_pptph);
+ if (!pptph) {
DEBUGP("no full PPTP header, can't track\n");
return NF_ACCEPT;
}
-
+
+ datalimit = (void *) pptph + datalen;
+
/* if it's not a control message we can't do anything with it */
- if (ntohs(pptph->packetType) != PPTP_PACKET_CONTROL ||
+ if (ntohs(pptph->packetType) != PPTP_PACKET_CONTROL ||
ntohl(pptph->magicCookie) != PPTP_MAGIC_COOKIE) {
DEBUGP("not a control packet\n");
return NF_ACCEPT;
LOCK_BH(&ip_pptp_lock);
+ nexthdr_off += sizeof(_pptph);
/* FIXME: We just blindly assume that the control connection is always
* established from PNS->PAC. However, RFC makes no guarantee */
if (dir == IP_CT_DIR_ORIGINAL)
/* client -> server (PNS -> PAC) */
- ret = pptp_outbound_pkt(tcph, pptph, datalen, ct, ctinfo);
+ ret = pptp_outbound_pkt(skb, tcph, nexthdr_off, datalen, ct);
else
/* server -> client (PAC -> PNS) */
- ret = pptp_inbound_pkt(tcph, pptph, datalen, ct, ctinfo);
+ ret = pptp_inbound_pkt(skb, tcph, nexthdr_off, datalen, ct);
DEBUGP("sstate: %d->%d, cstate: %d->%d\n",
oldsstate, info->sstate, oldcstate, info->cstate);
UNLOCK_BH(&ip_pptp_lock);
int retcode;
DEBUGP(__FILE__ ": registering helper\n");
- return 0;
if ((retcode = ip_conntrack_helper_register(&pptp))) {
- printk(KERN_ERR "Unable to register conntrack application "
+ printk(KERN_ERR "Unable to register conntrack application "
"helper for pptp: %d\n", retcode);
return -EIO;
}
static void __exit fini(void)
{
- return 0;
ip_conntrack_helper_unregister(&pptp);
printk("ip_conntrack_pptp version %s unloaded\n", IP_CT_PPTP_VERSION);
}