Subject: Re: Kernel panic (network stack)

From: [email-masked]
Date: Sat, 13 Oct 2007 11:54:34 +0400

> > Hello!
> > I develop network driver.
> > It works fine while less then 100 network interfaces exists.
> > Then i give kernel panic. What could cause it ?
>
> Since it probably in your code. Please code (or place to download)
> the driver code.

You can download from http://sourceforge.net/projects/accel-pptp

/*
 *  Point-to-Point Tunneling Protocol for Linux
 *
 *	Authors: Kozlov D. ([email-masked])
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version
 *	2 of the License, or (at your option) any later version.
 *
 */

#include <linux/string.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/init.h>
#include <linux/ppp_channel.h>
#include <linux/ppp_defs.h>
#include <linux/if_ppp.h>
#include "if_pppox.h"
#include <linux/notifier.h>
#include <linux/file.h>
#include <linux/proc_fs.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/version.h>
#include <linux/spinlock.h>

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
#include <linux/tqueue.h>
#include <linux/timer.h>
#else
#include <linux/workqueue.h>
#endif

#include <net/sock.h>
#include <net/protocol.h>
#include <net/ip.h>
#include <net/icmp.h>
#include <net/route.h>

#include <asm/uaccess.h>

#define PPTP_DRIVER_VERSION "0.7.8-r1"

MODULE_DESCRIPTION("Point-to-Point Tunneling Protocol for Linux");
MODULE_AUTHOR("Kozlov D. ([email-masked])");
MODULE_LICENSE("GPL");

static int log_level=0;
static int log_packets=10;
static int rx_stop=0;
static int min_window=5;
static int max_window=100;
static int statistics=0;
static int stat_collect_time=3;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
MODULE_PARM(min_window,"i");
MODULE_PARM(max_window,"i");
MODULE_PARM(log_level,"i");
MODULE_PARM(log_packets,"i");
MODULE_PARM(statistics,"i");
MODULE_PARM(stat_collect_time,"i");
#else
module_param(min_window,int,0);
module_param(max_window,int,0);
module_param(log_level,int,0);
module_param(log_packets,int,0);
module_param(statistics,int,0);
module_param(stat_collect_time,int,0);
#endif
MODULE_PARM_DESC(min_window,"Minimum sliding window size (default=3)");
MODULE_PARM_DESC(max_window,"Maximum sliding window size (default=100)");
MODULE_PARM_DESC(log_level,"Logging level (default=0)");
MODULE_PARM_DESC(statistics,"Performance statistics collection");
MODULE_PARM_DESC(stat_collect_time,"Statistics collect time (sec)");

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
#define __wait_event_timeout(wq, condition, ret)			\
do {									\
	wait_queue_t __wait;						\
	init_waitqueue_entry(&__wait, current);				\
									\
	for (;;) {							\
		set_current_state(TASK_UNINTERRUPTIBLE);		\
		if (condition)						\
			break;						\
		ret = schedule_timeout(ret);				\
		if (!ret)						\
			break;						\
	}								\
	current->state = TASK_RUNNING;					\
	remove_wait_queue(&wq, &__wait);				\
} while (0)
#define wait_event_timeout(wq, condition, timeout)			\
({									\
	long __ret = timeout;						\
	if (!(condition)) 						\
		__wait_event_timeout(wq, condition, __ret);		\
	__ret;								\
})

#define INIT_TIMER(_timer,_routine,_data) \
do { \
	(_timer)->function=_routine; \
	(_timer)->data=_data; \
	init_timer(_timer); \
} while (0); 

static inline void *kzalloc(size_t size,int gfp)
{
	void *p=kmalloc(size,gfp);
	memset(p,0,size);
	return p;
}

static inline void skb_get_timestamp(const struct sk_buff *skb, struct timeval 
*stamp)
{
	*stamp = skb->stamp;
}
static inline void __net_timestamp(struct sk_buff *skb, const struct timeval 
*stamp)
{
	struct timeval tv;
	do_gettimeofday(&tv);
	skb->stamp = *stamp;
}
#endif

//============= performance statistics =============
//static struct timeval stat_last_update={0,0};
unsigned long stat_last_update=0;
static unsigned int tx_packets_avg=0;
static unsigned int rx_packets_avg=0;
static unsigned int ack_timeouts_avg=0;
static unsigned int ack_works_avg=0;
static unsigned int buf_works_avg=0;
static unsigned int tx_packets_cur=0;
static unsigned int rx_packets_cur=0;
static unsigned int ack_timeouts_cur=0;
static unsigned int ack_works_cur=0;
static unsigned int buf_works_cur=0;
static rwlock_t stat_lock=RW_LOCK_UNLOCKED;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
struct tq_struct stat_work;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
struct delayed_work stat_work;
#else
struct work_struct stat_work;
#endif
static void inc_stat_counter(unsigned int *counter)
{
	if (statistics)
	{
		write_lock_bh(&stat_lock);
		(*counter)++;
		write_unlock_bh(&stat_lock);		
	}
}
#define INC_TX_PACKETS inc_stat_counter(&tx_packets_cur)
#define INC_RX_PACKETS inc_stat_counter(&rx_packets_cur)
#define INC_ACK_TIMEOUTS inc_stat_counter(&ack_timeouts_cur)
#define INC_ACK_WORKS inc_stat_counter(&ack_works_cur)
#define INC_BUF_WORKS inc_stat_counter(&buf_works_cur)

static void do_stat_work(void*p)
{
	unsigned long delta_jiff;
	
	write_lock_bh(&stat_lock);
	
	delta_jiff=jiffies-stat_last_update;
	stat_last_update=jiffies;
	if (delta_jiff<=0) goto exit;
	
	tx_packets_avg=(tx_packets_avg+tx_packets_cur*HZ/delta_jiff)/2;
	rx_packets_avg=(rx_packets_avg+rx_packets_cur*HZ/delta_jiff)/2;
	ack_timeouts_avg=(ack_timeouts_avg+ack_timeouts_cur*HZ/delta_jiff)/2;
	ack_works_avg=(ack_works_avg+ack_works_cur*HZ/delta_jiff)/2;
	buf_works_avg=(buf_works_avg+buf_works_cur*HZ/delta_jiff)/2;
	
	tx_packets_cur=0;
	rx_packets_cur=0;
	ack_timeouts_cur=0;
	ack_works_cur=0;
	buf_works_cur=0;
	
exit:
	write_unlock_bh(&stat_lock);		

	schedule_delayed_work(&stat_work,stat_collect_time*HZ);
}
//==================================================

static struct proc_dir_entry* proc_dir;

