patch-o-matic-ng-20050103 pptp-conntrack-nat
authorMark Huang <mlhuang@cs.princeton.edu>
Mon, 10 Jan 2005 18:29:17 +0000 (18:29 +0000)
committerMark Huang <mlhuang@cs.princeton.edu>
Mon, 10 Jan 2005 18:29:17 +0000 (18:29 +0000)
include/linux/netfilter_ipv4/ip_conntrack_pptp.h [new file with mode: 0644]
include/linux/netfilter_ipv4/ip_conntrack_proto_gre.h [new file with mode: 0644]
include/linux/netfilter_ipv4/ip_nat_pptp.h [new file with mode: 0644]
net/ipv4/netfilter/ip_conntrack_pptp.c [new file with mode: 0644]
net/ipv4/netfilter/ip_conntrack_pptp_priv.h [new file with mode: 0644]
net/ipv4/netfilter/ip_conntrack_proto_gre.c [new file with mode: 0644]
net/ipv4/netfilter/ip_nat_pptp.c [new file with mode: 0644]
net/ipv4/netfilter/ip_nat_proto_gre.c [new file with mode: 0644]

diff --git a/include/linux/netfilter_ipv4/ip_conntrack_pptp.h b/include/linux/netfilter_ipv4/ip_conntrack_pptp.h
new file mode 100644 (file)
index 0000000..0fbec88
--- /dev/null
@@ -0,0 +1,310 @@
+/* PPTP constants and structs */
+#ifndef _CONNTRACK_PPTP_H
+#define _CONNTRACK_PPTP_H
+
+/* state of the control session */
+enum pptp_ctrlsess_state {
+       PPTP_SESSION_NONE,                      /* no session present */
+       PPTP_SESSION_ERROR,                     /* some session error */
+       PPTP_SESSION_STOPREQ,                   /* stop_sess request seen */
+       PPTP_SESSION_REQUESTED,                 /* start_sess request seen */
+       PPTP_SESSION_CONFIRMED,                 /* session established */
+};
+
+/* state of the call inside the control session */
+enum pptp_ctrlcall_state {
+       PPTP_CALL_NONE,
+       PPTP_CALL_ERROR,
+       PPTP_CALL_OUT_REQ,
+       PPTP_CALL_OUT_CONF,
+       PPTP_CALL_IN_REQ,
+       PPTP_CALL_IN_REP,
+       PPTP_CALL_IN_CONF,
+       PPTP_CALL_CLEAR_REQ,
+};
+
+
+/* conntrack private data */
+struct ip_ct_pptp_master {
+       enum pptp_ctrlsess_state sstate;        /* session state */
+
+       /* everything below is going to be per-expectation in newnat,
+        * since there could be more than one call within one session */
+       enum pptp_ctrlcall_state cstate;        /* call state */
+       u_int16_t pac_call_id;                  /* call id of PAC, host byte order */
+       u_int16_t pns_call_id;                  /* call id of PNS, host byte order */
+};
+
+/* conntrack_expect private member */
+struct ip_ct_pptp_expect {
+       enum pptp_ctrlcall_state cstate;        /* call state */
+       u_int16_t pac_call_id;                  /* call id of PAC */
+       u_int16_t pns_call_id;                  /* call id of PNS */
+};
+
+
+#ifdef __KERNEL__
+
+#include <linux/netfilter_ipv4/lockhelp.h>
+DECLARE_LOCK_EXTERN(ip_pptp_lock);
+
+#define IP_CONNTR_PPTP         PPTP_CONTROL_PORT
+
+#define PPTP_CONTROL_PORT      1723
+
+#define PPTP_PACKET_CONTROL    1
+#define PPTP_PACKET_MGMT       2
+
+#define PPTP_MAGIC_COOKIE      0x1a2b3c4d
+
+struct pptp_pkt_hdr {
+       __u16   packetLength;
+       __u16   packetType;
+       __u32   magicCookie;
+};
+
+/* PptpControlMessageType values */
+#define PPTP_START_SESSION_REQUEST     1
+#define PPTP_START_SESSION_REPLY       2
+#define PPTP_STOP_SESSION_REQUEST      3
+#define PPTP_STOP_SESSION_REPLY                4
+#define PPTP_ECHO_REQUEST              5
+#define PPTP_ECHO_REPLY                        6
+#define PPTP_OUT_CALL_REQUEST          7
+#define PPTP_OUT_CALL_REPLY            8
+#define PPTP_IN_CALL_REQUEST           9
+#define PPTP_IN_CALL_REPLY             10
+#define PPTP_IN_CALL_CONNECT           11
+#define PPTP_CALL_CLEAR_REQUEST                12
+#define PPTP_CALL_DISCONNECT_NOTIFY    13
+#define PPTP_WAN_ERROR_NOTIFY          14
+#define PPTP_SET_LINK_INFO             15
+
+#define PPTP_MSG_MAX                   15
+
+/* PptpGeneralError values */
+#define PPTP_ERROR_CODE_NONE           0
+#define PPTP_NOT_CONNECTED             1
+#define PPTP_BAD_FORMAT                        2
+#define PPTP_BAD_VALUE                 3
+#define PPTP_NO_RESOURCE               4
+#define PPTP_BAD_CALLID                        5
+#define PPTP_REMOVE_DEVICE_ERROR       6
+
+struct PptpControlHeader {
+       __u16   messageType;
+       __u16   reserved;
+};
+
+/* FramingCapability Bitmap Values */
+#define PPTP_FRAME_CAP_ASYNC           0x1
+#define PPTP_FRAME_CAP_SYNC            0x2
+
+/* BearerCapability Bitmap Values */
+#define PPTP_BEARER_CAP_ANALOG         0x1
+#define PPTP_BEARER_CAP_DIGITAL                0x2
+
+struct PptpStartSessionRequest {
+       __u16   protocolVersion;
+       __u8    reserved1;
+       __u8    reserved2;
+       __u32   framingCapability;
+       __u32   bearerCapability;
+       __u16   maxChannels;
+       __u16   firmwareRevision;
+       __u8    hostName[64];
+       __u8    vendorString[64];
+};
+
+/* PptpStartSessionResultCode Values */
+#define PPTP_START_OK                  1
+#define PPTP_START_GENERAL_ERROR       2
+#define PPTP_START_ALREADY_CONNECTED   3
+#define PPTP_START_NOT_AUTHORIZED      4
+#define PPTP_START_UNKNOWN_PROTOCOL    5
+
+struct PptpStartSessionReply {
+       __u16   protocolVersion;
+       __u8    resultCode;
+       __u8    generalErrorCode;
+       __u32   framingCapability;
+       __u32   bearerCapability;
+       __u16   maxChannels;
+       __u16   firmwareRevision;
+       __u8    hostName[64];
+       __u8    vendorString[64];
+};
+
+/* PptpStopReasons */
+#define PPTP_STOP_NONE                 1
+#define PPTP_STOP_PROTOCOL             2
+#define PPTP_STOP_LOCAL_SHUTDOWN       3
+
+struct PptpStopSessionRequest {
+       __u8    reason;
+};
+
+/* PptpStopSessionResultCode */
+#define PPTP_STOP_OK                   1
+#define PPTP_STOP_GENERAL_ERROR                2
+
+struct PptpStopSessionReply {
+       __u8    resultCode;
+       __u8    generalErrorCode;
+};
+
+struct PptpEchoRequest {
+       __u32 identNumber;
+};
+
+/* PptpEchoReplyResultCode */
+#define PPTP_ECHO_OK                   1
+#define PPTP_ECHO_GENERAL_ERROR                2
+
+struct PptpEchoReply {
+       __u32   identNumber;
+       __u8    resultCode;
+       __u8    generalErrorCode;
+       __u16   reserved;
+};
+
+/* PptpFramingType */
+#define PPTP_ASYNC_FRAMING             1
+#define PPTP_SYNC_FRAMING              2
+#define PPTP_DONT_CARE_FRAMING         3
+
+/* PptpCallBearerType */
+#define PPTP_ANALOG_TYPE               1
+#define PPTP_DIGITAL_TYPE              2
+#define PPTP_DONT_CARE_BEARER_TYPE     3
+
+struct PptpOutCallRequest {
+       __u16   callID;
+       __u16   callSerialNumber;
+       __u32   minBPS;
+       __u32   maxBPS;
+       __u32   bearerType;
+       __u32   framingType;
+       __u16   packetWindow;
+       __u16   packetProcDelay;
+       __u16   reserved1;
+       __u16   phoneNumberLength;
+       __u16   reserved2;
+       __u8    phoneNumber[64];
+       __u8    subAddress[64];
+};
+
+/* PptpCallResultCode */
+#define PPTP_OUTCALL_CONNECT           1
+#define PPTP_OUTCALL_GENERAL_ERROR     2
+#define PPTP_OUTCALL_NO_CARRIER                3
+#define PPTP_OUTCALL_BUSY              4
+#define PPTP_OUTCALL_NO_DIAL_TONE      5
+#define PPTP_OUTCALL_TIMEOUT           6
+#define PPTP_OUTCALL_DONT_ACCEPT       7
+
+struct PptpOutCallReply {
+       __u16   callID;
+       __u16   peersCallID;
+       __u8    resultCode;
+       __u8    generalErrorCode;
+       __u16   causeCode;
+       __u32   connectSpeed;
+       __u16   packetWindow;
+       __u16   packetProcDelay;
+       __u32   physChannelID;
+};
+
+struct PptpInCallRequest {
+       __u16   callID;
+       __u16   callSerialNumber;
+       __u32   callBearerType;
+       __u32   physChannelID;
+       __u16   dialedNumberLength;
+       __u16   dialingNumberLength;
+       __u8    dialedNumber[64];
+       __u8    dialingNumber[64];
+       __u8    subAddress[64];
+};
+
+/* PptpInCallResultCode */
+#define PPTP_INCALL_ACCEPT             1
+#define PPTP_INCALL_GENERAL_ERROR      2
+#define PPTP_INCALL_DONT_ACCEPT                3
+
+struct PptpInCallReply {
+       __u16   callID;
+       __u16   peersCallID;
+       __u8    resultCode;
+       __u8    generalErrorCode;
+       __u16   packetWindow;
+       __u16   packetProcDelay;
+       __u16   reserved;
+};
+
+struct PptpInCallConnected {
+       __u16   peersCallID;
+       __u16   reserved;
+       __u32   connectSpeed;
+       __u16   packetWindow;
+       __u16   packetProcDelay;
+       __u32   callFramingType;
+};
+
+struct PptpClearCallRequest {
+       __u16   callID;
+       __u16   reserved;
+};
+
+struct PptpCallDisconnectNotify {
+       __u16   callID;
+       __u8    resultCode;
+       __u8    generalErrorCode;
+       __u16   causeCode;
+       __u16   reserved;
+       __u8    callStatistics[128];
+};
+
+struct PptpWanErrorNotify {
+       __u16   peersCallID;
+       __u16   reserved;
+       __u32   crcErrors;
+       __u32   framingErrors;
+       __u32   hardwareOverRuns;
+       __u32   bufferOverRuns;
+       __u32   timeoutErrors;
+       __u32   alignmentErrors;
+};
+
+struct PptpSetLinkInfo {
+       __u16   peersCallID;
+       __u16   reserved;
+       __u32   sendAccm;
+       __u32   recvAccm;
+};
+
+
+struct pptp_priv_data {
+       __u16   call_id;
+       __u16   mcall_id;
+       __u16   pcall_id;
+};
+
+union pptp_ctrl_union {
+               struct PptpStartSessionRequest  sreq;
+               struct PptpStartSessionReply    srep;
+               struct PptpStopSessionRequest   streq;
+               struct PptpStopSessionReply     strep;
+                struct PptpOutCallRequest       ocreq;
+                struct PptpOutCallReply         ocack;
+                struct PptpInCallRequest        icreq;
+                struct PptpInCallReply          icack;
+                struct PptpInCallConnected      iccon;
+               struct PptpClearCallRequest     clrreq;
+                struct PptpCallDisconnectNotify disc;
+                struct PptpWanErrorNotify       wanerr;
+                struct PptpSetLinkInfo          setlink;
+};
+
+#endif /* __KERNEL__ */
+#endif /* _CONNTRACK_PPTP_H */
diff --git a/include/linux/netfilter_ipv4/ip_conntrack_proto_gre.h b/include/linux/netfilter_ipv4/ip_conntrack_proto_gre.h
new file mode 100644 (file)
index 0000000..0764685
--- /dev/null
@@ -0,0 +1,123 @@
+#ifndef _CONNTRACK_PROTO_GRE_H
+#define _CONNTRACK_PROTO_GRE_H
+#include <asm/byteorder.h>
+
+/* GRE PROTOCOL HEADER */
+
+/* GRE Version field */
+#define GRE_VERSION_1701       0x0
+#define GRE_VERSION_PPTP       0x1
+
+/* GRE Protocol field */
+#define GRE_PROTOCOL_PPTP      0x880B
+
+/* GRE Flags */
+#define GRE_FLAG_C             0x80
+#define GRE_FLAG_R             0x40
+#define GRE_FLAG_K             0x20
+#define GRE_FLAG_S             0x10
+#define GRE_FLAG_A             0x80
+
+#define GRE_IS_C(f)    ((f)&GRE_FLAG_C)
+#define GRE_IS_R(f)    ((f)&GRE_FLAG_R)
+#define GRE_IS_K(f)    ((f)&GRE_FLAG_K)
+#define GRE_IS_S(f)    ((f)&GRE_FLAG_S)
+#define GRE_IS_A(f)    ((f)&GRE_FLAG_A)
+
+/* GRE is a mess: Four different standards */
+struct gre_hdr {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+       __u16   rec:3,
+               srr:1,
+               seq:1,
+               key:1,
+               routing:1,
+               csum:1,
+               version:3,
+               reserved:4,
+               ack:1;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+       __u16   csum:1,
+               routing:1,
+               key:1,
+               seq:1,
+               srr:1,
+               rec:3,
+               ack:1,
+               reserved:4,
+               version:3;
+#else
+#error "Adjust your <asm/byteorder.h> defines"
+#endif
+       __u16   protocol;
+};
+
+/* modified GRE header for PPTP */
+struct gre_hdr_pptp {
+       __u8  flags;            /* bitfield */
+       __u8  version;          /* should be GRE_VERSION_PPTP */
+       __u16 protocol;         /* should be GRE_PROTOCOL_PPTP */
+       __u16 payload_len;      /* size of ppp payload, not inc. gre header */
+       __u16 call_id;          /* peer's call_id for this session */
+       __u32 seq;              /* sequence number.  Present if S==1 */
+       __u32 ack;              /* seq number of highest packet recieved by */
+                               /*  sender in this session */
+};
+
+
+/* this is part of ip_conntrack */
+struct ip_ct_gre {
+       unsigned int stream_timeout;
+       unsigned int timeout;
+};
+
+/* this is part of ip_conntrack_expect */
+struct ip_ct_gre_expect {
+       struct ip_ct_gre_keymap *keymap_orig, *keymap_reply;
+};
+
+#ifdef __KERNEL__
+struct ip_conntrack_expect;
+
+/* structure for original <-> reply keymap */
+struct ip_ct_gre_keymap {
+       struct list_head list;
+
+       struct ip_conntrack_tuple tuple;
+};
+
+
+/* add new tuple->key_reply pair to keymap */
+int ip_ct_gre_keymap_add(struct ip_conntrack_expect *exp,
+                        struct ip_conntrack_tuple *t,
+                        int reply);
+
+/* change an existing keymap entry */
+void ip_ct_gre_keymap_change(struct ip_ct_gre_keymap *km,
+                            struct ip_conntrack_tuple *t);
+
+/* delete keymap entries */
+void ip_ct_gre_keymap_destroy(struct ip_conntrack_expect *exp);
+
+
+/* get pointer to gre key, if present */
+static inline u_int32_t *gre_key(struct gre_hdr *greh)
+{
+       if (!greh->key)
+               return NULL;
+       if (greh->csum || greh->routing)
+               return (u_int32_t *) (greh+sizeof(*greh)+4);
+       return (u_int32_t *) (greh+sizeof(*greh));
+}
+
+/* get pointer ot gre csum, if present */
+static inline u_int16_t *gre_csum(struct gre_hdr *greh)
+{
+       if (!greh->csum)
+               return NULL;
+       return (u_int16_t *) (greh+sizeof(*greh));
+}
+
+#endif /* __KERNEL__ */
+
+#endif /* _CONNTRACK_PROTO_GRE_H */
diff --git a/include/linux/netfilter_ipv4/ip_nat_pptp.h b/include/linux/netfilter_ipv4/ip_nat_pptp.h
new file mode 100644 (file)
index 0000000..eaf66c2
--- /dev/null
@@ -0,0 +1,11 @@
+/* PPTP constants and structs */
+#ifndef _NAT_PPTP_H
+#define _NAT_PPTP_H
+
+/* conntrack private data */
+struct ip_nat_pptp {
+       u_int16_t pns_call_id;          /* NAT'ed PNS call id */
+       u_int16_t pac_call_id;          /* NAT'ed PAC call id */
+};
+
+#endif /* _NAT_PPTP_H */
diff --git a/net/ipv4/netfilter/ip_conntrack_pptp.c b/net/ipv4/netfilter/ip_conntrack_pptp.c
new file mode 100644 (file)
index 0000000..e85e0df
--- /dev/null
@@ -0,0 +1,711 @@
+/*
+ * 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.
+ * It is a specification defined by Microsoft and some vendors
+ * working with Microsoft.  PPTP is built on top of a modified
+ * version of the Internet Generic Routing Encapsulation Protocol.
+ * 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>
+ *
+ * Development of this code funded by Astaro AG (http://www.astaro.com/)
+ *
+ * Limitations:
+ *      - We blindly assume that control connections are always
+ *        established in PNS->PAC direction.  This is a violation
+ *        of RFFC2673
+ *
+ * TODO: - finish support for multiple calls within one session
+ *        (needs expect reservations in newnat)
+ *      - testing of incoming PPTP calls 
+ *
+ * Changes: 
+ *     2002-02-05 - Version 1.3
+ *       - Call ip_conntrack_unexpect_related() from 
+ *         pptp_timeout_related() to destroy expectations in case
+ *         CALL_DISCONNECT_NOTIFY or tcp fin packet was seen
+ *         (Philip Craig <philipc@snapgear.com>)
+ *       - Add Version information at module loadtime
+ *     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
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <net/checksum.h>
+#include <net/tcp.h>
+
+#include <linux/netfilter_ipv4/lockhelp.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h>
+#include <linux/netfilter_ipv4/ip_conntrack_pptp.h>
+
+#define IP_CT_PPTP_VERSION "2.0"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("Netfilter connection tracking helper module for PPTP");
+
+DECLARE_LOCK(ip_pptp_lock);
+
+#if 0
+#include "ip_conntrack_pptp_priv.h"
+#define DEBUGP(format, args...)        printk(KERN_DEBUG __FILE__ ":" __FUNCTION__ \
+                                       ": " format, ## args)
+#else
+#define DEBUGP(format, args...)
+#endif
+
+#define SECS *HZ
+#define MINS * 60 SECS
+#define HOURS * 60 MINS
+#define DAYS * 24 HOURS
+
+#define PPTP_GRE_TIMEOUT               (10 MINS)
+#define PPTP_GRE_STREAM_TIMEOUT        (5 DAYS)
+
+static int pptp_expectfn(struct ip_conntrack *ct)
+{
+       struct ip_conntrack *master;
+       struct ip_conntrack_expect *exp;
+
+       DEBUGP("increasing timeouts\n");
+       /* increase timeout of GRE data channel conntrack entry */
+       ct->proto.gre.timeout = PPTP_GRE_TIMEOUT;
+       ct->proto.gre.stream_timeout = PPTP_GRE_STREAM_TIMEOUT;
+
+       master = master_ct(ct);
+       if (!master) {
+               DEBUGP(" no master!!!\n");
+               return 0;
+       }
+
+       exp = ct->master;
+       if (!exp) {
+               DEBUGP("no expectation!!\n");
+               return 0;
+       }
+
+       DEBUGP("completing tuples with ct info\n");
+       /* we can do this, since we're unconfirmed */
+       if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.gre.key == 
+               htonl(master->help.ct_pptp_info.pac_call_id)) { 
+               /* assume PNS->PAC */
+               ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key = 
+                       htonl(master->help.ct_pptp_info.pns_call_id);
+               ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key =
+                       htonl(master->help.ct_pptp_info.pns_call_id);
+       } else {
+               /* assume PAC->PNS */
+               ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key =
+                       htonl(master->help.ct_pptp_info.pac_call_id);
+               ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key =
+                       htonl(master->help.ct_pptp_info.pac_call_id);
+       }
+       
+       /* delete other expectation */
+       if (exp->expected_list.next != &exp->expected_list) {
+               struct ip_conntrack_expect *other_exp;
+               struct list_head *cur_item, *next;
+
+               for (cur_item = master->sibling_list.next;
+                    cur_item != &master->sibling_list; cur_item = next) {
+                       next = cur_item->next;
+                       other_exp = list_entry(cur_item,
+                                              struct ip_conntrack_expect,
+                                              expected_list);
+                       /* remove only if occurred at same sequence number */
+                       if (other_exp != exp && other_exp->seq == exp->seq) {
+                               DEBUGP("unexpecting other direction\n");
+                               ip_ct_gre_keymap_destroy(other_exp);
+                               ip_conntrack_unexpect_related(other_exp);
+                       }
+               }
+       }
+
+       return 0;
+}
+
+/* timeout GRE data connections */
+static int pptp_timeout_related(struct ip_conntrack *ct)
+{
+       struct list_head *cur_item, *next;
+       struct ip_conntrack_expect *exp;
+
+       /* FIXME: do we have to lock something ? */
+       for (cur_item = ct->sibling_list.next;
+           cur_item != &ct->sibling_list; cur_item = next) {
+               next = cur_item->next;
+               exp = list_entry(cur_item, struct ip_conntrack_expect,
+                                expected_list);
+
+               ip_ct_gre_keymap_destroy(exp);
+               if (!exp->sibling) {
+                       ip_conntrack_unexpect_related(exp);
+                       continue;
+               }
+
+               DEBUGP("setting timeout of conntrack %p to 0\n",
+                       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);
+       }
+
+       return 0;
+}
+
+/* expect GRE connections (PNS->PAC and PAC->PNS direction) */
+static inline int
+exp_gre(struct ip_conntrack *master,
+       u_int32_t seq,
+       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.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.ip = 0xffffffff;
+       exp.mask.dst.protonum = 0xffff;
+                       
+       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);
+
+       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;
+       }
+       
+       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));
+
+       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 :( */
+       
+       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;
+       }
+
+       return 0;
+}
+
+static inline int 
+pptp_inbound_pkt(struct sk_buff *skb,
+                struct tcphdr *tcph,
+                unsigned int ctlhoff,
+                size_t datalen,
+                struct ip_conntrack *ct)
+{
+       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 = 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(struct pptp_pkt_hdr),
+                                    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)
+                       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)
+                       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]);
+                       break;
+               }
+               if (info->cstate != PPTP_CALL_OUT_REQ &&
+                   info->cstate != PPTP_CALL_OUT_CONF) {
+                       DEBUGP("%s without OUTCALL_REQ\n", strMName[msg]);
+                       break;
+               }
+               if (pptpReq->ocack.resultCode != PPTP_OUTCALL_CONNECT) {
+                       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",
+                               strMName[msg], ntohs(*pcid));
+                       break;
+               }
+
+               DEBUGP("%s, CID=%X, PCID=%X\n", strMName[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", 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;
+               DEBUGP("%s, PCID=%X\n", strMName[msg], ntohs(*pcid));
+               info->cstate = PPTP_CALL_IN_REQ;
+               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;
+               }
+               if (info->sstate != PPTP_CALL_IN_REP
+                   && info->sstate != PPTP_CALL_IN_CONF) {
+                       DEBUGP("%s but never sent IN_CALL_REPLY\n",
+                               strMName[msg]);
+                       break;
+               }
+
+               pcid = &pptpReq->iccon.peersCallID;
+               cid = &info->pac_call_id;
+
+               if (info->pns_call_id != ntohs(*pcid)) {
+                       DEBUGP("%s for unknown CallID %u\n", 
+                               strMName[msg], ntohs(*cid));
+                       break;
+               }
+
+               DEBUGP("%s, PCID=%X\n", strMName[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");
+
+               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;
+               DEBUGP("%s, CID=%X\n", strMName[msg], ntohs(*cid));
+               info->cstate = PPTP_CALL_NONE;
+
+               /* untrack this call id, unexpect GRE packets */
+               pptp_timeout_related(ct);
+               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)
+                       ? strMName[msg]:strMName[0], msg);
+               break;
+       }
+
+       return NF_ACCEPT;
+
+}
+
+static inline int
+pptp_outbound_pkt(struct sk_buff *skb,
+                 struct tcphdr *tcph,
+                 unsigned int ctlhoff,
+                 size_t datalen,
+                 struct ip_conntrack *ct)
+{
+       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 = 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]);
+
+       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",
+                               strMName[msg]);
+               }
+               info->sstate = PPTP_SESSION_REQUESTED;
+               break;
+       case PPTP_STOP_SESSION_REQUEST:
+               /* client requests end of control session */
+               info->sstate = PPTP_SESSION_STOPREQ;
+               break;
+
+       case PPTP_OUT_CALL_REQUEST:
+               if (reqlen < sizeof(_pptpReq.ocreq)) {
+                       DEBUGP("%s: short packet\n", strMName[msg]);
+                       break;
+               }
+
+               /* client initiating connection to server */
+               if (info->sstate != PPTP_SESSION_CONFIRMED) {
+                       DEBUGP("%s but no session\n",
+                               strMName[msg]);
+                       break;
+               }
+               info->cstate = PPTP_CALL_OUT_REQ;
+               /* track PNS call id */
+               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) {
+                       DEBUGP("%s without incall_req\n", 
+                               strMName[msg]);
+                       break;
+               }
+               if (pptpReq->icack.resultCode != PPTP_INCALL_ACCEPT) {
+                       info->cstate = PPTP_CALL_NONE;
+                       break;
+               }
+               pcid = &pptpReq->icack.peersCallID;
+               if (info->pac_call_id != ntohs(*pcid)) {
+                       DEBUGP("%s for unknown call %u\n", 
+                               strMName[msg], ntohs(*pcid));
+                       break;
+               }
+               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);
+               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;
+               }
+               /* 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)? 
+                       strMName[msg]:strMName[0], msg);
+               /* unknown: no need to create GRE masq table entry */
+               break;
+       }
+
+       return NF_ACCEPT;
+}
+
+
+/* track caller id inside control connection, call expect_related */
+static int 
+conntrack_pptp_help(struct sk_buff *skb,
+                   struct ip_conntrack *ct, enum ip_conntrack_info ctinfo)
+
+{
+       struct pptp_pkt_hdr _pptph, *pptph;
+       
+       struct tcphdr _tcph, *tcph;
+       u_int32_t tcplen = skb->len - skb->nh.iph->ihl * 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;
+
+       /* don't do any tracking before tcp handshake complete */
+       if (ctinfo != IP_CT_ESTABLISHED 
+           && ctinfo != IP_CT_ESTABLISHED+IP_CT_IS_REPLY) {
+               DEBUGP("ctinfo = %u, skipping\n", ctinfo);
+               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))) {
+               printk(KERN_NOTICE __FILE__ ": bad csum\n");
+               /* W2K PPTP server sends TCP packets with wrong checksum :(( */
+               //return NF_ACCEPT;
+       }
+
+       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_timeout_related(ct);
+       }
+
+       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 ||
+           ntohl(pptph->magicCookie) != PPTP_MAGIC_COOKIE) {
+               DEBUGP("not a control packet\n");
+               return NF_ACCEPT;
+       }
+
+       oldsstate = info->sstate;
+       oldcstate = info->cstate;
+
+       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(skb, tcph, nexthdr_off, datalen, ct);
+       else
+               /* server -> client (PAC -> PNS) */
+               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);
+
+       return ret;
+}
+
+/* control protocol helper */
+static struct ip_conntrack_helper pptp = { 
+       .list = { NULL, NULL },
+       .name = "pptp", 
+       .flags = IP_CT_HELPER_F_REUSE_EXPECT,
+       .me = THIS_MODULE,
+       .max_expected = 2,
+       .timeout = 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 = 0xffff } } 
+                        }, 
+                 .dst = { .ip = 0, 
+                          .u = { .all = 0 },
+                          .protonum = 0xffff 
+                        } 
+               },
+       .help = conntrack_pptp_help
+};
+
+/* ip_conntrack_pptp initialization */
+static int __init init(void)
+{
+       int retcode;
+
+       DEBUGP(__FILE__ ": registering helper\n");
+       if ((retcode = ip_conntrack_helper_register(&pptp))) {
+               printk(KERN_ERR "Unable to register conntrack application "
+                               "helper for pptp: %d\n", retcode);
+               return -EIO;
+       }
+
+       printk("ip_conntrack_pptp version %s loaded\n", IP_CT_PPTP_VERSION);
+       return 0;
+}
+
+static void __exit fini(void)
+{
+       ip_conntrack_helper_unregister(&pptp);
+       printk("ip_conntrack_pptp version %s unloaded\n", IP_CT_PPTP_VERSION);
+}
+
+module_init(init);
+module_exit(fini);
+
+EXPORT_SYMBOL(ip_pptp_lock);
diff --git a/net/ipv4/netfilter/ip_conntrack_pptp_priv.h b/net/ipv4/netfilter/ip_conntrack_pptp_priv.h
new file mode 100644 (file)
index 0000000..6b52564
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef _IP_CT_PPTP_PRIV_H
+#define _IP_CT_PPTP_PRIV_H
+
+/* PptpControlMessageType names */
+static const char *strMName[] = {
+       "UNKNOWN_MESSAGE",
+       "START_SESSION_REQUEST",
+       "START_SESSION_REPLY",
+       "STOP_SESSION_REQUEST",
+       "STOP_SESSION_REPLY",
+       "ECHO_REQUEST",
+       "ECHO_REPLY",
+       "OUT_CALL_REQUEST",
+       "OUT_CALL_REPLY",
+       "IN_CALL_REQUEST",
+       "IN_CALL_REPLY",
+       "IN_CALL_CONNECT",
+       "CALL_CLEAR_REQUEST",
+       "CALL_DISCONNECT_NOTIFY",
+       "WAN_ERROR_NOTIFY",
+       "SET_LINK_INFO"
+};
+
+#endif
diff --git a/net/ipv4/netfilter/ip_conntrack_proto_gre.c b/net/ipv4/netfilter/ip_conntrack_proto_gre.c
new file mode 100644 (file)
index 0000000..2694e95
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+ * ip_conntrack_proto_gre.c - Version 2.0 
+ *
+ * Connection tracking protocol helper module for GRE.
+ *
+ * GRE is a generic encapsulation protocol, which is generally not very
+ * suited for NAT, as it has no protocol-specific part as port numbers.
+ *
+ * It has an optional key field, which may help us distinguishing two 
+ * connections between the same two hosts.
+ *
+ * GRE is defined in RFC 1701 and RFC 1702, as well as RFC 2784 
+ *
+ * PPTP is built on top of a modified version of GRE, and has a mandatory
+ * field called "CallID", which serves us for the same purpose as the key
+ * field in plain GRE.
+ *
+ * Documentation about PPTP can be found in RFC 2637
+ *
+ * (C) 2000-2004 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Development of this code funded by Astaro AG (http://www.astaro.com/)
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <linux/in.h>
+#include <linux/list.h>
+
+#include <linux/netfilter_ipv4/lockhelp.h>
+
+DECLARE_RWLOCK(ip_ct_gre_lock);
+#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_ct_gre_lock)
+#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_ct_gre_lock)
+
+#include <linux/netfilter_ipv4/listhelp.h>
+#include <linux/netfilter_ipv4/ip_conntrack_protocol.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_core.h>
+
+#include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h>
+#include <linux/netfilter_ipv4/ip_conntrack_pptp.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("netfilter connection tracking protocol helper for GRE");
+
+/* shamelessly stolen from ip_conntrack_proto_udp.c */
+#define GRE_TIMEOUT            (30*HZ)
+#define GRE_STREAM_TIMEOUT     (180*HZ)
+
+#if 0
+#define DEBUGP(format, args...) printk(KERN_DEBUG __FILE__ ":" __FUNCTION__ \
+                                      ": " format, ## args)
+#define DUMP_TUPLE_GRE(x) printk("%u.%u.%u.%u:0x%x -> %u.%u.%u.%u:0x%x:%u:0x%x\n", \
+                       NIPQUAD((x)->src.ip), ntohl((x)->src.u.gre.key), \
+                       NIPQUAD((x)->dst.ip), ntohl((x)->dst.u.gre.key))
+#else
+#define DEBUGP(x, args...)
+#define DUMP_TUPLE_GRE(x)
+#endif
+                               
+/* GRE KEYMAP HANDLING FUNCTIONS */
+static LIST_HEAD(gre_keymap_list);
+
+static inline int gre_key_cmpfn(const struct ip_ct_gre_keymap *km,
+                               const struct ip_conntrack_tuple *t)
+{
+       return ((km->tuple.src.ip == t->src.ip) &&
+               (km->tuple.dst.ip == t->dst.ip) &&
+               (km->tuple.dst.protonum == t->dst.protonum) &&
+               (km->tuple.dst.u.all == t->dst.u.all));
+}
+
+/* look up the source key for a given tuple */
+static u_int32_t gre_keymap_lookup(struct ip_conntrack_tuple *t)
+{
+       struct ip_ct_gre_keymap *km;
+       u_int32_t key;
+
+       READ_LOCK(&ip_ct_gre_lock);
+       km = LIST_FIND(&gre_keymap_list, gre_key_cmpfn,
+                       struct ip_ct_gre_keymap *, t);
+       if (!km) {
+               READ_UNLOCK(&ip_ct_gre_lock);
+               return 0;
+       }
+
+       key = km->tuple.src.u.gre.key;
+       READ_UNLOCK(&ip_ct_gre_lock);
+
+       return key;
+}
+
+/* add a single keymap entry, associate with specified expect */
+int ip_ct_gre_keymap_add(struct ip_conntrack_expect *exp,
+                        struct ip_conntrack_tuple *t, int reply)
+{
+       struct ip_ct_gre_keymap *km;
+
+       km = kmalloc(sizeof(*km), GFP_ATOMIC);
+       if (!km)
+               return -1;
+
+       /* initializing list head should be sufficient */
+       memset(km, 0, sizeof(*km));
+
+       memcpy(&km->tuple, t, sizeof(*t));
+
+       if (!reply)
+               exp->proto.gre.keymap_orig = km;
+       else
+               exp->proto.gre.keymap_reply = km;
+
+       DEBUGP("adding new entry %p: ", km);
+       DUMP_TUPLE_GRE(&km->tuple);
+
+       WRITE_LOCK(&ip_ct_gre_lock);
+       list_append(&gre_keymap_list, km);
+       WRITE_UNLOCK(&ip_ct_gre_lock);
+
+       return 0;
+}
+
+/* change the tuple of a keymap entry (used by nat helper) */
+void ip_ct_gre_keymap_change(struct ip_ct_gre_keymap *km,
+                            struct ip_conntrack_tuple *t)
+{
+       DEBUGP("changing entry %p to: ", km);
+       DUMP_TUPLE_GRE(t);
+
+       WRITE_LOCK(&ip_ct_gre_lock);
+       memcpy(&km->tuple, t, sizeof(km->tuple));
+       WRITE_UNLOCK(&ip_ct_gre_lock);
+}
+
+/* destroy the keymap entries associated with specified expect */
+void ip_ct_gre_keymap_destroy(struct ip_conntrack_expect *exp)
+{
+       DEBUGP("entering for exp %p\n", exp);
+       WRITE_LOCK(&ip_ct_gre_lock);
+       if (exp->proto.gre.keymap_orig) {
+               DEBUGP("removing %p from list\n", exp->proto.gre.keymap_orig);
+               list_del(&exp->proto.gre.keymap_orig->list);
+               kfree(exp->proto.gre.keymap_orig);
+               exp->proto.gre.keymap_orig = NULL;
+       }
+       if (exp->proto.gre.keymap_reply) {
+               DEBUGP("removing %p from list\n", exp->proto.gre.keymap_reply);
+               list_del(&exp->proto.gre.keymap_reply->list);
+               kfree(exp->proto.gre.keymap_reply);
+               exp->proto.gre.keymap_reply = NULL;
+       }
+       WRITE_UNLOCK(&ip_ct_gre_lock);
+}
+
+
+/* PUBLIC CONNTRACK PROTO HELPER FUNCTIONS */
+
+/* invert gre part of tuple */
+static int gre_invert_tuple(struct ip_conntrack_tuple *tuple,
+                           const struct ip_conntrack_tuple *orig)
+{
+       tuple->dst.u.gre.key = orig->src.u.gre.key;
+       tuple->src.u.gre.key = orig->dst.u.gre.key;
+
+       return 1;
+}
+
+/* gre hdr info to tuple */
+static int gre_pkt_to_tuple(const struct sk_buff *skb,
+                          unsigned int dataoff,
+                          struct ip_conntrack_tuple *tuple)
+{
+       struct gre_hdr _grehdr, *grehdr;
+       struct gre_hdr_pptp _pgrehdr, *pgrehdr;
+       u_int32_t srckey;
+
+       grehdr = skb_header_pointer(skb, dataoff, sizeof(_grehdr), &_grehdr);
+       pgrehdr = skb_header_pointer(skb, dataoff, sizeof(_pgrehdr), &_pgrehdr);
+
+       if (!grehdr || !pgrehdr)
+               return 0;
+
+       switch (grehdr->version) {
+               case GRE_VERSION_1701:
+                       if (!grehdr->key) {
+                               DEBUGP("Can't track GRE without key\n");
+                               return 0;
+                       }
+                       tuple->dst.u.gre.key = *(gre_key(grehdr));
+                       break;
+
+               case GRE_VERSION_PPTP:
+                       if (ntohs(grehdr->protocol) != GRE_PROTOCOL_PPTP) {
+                               DEBUGP("GRE_VERSION_PPTP but unknown proto\n");
+                               return 0;
+                       }
+                       tuple->dst.u.gre.key = htonl(ntohs(pgrehdr->call_id));
+                       break;
+
+               default:
+                       printk(KERN_WARNING "unknown GRE version %hu\n",
+                               grehdr->version);
+                       return 0;
+       }
+
+       srckey = gre_keymap_lookup(tuple);
+
+#if 0
+       DEBUGP("found src key %x for tuple ", ntohl(srckey));
+       DUMP_TUPLE_GRE(tuple);
+#endif
+       tuple->src.u.gre.key = srckey;
+
+       return 1;
+}
+
+/* print gre part of tuple */
+static int gre_print_tuple(struct seq_file *s,
+                          const struct ip_conntrack_tuple *tuple)
+{
+       return seq_printf(s, "srckey=0x%x dstkey=0x%x ", 
+                         ntohl(tuple->src.u.gre.key),
+                         ntohl(tuple->dst.u.gre.key));
+}
+
+/* print private data for conntrack */
+static int gre_print_conntrack(struct seq_file *s,
+                              const struct ip_conntrack *ct)
+{
+       return seq_printf(s, "timeout=%u, stream_timeout=%u ",
+                         (ct->proto.gre.timeout / HZ),
+                         (ct->proto.gre.stream_timeout / HZ));
+}
+
+/* Returns verdict for packet, and may modify conntrack */
+static int gre_packet(struct ip_conntrack *ct,
+                     const struct sk_buff *skb,
+                     enum ip_conntrack_info conntrackinfo)
+{
+       /* If we've seen traffic both ways, this is a GRE connection.
+        * Extend timeout. */
+       if (ct->status & IPS_SEEN_REPLY) {
+               ip_ct_refresh_acct(ct, conntrackinfo, skb,
+                                  ct->proto.gre.stream_timeout);
+               /* Also, more likely to be important, and not a probe. */
+               set_bit(IPS_ASSURED_BIT, &ct->status);
+       } else
+               ip_ct_refresh_acct(ct, conntrackinfo, skb,
+                                  ct->proto.gre.timeout);
+       
+       return NF_ACCEPT;
+}
+
+/* Called when a new connection for this protocol found. */
+static int gre_new(struct ip_conntrack *ct,
+                  const struct sk_buff *skb)
+{ 
+       DEBUGP(": ");
+       DUMP_TUPLE_GRE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
+
+       /* initialize to sane value.  Ideally a conntrack helper
+        * (e.g. in case of pptp) is increasing them */
+       ct->proto.gre.stream_timeout = GRE_STREAM_TIMEOUT;
+       ct->proto.gre.timeout = GRE_TIMEOUT;
+
+       return 1;
+}
+
+/* Called when a conntrack entry has already been removed from the hashes
+ * and is about to be deleted from memory */
+static void gre_destroy(struct ip_conntrack *ct)
+{
+       struct ip_conntrack_expect *master = ct->master;
+
+       DEBUGP(" entering\n");
+
+       if (!master) {
+               DEBUGP("no master exp for ct %p\n", ct);
+               return;
+       }
+
+       ip_ct_gre_keymap_destroy(master);
+}
+
+/* protocol helper struct */
+static struct ip_conntrack_protocol gre = { 
+       .proto           = IPPROTO_GRE,
+       .name            = "gre", 
+       .pkt_to_tuple    = gre_pkt_to_tuple,
+       .invert_tuple    = gre_invert_tuple,
+       .print_tuple     = gre_print_tuple,
+       .print_conntrack = gre_print_conntrack,
+       .packet          = gre_packet,
+       .new             = gre_new,
+       .destroy         = gre_destroy,
+       .exp_matches_pkt = NULL,
+       .me              = THIS_MODULE
+};
+
+/* ip_conntrack_proto_gre initialization */
+static int __init init(void)
+{
+       int retcode;
+
+       if ((retcode = ip_conntrack_protocol_register(&gre))) {
+               printk(KERN_ERR "Unable to register conntrack protocol "
+                      "helper for gre: %d\n", retcode);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static void __exit fini(void)
+{
+       struct list_head *pos, *n;
+
+       /* delete all keymap entries */
+       WRITE_LOCK(&ip_ct_gre_lock);
+       list_for_each_safe(pos, n, &gre_keymap_list) {
+               DEBUGP("deleting keymap %p at module unload time\n", pos);
+               list_del(pos);
+               kfree(pos);
+       }
+       WRITE_UNLOCK(&ip_ct_gre_lock);
+
+       ip_conntrack_protocol_unregister(&gre); 
+}
+
+EXPORT_SYMBOL(ip_ct_gre_keymap_add);
+EXPORT_SYMBOL(ip_ct_gre_keymap_change);
+EXPORT_SYMBOL(ip_ct_gre_keymap_destroy);
+
+module_init(init);
+module_exit(fini);
diff --git a/net/ipv4/netfilter/ip_nat_pptp.c b/net/ipv4/netfilter/ip_nat_pptp.c
new file mode 100644 (file)
index 0000000..2bbb815
--- /dev/null
@@ -0,0 +1,477 @@
+/*
+ * ip_nat_pptp.c       - Version 2.0
+ *
+ * NAT support for PPTP (Point to Point Tunneling Protocol).
+ * PPTP is a a protocol for creating virtual private networks.
+ * It is a specification defined by Microsoft and some vendors
+ * working with Microsoft.  PPTP is built on top of a modified
+ * version of the Internet Generic Routing Encapsulation Protocol.
+ * GRE is defined in RFC 1701 and RFC 1702.  Documentation of
+ * PPTP can be found in RFC 2637
+ *
+ * (C) 2000-2004 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Development of this code funded by Astaro AG (http://www.astaro.com/)
+ *
+ * TODO: - Support for multiple calls within one session
+ *        (needs netfilter newnat code)
+ *      - NAT to a unique tuple, not to TCP source port
+ *        (needs netfilter tuple reservation)
+ *
+ * Changes:
+ *     2002-02-10 - Version 1.3
+ *       - Use ip_nat_mangle_tcp_packet() because of cloned skb's
+ *        in local connections (Philip Craig <philipc@snapgear.com>)
+ *       - add checks for magicCookie and pptp version
+ *       - make argument list of pptp_{out,in}bound_packet() shorter
+ *       - move to C99 style initializers
+ *       - print version number at module loadtime
+ *     2003-09-22 - Version 1.5
+ *       - use SNATed tcp sourceport as callid, since we get called before
+ *        TCP header is mangled (Philip Craig <philipc@snapgear.com>)
+ *     2004-10-22 - Version 2.0
+ *       - kernel 2.6.x version
+ * 
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <net/tcp.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/ip_nat_helper.h>
+#include <linux/netfilter_ipv4/ip_nat_pptp.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h>
+#include <linux/netfilter_ipv4/ip_conntrack_pptp.h>
+
+#define IP_NAT_PPTP_VERSION "2.0"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("Netfilter NAT helper module for PPTP");
+
+
+#if 0
+#include "ip_conntrack_pptp_priv.h"
+#define DEBUGP(format, args...) printk(KERN_DEBUG __FILE__ ":" __FUNCTION__ \
+                                      ": " format, ## args)
+#else
+#define DEBUGP(format, args...)
+#endif
+
+static unsigned int
+pptp_nat_expected(struct sk_buff **pskb,
+                 unsigned int hooknum,
+                 struct ip_conntrack *ct,
+                 struct ip_nat_info *info)
+{
+       struct ip_conntrack *master = master_ct(ct);
+       struct ip_nat_multi_range mr;
+       struct ip_ct_pptp_master *ct_pptp_info;
+       struct ip_nat_pptp *nat_pptp_info;
+       u_int32_t newip, newcid;
+       int ret;
+
+       IP_NF_ASSERT(info);
+       IP_NF_ASSERT(master);
+       IP_NF_ASSERT(!(info->initialized & (1 << HOOK2MANIP(hooknum))));
+
+       DEBUGP("we have a connection!\n");
+
+       LOCK_BH(&ip_pptp_lock);
+       ct_pptp_info = &master->help.ct_pptp_info;
+       nat_pptp_info = &master->nat.help.nat_pptp_info;
+
+       /* need to alter GRE tuple because conntrack expectfn() used 'wrong'
+        * (unmanipulated) values */
+       if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST) {
+               DEBUGP("completing tuples with NAT info \n");
+               /* we can do this, since we're unconfirmed */
+               if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.gre.key ==
+                       htonl(ct_pptp_info->pac_call_id)) {     
+                       /* assume PNS->PAC */
+                       ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key =
+                               htonl(nat_pptp_info->pns_call_id);
+                       ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key =
+                               htonl(nat_pptp_info->pns_call_id);
+                       newip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
+                       newcid = htonl(nat_pptp_info->pac_call_id);
+               } else {
+                       /* assume PAC->PNS */
+                       ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key =
+                               htonl(nat_pptp_info->pac_call_id);
+                       ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key =
+                               htonl(nat_pptp_info->pac_call_id);
+                       newip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
+                       newcid = htonl(nat_pptp_info->pns_call_id);
+               }
+       } else {
+               if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.gre.key ==
+                       htonl(ct_pptp_info->pac_call_id)) {     
+                       /* assume PNS->PAC */
+                       newip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+                       newcid = htonl(ct_pptp_info->pns_call_id);
+               }
+               else {
+                       /* assume PAC->PNS */
+                       newip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
+                       newcid = htonl(ct_pptp_info->pac_call_id);
+               }
+       }
+
+       mr.rangesize = 1;
+       mr.range[0].flags = IP_NAT_RANGE_MAP_IPS | IP_NAT_RANGE_PROTO_SPECIFIED;
+       mr.range[0].min_ip = mr.range[0].max_ip = newip;
+       mr.range[0].min = mr.range[0].max = 
+               ((union ip_conntrack_manip_proto ) { newcid }); 
+       DEBUGP("change ip to %u.%u.%u.%u\n", 
+               NIPQUAD(newip));
+       DEBUGP("change key to 0x%x\n", ntohl(newcid));
+       ret = ip_nat_setup_info(ct, &mr, hooknum);
+
+       UNLOCK_BH(&ip_pptp_lock);
+
+       return ret;
+
+}
+
+/* outbound packets == from PNS to PAC */
+static inline unsigned int
+pptp_outbound_pkt(struct sk_buff **pskb,
+                 struct ip_conntrack *ct,
+                 enum ip_conntrack_info ctinfo,
+                 struct ip_conntrack_expect *exp)
+
+{
+       struct iphdr *iph = (*pskb)->nh.iph;
+       struct tcphdr *tcph = (void *) iph + iph->ihl*4;
+       struct pptp_pkt_hdr *pptph = (struct pptp_pkt_hdr *) 
+                                       ((void *)tcph + tcph->doff*4);
+
+       struct PptpControlHeader *ctlh;
+       union pptp_ctrl_union *pptpReq;
+       struct ip_ct_pptp_master *ct_pptp_info = &ct->help.ct_pptp_info;
+       struct ip_nat_pptp *nat_pptp_info = &ct->nat.help.nat_pptp_info;
+
+       u_int16_t msg, *cid = NULL, new_callid;
+
+       /* FIXME: size checks !!! */
+       ctlh = (struct PptpControlHeader *) ((void *) pptph + sizeof(*pptph));
+       pptpReq = (void *) ((void *) ctlh + sizeof(*ctlh));
+
+       new_callid = htons(ct_pptp_info->pns_call_id);
+       
+       switch (msg = ntohs(ctlh->messageType)) {
+               case PPTP_OUT_CALL_REQUEST:
+                       cid = &pptpReq->ocreq.callID;
+                       /* FIXME: ideally we would want to reserve a call ID
+                        * here.  current netfilter NAT core is not able to do
+                        * this :( For now we use TCP source port. This breaks
+                        * multiple calls within one control session */
+
+                       /* save original call ID in nat_info */
+                       nat_pptp_info->pns_call_id = ct_pptp_info->pns_call_id;
+
+                       /* don't use tcph->source since we are at a DSTmanip
+                        * hook (e.g. PREROUTING) and pkt is not mangled yet */
+                       new_callid = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.tcp.port;
+
+                       /* save new call ID in ct info */
+                       ct_pptp_info->pns_call_id = ntohs(new_callid);
+                       break;
+               case PPTP_IN_CALL_REPLY:
+                       cid = &pptpReq->icreq.callID;
+                       break;
+               case PPTP_CALL_CLEAR_REQUEST:
+                       cid = &pptpReq->clrreq.callID;
+                       break;
+               default:
+                       DEBUGP("unknown outbound packet 0x%04x:%s\n", msg,
+                             (msg <= PPTP_MSG_MAX)? strMName[msg]:strMName[0]);
+                       /* fall through */
+
+               case PPTP_SET_LINK_INFO:
+                       /* only need to NAT in case PAC is behind NAT box */
+               case PPTP_START_SESSION_REQUEST:
+               case PPTP_START_SESSION_REPLY:
+               case PPTP_STOP_SESSION_REQUEST:
+               case PPTP_STOP_SESSION_REPLY:
+               case PPTP_ECHO_REQUEST:
+               case PPTP_ECHO_REPLY:
+                       /* no need to alter packet */
+                       return NF_ACCEPT;
+       }
+
+       IP_NF_ASSERT(cid);
+
+       DEBUGP("altering call id from 0x%04x to 0x%04x\n",
+               ntohs(*cid), ntohs(new_callid));
+
+       /* mangle packet */
+       ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, (void *)cid - (void *)pptph,
+                                sizeof(new_callid), (char *)&new_callid,
+                                sizeof(new_callid));
+
+       return NF_ACCEPT;
+}
+
+/* inbound packets == from PAC to PNS */
+static inline unsigned int
+pptp_inbound_pkt(struct sk_buff **pskb,
+                struct ip_conntrack *ct,
+                enum ip_conntrack_info ctinfo,
+                struct ip_conntrack_expect *oldexp)
+{
+       struct iphdr *iph = (*pskb)->nh.iph;
+       struct tcphdr *tcph = (void *) iph + iph->ihl*4;
+       struct pptp_pkt_hdr *pptph = (struct pptp_pkt_hdr *) 
+                                       ((void *)tcph + tcph->doff*4);
+
+       struct PptpControlHeader *ctlh;
+       union pptp_ctrl_union *pptpReq;
+       struct ip_ct_pptp_master *ct_pptp_info = &ct->help.ct_pptp_info;
+       struct ip_nat_pptp *nat_pptp_info = &ct->nat.help.nat_pptp_info;
+
+       u_int16_t msg, new_cid = 0, new_pcid, *pcid = NULL, *cid = NULL;
+       u_int32_t old_dst_ip;
+
+       struct ip_conntrack_tuple t, inv_t;
+       struct ip_conntrack_tuple *orig_t, *reply_t;
+
+       /* FIXME: size checks !!! */
+       ctlh = (struct PptpControlHeader *) ((void *) pptph + sizeof(*pptph));
+       pptpReq = (void *) ((void *) ctlh + sizeof(*ctlh));
+
+       new_pcid = htons(nat_pptp_info->pns_call_id);
+
+       switch (msg = ntohs(ctlh->messageType)) {
+       case PPTP_OUT_CALL_REPLY:
+               pcid = &pptpReq->ocack.peersCallID;     
+               cid = &pptpReq->ocack.callID;
+               if (!oldexp) {
+                       DEBUGP("outcall but no expectation\n");
+                       break;
+               }
+               old_dst_ip = oldexp->tuple.dst.ip;
+               t = oldexp->tuple;
+               invert_tuplepr(&inv_t, &t);
+
+               /* save original PAC call ID in nat_info */
+               nat_pptp_info->pac_call_id = ct_pptp_info->pac_call_id;
+
+               /* alter expectation */
+               orig_t = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
+               reply_t = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;
+               if (t.src.ip == orig_t->src.ip && t.dst.ip == orig_t->dst.ip) {
+                       /* expectation for PNS->PAC direction */
+                       t.src.u.gre.key = htonl(nat_pptp_info->pns_call_id);
+                       t.dst.u.gre.key = htonl(ct_pptp_info->pac_call_id);
+                       inv_t.src.ip = reply_t->src.ip;
+                       inv_t.dst.ip = reply_t->dst.ip;
+                       inv_t.src.u.gre.key = htonl(nat_pptp_info->pac_call_id);
+                       inv_t.dst.u.gre.key = htonl(ct_pptp_info->pns_call_id);
+               } else {
+                       /* expectation for PAC->PNS direction */
+                       t.src.u.gre.key = htonl(nat_pptp_info->pac_call_id);
+                       t.dst.u.gre.key = htonl(ct_pptp_info->pns_call_id);
+                       inv_t.src.ip = orig_t->src.ip;
+                       inv_t.dst.ip = orig_t->dst.ip;
+                       inv_t.src.u.gre.key = htonl(nat_pptp_info->pns_call_id);
+                       inv_t.dst.u.gre.key = htonl(ct_pptp_info->pac_call_id);
+               }
+
+               if (!ip_conntrack_change_expect(oldexp, &t)) {
+                       DEBUGP("successfully changed expect\n");
+               } else {
+                       DEBUGP("can't change expect\n");
+               }
+               ip_ct_gre_keymap_change(oldexp->proto.gre.keymap_orig, &t);
+               ip_ct_gre_keymap_change(oldexp->proto.gre.keymap_reply, &inv_t);
+               break;
+       case PPTP_IN_CALL_CONNECT:
+               pcid = &pptpReq->iccon.peersCallID;
+               if (!oldexp)
+                       break;
+               old_dst_ip = oldexp->tuple.dst.ip;
+               t = oldexp->tuple;
+
+               /* alter expectation, no need for callID */
+               if (t.dst.ip == ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip) {
+                       /* expectation for PNS->PAC direction */
+                       t.src.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+               } else {
+                       /* expectation for PAC->PNS direction */
+                       t.dst.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+               }
+
+               if (!ip_conntrack_change_expect(oldexp, &t)) {
+                       DEBUGP("successfully changed expect\n");
+               } else {
+                       DEBUGP("can't change expect\n");
+               }
+               break;
+       case PPTP_IN_CALL_REQUEST:
+               /* only need to nat in case PAC is behind NAT box */
+               break;
+       case PPTP_WAN_ERROR_NOTIFY:
+               pcid = &pptpReq->wanerr.peersCallID;
+               break;
+       case PPTP_CALL_DISCONNECT_NOTIFY:
+               pcid = &pptpReq->disc.callID;
+               break;
+
+       default:
+               DEBUGP("unknown inbound packet %s\n",
+                       (msg <= PPTP_MSG_MAX)? strMName[msg]:strMName[0]);
+               /* fall through */
+
+       case PPTP_START_SESSION_REQUEST:
+       case PPTP_START_SESSION_REPLY:
+       case PPTP_STOP_SESSION_REQUEST:
+       case PPTP_STOP_SESSION_REPLY:
+       case PPTP_ECHO_REQUEST:
+       case PPTP_ECHO_REPLY:
+               /* no need to alter packet */
+               return NF_ACCEPT;
+       }
+
+       /* mangle packet */
+       IP_NF_ASSERT(pcid);
+       DEBUGP("altering peer call id from 0x%04x to 0x%04x\n",
+               ntohs(*pcid), ntohs(new_pcid));
+       ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, (void *)pcid - (void *)pptph,
+                                sizeof(new_pcid), (char *)&new_pcid, 
+                                sizeof(new_pcid));
+
+       if (new_cid) {
+               IP_NF_ASSERT(cid);
+               DEBUGP("altering call id from 0x%04x to 0x%04x\n",
+                       ntohs(*cid), ntohs(new_cid));
+               ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, 
+                                        (void *)cid - (void *)pptph, 
+                                        sizeof(new_cid), (char *)&new_cid, 
+                                        sizeof(new_cid));
+       }
+
+       /* great, at least we don't need to resize packets */
+       return NF_ACCEPT;
+}
+
+
+static unsigned int tcp_help(struct ip_conntrack *ct,
+                            struct ip_conntrack_expect *exp,
+                            struct ip_nat_info *info,
+                            enum ip_conntrack_info ctinfo,
+                            unsigned int hooknum, struct sk_buff **pskb)
+{
+       struct iphdr *iph = (*pskb)->nh.iph;
+       struct tcphdr *tcph = (void *) iph + iph->ihl*4;
+       unsigned int datalen = (*pskb)->len - iph->ihl*4 - tcph->doff*4;
+       struct pptp_pkt_hdr *pptph;
+
+       int dir;
+
+       DEBUGP("entering\n");
+
+       /* Only mangle things once: DST for original direction
+          and SRC for reply direction. */
+       dir = CTINFO2DIR(ctinfo);
+       if (!((HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC
+            && dir == IP_CT_DIR_ORIGINAL)
+             || (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST
+                 && dir == IP_CT_DIR_REPLY))) {
+               DEBUGP("Not touching dir %s at hook %s\n",
+                      dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY",
+                      hooknum == NF_IP_POST_ROUTING ? "POSTROUTING"
+                      : hooknum == NF_IP_PRE_ROUTING ? "PREROUTING"
+                      : hooknum == NF_IP_LOCAL_OUT ? "OUTPUT"
+                      : hooknum == NF_IP_LOCAL_IN ? "INPUT" : "???");
+               return NF_ACCEPT;
+       }
+
+       /* if packet is too small, just skip it */
+       if (datalen < sizeof(struct pptp_pkt_hdr)+
+                     sizeof(struct PptpControlHeader)) {
+               DEBUGP("pptp packet too short\n");
+               return NF_ACCEPT;       
+       }
+
+       pptph = (struct pptp_pkt_hdr *) ((void *)tcph + tcph->doff*4);
+
+       /* if it's not a control message, we can't handle it */
+       if (ntohs(pptph->packetType) != PPTP_PACKET_CONTROL ||
+           ntohl(pptph->magicCookie) != PPTP_MAGIC_COOKIE) {
+               DEBUGP("not a pptp control packet\n");
+               return NF_ACCEPT;
+       }
+
+       LOCK_BH(&ip_pptp_lock);
+
+       if (dir == IP_CT_DIR_ORIGINAL) {
+               /* reuqests sent by client to server (PNS->PAC) */
+               pptp_outbound_pkt(pskb, ct, ctinfo, exp);
+       } else {
+               /* response from the server to the client (PAC->PNS) */
+               pptp_inbound_pkt(pskb, ct, ctinfo, exp);
+       }
+
+       UNLOCK_BH(&ip_pptp_lock);
+
+       return NF_ACCEPT;
+}
+
+/* nat helper struct for control connection */
+static struct ip_nat_helper pptp_tcp_helper = { 
+       .list = { NULL, NULL },
+       .name = "pptp", 
+       .flags = IP_NAT_HELPER_F_ALWAYS, 
+       .me = THIS_MODULE,
+       .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 = 0xFFFF } } 
+                        },
+                 .dst = { .ip = 0, 
+                          .u = { .all = 0 }, 
+                          .protonum = 0xFFFF 
+                        } 
+               },
+       .help = tcp_help, 
+       .expect = pptp_nat_expected 
+};
+
+                         
+static int __init init(void)
+{
+       DEBUGP("%s: registering NAT helper\n", __FILE__);
+       if (ip_nat_helper_register(&pptp_tcp_helper)) {
+               printk(KERN_ERR "Unable to register NAT application helper "
+                               "for pptp\n");
+               return -EIO;
+       }
+
+       printk("ip_nat_pptp version %s loaded\n", IP_NAT_PPTP_VERSION);
+       return 0;
+}
+
+static void __exit fini(void)
+{
+       DEBUGP("cleanup_module\n" );
+       ip_nat_helper_unregister(&pptp_tcp_helper);
+       printk("ip_nat_pptp version %s unloaded\n", IP_NAT_PPTP_VERSION);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/net/ipv4/netfilter/ip_nat_proto_gre.c b/net/ipv4/netfilter/ip_nat_proto_gre.c
new file mode 100644 (file)
index 0000000..5691a10
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * ip_nat_proto_gre.c - Version 2.0
+ *
+ * NAT protocol helper module for GRE.
+ *
+ * GRE is a generic encapsulation protocol, which is generally not very
+ * suited for NAT, as it has no protocol-specific part as port numbers.
+ *
+ * It has an optional key field, which may help us distinguishing two 
+ * connections between the same two hosts.
+ *
+ * GRE is defined in RFC 1701 and RFC 1702, as well as RFC 2784 
+ *
+ * PPTP is built on top of a modified version of GRE, and has a mandatory
+ * field called "CallID", which serves us for the same purpose as the key
+ * field in plain GRE.
+ *
+ * Documentation about PPTP can be found in RFC 2637
+ *
+ * (C) 2000-2004 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Development of this code funded by Astaro AG (http://www.astaro.com/)
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/ip_nat_protocol.h>
+#include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("Netfilter NAT protocol helper module for GRE");
+
+#if 0
+#define DEBUGP(format, args...) printk(KERN_DEBUG __FILE__ ":" __FUNCTION__ \
+                                      ": " format, ## args)
+#else
+#define DEBUGP(x, args...)
+#endif
+
+/* is key in given range between min and max */
+static int
+gre_in_range(const struct ip_conntrack_tuple *tuple,
+            enum ip_nat_manip_type maniptype,
+            const union ip_conntrack_manip_proto *min,
+            const union ip_conntrack_manip_proto *max)
+{
+       u_int32_t key;
+
+       if (maniptype == IP_NAT_MANIP_SRC)
+               key = tuple->src.u.gre.key;
+       else
+               key = tuple->dst.u.gre.key;
+
+       return ntohl(key) >= ntohl(min->gre.key)
+               && ntohl(key) <= ntohl(max->gre.key);
+}
+
+/* generate unique tuple ... */
+static int 
+gre_unique_tuple(struct ip_conntrack_tuple *tuple,
+                const struct ip_nat_range *range,
+                enum ip_nat_manip_type maniptype,
+                const struct ip_conntrack *conntrack)
+{
+       u_int32_t min, i, range_size;
+       u_int32_t key = 0, *keyptr;
+
+       if (maniptype == IP_NAT_MANIP_SRC)
+               keyptr = &tuple->src.u.gre.key;
+       else
+               keyptr = &tuple->dst.u.gre.key;
+
+       if (!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED)) {
+               DEBUGP("%p: NATing GRE PPTP\n", conntrack);
+               min = 1;
+               range_size = 0xffff;
+       } else {
+               min = ntohl(range->min.gre.key);
+               range_size = ntohl(range->max.gre.key) - min + 1;
+       }
+
+       DEBUGP("min = %u, range_size = %u\n", min, range_size); 
+
+       for (i = 0; i < range_size; i++, key++) {
+               *keyptr = htonl(min + key % range_size);
+               if (!ip_nat_used_tuple(tuple, conntrack))
+                       return 1;
+       }
+
+       DEBUGP("%p: no NAT mapping\n", conntrack);
+
+       return 0;
+}
+
+/* manipulate a GRE packet according to maniptype */
+static int
+gre_manip_pkt(struct sk_buff **pskb,
+             unsigned int hdroff,
+             const struct ip_conntrack_manip *manip,
+             enum ip_nat_manip_type maniptype)
+{
+       struct gre_hdr *greh;
+       struct gre_hdr_pptp *pgreh;
+
+       if (!skb_ip_make_writable(pskb, hdroff + sizeof(*pgreh)))
+               return 0;
+
+       greh = (void *)(*pskb)->data + hdroff;
+       pgreh = (struct gre_hdr_pptp *) greh;
+
+       /* we only have destination manip of a packet, since 'source key' 
+        * is not present in the packet itself */
+       if (maniptype == IP_NAT_MANIP_DST) {
+               /* key manipulation is always dest */
+               switch (greh->version) {
+               case 0:
+                       if (!greh->key) {
+                               DEBUGP("can't nat GRE w/o key\n");
+                               break;
+                       }
+                       if (greh->csum) {
+                               /* FIXME: Never tested this code... */
+                               *(gre_csum(greh)) = 
+                                       ip_nat_cheat_check(~*(gre_key(greh)),
+                                                       manip->u.gre.key,
+                                                       *(gre_csum(greh)));
+                       }
+                       *(gre_key(greh)) = manip->u.gre.key;
+                       break;
+               case GRE_VERSION_PPTP:
+                       DEBUGP("call_id -> 0x%04x\n", 
+                               ntohl(manip->u.gre.key));
+                       pgreh->call_id = htons(ntohl(manip->u.gre.key));
+                       break;
+               default:
+                       DEBUGP("can't nat unknown GRE version\n");
+                       return 0;
+                       break;
+               }
+       }
+       return 1;
+}
+
+/* print out a nat tuple */
+static unsigned int 
+gre_print(char *buffer, 
+         const struct ip_conntrack_tuple *match,
+         const struct ip_conntrack_tuple *mask)
+{
+       unsigned int len = 0;
+
+       if (mask->src.u.gre.key)
+               len += sprintf(buffer + len, "srckey=0x%x ", 
+                               ntohl(match->src.u.gre.key));
+
+       if (mask->dst.u.gre.key)
+               len += sprintf(buffer + len, "dstkey=0x%x ",
+                               ntohl(match->src.u.gre.key));
+
+       return len;
+}
+
+/* print a range of keys */
+static unsigned int 
+gre_print_range(char *buffer, const struct ip_nat_range *range)
+{
+       if (range->min.gre.key != 0 
+           || range->max.gre.key != 0xFFFF) {
+               if (range->min.gre.key == range->max.gre.key)
+                       return sprintf(buffer, "key 0x%x ",
+                                       ntohl(range->min.gre.key));
+               else
+                       return sprintf(buffer, "keys 0x%u-0x%u ",
+                                       ntohl(range->min.gre.key),
+                                       ntohl(range->max.gre.key));
+       } else
+               return 0;
+}
+
+/* nat helper struct */
+static struct ip_nat_protocol gre = { 
+       .name           = "GRE", 
+       .protonum       = IPPROTO_GRE,
+       .manip_pkt      = gre_manip_pkt,
+       .in_range       = gre_in_range,
+       .unique_tuple   = gre_unique_tuple,
+       .print          = gre_print,
+       .print_range    = gre_print_range 
+};
+                                 
+static int __init init(void)
+{
+       if (ip_nat_protocol_register(&gre))
+               return -EIO;
+
+       return 0;
+}
+
+static void __exit fini(void)
+{
+       ip_nat_protocol_unregister(&gre);
+}
+
+module_init(init);
+module_exit(fini);