#define HASH_SIZE  32
#define HASH(addr) ((addr^(addr>>3))&0x1F)

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
static rwlock_t chan_lock=RW_LOCK_UNLOCKED;
#define SK_STATE(sk) (sk)->state
#else
static DEFINE_RWLOCK(chan_lock);
#define SK_STATE(sk) (sk)->sk_state
#endif
static struct pppox_sock *chans[HASH_SIZE];

static int pptp_xmit(struct ppp_channel *chan, struct sk_buff *skb);
static int read_proc(char *page, char **start, off_t off,int count,
                     int *eof, void *data);
static int __pptp_rcv(struct pppox_sock *po,struct sk_buff *skb,int new);

static struct ppp_channel_ops pptp_chan_ops= {
	.start_xmit = pptp_xmit,
};

#define MISSING_WINDOW 20
#define WRAPPED( curseq, lastseq) \
    ((((curseq) & 0xffffff00) == 0) && \
     (((lastseq) & 0xffffff00 ) == 0xffffff00))

/* gre header structure: -------------------------------------------- */

#define PPTP_GRE_PROTO  0x880B
#define PPTP_GRE_VER    0x1

#define PPTP_GRE_FLAG_C	0x80
#define PPTP_GRE_FLAG_R	0x40
#define PPTP_GRE_FLAG_K	0x20
#define PPTP_GRE_FLAG_S	0x10
#define PPTP_GRE_FLAG_A	0x80

#define PPTP_GRE_IS_C(f) ((f)&PPTP_GRE_FLAG_C)
#define PPTP_GRE_IS_R(f) ((f)&PPTP_GRE_FLAG_R)
#define PPTP_GRE_IS_K(f) ((f)&PPTP_GRE_FLAG_K)
#define PPTP_GRE_IS_S(f) ((f)&PPTP_GRE_FLAG_S)
#define PPTP_GRE_IS_A(f) ((f)&PPTP_GRE_FLAG_A)

struct pptp_gre_header {
  u_int8_t flags;		/* bitfield */
  u_int8_t ver;			/* should be PPTP_GRE_VER (enhanced GRE) */
  u_int16_t protocol;		/* should be PPTP_GRE_PROTO (ppp-encaps) */
  u_int16_t payload_len;	/* size of ppp payload, not inc. gre header */
  u_int16_t call_id;		/* peer's call_id for this session */
  u_int32_t seq;		/* sequence number.  Present if S==1 */
  u_int32_t ack;		/* seq number of highest packet recieved by */
  				/*  sender in this session */
};
#define PPTP_HEADER_OVERHEAD (2+sizeof(struct pptp_gre_header))

struct gre_statistics {
  /* statistics for GRE receive */
  unsigned int rx_accepted;  // data packet accepted
  unsigned int rx_lost;      // data packet did not arrive before timeout
  unsigned int rx_underwin;  // data packet was under window (arrived too late
                             // or duplicate packet)
  unsigned int rx_buffered;  // data packet arrived earlier than expected,
                             // packet(s) before it were lost or reordered
  unsigned int rx_errors;    // OS error on receive
  unsigned int rx_truncated; // truncated packet
  unsigned int rx_invalid;   // wrong protocol or invalid flags
  unsigned int rx_acks;      // acknowledgement only

  /* statistics for GRE transmit */
  unsigned int tx_sent;      // data packet sent
  unsigned int tx_failed;    //
  unsigned int tx_acks;      // sent packet with just ACK

  __u32 pt_seq;
  struct timeval pt_time;
  unsigned int rtt;
};

static struct pppox_sock * lookup_chan(__u16 call_id)
{
	struct pppox_sock *po;
	read_lock_bh(&chan_lock);
	for(po=chans[HASH(call_id)]; po; po=po->next)
		if (po->proto.pptp.src_addr.call_id==call_id)
			break;
	read_unlock_bh(&chan_lock);
	return po;
}

static void add_chan(struct pppox_sock *po)
{
	write_lock_bh(&chan_lock);
	po->next=chans[HASH(po->proto.pptp.src_addr.call_id)];
	chans[HASH(po->proto.pptp.src_addr.call_id)]=po;
	write_unlock_bh(&chan_lock);
}

static void add_free_chan(struct pppox_sock *po)
{
	static __u16 call_id=0;
	struct pppox_sock *p;

	write_lock_bh(&chan_lock);
	while (1) {
		if (++call_id==0) continue;
		for(p=chans[HASH(call_id)]; p; p=p->next)
			if (p->proto.pptp.src_addr.call_id==call_id)
				break;
		if (!p){
			po->proto.pptp.src_addr.call_id=call_id;
			po->next=chans[HASH(call_id)];
			chans[HASH(call_id)]=po;
			break;
		}
	}
	write_unlock_bh(&chan_lock);
}

static void del_chan(struct pppox_sock *po)
{
	struct pppox_sock *p1,*p2;

	write_lock_bh(&chan_lock);
	for(p2=NULL,p1=chans[HASH(po->proto.pptp.src_addr.call_id)]; p1 && p1!=po;
				p2=p1,p1=p1->next);
	if (p1){
	    if (p2) p2->next=p1->next;
	    else chans[HASH(po->proto.pptp.src_addr.call_id)]=p1->next;
	}
	write_unlock_bh(&chan_lock);
}

static int pptp_xmit(struct ppp_channel *chan, struct sk_buff *skb)
{
	struct sock *sk = (struct sock *) chan->private;
	struct pppox_sock *po = pppox_sk(sk);
	struct pptp_opt *opt=&po->proto.pptp;
	struct pptp_gre_header *hdr;
	unsigned int header_len=sizeof(*hdr);
	int len=skb?skb->len:0;
	int err=0;
	int window;

	struct rtable *rt;     			/* Route to the other host */
	struct net_device *tdev;			/* Device to other host */
	struct iphdr  *iph;			/* Our new IP header */
	int    max_headroom;			/* The extra header space needed */

	INC_TX_PACKETS;

	spin_lock_bh(&opt->xmit_lock);
	
	window=WRAPPED(opt->ack_recv,opt->seq_sent)?(__u32)
0xffffffff-opt->seq_sent+opt->ack_recv:opt->seq_sent-opt->ack_recv;

	if (!skb){
	    if (opt->ack_sent == opt->seq_recv) goto exit;
	}else if (window>opt->window){
		__set_bit(PPTP_FLAG_PAUSE,(unsigned long*)&opt->flags);
		#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
		mod_timer(&opt->ack_timeout_timer,opt->stat->rtt/100*HZ/10000);
		#else
		schedule_delayed_work(&opt->ack_timeout_work,opt->stat->rtt/100*HZ/10000);
		#endif
		goto exit;
	}

	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	{
		struct rt_key key = {
			.dst=opt->dst_addr.sin_addr.s_addr,
			.src=opt->src_addr.sin_addr.s_addr,
			.tos=RT_TOS(0),
		};
		if ((err=ip_route_output_key(&rt, &key))) {
			goto tx_error;
		}
	}
	#else
	{
		struct flowi fl = { .oif = 0,
				    .nl_u = { .ip4_u =
					      { .daddr = opt->dst_addr.sin_addr.s_addr,
						.saddr = opt->src_addr.sin_addr.s_addr,
						.tos = RT_TOS(0) } },
				    .proto = IPPROTO_GRE };
		if ((err=ip_route_output_key(&rt, &fl))) {
			goto tx_error;
		}
	}
	#endif
	tdev = rt->u.dst.dev;
	
	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	max_headroom = ((tdev->hard_header_len+15)&~15) + 
sizeof(*iph)+sizeof(*hdr)+2;
	#else
	max_headroom = LL_RESERVED_SPACE(tdev) + sizeof(*iph)+sizeof(*hdr)+2;
	#endif

	if (!skb){
		skb=dev_alloc_skb(max_headroom);
		if (!skb) {
			ip_rt_put(rt);
			goto tx_error;
		}
		skb_reserve(skb,max_headroom-skb_headroom(skb));
	}else if (skb_headroom(skb) < max_headroom ||
						skb_cloned(skb) || skb_shared(skb)) {
		struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
		if (!new_skb) {
			ip_rt_put(rt);
			goto tx_error;
		}
		if (skb->sk)
		skb_set_owner_w(new_skb, skb->sk);
		kfree_skb(skb);
		skb = new_skb;
	}

	if (skb->len){
		int islcp;
		unsigned char *data=skb->data;
		islcp=((data[0] << 8) + data[1])== PPP_LCP && 1 <= data[2] && data[2] <= 7;
		if (islcp) {
			data=skb_push(skb,2);
			data[0]=0xff;
			data[1]=0x03;
		}
	}
	len=skb->len;

	if (len==0) header_len-=sizeof(hdr->seq);
	if (opt->ack_sent == opt->seq_recv) header_len-=sizeof(hdr->ack);

	// Push down and install GRE header
	skb_push(skb,header_len);
	hdr=(struct pptp_gre_header *)(skb->data);

	hdr->flags       = PPTP_GRE_FLAG_K;
	hdr->ver         = PPTP_GRE_VER;
	hdr->protocol    = htons(PPTP_GRE_PROTO);
	hdr->call_id     = htons(opt->dst_addr.call_id);

	if (!len){
		hdr->payload_len = 0;
		hdr->ver |= PPTP_GRE_FLAG_A;
		/* ack is in odd place because S == 0 */
		hdr->seq = htonl(opt->seq_recv);
		opt->ack_sent = opt->seq_recv;
		opt->stat->tx_acks++;
	}else {
		//if (!opt->seq_sent){
		//}

		hdr->flags |= PPTP_GRE_FLAG_S;
		hdr->seq    = htonl(opt->seq_sent++);
		if (log_level>=3 && opt->seq_sent<=log_packets)
			printk("PPTP[%i]: send packet: 
seq=%i",opt->src_addr.call_id,opt->seq_sent);
		if (opt->ack_sent != opt->seq_recv)	{
		/* send ack with this message */
			hdr->ver |= PPTP_GRE_FLAG_A;
			hdr->ack  = htonl(opt->seq_recv);
			opt->ack_sent = opt->seq_recv;
			if (log_level>=3 && opt->seq_sent<=log_packets)
				printk(" ack=%i",opt->seq_recv);
		}
		hdr->payload_len = htons(len);
		if (log_level>=3 && opt->seq_sent<=log_packets)
			printk("\n");
	}

	/*
	 *	Push down and install the IP header.
	 */

	#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
	skb->transport_header = skb->network_header;
	skb_push(skb, sizeof(*iph));
	skb_reset_network_header(skb);
	#else
	skb->h.raw = skb->nh.raw;
	skb->nh.raw = skb_push(skb, sizeof(*iph));
	#endif
	memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
	#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
	IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED |
			      IPSKB_REROUTED);
	#endif

	#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
	iph 			=	ip_hdr(skb);
	#else
	iph 			=	skb->nh.iph;
	#endif
	iph->version		=	4;
	iph->ihl		=	sizeof(struct iphdr) >> 2;
	iph->frag_off		=	0;//df;
	iph->protocol		=	IPPROTO_GRE;
	iph->tos		=	0;
	iph->daddr		=	rt->rt_dst;
	iph->saddr		=	rt->rt_src;
	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	iph->ttl = sysctl_ip_default_ttl;
	#else
	iph->ttl = dst_metric(&rt->u.dst, RTAX_HOPLIMIT);
	#endif
	iph->tot_len = htons(skb->len);

	dst_release(skb->dst);
	skb->dst = &rt->u.dst;
	
	nf_reset(skb);

	skb->ip_summed = CHECKSUM_NONE;
	ip_select_ident(iph, &rt->u.dst, NULL);
	ip_send_check(iph);

	err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev, 
dst_output);
	
	wake_up(&opt->wait);

	if (err == NET_XMIT_SUCCESS || err == NET_XMIT_CN) {
		opt->stat->tx_sent++;
		if (!opt->stat->pt_seq){
			opt->stat->pt_seq  = opt->seq_sent;
			do_gettimeofday(&opt->stat->pt_time);
		}
	}else goto tx_error;

	spin_unlock_bh(&opt->xmit_lock);
	return 1;

tx_error:
	opt->stat->tx_failed++;
	if (!len) kfree_skb(skb);
	spin_unlock_bh(&opt->xmit_lock);
	return 1;
exit:
	spin_unlock_bh(&opt->xmit_lock);
	return 0;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
static void ack_work(struct work_struct *work)
{
    struct pptp_opt *opt=container_of(work,struct pptp_opt,ack_work);
    struct pppox_sock *po=container_of(opt,struct pppox_sock,proto.pptp);
#else
static void ack_work(struct pppox_sock *po)
{
	struct pptp_opt *opt=&po->proto.pptp;
#endif
	INC_ACK_WORKS;
	pptp_xmit(&po->chan,0);

	if (!test_and_set_bit(PPTP_FLAG_PROC,(unsigned long*)&opt->flags)){
		if (ppp_unit_number(&po->chan)!=-1){
			char unit[10];
			sprintf(unit,"ppp%i",ppp_unit_number(&po->chan));
			create_proc_read_entry(unit,0,proc_dir,read_proc,po);
		}else clear_bit(PPTP_FLAG_PROC,(unsigned long*)&opt->flags);
	}
}

static void do_ack_timeout_work(struct pppox_sock *po)
{
    struct pptp_opt *opt=&po->proto.pptp;
    int paused;
    
    spin_lock_bh(&opt->xmit_lock);
    paused=__test_and_clear_bit(PPTP_FLAG_PAUSE,(unsigned long*)&opt->flags);
    if (paused){
			if (opt->window>min_window) --opt->window;
			opt->ack_recv=opt->seq_sent;
    }
    spin_unlock_bh(&opt->xmit_lock);
    if (paused) ppp_output_wakeup(&po->chan);
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
static void _ack_timeout_work(struct work_struct *work)
{
    struct pptp_opt *opt=container_of(work,struct 
pptp_opt,ack_timeout_work.work);
    struct pppox_sock *po=container_of(opt,struct pppox_sock,proto.pptp);
		INC_ACK_TIMEOUTS;
    do_ack_timeout_work(po);
}
#else
static void do_ack_timeout_work(struct pppox_sock *po)
{
	INC_ACK_TIMEOUTS;
  do_ack_timeout_work(po);
}
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
static void ack_timeout_timer(struct pppox_sock *po)
{
    struct pptp_opt *opt;
    opt=&po->proto.pptp;
    schedule_task(&opt->ack_timeout_work);
}
#endif

static int get_seq(struct sk_buff *skb)
{
	struct iphdr *iph;
	u8 *payload;
	struct pptp_gre_header *header;

	iph = (struct iphdr*)skb->data;
	payload = skb->data + (iph->ihl << 2);
	header = (struct pptp_gre_header *)(payload);

	return ntohl(header->seq);
}
static void do_buf_work(struct pppox_sock *po)
{
	struct timeval tv1,tv2;
	struct sk_buff *skb;
	struct pptp_opt *opt=&po->proto.pptp;
	unsigned int t;

	spin_lock_bh(&opt->rcv_lock);
	do_gettimeofday(&tv1);
	while((skb=skb_dequeue(&opt->skb_buf))){
		if (!__pptp_rcv(po,skb,0)){
			skb_get_timestamp(skb,&tv2);
			t=(tv1.tv_sec-tv2.tv_sec)*1000000+(tv1.tv_usec-tv2.tv_usec);
			if (t<opt->stat->rtt){
				skb_queue_head(&opt->skb_buf,skb);
				#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
				mod_timer(&opt->buf_timer,t/100*HZ/10000);
				#else
				schedule_delayed_work(&opt->buf_work,t/100*HZ/10000);
				#endif
				goto exit;
			}
			t=get_seq(skb)-1;
			opt->stat->rx_lost+=t-opt->seq_recv;
			opt->seq_recv=t;
			if (log_level>=2)
				printk("PPTP[%i]: unbuffer packet %i\n",opt->src_addr.call_id,t+1);
			__pptp_rcv(po,skb,0);
		}
	}
exit:
	spin_unlock_bh(&opt->rcv_lock);
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
static void inline _buf_work(struct work_struct *work)
{
	struct pptp_opt *opt=container_of(work,struct pptp_opt,buf_work.work);
	struct pppox_sock *po=container_of(opt,struct pppox_sock,proto.pptp);
	
	INC_BUF_WORKS;
	do_buf_work(po);
}
#else
static void buf_work(struct pppox_sock *po)
{
	INC_BUF_WORKS;
	do_buf_work(po);
}
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
static void buf_timer(struct pppox_sock *po)
{
    struct pptp_opt *opt;
    opt=&po->proto.pptp;
    schedule_task(&opt->buf_work);
}
#endif

static int __pptp_rcv(struct pppox_sock *po,struct sk_buff *skb,int new)
{
	struct pptp_opt *opt=&po->proto.pptp;
	int headersize,payload_len,seq;
	__u8 *payload;
	struct pptp_gre_header *header;

	INC_RX_PACKETS;
	
	header = (struct pptp_gre_header *)(skb->data);

	if (log_level>=4) printk("PPTP[%i]: 
__pptv_rcv %i\n",opt->src_addr.call_id,new);

	if (new){
		/* test if acknowledgement present */
		if (PPTP_GRE_IS_A(header->ver)){
				int paused;
				__u32 ack = (PPTP_GRE_IS_S(header->flags))?
						header->ack:header->seq; /* ack in different place if S = 0 */
				
				ack = ntohl( ack);

				spin_lock_bh(&opt->xmit_lock);

				if (ack > opt->ack_recv) opt->ack_recv = ack;
				/* also handle sequence number wrap-around  */
				if (WRAPPED(ack,opt->ack_recv)) opt->ack_recv = ack;

				paused=__test_and_clear_bit(PPTP_FLAG_PAUSE,(unsigned long*)&opt->flags);
				spin_unlock_bh(&opt->xmit_lock);
				
				if (paused){
						#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
						del_timer(&opt->ack_timeout_timer);
						#else
				    cancel_delayed_work(&opt->ack_timeout_work);
				    #endif
				    ppp_output_wakeup(&po->chan);
				}else if (opt->window<opt->max_window) ++opt->window;

				if (opt->stat->pt_seq && opt->ack_recv > opt->stat->pt_seq){
					struct timeval tv;
					unsigned int rtt;
					do_gettimeofday(&tv);
					rtt = (tv.tv_sec - opt->stat->pt_time.tv_sec)*1000000+
						tv.tv_usec-opt->stat->pt_time.tv_usec;
					opt->stat->rtt = (opt->stat->rtt + rtt) / 2;
					if (opt->stat->rtt>opt->timeout) opt->stat->rtt=opt->timeout;
					opt->stat->pt_seq=0;
				}
		}else do_ack_timeout_work(po);

		/* test if payload present */
		if (!PPTP_GRE_IS_S(header->flags)){
			opt->stat->rx_acks++;
			goto drop;
		}
	}

	headersize  = sizeof(*header);
	payload_len = ntohs(header->payload_len);
	seq         = ntohl(header->seq);

	/* no ack present? */
	if (!PPTP_GRE_IS_A(header->ver)) headersize -= sizeof(header->ack);
	/* check for incomplete packet (length smaller than expected) */
	if (skb->len- headersize < payload_len){
		if (log_level>=1)
			printk("PPTP: discarding truncated packet (expected %d, got %d bytes)\n",
						payload_len, skb->len- headersize);
		opt->stat->rx_truncated++;
		goto drop;
	}

	payload=skb->data+headersize;
	/* check for expected sequence number */
	if ((seq == opt->seq_recv + 1) || (!opt->timeout &&
			(seq > opt->seq_recv + 1 || WRAPPED(seq, opt->seq_recv)))){
		if ( log_level >= 3 && opt->seq_sent<=log_packets)
			printk("PPTP[%i]: accepting packet %d size=%i 
(%02x %02x %02x %02x %02x %02x)\n",opt->src_addr.call_id, seq,payload_len,
				*(payload +0),
				*(payload +1),
				*(payload +2),
				*(payload +3),
				*(payload +4),
				*(payload +5));
		opt->stat->rx_accepted++;
		opt->stat->rx_lost+=seq-(opt->seq_recv + 1);
		opt->seq_recv = seq;
		#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
		schedule_task(&opt->ack_work);
		#else
		schedule_work(&opt->ack_work);
		#endif

		skb_pull(skb,headersize);

		if (payload[0] == PPP_ALLSTATIONS && payload[1] == PPP_UI){
			/* chop off address/control */
			if (skb->len < 3)
				goto drop;
			skb_pull(skb,2);
		}

		if ((*skb->data) & 1){
			/* protocol is compressed */
			skb_push(skb, 1)[0] = 0;
		}
		
		skb->ip_summed=CHECKSUM_NONE;
		ppp_input(&po->chan,skb);

		return 1;
	/* out of order, check if the number is too low and discard the packet.
	* (handle sequence number wrap-around, and try to do it right) */
	}else if ( seq < opt->seq_recv + 1 || WRAPPED(opt->seq_recv, seq) ){
		if ( log_level >= 1)
			printk("PPTP[%i]: discarding duplicate or old packet %d 
(expecting %d)\n",opt->src_addr.call_id,
							seq, opt->seq_recv + 1);
		opt->stat->rx_underwin++;
	/* sequence number too high, is it reasonably close? */
	}else /*if ( seq < opt->seq_recv + MISSING_WINDOW ||
						 WRAPPED(seq, opt->seq_recv + MISSING_WINDOW) )*/{
		opt->stat->rx_buffered++;
		if ( log_level >= 2 && new )
				printk("PPTP[%i]: buffering packet %d (expecting %d, lost or 
reordered)\n",opt->src_addr.call_id,
						seq, opt->seq_recv+1);
		return 0;
	/* no, packet must be discarded */
	}/*else{
		if ( log_level >= 1 )
			printk("PPTP[%i]: discarding bogus packet %d 
(expecting %d)\n",opt->src_addr.call_id,
							seq, opt->seq_recv + 1);
	}*/
drop:
	kfree_skb(skb);
	return -1;
}

static int pptp_rcv(struct sk_buff *skb)
{
	struct pptp_gre_header *header;
	struct pppox_sock *po;
	struct pptp_opt *opt;
	
	if (log_level>=4) printk("PPTP: pptp_rcv rx_stop=%i\n",rx_stop);
	
	if (rx_stop) goto drop;

  if (!pskb_may_pull(skb, 12))
		goto drop;

	header = (struct pptp_gre_header *)skb->data;

	if (    /* version should be 1 */
					((header->ver & 0x7F) != PPTP_GRE_VER) ||
					/* PPTP-GRE protocol for PPTP */
					(ntohs(header->protocol) != PPTP_GRE_PROTO)||
					/* flag C should be clear   */
					PPTP_GRE_IS_C(header->flags) ||
					/* flag R should be clear   */
					PPTP_GRE_IS_R(header->flags) ||
					/* flag K should be set     */
					(!PPTP_GRE_IS_K(header->flags)) ||
					/* routing and recursion ctrl = 0  */
					((header->flags&0xF) != 0)){
			/* if invalid, discard this packet */
		if (log_level>=1)
			printk("PPTP: Discarding GRE: %X %X %X %X %X %X\n",
							header->ver&0x7F, ntohs(header->protocol),
							PPTP_GRE_IS_C(header->flags),
							PPTP_GRE_IS_R(header->flags),
							PPTP_GRE_IS_K(header->flags),
							header->flags & 0xF);
		goto drop;
	}

	dst_release(skb->dst);
	skb->dst = NULL;
	nf_reset(skb);

	if ((po=lookup_chan(htons(header->call_id)))) {
		if (!(SK_STATE(sk_pppox(po))&PPPOX_BOUND))
			goto drop;
		if (!po->chan.ppp){
			printk("PPTP: received packed, but ppp is down\n");
			goto drop;
		}
		opt=&po->proto.pptp;
		spin_lock_bh(&opt->rcv_lock);
		if (__pptp_rcv(po,skb,1)){
			spin_unlock_bh(&opt->rcv_lock);
			do_buf_work(po);
		}else{
			__net_timestamp(skb);
			skb_queue_tail(&opt->skb_buf, skb);
			#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
			mod_timer(&opt->buf_timer,opt->stat->rtt/100*HZ/100000);
			#else
			schedule_delayed_work(&opt->buf_work,opt->stat->rtt/100*HZ/10000);
			#endif
			spin_unlock_bh(&opt->rcv_lock);
		}
		goto out;
	}else {
		if (log_level>=1)
			printk("PPTP: Discarding packet from unknown 
call_id %i\n",header->call_id);
		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0);
	}

drop:
	kfree_skb(skb);
out:
	return 0;
}

//=============== PROC =======================
static int proc_output (struct pppox_sock *po,char *buf)
{
	struct pptp_opt *opt=&po->proto.pptp;
	struct gre_statistics *stat=opt->stat;
	char *p=buf;
	p+=sprintf(p,"rx accepted  = %d\n",stat->rx_accepted);
	p+=sprintf(p,"rx lost      = %d\n",stat->rx_lost);
	p+=sprintf(p,"rx under win = %d\n",stat->rx_underwin);
	p+=sprintf(p,"rx buffered  = %d\n",stat->rx_buffered);
	p+=sprintf(p,"rx invalid   = %d\n",stat->rx_invalid);
	p+=sprintf(p,"rx acks      = %d\n",stat->rx_acks);
	p+=sprintf(p,"tx sent      = %d\n",stat->tx_sent);
	p+=sprintf(p,"tx failed    = %d\n",stat->tx_failed);
	p+=sprintf(p,"tx acks      = %d\n",stat->tx_acks);
	p+=sprintf(p,"rtt          = %d\n",stat->rtt);
	p+=sprintf(p,"timeout      = %d\n",opt->timeout);
	p+=sprintf(p,"window       = %d\n",opt->window);
	p+=sprintf(p,"max window   = %d\n",opt->max_window);

	return p-buf;
}
static int read_proc(char *page, char **start, off_t off,int count, int *eof, 
void *data)
{
	struct pppox_sock *po = data;
	int len = proc_output (po,page);
	if (len <= off+count) *eof = 1;
	*start = page + off;
	len -= off;
	if (len>count) len = count;
	if (len<0) len = 0;
	return len;
}

int ctrl_write_proc(struct file* file,const char __user *buffer,unsigned long 
count,void *data)
{
	int res=count;
	char *tmp_buf=kmalloc(count+1,GFP_KERNEL);
	if (copy_from_user(tmp_buf,buffer,count))
		res=-EFAULT;
	else{
		int val;
		tmp_buf[count]=0;
		if (sscanf(tmp_buf," log_level = %i",&val)==1) log_level=val;
		else if (sscanf(tmp_buf," log_packets = %i",&val)==1) log_packets=val;
		else if (sscanf(tmp_buf," rx_stop = %i",&val)==1) rx_stop=val;
		else res=-EINVAL;
	}
	kfree(tmp_buf);
	return res;
}
int stat_write_proc(struct file* file,const char __user *buffer,unsigned long 
count,void *data)
{
	int res=count;
	char *tmp_buf=kmalloc(count+1,GFP_KERNEL);
	if (copy_from_user(tmp_buf,buffer,count))
		res=-EFAULT;
	else{
		int val;
		tmp_buf[count]=0;
		if (sscanf(tmp_buf," enable = %i",&val)==1){
			if (!statistics && val){
				stat_last_update=jiffies;
				schedule_delayed_work(&stat_work,stat_collect_time*HZ);
			}
			else if (statistics && !val){
				cancel_delayed_work(&stat_work);
			}
			statistics=val;
		}
		else if (sscanf(tmp_buf," collect_time = %i",&val)==1){
			if (val<=0) res=-EINVAL;
			else stat_collect_time=val;
		}
		else res=-EINVAL;
	}
	kfree(tmp_buf);
	return res;
}
static int stat_read_proc(char *page, char **start, off_t off,int count, int 
*eof, void *data)
{
	int len;
	char *p=page;
	p+=sprintf(p,"enabled      = %i\n",statistics);
	p+=sprintf(p,"collect_time = %i\n",stat_collect_time);
	p+=sprintf(p,"\nperformance counters per sec:\n");
	p+=sprintf(p,"tx_packets   = %i\n",tx_packets_avg);
	p+=sprintf(p,"rx_packets   = %i\n",rx_packets_avg);
	p+=sprintf(p,"ack_timeouts = %i\n",ack_timeouts_avg);
	p+=sprintf(p,"ack_works    = %i\n",ack_works_avg);
	p+=sprintf(p,"buf_works    = %i\n",buf_works_avg);
	len=p-page;
	
	if (len <= off+count) *eof = 1;
	*start = page + off;
	len -= off;
	if (len>count) len = count;
	if (len<0) len = 0;
	return len;
}
//============================================

static int pptp_bind(struct socket *sock,struct sockaddr *uservaddr,int 
sockaddr_len)
{
	struct sock *sk = sock->sk;
	struct sockaddr_pppox *sp = (struct sockaddr_pppox *) uservaddr;
	struct pppox_sock *po = pppox_sk(sk);
	struct pptp_opt *opt=&po->proto.pptp;
	int error=0;

	if (log_level>=1)
		printk("PPTP: bind: addr=%X call_id=%i\n",sp->sa_addr.pptp.sin_addr.s_addr,
						sp->sa_addr.pptp.call_id);
	lock_sock(sk);

	opt->src_addr=sp->sa_addr.pptp;
	if (sp->sa_addr.pptp.call_id){
		if (lookup_chan(sp->sa_addr.pptp.call_id)){
			error=-EBUSY;
			goto end;
		}
		add_chan(po);
	}else{
		add_free_chan(po);
		if (!opt->src_addr.call_id)
			error=-EBUSY;
		if (log_level>=1)
			printk("PPTP: using call_id %i\n",opt->src_addr.call_id);
	}

 end:
	release_sock(sk);
	return error;
}

static int pptp_connect(struct socket *sock, struct sockaddr *uservaddr,
		  int sockaddr_len, int flags)
{
	struct sock *sk = sock->sk;
	struct sockaddr_pppox *sp = (struct sockaddr_pppox *) uservaddr;
	struct pppox_sock *po = pppox_sk(sk);
	struct pptp_opt *opt=&po->proto.pptp;
	struct rtable *rt;     			/* Route to the other host */
	int error=0;

	if (log_level>=1)
		printk("PPTP[%i]: connect: addr=%X call_id=%i\n",opt->src_addr.call_id,
						sp->sa_addr.pptp.sin_addr.s_addr,sp->sa_addr.pptp.call_id);

	lock_sock(sk);

	if (sp->sa_protocol != PX_PROTO_PPTP){
		error = -EINVAL;
		goto end;
	}

	/* Check for already bound sockets */
	if (SK_STATE(sk) & PPPOX_CONNECTED){
		error = -EBUSY;
		goto end;
	}

	/* Check for already disconnected sockets, on attempts to disconnect */
	if (SK_STATE(sk) & PPPOX_DEAD){
		error = -EALREADY;
		goto end;
	}

	if (!opt->src_addr.sin_addr.s_addr || !sp->sa_addr.pptp.sin_addr.s_addr){
		error = -EINVAL;
		goto end;
	}

	po->chan.private=sk;
	po->chan.ops=&pptp_chan_ops;
	
	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	{
		struct rt_key key = {
			.dst=opt->dst_addr.sin_addr.s_addr,
			.src=opt->src_addr.sin_addr.s_addr,
			.tos=RT_TOS(0),
		};
		if (ip_route_output_key(&rt, &key)) {
			return -EHOSTUNREACH;
		}
	}
	#else
	{
		struct flowi fl = {
				    .nl_u = { .ip4_u =
					      { .daddr = opt->dst_addr.sin_addr.s_addr,
						.saddr = opt->src_addr.sin_addr.s_addr,
						.tos = RT_CONN_FLAGS(sk) } },
				    .proto = IPPROTO_GRE };
		security_sk_classify_flow(sk, &fl);
		if (ip_route_output_key(&rt, &fl))
			return -EHOSTUNREACH;
		sk_setup_caps(sk, &rt->u.dst);
	}
	#endif
	po->chan.mtu=dst_mtu(&rt->u.dst);
	if (!po->chan.mtu) po->chan.mtu=PPP_MTU;
	po->chan.mtu-=PPTP_HEADER_OVERHEAD;
	
	po->chan.hdrlen=2+sizeof(struct pptp_gre_header);
	error = ppp_register_channel(&po->chan);
	if (error){
		printk(KERN_ERR "PPTP: failed to register PPP channel (%d)\n",error);
		goto end;
	}

	opt->dst_addr=sp->sa_addr.pptp;
	SK_STATE(sk) = PPPOX_CONNECTED;

 end:
	release_sock(sk);
	return error;
}

static int pptp_getname(struct socket *sock, struct sockaddr *uaddr,
		  int *usockaddr_len, int peer)
{
	int len = sizeof(struct sockaddr_pppox);
	struct sockaddr_pppox sp;

	sp.sa_family	= AF_PPPOX;
	sp.sa_protocol	= PX_PROTO_PPTP;
	sp.sa_addr.pptp=pppox_sk(sock->sk)->proto.pptp.src_addr;

	memcpy(uaddr, &sp, len);

	*usockaddr_len = len;

	return 0;
}

static int pptp_setsockopt(struct socket *sock, int level, int optname,
	char* optval, int optlen)
{
	struct sock *sk = sock->sk;
	struct pppox_sock *po = pppox_sk(sk);
	struct pptp_opt *opt=&po->proto.pptp;
	int val;
	
	if (optlen!=sizeof(int))
		return -EINVAL;

	if (get_user(val,(int __user*)optval))
		return -EFAULT;

	switch(optname) {
		case PPTP_SO_TIMEOUT:
			opt->timeout=val;
			break;
		case PPTP_SO_WINDOW:
			opt->max_window=val;
			opt->window=val/2;
			break;
		default:
				return -ENOPROTOOPT;
	}

	return 0;
}

static int pptp_getsockopt(struct socket *sock, int level, int optname,
	char* optval, int *optlen)
{
	struct sock *sk = sock->sk;
	struct pppox_sock *po = pppox_sk(sk);
	struct pptp_opt *opt=&po->proto.pptp;
	int len,val;

	if (get_user(len,(int __user*)optlen))
		return -EFAULT;

	if (len<sizeof(int))
		return -EINVAL;

	switch(optname) {
		case PPTP_SO_TIMEOUT:
			val=opt->timeout;
			break;
		case PPTP_SO_WINDOW:
			val=opt->window;
			break;
		default:
				return -ENOPROTOOPT;
	}

	if (put_user(sizeof(int),(int __user*)optlen))
		return -EFAULT;

	if (put_user(val,(int __user*)optval))
		return -EFAULT;

	return 0;
}

static int pptp_release(struct socket *sock)
{
	struct sock *sk = sock->sk;
	struct pppox_sock *po;
	struct pptp_opt *opt;
	int error = 0;

	if (!sk)
		return 0;

	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)	
	if (sk->dead)
	#else
	if (sock_flag(sk, SOCK_DEAD))
	#endif
		return -EBADF;
	SK_STATE(sk) = PPPOX_DEAD;
	po = pppox_sk(sk);
	opt=&po->proto.pptp;

	if (log_level>=1)
		printk("PPTP[%i]: release\n",opt->src_addr.call_id);

	wake_up(&opt->wait);

	if (opt->src_addr.sin_addr.s_addr) {
		#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
		del_timer(&opt->buf_timer);
		del_timer(&opt->ack_timeout_timer);
		flush_scheduled_tasks();
		#else
		cancel_delayed_work(&opt->buf_work);
		cancel_delayed_work(&opt->ack_timeout_work);
		flush_scheduled_work();
		#endif
		skb_queue_purge(&opt->skb_buf);
		del_chan(po);

		if (test_bit(PPTP_FLAG_PROC,(unsigned long*)&opt->flags)) {
			char unit[10];
			sprintf(unit,"ppp%i",ppp_unit_number(&po->chan));
			remove_proc_entry(unit,proc_dir);
		}
	}

	pppox_unbind_sock(sk);

	kfree(opt->stat);

	sock_orphan(sk);
	sock->sk = NULL;

	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)	
	skb_queue_purge(&sk->receive_queue);
	#else
	skb_queue_purge(&sk->sk_receive_queue);
	#endif
	sock_put(sk);

	return error;
}

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
static struct proto pptp_sk_proto = {
	.name	  = "PPTP",
	.owner	  = THIS_MODULE,
	.obj_size = sizeof(struct pppox_sock),
};
#endif

static struct proto_ops pptp_ops = {
    .family		= AF_PPPOX,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
    .owner		= THIS_MODULE,
#endif    
    .release		= pptp_release,
    .bind		=  pptp_bind,
    .connect		= pptp_connect,
    .socketpair		= sock_no_socketpair,
    .accept		= sock_no_accept,
    .getname		= pptp_getname,
    .poll		= sock_no_poll,
    .listen		= sock_no_listen,
    .shutdown		= sock_no_shutdown,
    .setsockopt		= pptp_setsockopt,
    .getsockopt		= pptp_getsockopt,
    .sendmsg		= sock_no_sendmsg,
    .recvmsg		= sock_no_recvmsg,
    .mmap		= sock_no_mmap,
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
    .ioctl		= pppox_ioctl,
    #endif
};

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
static void pptp_sock_destruct(struct sock *sk)
{
	if (sk->protinfo.destruct_hook)
		kfree(sk->protinfo.destruct_hook);
		
	MOD_DEC_USE_COUNT;
}

static int pptp_create(struct socket *sock)
{
	int error = -ENOMEM;
	struct sock *sk;
	struct pppox_sock *po;
	struct pptp_opt *opt;

	MOD_INC_USE_COUNT;

	sk = sk_alloc(PF_PPPOX, GFP_KERNEL, 1);
	if (!sk)
		goto out;

	sock_init_data(sock, sk);

	sock->state = SS_UNCONNECTED;
	sock->ops   = &pptp_ops;

	//sk->sk_backlog_rcv = pppoe_rcv_core;
	sk->state	   = PPPOX_NONE;
	sk->type	   = SOCK_STREAM;
	sk->family	   = PF_PPPOX;
	sk->protocol	   = PX_PROTO_PPTP;

	sk->protinfo.pppox=kzalloc(sizeof(struct pppox_sock),GFP_KERNEL);
	sk->destruct=pptp_sock_destruct;
	sk->protinfo.destruct_hook=sk->protinfo.pppox;
	
	po = pppox_sk(sk);
	po->sk=sk;
	opt=&po->proto.pptp;

	opt->window=max_window/2;
	opt->max_window=max_window;
	opt->timeout=0;
	opt->flags=0;
	opt->seq_sent=0; opt->seq_recv=-1;
	opt->ack_recv=0; opt->ack_sent=-1;
	skb_queue_head_init(&opt->skb_buf);
  INIT_TQUEUE(&opt->ack_work,(void(*)(void*))ack_work,po);
	INIT_TQUEUE(&opt->buf_work,(void(*)(void*))buf_work,po);
	INIT_TQUEUE(&opt->ack_timeout_work,(void(*)(void*))ack_timeout_work,po);
	INIT_TIMER(&opt->buf_timer,(void(*)(unsigned long))buf_timer,(long int)po);
	INIT_TIMER(&opt->ack_timeout_timer,(void(*)(unsigned long))ack_timeout_timer,
(long int)po);
	opt->stat=kzalloc(sizeof(*opt->stat),GFP_KERNEL);
	init_waitqueue_head(&opt->wait);
	spin_lock_init(&opt->xmit_lock);
	spin_lock_init(&opt->rcv_lock);

	error = 0;
out:
	return error;
}
#else
static int pptp_create(struct socket *sock)
{
	int error = -ENOMEM;
	struct sock *sk;
	struct pppox_sock *po;
	struct pptp_opt *opt;

	sk = sk_alloc(PF_PPPOX, GFP_KERNEL, &pptp_sk_proto, 1);
	if (!sk)
		goto out;

	sock_init_data(sock, sk);

	sock->state = SS_UNCONNECTED;
	sock->ops   = &pptp_ops;

	//sk->sk_backlog_rcv = pppoe_rcv_core;
	sk->sk_state	   = PPPOX_NONE;
	sk->sk_type	   = SOCK_STREAM;
	sk->sk_family	   = PF_PPPOX;
	sk->sk_protocol	   = PX_PROTO_PPTP;

	po = pppox_sk(sk);
	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	po->sk=sk;
	#endif
	opt=&po->proto.pptp;

	opt->window=max_window/2;
	opt->max_window=max_window;
	opt->timeout=0;
	opt->flags=0;
	opt->seq_sent=0; opt->seq_recv=-1;
	opt->ack_recv=0; opt->ack_sent=-1;
	skb_queue_head_init(&opt->skb_buf);
  #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
  INIT_WORK(&opt->ack_work,(work_func_t)ack_work);
	INIT_DELAYED_WORK(&opt->buf_work,(work_func_t)_buf_work);
	INIT_DELAYED_WORK(&opt->ack_timeout_work,(work_func_t)_ack_timeout_work);
  #else
	INIT_WORK(&opt->ack_work,(void(*)(void*))ack_work,po);
	INIT_WORK(&opt->buf_work,(void(*)(void*))buf_work,po);
	INIT_WORK(&opt->ack_timeout_work,(void(*)(void*))ack_timeout_work,po);
  #endif
	opt->stat=kzalloc(sizeof(*opt->stat),GFP_KERNEL);
	init_waitqueue_head(&opt->wait);
	spin_lock_init(&opt->xmit_lock);
	spin_lock_init(&opt->rcv_lock);

	error = 0;
out:
	return error;
}
#endif

static int pptp_ioctl(struct socket *sock, unsigned int cmd, unsigned long 
arg)
{
	struct sock *sk = sock->sk;
	struct pppox_sock *po = pppox_sk(sk);
	struct pptp_opt *opt=&po->proto.pptp;
	int res=-EINVAL;

	switch (cmd) {
	case PPPTPIOWFP:
		release_sock(sk);
		res=wait_event_timeout(opt->wait,opt->seq_sent||SK_STATE(sk) == 
PPPOX_DEAD,arg*HZ);
		res=(res&&res<HZ)?1:res/HZ;
		lock_sock(sk);
		break;
	}
	
	return res;
}

static struct pppox_proto pppox_pptp_proto = {
    .create	= pptp_create,
    .ioctl	= pptp_ioctl,
	  #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15)
    .owner	= THIS_MODULE,
    #endif
};

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
static struct inet_protocol net_pptp_protocol = {
	.handler	= pptp_rcv,
	//.err_handler	=	pptp_err,
	.protocol = IPPROTO_GRE,
	.name     = "PPTP",
};
#else
static struct net_protocol net_pptp_protocol = {
	.handler	= pptp_rcv,
	//.err_handler	=	pptp_err,
};
#endif

static int pptp_init_module(void)
{
	struct proc_dir_entry *res;
	int err=0;
	printk(KERN_INFO "PPTP driver version " PPTP_DRIVER_VERSION "\n");

	if (stat_collect_time==0)
	{
		printk(KERN_ERR "PPTP: stat_collect_time must be >0\n");
		return -EINVAL;
	}

	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	inet_add_protocol(&net_pptp_protocol);
	#else
	if (inet_add_protocol(&net_pptp_protocol, IPPROTO_GRE) < 0) {
		printk(KERN_INFO "PPTP: can't add protocol\n");
		goto out;
	}
	#endif

	#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
	err = proto_register(&pptp_sk_proto, 0);
	if (err){
		printk(KERN_INFO "PPTP: can't register sk_proto\n");
		goto out_inet_del_protocol;
	}
	#endif

 	err = register_pppox_proto(PX_PROTO_PPTP, &pppox_pptp_proto);
	if (err){
		printk(KERN_INFO "PPTP: can't register pppox_proto\n");
		goto out_unregister_sk_proto;
	}

	proc_dir=proc_mkdir("net/pptp",NULL);
	if (proc_dir){
	    proc_dir->owner=THIS_MODULE;
	    
	    res=create_proc_entry("ctrl",0,proc_dir);
	    if (!res)printk(KERN_ERR "PPTP: failed to create ctrl proc entry\n");
	    else res->write_proc=ctrl_write_proc;
	
	    res=create_proc_entry("stat",0,proc_dir);
	    if (!res)printk(KERN_ERR "PPTP: failed to create stat proc entry\n");
	    else{
	    	res->write_proc=stat_write_proc;
	    	res->read_proc=stat_read_proc;
	    }
	}else printk(KERN_ERR "PPTP: failed to create proc dir\n");
	//console_verbose();

  #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
  // TODO: not implemented
  #elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
	INIT_DELAYED_WORK(&stat_work,(work_func_t)do_stat_work);
  #else
	INIT_WORK(&stat_work,(void(*)(void*))do_stat_work,NULL);
  #endif
	
	stat_last_update=jiffies;
	if (statistics)
		schedule_delayed_work(&stat_work,stat_collect_time*HZ);

out:
	return err;
out_unregister_sk_proto:
	#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
	proto_unregister(&pptp_sk_proto);
	#endif
out_inet_del_protocol:
	
	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	inet_del_protocol(&net_pptp_protocol);
	#else
	inet_del_protocol(&net_pptp_protocol, IPPROTO_GRE);
	#endif
	goto out;
}

static void pptp_exit_module(void)
{
	cancel_delayed_work(&stat_work);
	flush_scheduled_work();
	
	unregister_pppox_proto(PX_PROTO_PPTP);
	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	inet_del_protocol(&net_pptp_protocol);
	#else
	proto_unregister(&pptp_sk_proto);
	inet_del_protocol(&net_pptp_protocol, IPPROTO_GRE);
	#endif

	if (proc_dir)
	{
		remove_proc_entry("ctrl",proc_dir);
		remove_proc_entry("stat",proc_dir);
		remove_proc_entry("pptp",NULL);
	}
}

module_init(pptp_init_module);
module_exit(pptp_exit_module);