
/* $Id: isdn_dwabc.c,v 1.24 2000/11/01 17:54:00 detabc Exp $

 * Linux ISDN subsystem, abc-extension releated funktions.
 *
 * Copyright           by abc GmbH
 *                     written by Detlef Wengorz <detlefw@isdn4linux.de>
 *
 * Many thanks for testing, debugging and writing Doku to:
 * Mario Schugowski <mario@mediatronix.de>
 *
 * 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, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <linux/config.h>
#define __NO_VERSION__

#ifdef CONFIG_ISDN_WITH_ABC

static char *dwabcrevison = "$Revision: 1.24 $";

#include <asm/semaphore.h>
#define CONFIG_ISDN_WITH_ABC_NEED_DWSJIFFIES 	1
#include <linux/list.h>
#include <linux/isdn.h>
#include "isdn_common.h"
#include "isdn_net.h"

#if CONFIG_ISDN_WITH_ABC_IPTABLES_NETFILTER
#include <linux/module.h>
#endif

#ifdef CONFIG_ISDN_WITH_ABC_IPTABLES_NETFILTER
#include <linux/netfilter_ipv4/ip_tables.h>
static void	dwabcnetfilter_init(void);
static void	dwabcnetfilter_fini(void);
#endif


struct PSH { 
	u_long saddr;
	u_long daddr;
	u_char zp[2]; 
	u_short len;
};

#include <linux/skbuff.h>

#ifdef CONFIG_ISDN_WITH_ABC_IPV4_TCP_KEEPALIVE
#include <net/ip.h>
#include <net/tcp.h>
#if CONFIG_ISDN_WITH_ABC_IPV4_DYNADDR || CONFIG_ISDN_WITH_ABC_IPTABLES_NETFILTER
#include <linux/inetdevice.h>
#endif
#endif

#include <net/udp.h>
#include <net/checksum.h>
#include <linux/isdn_dwabc.h>


#if CONFIG_ISDN_WITH_ABC_RAWIPCOMPRESS && CONFIG_ISDN_PPP
#include <linux/isdn_ppp.h>
extern struct isdn_ppp_compressor *isdn_ippp_comp_head;
#define ipc_head isdn_ippp_comp_head
#ifndef CI_BSD_COMPRESS
#define CI_BSD_COMPRESS 21
#endif
#endif

#define NBYTEORDER_30BYTES      0x1e00 
#define DWABC_TMRES (HZ / 10)

//#define KEEPALIVE_VERBOSE 1
//#define DYNADDR_VERBOSE	 1

#define VERBLEVEL (dev->net_verbose > 2)

static void dw_nfw_dlink(isdn_net_local *lp,struct sk_buff *skb,char *msg);
static int dwisdn_nfw_rw_sock(isdn_net_local *,struct sk_buff *);

static struct timer_list dw_abc_timer;


#ifdef CONFIG_ISDN_WITH_ABC_LCR_SUPPORT
static ISDN_DWSPINLOCK lcr_spin = ISDN_DWSPIN_UNLOCKED;
#define LCR_LOCK() isdn_dwspin_trylock(&lcr_spin)
#define LCR_ULOCK() isdn_dwspin_unlock(&lcr_spin)

typedef struct ISDN_DW_ABC_LCR {

	struct list_head dll;
	char lcr_printbuf[64 + ISDN_MSNLEN + ISDN_MSNLEN];
	char *lcr_poin;
	char *lcr_epoin;

} ISDN_DW_ABC_LCR;

static LIST_HEAD(lcr_dll);
static atomic_t lcr_open_count		=	ATOMIC_INIT(0);
static volatile  ulong lcr_call_counter	= 0;

#endif

#ifdef CONFIG_ISDN_WITH_ABC_IPV4_TCP_KEEPALIVE

static short 	deadloop					= 0;
static ISDN_DWSPINLOCK ipv4keep_spin = ISDN_DWSPIN_UNLOCKED;
#define TKAL_LOCK 	isdn_dwspin_trylock(&ipv4keep_spin)
#define TKAL_ULOCK 	isdn_dwspin_unlock(&ipv4keep_spin)

struct TCPM {
	struct list_head dll;
	u_short         tcpm_srcport;
	u_short         tcpm_dstport;
	u_short         tcpm_window;
	u_long          tcpm_srcadr;
	u_long          tcpm_dstadr;
	u_long          tcpm_seqnr;
	u_long          tcpm_acknr;
	u_long          tcpm_time;
};

static u_long   next_police		= 0;
static u_long   last_police		= 0;

#define MAX_MMA 16
static void    *MMA[MAX_MMA];
static LIST_HEAD(tcp_dll);
static LIST_HEAD(tcp_free_dll);
#endif

#ifdef CONFIG_ISDN_WITH_ABC_LCR_SUPPORT

static int myjiftime(char *p,u_long nj)
{
	sprintf(p,"%02ld:%02ld.%02ld",
		((nj / 100) / 60) % 100, (nj / 100) % 60,nj % 100);

	return(8);
}


static void dw_lcr_clear_all(void)
{
	struct list_head *lh;

	if(!LCR_LOCK()) {

		while((lh = lcr_dll.next) != &lcr_dll) {

			ISDN_DW_ABC_LCR *p = list_entry(lh,ISDN_DW_ABC_LCR,dll);
			list_del(&p->dll);
			kfree(p);
		}

		LCR_ULOCK();
	}
}

void isdn_dw_abc_lcr_open(void) 
{ atomic_inc(&lcr_open_count); }

void isdn_dw_abc_lcr_close(void) 
{ 
	if(atomic_dec_and_test(&lcr_open_count))
		dw_lcr_clear_all();
}

int isdn_dw_abc_lcr_lock(void) 
{ return(LCR_LOCK()); }

void isdn_dw_abc_lcr_ulock(void) 
{ LCR_ULOCK(); }


size_t isdn_dw_abc_lcr_readstat(char *buf,size_t count) 
{
	size_t  retw = 0;

	while(buf != NULL && count > 0) {

		struct list_head *lh = NULL;
		ISDN_DW_ABC_LCR *p = NULL;
		char *dp = NULL;
		size_t  n;

		if((n = LCR_LOCK())) {

			if(!retw)
				retw = n;

			break;
		}


		while((lh = lcr_dll.next) != &lcr_dll) {

			p = list_entry(lh,ISDN_DW_ABC_LCR,dll);

			if(p->lcr_poin >= p->lcr_epoin) {

				list_del(&p->dll);
				kfree(p);
				p = NULL;

			} else break;
		}

		if(p == NULL) {
				
			LCR_ULOCK();
			break;
		}

		n = p->lcr_epoin - p->lcr_poin;

		if(n > count)
			n = count;

		dp = p->lcr_poin;
		p->lcr_poin += n;
		retw += n;
		LCR_ULOCK();
		copy_to_user(buf,dp,n);
		buf += n;
	}

	return(retw);
}


static void isdn_dw_abc_lcr_clear_helper(isdn_net_local *lp)
{
	if(lp != NULL) {

		void *a,*b;

		a = lp->dw_abc_lcr_cmd;  
		b = lp->dw_abc_lcr_io;
		lp->dw_abc_lcr_io = NULL;
		lp->dw_abc_lcr_cmd = NULL;
		lp->dw_abc_lcr_callid = 
		lp->dw_abc_lcr_start_request =
		lp->dw_abc_lcr_end_request = 0;
		
		if(a) kfree(a);
		if(b) kfree(b);
	}
}

void isdn_dw_abc_lcr_clear(isdn_net_local *lp)
{
	if(!LCR_LOCK()) {
		isdn_dw_abc_lcr_clear_helper(lp);
		LCR_ULOCK();
	}
}


u_long isdn_dw_abc_lcr_call_number( isdn_net_local *lp,isdn_ctrl *call_cmd)
{
	u_long mid = 0;

	if(LCR_LOCK())
		return(0);

	isdn_dw_abc_lcr_clear_helper(lp);

	if( atomic_read(&lcr_open_count) > 0 && 
		lp != NULL 						&& 
		call_cmd != NULL) {

		ISDN_DW_ABC_LCR  *lc = NULL;
		int ab = 0;

		if((lp->dw_abc_lcr_cmd = 
			( isdn_ctrl *)kmalloc(sizeof(isdn_ctrl),GFP_ATOMIC)) == NULL) {

no_mem_out:;
			isdn_dw_abc_lcr_clear_helper(lp);
			LCR_ULOCK();
			printk(KERN_DEBUG "%s %d : LCR no memory\n",__FILE__,__LINE__);
			return(0);
		}

		memcpy(lp->dw_abc_lcr_cmd,call_cmd,sizeof(*call_cmd));
		while(!(lp->dw_abc_lcr_callid = mid = lcr_call_counter++));
		
		lp->dw_abc_lcr_end_request = lp->dw_abc_lcr_start_request = jiffies;
		lp->dw_abc_lcr_end_request += HZ * 3;

		if((lc = (ISDN_DW_ABC_LCR  *)kmalloc(sizeof(*lc),GFP_KERNEL)) == NULL)
			goto no_mem_out;

		lc->lcr_poin = lc->lcr_epoin = lc->lcr_printbuf;
		lc->lcr_epoin += myjiftime(lc->lcr_epoin,jiffies);

		sprintf(lc->lcr_epoin," DW_ABC_LCR\t%lu\t%.*s\t%.*s\n",
			mid,
			(int)ISDN_MSNLEN,
			call_cmd->parm.setup.eazmsn,
			(int)ISDN_MSNLEN,
			call_cmd->parm.setup.phone);

		lc->lcr_epoin += strlen(lc->lcr_epoin);
		ab = lc->lcr_epoin - lc->lcr_poin;

		list_add_tail(&lc->dll,&lcr_dll);
		LCR_ULOCK();

		if(ab > 0) {

			if(dev->drv[0] != NULL ) {

				dev->drv[0]->stavail += ab;
				wake_up_interruptible(&dev->drv[0]->st_waitq);
			}
		}

	} else LCR_ULOCK();

	return(mid);
}


int isdn_dw_abc_lcr_ioctl(u_long arg)
{
	struct ISDN_DWABC_LCR_IOCTL	i;
	int need = sizeof(struct ISDN_DWABC_LCR_IOCTL); 
	isdn_net_dev *p; 

	memset(&i,0,sizeof(struct ISDN_DWABC_LCR_IOCTL));
	copy_from_user(&i,(char *)arg,sizeof(int));

	if(i.lcr_ioctl_sizeof < need)
		need = i.lcr_ioctl_sizeof;

	if(need > 0) 
		copy_from_user(&i,(char *)arg,need);

	 if(LCR_LOCK())
	 	return(-EAGAIN);

	 p = dev->netdev; 

	 for(;p ; p = p->next) {

	 	isdn_net_local *lp = p->local;

	 	if(	lp->dw_abc_lcr_callid != i.lcr_ioctl_callid)
			continue;

		if(lp->dw_abc_lcr_cmd == NULL) 
			continue;

		if(lp->dw_abc_lcr_io == NULL)
			lp->dw_abc_lcr_io = (struct ISDN_DWABC_LCR_IOCTL *)
				kmalloc(sizeof(struct ISDN_DWABC_LCR_IOCTL),GFP_ATOMIC);

		if(lp->dw_abc_lcr_io == NULL) {

			printk(KERN_DEBUG "%s %d : no memory\n",__FILE__,__LINE__);
			continue;
		}

		memcpy(lp->dw_abc_lcr_io,&i,sizeof(struct ISDN_DWABC_LCR_IOCTL));

		if(i.lcr_ioctl_flags & DWABC_LCR_FLG_NEWNUMBER) {

			char *xx = i.lcr_ioctl_nr;
			char *exx = xx + sizeof(i.lcr_ioctl_nr);
			char *d = lp->dw_abc_lcr_cmd->parm.setup.phone;
			char *ed = d + sizeof(lp->dw_abc_lcr_cmd->parm.setup.phone) - 1;

			while(d < ed && xx < exx && *xx) *(d++) = *(xx++);
			while(d < ed) *(d++) = 0;
			*d = 0;
		}
	 }

	 LCR_ULOCK();
	 return(0);
}

#endif


#ifdef CONFIG_ISDN_WITH_ABC_UDP_CHECK
int dw_abc_udp_test(struct sk_buff *skb,struct net_device *ndev)
{
	if(ndev != NULL && skb != NULL && skb->protocol == htons(ETH_P_IP)) {

		struct iphdr *iph = (struct iphdr *)skb->data;
		isdn_net_local *lp = (isdn_net_local *) ndev->priv;
		int rklen = skb->len;

		if (skb->nh.raw > skb->data && skb->nh.raw < skb->tail) {

			rklen -= (char *)skb->nh.raw - (char *)skb->data;
			iph = (struct iphdr *)skb->nh.raw;
		}

		if(rklen >= 20 && iph->version == 4 && 
			!(lp->dw_abc_flags & ISDN_DW_ABC_FLAG_NO_UDP_CHECK)) {

			if(	iph->tot_len == NBYTEORDER_30BYTES	&& 
				iph->protocol == IPPROTO_UDP) {

				struct udphdr *udp = 
					(struct udphdr *)((char *)iph + (iph->ihl << 2));

				ushort usrc = ntohs(udp->source);

				if(	udp->dest == htons(25001) && 
					usrc >= 20000 && usrc < 25000) {

					char *p = (char *)(udp + 1);

					if(p[0] == p[1]) {

						char mc = 0;

						switch(*p) {
						case 0x30:

							mc = *p;

							if((lp->flags & ISDN_NET_CONNECTED) && (!lp->dialstate))
								mc++;

							break;

						case 0x32:

							mc = *p;
#ifdef CONFIG_ISDN_WITH_ABC_UDP_CHECK_DIAL
							if((lp->flags & ISDN_NET_CONNECTED) && (!lp->dialstate)) {

								mc++;
								break;
							}

							if(!isdn_net_force_dial_lp(lp)) mc++;
#endif
							break;

						case 0x11:
							mc = *p + 1;
							isdn_dw_abc_reset_interface(lp,1);
							break;

						case 0x28:	mc = *p + 1;	break;
						case 0x2a:
						case 0x2c:

							mc = *p;
#ifdef CONFIG_ISDN_WITH_ABC_UDP_CHECK_HANGUP
							if(!(lp->dw_abc_flags & ISDN_DW_ABC_FLAG_NO_UDP_HANGUP)) {

								if(lp->isdn_device >= 0) {

									isdn_net_hangup(ndev);
									mc = *p + 1;
								}
							}
#endif
							break;
						}

						if(mc) {

							struct sk_buff *nskb;
							int need = 2+sizeof(struct iphdr)+sizeof(struct udphdr);
							int hneed = need + ndev->hard_header_len;

							if((nskb = (struct sk_buff *)dev_alloc_skb(hneed)) != NULL) {

								ushort n = sizeof(struct udphdr) + 2;
								struct iphdr *niph;
								struct udphdr *nup;
								skb_reserve(nskb,ndev->hard_header_len);

								if((niph = (struct iphdr *)skb_put(nskb,need))==NULL){

									printk(KERN_DEBUG "%s: skb_put failt (%d bytes)\n", lp->name,hneed);
									dev_kfree_skb(nskb);
									return(0);
								}

								nup = (struct udphdr *)(niph + 1);
								((char *)(nup + 1))[0] = mc;
								((char *)(nup + 1))[1] = mc;
								nup->source=udp->dest;
								nup->dest=udp->source;
								nup->len=htons(n);
								nup->check=0; /* dont need checksum */
								memset((void *)niph,0,sizeof(*niph));
								niph->version=4;
								niph->ihl=5;
								niph->tot_len=NBYTEORDER_30BYTES;
								niph->ttl = 32;
								niph->protocol = IPPROTO_UDP;
								niph->saddr=iph->daddr;
								niph->daddr=iph->saddr;
								niph->id=iph->id;
								niph->check=ip_fast_csum((unsigned char *)niph,niph->ihl);
								nskb->dev = ndev;
								nskb->pkt_type = PACKET_HOST;
								nskb->protocol = htons(ETH_P_IP);
								nskb->mac.raw = nskb->data;
								netif_rx(nskb);
							}

							return(1);
						}
					}
				}
			}
		}
	}

	return(0);
}
#endif




void isdn_dw_clear_if(ulong pm,isdn_net_local *lp)
{
	if(lp != NULL) {

#ifdef CONFIG_ISDN_WITH_ABC_LCR_SUPPORT
		isdn_dw_abc_lcr_clear(lp);
#endif
#if CONFIG_ISDN_WITH_ABC_IPTABLES_NETFILTER
		lp->dw_abc_addr_ready = 0;
#endif
	}
}



static void dw_abc_timer_func(u_long dont_need_yet)
{
	register u_long t;

	if(!((t = ++isdn_dwabc_jiffies.msec_100) & 1))
		if(isdn_dwabc_jiffies.msec_200++ & 1)
			isdn_dwabc_jiffies.msec_400++;
	
	if(!(t % 5)) 
		if(isdn_dwabc_jiffies.msec_500++ & 1)
			isdn_dwabc_jiffies.msec_1000++;

	dw_abc_timer.expires = jiffies + DWABC_TMRES;
	add_timer(&dw_abc_timer);
}


#if CONFIG_ISDN_WITH_ABC_IPV4_TCP_KEEPALIVE || CONFIG_ISDN_WITH_ABC_IPV4_DYNADDR

static char    *ipnr2buf(u_long ipadr)
{
	static char     buf[8][16];
	static u_char   bufp = 0;
	char           *p = buf[bufp = (bufp + 1) & 7];
	u_char         *up = (u_char *) & ipadr;

	sprintf(p, "%d.%d.%d.%d", up[0], up[1], up[2], up[3]);
	return (p);
}
#endif

#if CONFIG_ISDN_WITH_ABC_IPV4_TCP_KEEPALIVE

static __inline__ void free_tcpm(struct TCPM *fb)
{
	if (fb != NULL) {

		list_del(&fb->dll);
		list_add_tail(&fb->dll,&tcp_free_dll);
	}
}

static void     ip4keepalive_get_memory(void)
{
	struct TCPM    *nb;
	struct TCPM    *enb;
	int             anz = 1024 / sizeof(struct TCPM);
	int             shl;

	for (shl = 0; shl < MAX_MMA && MMA[shl] != NULL; shl++) ;

	if (shl >= MAX_MMA)
		return;

	nb = (struct TCPM *) kmalloc(sizeof(struct TCPM) * anz, GFP_ATOMIC);

	if (nb == NULL) {

		printk(KERN_DEBUG "ip4keepalive no mem\n");
		return;

	}

	memset((void *) nb, 0, sizeof(struct TCPM) * anz);
	enb = nb + anz;
	MMA[shl] = (void *) nb;

	for (; nb < enb; nb++) 
		list_add_tail(&nb->dll,&tcp_free_dll);
}

static __inline struct TCPM *get_free_tm(void)
{
	struct TCPM    *nb;
	struct list_head *lh = NULL;

	if(list_empty(&tcp_free_dll)) {

		if(MMA[MAX_MMA - 1] == NULL) 
			ip4keepalive_get_memory();
	}

	if(list_empty(&tcp_free_dll)) {

		if(!list_empty(&tcp_dll))
			lh = tcp_dll.prev;

	} else lh = tcp_free_dll.next;

	if(lh == NULL)
		return(NULL);

	list_del(lh);
	nb = list_entry(lh,struct TCPM,dll);
	memset((void *) nb, 0, sizeof(*nb));
	list_add(&nb->dll,&tcp_dll);
	
	return (nb);
}


static int  isdn_tcpipv4_test(struct net_device *ndev, struct iphdr *ip, struct sk_buff *skb)
/******************************************************************************
	
	return	==	0	==	no keepalive 
			==	1	==	keepalive response transmited

****************************************************************************/

{
	int             ip_hd_len = ip->ihl << 2;
	struct tcphdr  *tcp = (struct tcphdr *) (((u_char *) ip) + ip_hd_len);

	int             tcp_hdr_len = tcp->doff << 2;
	int             tcp_data_len = ntohs(ip->tot_len) - tcp_hdr_len - ip_hd_len;

	u_long          tcp_seqnr;
	struct TCPM    *nb;
	int             retw = 0;
	int				shl = 0;
	int				secure = 10000;

#ifdef KEEPALIVE_VERBOSE
	if (VERBLEVEL)
		printk(KERN_DEBUG "isdn_keepalive %s-called ipver %d ipproto %d %s->%s len %d\n",
				(ndev != NULL) ? "Tx" : "Rx",
			   ip->version,
			   ip->protocol,
			   ipnr2buf(ip->saddr),
			   ipnr2buf(ip->daddr),
			   ntohs(ip->tot_len));
#endif

	if (next_police < jiffies || last_police > jiffies) {

		struct list_head *lh = NULL;
		next_police = last_police = jiffies;
		next_police += HZ * 60;

		for(lh = tcp_dll.next,shl=0; lh != &tcp_dll && shl < secure;shl++) {

			nb = list_entry(lh,struct TCPM,dll);
			lh = lh->next;

			if (nb->tcpm_time > jiffies || 
				((jiffies - nb->tcpm_time) > (HZ * 3600 * 12)))
				free_tcpm(nb);
		}

		if(shl >= secure) {

			printk(KERN_WARNING "ip_isdn_tcp_keepalive: police deadloop\n");
			deadloop = 1;
		}
	}

#ifdef KEEPALIVE_VERBOSE
	if (VERBLEVEL) {

		printk(KERN_DEBUG
			   "isdn_keepalive %hd->%hd %d %d %d/%d\n",
			   ntohs(tcp->source),
			   ntohs(tcp->dest),
			   ip_hd_len,
			   tcp->doff,
			   tcp_hdr_len,
			   tcp_data_len);
	}
#endif
	if (tcp_data_len < 0) {

		printk(KERN_DEBUG
			   "isdn_keepalive adaten < 0 %d %d %d/%d\n",
			   ip_hd_len,
			   tcp->doff,
			   tcp_hdr_len,
			   tcp_data_len);

		return (0);
	}

	nb = NULL;

	{
		struct list_head *lh = NULL;

		shl = 0;

		list_for_each(lh,&tcp_dll) {

			if(shl++ >= secure)
				break;

			nb = list_entry(lh,struct TCPM,dll);

			 if(!((nb->tcpm_srcadr ^ ip->saddr) ||
		  		(nb->tcpm_dstadr ^ ip->daddr) ||
		  		(nb->tcpm_srcport ^ tcp->source) ||
		  		(nb->tcpm_dstport ^ tcp->dest)))
				break;

			nb = NULL;
		}

		if(shl >= secure) {

			printk(KERN_WARNING "ip_isdn_tcp_keepalive: search deadloop\n");
			deadloop = 1;
		}
	}

#ifdef KEEPALIVE_VERBOSE
	if (VERBLEVEL) {

		printk(KERN_DEBUG
			   "isdn_keepalive found tcp_merk 0x%08lX %d %d/%d\n",
			   (long) nb,
			   tcp->doff,
			   tcp_hdr_len,
			   tcp_data_len);

		printk(KERN_DEBUG
			   "isdn_keepalive syn %d ack %d fin %d rst %d psh %d urg %d\n",
			   tcp->syn,
			   tcp->ack,
			   tcp->fin,
			   tcp->rst,
			   tcp->psh,
			   tcp->urg);
	}
#endif
	if (nb == NULL) {

		if(!tcp->ack || tcp->fin || tcp->rst || tcp->syn || tcp->urg) {
			

#ifdef KEEPALIVE_VERBOSE
			if (VERBLEVEL)
				printk(KERN_DEBUG "isdn_keepalive flags ausgang\n");
#endif
			return (0);
		}

		if ((nb = get_free_tm()) == NULL) {

#ifdef KEEPALIVE_VERBOSE
			if (VERBLEVEL)
				printk(KERN_DEBUG "isdn_keepalive get_free_tm == NULL\n");
#endif

			return (0);
		}

		nb->tcpm_srcadr = ip->saddr;
		nb->tcpm_dstadr = ip->daddr;
		nb->tcpm_srcport = tcp->source;
		nb->tcpm_dstport = tcp->dest;
		nb->tcpm_acknr = tcp->ack_seq;
		nb->tcpm_window = tcp->window;
		nb->tcpm_time = jiffies;
		nb->tcpm_seqnr = ntohl(tcp->seq) + tcp_data_len;

#ifdef KEEPALIVE_VERBOSE
		if (VERBLEVEL) {

			printk(KERN_DEBUG
				   "isdn_keepalive put new %s:%hu->%s:%hu  ack %lu oseq %lu len %d nseq %lu\n",
				   ipnr2buf(ip->saddr),
				   ntohs(tcp->source),
				   ipnr2buf(ip->daddr),
				   ntohs(tcp->dest),
				   ntohl(tcp->ack_seq),
				   ntohl(tcp->seq),
				   tcp_data_len,
				   nb->tcpm_seqnr);
		}
#endif
		return (0);
	}

	if (!tcp->ack || tcp->fin || tcp->rst || tcp->urg) {

#ifdef KEEPALIVE_VERBOSE
		if (VERBLEVEL)
			printk(KERN_DEBUG
				   "isdn_keepalive received || flags != 0 || ack == 0\n");
#endif

		free_tcpm(nb);
		return (0);
	}

	tcp_seqnr = ntohl(tcp->seq);

	if (!tcp->psh 						&& 
		tcp_data_len < 2 				&&
		nb->tcpm_acknr == tcp->ack_seq 	&&
		nb->tcpm_window == tcp->window	) {

		if (nb->tcpm_seqnr == tcp_seqnr || (nb->tcpm_seqnr - 1) == tcp_seqnr) {

			/*
			 * so, das ist schon ein fast ein sicherer
			 * keepalive-request.
			 * aber es koennte immer noch ein retransmit sein
			 */

			if (nb->tcpm_time < jiffies &&
				(jiffies - nb->tcpm_time) >= ((u_long) 50 * HZ)) {

				struct sk_buff *nskb;
				int             need;
				int             hlen;

				/*
				 * ok jetzt eine antwort generieren
				 */

				need = sizeof(struct iphdr) + sizeof(struct tcphdr);

				hlen = ndev->hard_header_len;
				hlen = (hlen + 15) & ~15;
				nskb = dev_alloc_skb(need + hlen);

				if (nskb != NULL) {

					struct tcphdr  *ntcp;
					struct iphdr   *iph = (struct iphdr *) ((u_char *) skb->data);

					struct PSH *psh;

					skb_reserve(nskb, hlen);
					iph = (struct iphdr *) skb_put(nskb, sizeof(*iph));
					ntcp = (struct tcphdr *) skb_put(nskb, sizeof(*ntcp));

					if(dev->net_verbose > 0)
						printk(KERN_DEBUG "isdn_keepalive send response %s->%s %d->%d\n",
							   ipnr2buf(ip->daddr),
							   ipnr2buf(ip->saddr),
							   ntohs(tcp->dest),
							   ntohs(tcp->source));

					memset((void *) ntcp, 0, sizeof(*ntcp));
					psh = (struct PSH *) (((u_char *) ntcp) - sizeof(*psh));
					psh->saddr = ip->daddr;
					psh->daddr = ip->saddr;
					psh->zp[0] = 0;
					psh->zp[1] = 6;
					psh->len = htons(sizeof(*tcp));

					ntcp->source = tcp->dest;
					ntcp->dest = tcp->source;
					ntcp->seq = tcp->ack_seq;
					ntcp->ack_seq = htonl(tcp_seqnr + tcp_data_len);
					ntcp->window = tcp->window;
					ntcp->doff = sizeof(*tcp) >> 2;
					ntcp->check = 0;
					ntcp->urg_ptr = 0;
					ntcp->ack = 1;
					ntcp->check = ip_compute_csum((void *) psh, 32);
					memset(iph, 0, sizeof(*iph));
					iph->version = 4;
					iph->ihl = 5;
					iph->tos = ip->tos;
					iph->tot_len = htons(sizeof(*iph) + sizeof(*ntcp));
					iph->id = (u_short) (jiffies & 0xFFFF);
					iph->frag_off = 0;
					iph->ttl = IPDEFTTL;
					iph->protocol = ip->protocol;
					iph->daddr = ip->saddr;
					iph->saddr = ip->daddr;
					iph->check = ip_compute_csum((void *) iph, sizeof(*iph));
					nskb->dev = ndev;
					nskb->mac.raw = nskb->data;
					nskb->protocol = htons(ETH_P_IP);
					nskb->pkt_type = PACKET_HOST;
					netif_rx(nskb);
					retw = 1;

				} else {

					printk(KERN_DEBUG
						   "isdn_keepalive no space for new skb\n");
				}

			} else {

#ifdef KEEPALIVE_VERBOSE
				if (VERBLEVEL) {

					printk(KERN_DEBUG
						   "isdn_keepalive jiffies %lu %lu\n",
						   jiffies, nb->tcpm_time);
				}
#endif
			}

		} else {

#ifdef KEEPALIVE_VERBOSE
			if (VERBLEVEL) {

				printk(KERN_DEBUG
					   "isdn_keepalive no keep  nb->tcpm_seqnr %lu tcp->seqnr %lu dlen %d nb->ack %lu tcp->ack %lu nm->window %d tcp->window %d\n",
					   nb->tcpm_seqnr,
					   tcp_seqnr,
					   tcp_data_len,
					   ntohl(nb->tcpm_acknr),
					   ntohl(tcp->ack_seq),
					   ntohs(nb->tcpm_window),
					   ntohs(tcp->window));
			}
#endif
		}

	} else {

#ifdef KEEPALIVE_VERBOSE
		if (VERBLEVEL) {

			printk(KERN_DEBUG
				   "isdn_keepalive datenlen %d ack==seq %d win==win %d\n",
				   tcp_data_len,
				   nb->tcpm_acknr == tcp->ack_seq,
				   nb->tcpm_window == tcp->window);
		}
#endif
	}

	nb->tcpm_seqnr = tcp_seqnr + tcp_data_len;
	nb->tcpm_acknr = tcp->ack_seq;
	nb->tcpm_window = tcp->window;
	nb->tcpm_time = jiffies;

	list_del(&nb->dll);
	list_add(&nb->dll,&tcp_dll);

	return (retw);
}

static void isdn_tcp_keepalive_init(void)
{
	if(!TKAL_LOCK) {

		INIT_LIST_HEAD(&tcp_dll);
		INIT_LIST_HEAD(&tcp_free_dll);
		next_police = last_police = 0;
		deadloop = 0;
		memset(MMA,0,sizeof(MMA));
		TKAL_ULOCK;

	} else printk(KERN_INFO "isdn keepalive_init can't lcok\n");
}

static void isdn_tcp_keepalive_done(void)
{
	int shl;

	if(!TKAL_LOCK) {

		INIT_LIST_HEAD(&tcp_dll);
		INIT_LIST_HEAD(&tcp_free_dll);
		next_police = last_police = 0;
		for (shl = 0; shl < MAX_MMA && MMA[shl] != NULL; shl++) kfree(MMA[shl]);
		memset(MMA,0,sizeof(MMA));
		TKAL_ULOCK;

	} else printk(KERN_INFO "isdn keepalive_done can't lcok\n");
}
#endif

#if CONFIG_ISDN_WITH_ABC_IPV4_TCP_KEEPALIVE || CONFIG_ISDN_WITH_ABC_IPV4_DYNADDR
struct sk_buff *isdn_dw_abc_ip4_keepalive_test(struct net_device *ndev,struct sk_buff *skb)
{
	int rklen;
	struct iphdr *ip;
	isdn_net_local *lp = NULL;

	if(ndev == NULL || skb == NULL)
		return(skb);

	lp = (isdn_net_local *)ndev->priv;

	if(ntohs(skb->protocol) != ETH_P_IP) {

#ifdef KEEPALIVE_VERBOSE
		if(VERBLEVEL)
			printk(KERN_WARNING "ip_isdn_keepalive: protocol != ETH_P_IP\n");
#endif
		return(skb);
	}

	rklen = skb->len;
	ip = (struct iphdr *)skb->data;

	if (skb->nh.raw > skb->data && skb->nh.raw < skb->tail) {

		rklen -= (char *)skb->nh.raw - (char *)skb->data;
		ip = (struct iphdr *)skb->nh.raw;
	}

	if (rklen < sizeof(struct iphdr)) {
#ifdef KEEPALIVE_VERBOSE
		if(VERBLEVEL)
			printk(KERN_WARNING "ip_isdn_keepalive: len %d < iphdr\n",
			rklen);
#endif
		return (skb);
	}

	rklen -= sizeof(struct iphdr);
	
	if(ip->version != 4 ) {
#ifdef KEEPALIVE_VERBOSE
		if(VERBLEVEL)
			printk(KERN_WARNING
				"ip_isdn_keepalive: ipversion %d != 4\n",
				ip->version);
#endif
		return(skb);
	}

#ifdef CONFIG_ISDN_WITH_ABC_IPV4_DYNADDR
	if(ndev->ip_ptr != NULL && (lp->dw_abc_flags & ISDN_DW_ABC_FLAG_DYNADDR)) {

		struct in_device *indev = (struct in_device *)ndev->ip_ptr;
		struct in_ifaddr *ifaddr = NULL;

		if((ifaddr = indev->ifa_list) != NULL) {

			ulong ipaddr = ifaddr->ifa_local;

#ifdef DYNADDR_VERBOSE
			if(VERBLEVEL)
				printk(KERN_DEBUG 
					"isdn_dynaddr: %s syncpp %d %s->%s %s\n",
					lp->name,
					lp->p_encap == ISDN_NET_ENCAP_SYNCPPP,
					ipnr2buf(ip->saddr),
					ipnr2buf(ip->daddr),
					ipnr2buf(ipaddr));
#endif
			if((ip->saddr ^ ipaddr)) {

				if(skb->sk == NULL || dwisdn_nfw_rw_sock(lp,skb)) {

					dw_nfw_dlink(lp,skb,NULL);
					return(NULL);
				}
			}
		}
	}
#endif

#ifdef CONFIG_ISDN_WITH_ABC_IPV4_TCP_KEEPALIVE
	if((lp->dw_abc_flags & ISDN_DW_ABC_FLAG_NO_TCP_KEEPALIVE)) 
		return(skb);

	if (rklen < sizeof(struct tcphdr)) {
#ifdef KEEPALIVE_VERBOSE
			if(VERBLEVEL)
				printk(KERN_WARNING "ip_isdn_keepalive: len %d < \n",skb->len);
#endif
		return (skb);
	}

	if(ip->protocol != IPPROTO_TCP) {
#ifdef KEEPALIVE_VERBOSE
		if(VERBLEVEL)
			printk(KERN_WARNING
				"ip_isdn_keepalive: ip->proto %d != IPPROTO_TCP\n",
				ip->protocol);
#endif
		return(skb);
	}

	if(deadloop) {

		if(deadloop < 10) {
			printk(KERN_WARNING "ip_isdn_keepalive: sorry deadloop detected\n");
			deadloop++;
		}

		return(skb);
	}

	rklen = 0;

	if(!TKAL_LOCK) {

		rklen = isdn_tcpipv4_test(ndev,ip,skb);
		TKAL_ULOCK;
	}

	return(rklen ? NULL : skb);
#else
	return(skb);
#endif
}
#endif

void isdn_dw_abc_init_func(void)
{

#ifdef CONFIG_ISDN_WITH_ABC_IPV4_TCP_KEEPALIVE
	isdn_tcp_keepalive_init();
#endif

	init_timer(&dw_abc_timer);
	dw_abc_timer.function = dw_abc_timer_func;


	printk( KERN_INFO
		"abc-extension %s Kernel 0x%06X\n"
		"written by\nDetlef Wengorz <detlefw@isdn4linux.de>\n"
		"Thanks for test's etc. to:\n"
		"Mario Schugowski <mario@mediatronix.de>\n"
		"For more details see http://i4l.mediatronix.de\n"
		"Installed options:\n"
#ifdef CONFIG_ISDN_WITH_ABC_CALLB
		"CONFIG_ISDN_WITH_ABC_CALLB\n"
#endif
#ifdef CONFIG_ISDN_WITH_ABC_UDP_CHECK
		"CONFIG_ISDN_WITH_ABC_UDP_CHECK\n"
#endif
#ifdef CONFIG_ISDN_WITH_ABC_UDP_CHECK_HANGUP
		"CONFIG_ISDN_WITH_ABC_UDP_CHECK_HANGUP\n"
#endif
#ifdef CONFIG_ISDN_WITH_ABC_UDP_CHECK_DIAL
		"CONFIG_ISDN_WITH_ABC_UDP_CHECK_DIAL\n"
#endif
#ifdef CONFIG_ISDN_WITH_ABC_OUTGOING_EAZ
		"CONFIG_ISDN_WITH_ABC_OUTGOING_EAZ\n"
#endif
#ifdef CONFIG_ISDN_WITH_ABC_LCR_SUPPORT
		"CONFIG_ISDN_WITH_ABC_LCR_SUPPORT\n"
#endif
#ifdef CONFIG_ISDN_WITH_ABC_IPV4_TCP_KEEPALIVE
		"CONFIG_ISDN_WITH_ABC_IPV4_TCP_KEEPALIVE\n"
#endif
#ifdef CONFIG_ISDN_WITH_ABC_IPV4_DYNADDR
		"CONFIG_ISDN_WITH_ABC_IPV4_DYNADDR\n"
#endif
#ifdef CONFIG_ISDN_WITH_ABC_RCV_NO_HUPTIMER
		"CONFIG_ISDN_WITH_ABC_RCV_NO_HUPTIMER\n"
#endif
#ifdef CONFIG_ISDN_WITH_ABC_CH_EXTINUSE
		"CONFIG_ISDN_WITH_ABC_CH_EXTINUSE\n"
#endif
#ifdef CONFIG_ISDN_WITH_ABC_CONN_ERROR
		"CONFIG_ISDN_WITH_ABC_CONN_ERROR\n"
#endif
#ifdef CONFIG_ISDN_WITH_ABC_RAWIPCOMPRESS
		"CONFIG_ISDN_WITH_ABC_RAWIPCOMPRESS\n"
#endif
#ifdef CONFIG_ISDN_WITH_ABC_IPTABLES_NETFILTER
		"CONFIG_ISDN_WITH_ABC_IPTABLES_NETFILTER\n"
#endif
		"loaded\n",
		dwabcrevison,LINUX_VERSION_CODE);

		dwsjiffies = 0;
		dw_abc_timer.expires = jiffies + DWABC_TMRES;
		add_timer(&dw_abc_timer);
#ifdef CONFIG_ISDN_WITH_ABC_IPTABLES_NETFILTER
		dwabcnetfilter_init();
#endif
}

void isdn_dw_abc_release_func(void)
{
#ifdef CONFIG_ISDN_WITH_ABC_IPTABLES_NETFILTER
	dwabcnetfilter_fini();
#endif
	del_timer(&dw_abc_timer);
#ifdef CONFIG_ISDN_WITH_ABC_LCR_SUPPORT
	dw_lcr_clear_all();
#endif
#ifdef CONFIG_ISDN_WITH_ABC_IPV4_TCP_KEEPALIVE
	isdn_tcp_keepalive_done();
#endif
	printk( KERN_INFO
		"abc-extension %s  Kernel 0x%06X\n"
		"written by\n"
		"Detlef Wengorz <detlefw@isdn4linux.de>\n"
		"Thanks for test's etc. to:\n"
		"Mario Schugowski <mario@mediatronix.de>\n"
		"unloaded\n"
		"For more details see http://i4l.mediatronix.de\n",
		dwabcrevison,LINUX_VERSION_CODE);
}


void isdn_dwabc_test_phone(isdn_net_local *lp) 
{
	if(lp != NULL) {

		isdn_net_phone *h = lp->phone[0];
		ulong oflags = lp->dw_abc_flags;
		int secure = 0;

		lp->dw_abc_flags = 0;
#ifdef CONFIG_ISDN_WITH_ABC_OUTGOING_EAZ
		*lp->dw_out_msn = 0;
#endif

		for(;h != NULL && secure < 1000;secure++,h = h->next) {

			char *p 	= 	h->num;
			char *ep 	= 	p + ISDN_MSNLEN;

			for(;p < ep && *p && (*p <= ' ' || *p == '"' || *p == '\'');p++);

			if(p >= ep)
				continue;

#ifdef CONFIG_ISDN_WITH_ABC_OUTGOING_EAZ
			if(*p == '>') {

				if(++p < ep && *p != '<' && *p != '>') {

					char *d = lp->dw_out_msn;

					for(;*p && (p < ep) && (*p == ' ' || *p == '\t');p++);
					for(ep--;*p && (p < ep);) *(d++) = *(p++);
					*d = 0;
					continue;
				}
			}
#endif

			if(*p == '~') {

				/* abc switch's */

				for(p++;p < ep && *p;p++) switch(*p) {
				case 'k':	lp->dw_abc_flags |= ISDN_DW_ABC_FLAG_NO_TCP_KEEPALIVE;		break;
				case 'u':	lp->dw_abc_flags |= ISDN_DW_ABC_FLAG_NO_UDP_CHECK;			break;
				case 'h':	lp->dw_abc_flags |= ISDN_DW_ABC_FLAG_NO_UDP_HANGUP;			break;
				case 'd':	lp->dw_abc_flags |= ISDN_DW_ABC_FLAG_NO_UDP_DIAL;			break;
				case 'c':	lp->dw_abc_flags |= ISDN_DW_ABC_FLAG_NO_CH_EXTINUSE;		break;
				case 'e':   lp->dw_abc_flags |= ISDN_DW_ABC_FLAG_NO_CONN_ERROR;			break;
				case 'l':   lp->dw_abc_flags |= ISDN_DW_ABC_FLAG_NO_LCR;				break;

				case 'x':
				case 'X':	lp->dw_abc_flags |= ISDN_DW_ABC_FLAG_RCV_NO_HUPTIMER;		break;

				case 'D':	lp->dw_abc_flags |= ISDN_DW_ABC_FLAG_DYNADDR;				break;
				case 'B':	lp->dw_abc_flags |= ISDN_DW_ABC_FLAG_BSD_COMPRESS;			break;
				case 'L': 	lp->dw_abc_flags |= ISDN_DW_ABC_FLAG_LEASED_LINE;			break;

				case '"':
				case ' ':
				case '\t':
				case '\'':	break;

				default:	
					printk(KERN_DEBUG"isdn_net: %s abc-switch <~%c> unknown\n",lp->name,*p);
					break;
				}
			}
		}

		if(lp->dw_abc_flags & ISDN_DW_ABC_FLAG_LEASED_LINE) {

			lp->dw_abc_flags &= ~ISDN_DW_ABC_FLAG_DYNADDR;

			lp->dw_abc_flags |= 
					ISDN_DW_ABC_FLAG_NO_TCP_KEEPALIVE	|
					ISDN_DW_ABC_FLAG_NO_UDP_CHECK		|
					ISDN_DW_ABC_FLAG_NO_UDP_HANGUP		|
					ISDN_DW_ABC_FLAG_NO_UDP_DIAL		|
					ISDN_DW_ABC_FLAG_NO_CH_EXTINUSE		|
					ISDN_DW_ABC_FLAG_NO_CONN_ERROR		|
					ISDN_DW_ABC_FLAG_NO_LCR;
		}

		if(dev->net_verbose  && (lp->dw_abc_flags != oflags || dev->net_verbose > 4))
			printk(KERN_DEBUG "isdn_net %s abc-flags 0x%lx\n",lp->name,lp->dw_abc_flags);

	}
}


#ifdef CONFIG_ISDN_WITH_ABC_ICALL_BIND 
void isdn_dw_abc_free_lch_with_pch(int di,int ch)
{
	if(di >= 0 && di < ISDN_MAX_DRIVERS && ch >= 0) {

		driver *d = dev->drv[di];

		if(d != NULL) {

			u_char *p = d->dwabc_lchmap;
			u_char *ep = d->dwabc_lchmap + ISDN_DW_ABC_MAX_CH_P_RIVER;
			ch++;

			for(;p < ep;p++) {

				if(*p == ch)
					*p = 0;
			}
		}
	}
}



static void dwabc_check_lchmap(void)
{
	if(dev->dwabc_lch_check > jiffies || (dev->dwabc_lch_check + HZ) < jiffies) {
		int i;
		dev->dwabc_lch_check = jiffies;

		for(i = 0; i < ISDN_MAX_CHANNELS; i++) {

			int di = dev->drvmap[i];
			driver *p;

			if(di < 0 || di >= ISDN_MAX_DRIVERS)
				continue;

			if((p = dev->drv[di]) == NULL)
				continue;

			if(p->dwabc_lch_use < jiffies && (p->dwabc_lch_use + HZ) < jiffies)
				continue;

			if(USG_NONE(dev->usage[i])) 
				isdn_dw_abc_free_lch_with_pch(di,dev->chanmap[i]);
		}
	}
}


static int get_driverid(isdn_net_local *lp,char *name,char *ename,ulong *bits)
{
	int retw = -1;

	if(name != NULL && lp != NULL) {

		int i = 0;
		char *p;

		for(;name < ename && *name && *name <= ' ' ; name++);
		for(p = name;p < ename && *p && *p != ',';p++);

		for (i = 0; i < ISDN_MAX_DRIVERS; i++) {

			char *s = name;
			char *d = dev->drvid[i];

			for(;s < p && *s == *d && *s;s++,d++);

			if(!*d && s >= p) 
				break;
		}

		if(i >= ISDN_MAX_DRIVERS) {

			printk(KERN_DEBUG "isdn_dwabc_bind %s interface %s not found\n",
				lp->name,name);

		} else {
		
			retw = i;

			if(bits != NULL) {

				char buf[16];
				char *d = buf;
				char *ed = buf + sizeof(buf) - 1;

				*bits = ~0L;

				for(;p < ename && *p == ',';p++);

				while(p < ename && *p && d < ed)
					*(d++) = *(p++);

				*d = 0;

				if(*buf)
					*bits = (ulong)simple_strtoul(buf,&d,0);
			}
		}
	}

	return(retw);
}
	

int isdn_dwabc_check_icall_bind(isdn_net_local *lp,int di,int ch)
{
	int ret = 0;
	short isset = 0;

	dwabc_check_lchmap();

	if(di < 0 || di >= ISDN_MAX_DRIVERS)
		return(0);

	if(lp != NULL && lp->pre_device < 0 && lp->pre_channel < 0 && 
		di >= 0 && di < ISDN_MAX_DRIVERS) {

		isdn_net_phone *h = lp->phone[0];
		int secure = 0;
		short sd = 0;

		for(;h != NULL && secure < 1000;secure++,h = h->next) {

			char *p 	= 	h->num;
			char *ep 	= 	p + ISDN_MSNLEN;
			ulong bits	=	0;
			
			for(;p < ep && *p && (*p <= ' ' || *p == '"' || *p == '\'');p++);

			if(p >= (ep-1) || *p != '>')
				continue;

			if(*(++p) != '<')
				continue;

			ret = -1;
			p++;
			sd = 0;

			if(p < ep && (*p == '<' || *p == '>')) {

				sd = (*p == '<');
				p++;
			}

			if(get_driverid(lp,p,ep,&bits) == di) {

				driver *dri = dev->drv[di];

				if(dri != NULL) {

					int shl = 0;

					if(sd) for(shl = dri->channels - 1; 
							ret != 0 && shl >= 0; shl--) {

						if(shl >= ISDN_DW_ABC_MAX_CH_P_RIVER)
							continue;

						if(bits & (1L << shl)) {

							if(!dri->dwabc_lchmap[shl]) {

								dri->dwabc_lchmap[shl] = ch + 1;
								dri->dwabc_lch_use = jiffies;
								isset = 1;
								ret = 0;
							}
						}

					} else for(shl = 0;
								ret != 0 && shl < ISDN_DW_ABC_MAX_CH_P_RIVER &&
								shl < dri->channels	;shl++) {

						if(bits & (1L << shl)) {

							if(!dri->dwabc_lchmap[shl]) {

								dri->dwabc_lchmap[shl] = ch + 1;
								dri->dwabc_lch_use = jiffies;
								isset = 1;
								ret = 0;
							}
						}
					}

					if(!ret)
						break;
				}
			}
		}

		if(ret) {

			printk(KERN_DEBUG "isdn_dwabc_ibind: %s IN-CALL driver %d ch %d %s\n",
				lp->name,
				di,
				ch,
				"not allowed for this interface and channel");
		}
	}

	if(!ret && !isset && ch >= 0) {

		driver *dri = dev->drv[di];

		if(dri != NULL) {

			int shl;

			for(shl = 0; shl < ISDN_DW_ABC_MAX_CH_P_RIVER && shl < dri->channels;shl++) {

				if(!dri->dwabc_lchmap[shl]) {

					dri->dwabc_lchmap[shl] = ch + 1;
					dri->dwabc_lch_use = jiffies;
					break;
				}
			}
		}
	}

	return(ret);
}

int dwabc_isdn_get_net_free_channel(isdn_net_local *lp) 
{
	int retw = -1;
	int isconf = 0;

	if(lp != NULL) {
#ifdef CONFIG_ISDN_WITH_ABC_OUTGOING_EAZ 
		char *now_msn = (*lp->dw_out_msn) ? lp->dw_out_msn : lp->msn;
#else
		char *now_msn = lp->msn;
#endif

		if(lp->pre_device < 0 && lp->pre_channel < 0) {

			isdn_net_phone *h = lp->phone[0];
			int secure = 0;
			dwabc_check_lchmap();

			for(;retw < 0 && h != NULL && secure < 1000;secure++,h = h->next) {

				char *p 	= 	h->num;
				char *ep 	= 	p + ISDN_MSNLEN;
				int di		=	0;
				int shl		=	0;
				ulong bits	=	0;
				short dir_down  = 	0;
				driver *dri = NULL;

				for(;p < ep && *p && (*p <= ' ' || *p == '"' || *p == '\'');p++);

				if(p >= (ep-1) || *p != '>') continue;
				if(*(++p) != '>') continue;

				isconf = 1;
				p++;

				if(p < ep && (*p == '<' || *p == '>')) {

					dir_down = *p == '<';
					p++;
				}

				if((di = get_driverid(lp,p,ep,&bits)) < 0 || di >= ISDN_MAX_DRIVERS)
					continue;

				if((dri = dev->drv[di]) == NULL)
					continue;

				if(dir_down) for(shl = dri->channels -1 ; shl >= 0  && retw < 0; shl--) {

					if(shl >=  ISDN_DW_ABC_MAX_CH_P_RIVER)
						continue;

					if(bits & (1L << shl)) {

						if(dri->dwabc_lchmap[shl])
							continue;

						if(isdn_dc2minor(di,shl) < 0)
							continue;

						if((retw = isdn_get_free_channel(
								ISDN_USAGE_NET,
								lp->l2_proto,
								lp->l3_proto,
								di,
								9999,
								now_msn
								)) >= 0) {

							int c = dev->chanmap[retw];

							if(c >= 0) {

								dri->dwabc_lchmap[shl] = c + 1;
								dri->dwabc_lch_use = jiffies;
							}
						}
					}

				} else for(shl = 0; shl < ISDN_DW_ABC_MAX_CH_P_RIVER && 
								retw < 0 && shl < dri->channels; shl++) {

					if(bits & (1L << shl)) {

						if(dri->dwabc_lchmap[shl])
							continue;

						if(isdn_dc2minor(di,shl) < 0)
							break;

						if((retw = isdn_get_free_channel(
								ISDN_USAGE_NET,
								lp->l2_proto,
								lp->l3_proto,
								di,
								9999,
								now_msn)) >= 0) {

							int c = dev->chanmap[retw];

							if(c >= 0) {

								dri->dwabc_lchmap[shl] = c + 1;
								dri->dwabc_lch_use = jiffies;
							}
						}
					}
				}
			}
		}

		if(!isconf) {

			retw = isdn_get_free_channel(
					ISDN_USAGE_NET,
					lp->l2_proto,
					lp->l3_proto,
					lp->pre_device,
					lp->pre_channel,
					now_msn);

			if(retw >= 0) {

				int di = dev->drvmap[retw];
				int ch = dev->chanmap[retw];

				if(di >= 0 && di < ISDN_MAX_DRIVERS && ch >= 0) {

					driver *dri = dev->drv[di];

					if(dri != NULL) {

						int i;

						for(i = 0; i < dri->channels && i < ISDN_DW_ABC_MAX_CH_P_RIVER;i++) {

							if(!dri->dwabc_lchmap[i]) {

								dri->dwabc_lchmap[i] = ch + 1;
								dri->dwabc_lch_use = jiffies;
								break;
							}
						}
					}
				}
			}

		} else if(retw < 0 && lp != NULL) {

			printk(KERN_INFO "%s: No free locical Channel found\n",lp->name);
		}

	} else {

		printk(KERN_WARNING 
			"dwabc_isdn_get_net_free_channel  called with *lp == NULL\n");
	}

	return(retw);
}
#endif

int isdn_dw_abc_reset_interface(isdn_net_local *lp,int with_message)
{
	int r = -EINVAL;

	if(lp != NULL) {

		r = 0;

		lp->dw_abc_bchan_last_connect = 0;
		lp->dw_abc_dialstart = 0;
		lp->dw_abc_inuse_secure = 0;
#ifdef CONFIG_ISDN_WITH_ABC_CONN_ERROR
		lp->dw_abc_bchan_errcnt = 0;
#endif

		if(with_message && dev->net_verbose > 0)
			printk(KERN_INFO
				"%s: NOTE: reset (clear) abc-interface-secure-counter\n",
				lp->name);
	}

	return(r);
}

	
#if CONFIG_ISDN_WITH_ABC_RAWIPCOMPRESS && CONFIG_ISDN_PPP

#define DWBSD_PKT_FIRST_LEN 16
#define DWBSD_PKT_SWITCH	165
#define DWBSD_PKT_BSD		189

#define DWBSD_VERSION 		0x2

void dwabc_bsd_first_gen(isdn_net_local *lp)
{
	if(lp != NULL && lp->p_encap == ISDN_NET_ENCAP_RAWIP && 
		(lp->dw_abc_flags & ISDN_DW_ABC_FLAG_BSD_COMPRESS)) { 
		
		struct sk_buff *skb = NULL;
		char *p = NULL;
		char *ep = NULL;

		if((skb =(struct sk_buff *)dev_alloc_skb(128)) == NULL) {

			printk(KERN_INFO "%s: dwabc: alloc-skb failed for 128 bytes\n",lp->name);
			return;
		}

		skb_reserve(skb,64);
		p = skb_put(skb,DWBSD_PKT_FIRST_LEN);
		ep = p + DWBSD_PKT_FIRST_LEN;

		*(p++) = DWBSD_PKT_SWITCH;
		*(p++) = DWBSD_VERSION;
		for(;p < ep;p++)	*(p++) = 0;

		isdn_net_write_super(lp, skb);
		lp->dw_abc_comhd_last_send = dwsjiffies;

		if(dev->net_verbose > 2)
			printk(KERN_INFO "%s: dwabc: sending comm-header version 0x%x\n",lp->name,DWBSD_VERSION);
	}
}


void dwabc_bsd_free(isdn_net_local *lp)
{
	if(lp != NULL) {

		if(lp->dw_abc_bsd_stat_rx || lp->dw_abc_bsd_stat_tx) {

			struct isdn_ppp_compressor *c = NULL;

			if(!(c = (struct isdn_ppp_compressor *)lp->dw_abc_bsd_compressor)) {

				printk(KERN_WARNING
				"%s: PANIC: freeing bsd compressmemory without compressor\n",
					lp->name);

			} else {

				if(lp->dw_abc_bsd_stat_rx) (*c->free)(lp->dw_abc_bsd_stat_rx);
				if(lp->dw_abc_bsd_stat_tx) (*c->free)(lp->dw_abc_bsd_stat_tx);

				if(dev->net_verbose > 2)
					printk(KERN_INFO
						"%s: free bsd compress-memory\n",
						lp->name);
			}
		}

		lp->dw_abc_bsd_compressor = NULL;
		lp->dw_abc_bsd_stat_rx = NULL;
		lp->dw_abc_bsd_stat_tx = NULL;
		lp->dw_abc_if_flags &= ~ISDN_DW_ABC_IFFLAG_BSDAKTIV;

		if(dev->net_verbose > 0) {

			if(lp->dw_abc_bsd_rcv != lp->dw_abc_bsd_bsd_rcv) {

				printk(KERN_INFO "%s: Receive %lu<-%lu kb\n",lp->name,
					lp->dw_abc_bsd_rcv >> 10 , lp->dw_abc_bsd_bsd_rcv >> 10);
			}


			if(lp->dw_abc_bsd_snd != lp->dw_abc_bsd_bsd_snd) {

				printk(KERN_INFO "%s: Send  %lu->%lu kb\n",lp->name,
					lp->dw_abc_bsd_snd >> 10 , lp->dw_abc_bsd_bsd_snd >> 10);
			}
		}

		lp->dw_abc_bsd_rcv 		=
		lp->dw_abc_bsd_bsd_rcv	=
		lp->dw_abc_bsd_snd 		=
		lp->dw_abc_bsd_bsd_snd 	= 0;
	}
}


int dwabc_bsd_init(isdn_net_local *lp)
{
	int r = 1;

	if(lp != NULL) {

		dwabc_bsd_free(lp);

		if(lp->p_encap == ISDN_NET_ENCAP_RAWIP) {

			void *rx = NULL;
			void *tx = NULL;
			struct isdn_ppp_comp_data *cp = NULL;
			struct isdn_ppp_compressor *c = NULL;

			if(lp->dw_abc_flags & ISDN_DW_ABC_FLAG_BSD_COMPRESS) do {

				for(c = ipc_head ;
					c != NULL && c->num != CI_BSD_COMPRESS; c = c->next);

				if(c == NULL) {

					printk(KERN_INFO
						"%s: Module isdn_bsdcompress not loaded\n",
						lp->name);

					break;
				}

				cp = (struct isdn_ppp_comp_data *)
					kmalloc(sizeof(struct isdn_ppp_comp_data),GFP_ATOMIC);

				if(cp == NULL) {

					printk(KERN_INFO
						"%s: allocation of isdn_ppp_comp_data failed\n",
						lp->name);

					break;
				}

				memset(cp,0,sizeof(*cp));
				cp->num = CI_BSD_COMPRESS;
				cp->optlen = 1;
					
				/*
				** set BSD_VERSION 1 and 12 bits compressmode
				*/
				*cp->options = (1 << 5) | 12;

				if((rx = (*c->alloc)(cp)) == NULL) {

					printk(KERN_INFO
						"%s: allocation of bsd rx-memory failed\n",
						lp->name);

					break;
				}
					
				if(!(*c->init)(rx,cp,0,1)) {

					printk(KERN_INFO 
						"%s: init of bsd rx-stream  failed\n",lp->name);

					break;
				}

				cp->flags = IPPP_COMP_FLAG_XMIT;
						
				if((tx = (*c->alloc)(cp)) == NULL) {

					printk(KERN_INFO
						"%s: allocation of bsd tx-memory failed\n",
						lp->name);

					break;
				}

				if(!(*c->init)(tx,cp,0,1)) {

					printk(KERN_INFO
						"%s: init of bsd tx-stream  failed\n",
						lp->name);

					break;
				}

				lp->dw_abc_bsd_compressor = (void *)c;
				lp->dw_abc_bsd_stat_rx = rx;
				lp->dw_abc_bsd_stat_tx = tx;
				rx = tx = NULL;
				r = 0;

				if(dev->net_verbose > 2)
					printk(KERN_INFO
						"%s: bsd compress-memory and init ok\n",
						lp->name);

			} while(0);

			if(cp != NULL)
				kfree(cp);

			if(c != NULL) {

				if(tx != NULL) (*c->free)(tx);
				if(rx != NULL) (*c->free)(rx);
			}

		} else if(lp->dw_abc_flags & ISDN_DW_ABC_FLAG_BSD_COMPRESS) {
		
			printk(KERN_INFO
				"%s: bsd-compress only with encapsulation rawip allowed\n",
				lp->name);
		}
	}

	return(r);
}

struct sk_buff *dwabc_bsd_compress(	isdn_net_local *lp,
									struct sk_buff *skb,
									struct net_device *ndev)
{
	if(lp != NULL && lp->p_encap == ISDN_NET_ENCAP_RAWIP 	&& 
		(lp->dw_abc_flags & ISDN_DW_ABC_FLAG_BSD_COMPRESS)	&&
		(lp->dw_abc_if_flags & ISDN_DW_ABC_IFFLAG_BSDAKTIV)) {

		if(lp->dw_abc_bsd_stat_tx != NULL && lp->dw_abc_bsd_compressor) {

			struct isdn_ppp_compressor *cp = 
				(struct isdn_ppp_compressor *)lp->dw_abc_bsd_compressor;

			struct sk_buff *nskb = (struct sk_buff *)
				dev_alloc_skb(skb->len * 2 + ndev->hard_header_len);

			int l = 0;

			if(nskb == NULL) {

				(void)(*cp->reset)(lp->dw_abc_bsd_stat_tx,0,0,NULL,0,NULL);
				printk(KERN_INFO "%s: dwabc-compress no memory\n",lp->name);

			} else {

				skb_reserve(nskb,ndev->hard_header_len);
				*(unsigned char *)skb_put(nskb,1) = DWBSD_PKT_BSD;
				l = (*cp->compress)(lp->dw_abc_bsd_stat_tx,skb,nskb,0x21);

				if(l < 1 || l > skb->len) {

					(void)(*cp->reset)(lp->dw_abc_bsd_stat_tx,0,0,NULL,0,NULL);
					dev_kfree_skb(nskb);

				} else {

					u_short sqnr;

					dev_kfree_skb(skb);
					skb = nskb;
					sqnr = ((*(u_char *)skb->data) << 8) + 
							((u_char)skb->data[1]);

					if(sqnr > 65500)
						(void)(*cp->reset)
							(lp->dw_abc_bsd_stat_tx,0,0,NULL,0,NULL);
				}
			}
		}
	}

	return(skb);
}

struct sk_buff *dwabc_bsd_rx_pkt(	isdn_net_local *lp,
									struct sk_buff *skb,
									struct net_device *ndev)
{
	struct sk_buff *r = skb;

	if(lp != NULL && lp->p_encap == ISDN_NET_ENCAP_RAWIP && 
		(lp->dw_abc_flags & ISDN_DW_ABC_FLAG_BSD_COMPRESS)) { 

		unsigned char *p = (unsigned char *)skb->data;
		struct isdn_ppp_compressor *cp = 
			(struct isdn_ppp_compressor *)lp->dw_abc_bsd_compressor;

		if(*p == DWBSD_PKT_SWITCH) {

			if(skb->len == DWBSD_PKT_FIRST_LEN) {

				if((lp->dw_abc_remote_version = p[1]) < 0x2) {

					printk(KERN_INFO 
						"%s: I can't really talk witk remote version 0x%x\n"
						"Please upgrade remote or disable rawip-compression\n",
						lp->name,
						p[1]);
				}

				lp->dw_abc_if_flags |= ISDN_DW_ABC_IFFLAG_BSDAKTIV;
				dev_kfree_skb(skb);

				if(cp && lp->dw_abc_bsd_stat_tx) 
					(void)(*cp->reset)(lp->dw_abc_bsd_stat_tx,0,0,NULL,0,NULL);

				if(dev->net_verbose > 2)
					printk(KERN_INFO 
						"%s: receive comm-header rem-version 0x%02x\n",
						lp->name,
						lp->dw_abc_remote_version);

				return(NULL);
			}

		} else if(*p == DWBSD_PKT_BSD && lp->dw_abc_bsd_stat_rx != NULL && cp) {

			struct sk_buff *nskb = NULL;

			if(test_and_set_bit(ISDN_DW_ABC_BITLOCK_RECEIVE,
				&lp->dw_abc_bitlocks)) {

				printk(KERN_INFO "%s: bsd-decomp called recursivly\n",lp->name);
				dev_kfree_skb(skb);
				dwabc_bsd_first_gen(lp);
				return(NULL);
			} 
			
			nskb = (struct sk_buff *)
				dev_alloc_skb(2048 + ndev->hard_header_len);

			if(nskb != NULL) {

				int l = 0;
				u_short sqnr;

				skb_reserve(nskb,ndev->hard_header_len);
				skb_pull(skb, 1);
				sqnr = ((*(u_char *)skb->data) << 8) | ((u_char)skb->data[1]);

				if(!sqnr && cp && lp->dw_abc_bsd_stat_rx)
					(void)(*cp->reset)(lp->dw_abc_bsd_stat_rx,0,0,NULL,0,NULL);

				if((l = (*cp->decompress)
					(lp->dw_abc_bsd_stat_rx,skb,nskb,NULL)) < 1 || l>8000) {

					printk(KERN_INFO "%s: abc-decomp failed\n",lp->name);
					dev_kfree_skb(nskb);
					dev_kfree_skb(skb);
					nskb = NULL;
					dwabc_bsd_first_gen(lp);

				} else {

					if (nskb->data[0] & 0x1)
						skb_pull(nskb, 1);   /* protocol ID is only 8 bit */
					else
						skb_pull(nskb, 2);

					nskb->dev = skb->dev;
					nskb->pkt_type = skb->pkt_type;
					nskb->mac.raw = nskb->data;
					dev_kfree_skb(skb);
				}

			} else {

				printk(KERN_INFO "%s: PANIC abc-decomp no memory\n",lp->name);
				dev_kfree_skb(skb);
				dwabc_bsd_first_gen(lp);
			}

			clear_bit(ISDN_DW_ABC_BITLOCK_RECEIVE,&lp->dw_abc_bitlocks);
			r = nskb;
		}
	}

	return(r);
}

#else
int dwabc_bsd_init(isdn_net_local *lp) { return(1); }
void dwabc_bsd_free(isdn_net_local *lp) { return; }
void dwabc_bsd_first_gen(isdn_net_local *lp) { return ; }

struct sk_buff *dwabc_bsd_compress(isdn_net_local *lp,struct sk_buff *skb,struct net_device *ndev)
{ return(skb); }

struct sk_buff *dwabc_bsd_rx_pkt(isdn_net_local *lp,struct sk_buff *skb,struct net_device *ndev)
{ return(skb); }
#endif




static void dw_nfw_dlink(isdn_net_local *lp,struct sk_buff *skb,char *msg)
{
	if(skb != NULL) {

		if(dev->net_verbose > 0 && lp != NULL) {

			isdn_net_log_skb_dwabc(skb,lp,
				(msg != NULL) ? msg :
					"frame's-saddr != if-saddr sent dst_link_failure");
		}

		dst_link_failure(skb);
	}
}



static int dwisdn_nfw_rw_sock(	isdn_net_local *lp, struct sk_buff *skb)
/******************************************************************

	0	==	addr rewritten or rewrite possible
	1	==	addr not rewritable 
			
*******************************************************************/
{
#if !defined(CONFIG_ISDN_WITH_ABC_IPV4_DYNADDR) || !defined(CONFIG_ISDN_WITH_ABC_IPV4_RW_SOCKADDR)
	return(0);
#else
	int retw = 0;
	struct sock *sk;

	if(	skb != NULL && 
		(sk = skb->sk) != NULL && skb->len >= sizeof(struct iphdr)) {
	
		struct net_device *ndev = &lp->netdev->dev;
		struct in_device *indev = (struct in_device *)ndev->ip_ptr;
		struct in_ifaddr *ifaddr = (indev != NULL) ? indev->ifa_list :NULL;
		ulong ipaddr = (ifaddr != NULL) ? ifaddr->ifa_local : 0;
		struct iphdr *ip = skb->nh.iph;
		int len = skb->len;

		if(	ipaddr != 0 && (ip->saddr ^ ipaddr)) {
			/*!(ip->frag_off & ~IP_DF) && */

			if(sk->dead || (sk->shutdown == SHUTDOWN_MASK))
				return(1);

			len -= ip->ihl * 4;

			if(ip->protocol == IPPROTO_TCP) {

				if(len < sizeof(struct tcphdr))
					return(0);

				lock_sock(sk);

				if(sk->saddr != 0 && sk->saddr != ipaddr) {

					retw = 1;

					if(sk->prot != NULL) {

						if(	sk->prot->unhash != NULL	&&
							sk->prot->hash != NULL		){

							sk->saddr = ipaddr;
							sk->rcv_saddr = ipaddr;
							sk->prot->unhash(sk);
							sk->prot->hash(sk);
							retw = 0;
						}
					}

					if(!retw && dev->net_verbose > 2)
						isdn_net_log_skb_dwabc(skb,lp,"rewrite sock-addr for");
				}

				release_sock(sk);


			} else if(ip->protocol == IPPROTO_UDP) {

				if(len < sizeof(struct udphdr))
					return(0);

				if(sk->saddr && (sk->saddr != ipaddr))
					retw = 1;

				if(sk->rcv_saddr && (sk->rcv_saddr != ipaddr))
					retw = 1;

			} else if(ip->protocol != IPPROTO_ICMP) {

				retw = 1;
			}


			if(!retw) {

				if(dev->net_verbose > 2)
					isdn_net_log_skb_dwabc(skb,lp,"rewrite frame's saddr");

				ip->check = 0;
				ip->saddr = ipaddr;
				ip->check = ip_fast_csum((unsigned char *)ip,ip->ihl);
#if CONFIG_ISDN_WITH_ABC_IPTABLES_NETFILTER
				skb->nfcache |= NFC_ALTERED;
#endif

				switch(ip->protocol) {
				case IPPROTO_UDP:

					{
						ushort l = ntohs(ip->tot_len) - (ip->ihl << 2);

						struct udphdr *udp = (struct udphdr *)
											(((u_char *) ip) + (ip->ihl << 2));

						udp->check = 0;
						udp->check = csum_tcpudp_magic(
											ip->saddr, ip->daddr,
											l,IPPROTO_UDP,
											csum_partial((char *)udp,l,0));
					}

					break;

				case IPPROTO_TCP:

					{
						ushort l = ntohs(ip->tot_len) - (ip->ihl << 2);

						struct tcphdr *tcp = (struct tcphdr *) 
									(((u_char *) ip) + (ip->ihl << 2));

						tcp->check = 0;
						tcp->check = csum_tcpudp_magic(
										ip->saddr, ip->daddr,
										l,IPPROTO_TCP,
										csum_partial((char *)tcp,l,0));
					}

					break;
				}
			}
		}
	}

	return(retw);
#endif
}

#if CONFIG_ISDN_WITH_ABC_IPTABLES_NETFILTER
void dwisdn_nfw_send(isdn_net_local *lp,int drop_only)
{
	if(lp != NULL) {

		struct sk_buff *skb = NULL;

		if(drop_only)  {

			lp->dw_abc_addr_ready = 0;

			while((skb = skb_dequeue(&lp->dw_abc_nfq)) != NULL) {

				dw_nfw_dlink(lp,skb,"freeing nfw-queue");
				dev_kfree_skb(skb);
				lp->dw_abc_addr_ready = 0;
			}

		} else while((skb = skb_dequeue(&lp->dw_abc_nfq)) != NULL) {

			if(dwisdn_nfw_rw_sock(lp,skb)) {

				dw_nfw_dlink(lp,skb,"cannot rewrite saddr");
				dev_kfree_skb(skb);
				continue;
			}

			if (dev->net_verbose > 2)
				isdn_net_log_skb_dwabc(skb,lp,"netfilter-queue-sending");

#ifdef CONFIG_NETFILTER_DEBUG 
			skb->nf_debug &= ~(1 << NF_IP_POST_ROUTING);
#endif
			skb->isdn_skb_bits |= (1lu << ISDN_SKB_BIT_NF_DIALLOOP);
			ip_finish_output(skb);
		}

		lp->dw_abc_addr_ready = 1;
	}
}


static isdn_net_local *dwisdn_get_lp(const struct net_device *nd)
{
	isdn_net_local *r = NULL;

	if(nd != NULL) {

		r = (isdn_net_local *)nd->priv;

		if(r != NULL && r->magic != ISDN_NET_MAGIC)
			r = NULL;
	}

	return(r);
}



static int isdn_ipt_dwisdn(	const struct net_device *in_ndev,
							const struct net_device *out_ndev,
							const IPTDWISDN_INFO *dw,
							const struct sk_buff *skb)
{
	int retw = 0;

	if(dw != NULL) {

		int shl = 0;
		isdn_net_local *lp = NULL;

		if(dw->parcount > 0 && 
			(*dw->inst & ~IPT_DWISDN_NOT) == IPT_DWISDN_IDEV) {

			if(*dw->inst & IPT_DWISDN_NOT)
				lp = dwisdn_get_lp(out_ndev);
			else
				lp = dwisdn_get_lp(in_ndev);

			shl = 1;

		} else lp = dwisdn_get_lp(out_ndev);

		retw = lp != NULL;

		for(;retw && shl < IPTDWISDN_MAXOPS && shl < dw->parcount; shl++) {

			enum iptdwisdn inst = dw->inst[shl];
			u_long v = dw->value[shl];
			int not;

			if((not = (inst & IPT_DWISDN_NOT)))
				inst &= ~IPT_DWISDN_NOT;

			switch(inst) {

			default:

				printk(KERN_DEBUG
				"ipt_dwisdn instruction 0x%X unknown\n",inst);
				retw = 0;
				break;

			case IPT_DWISDN_ADDROK:
			case IPT_DWISDN_FEQIADR:

				retw = 1;

				if(!(lp->dw_abc_flags & ISDN_DW_ABC_FLAG_DYNADDR))
					break;

				retw = 	!!(lp->flags & ISDN_NET_CONNECTED)	&&
						!lp->dialstate						&&
						!!lp->dw_abc_addr_ready;

				if(!retw || inst == IPT_DWISDN_ADDROK)
					break;

				if(skb == NULL) {

					retw = 0;

				} else {

					struct net_device *ndev = &lp->netdev->dev;
					struct in_device *indev = (struct in_device *)ndev->ip_ptr;
					struct in_ifaddr *ifaddr = 
						(indev != NULL) ? indev->ifa_list :NULL;

					ulong ipaddr = (ifaddr != NULL) ? ifaddr->ifa_local : 0;
					struct iphdr *ip = skb->nh.iph;

					if(	ipaddr != 0 && (ip->saddr ^ ipaddr)) 
						retw = 0;
				}

				break;

			case IPT_DWISDN_DIALMODE:

				retw = 0;

				switch(v) {

				case 1:	
					retw = ISDN_NET_DIALMODE(*lp) == ISDN_NET_DM_AUTO;
					break;

				case 2:	
					retw = ISDN_NET_DIALMODE(*lp) == ISDN_NET_DM_MANUAL;
					break;

				case 3:	
					retw = ISDN_NET_DIALMODE(*lp) == ISDN_NET_DM_OFF;
					break;
				}

				break;

			case IPT_DWISDN_CBOUT:

				retw = 0;

				if(lp->flags & ISDN_NET_CALLBACK)
					retw = !!(lp->flags & ISDN_NET_CBOUT);
						
				continue;

			case IPT_DWISDN_OUTGOING:

				if(lp->flags & ISDN_NET_CONNECTED)
					retw = !!lp->outgoing;
				else
					retw = 0;

				break;

			case IPT_DWISDN_IDEV:

				if(not) 
					lp = dwisdn_get_lp(out_ndev);
				else
					lp = dwisdn_get_lp(in_ndev);

				retw = lp != NULL;
				continue;

			case IPT_DWISDN_CHARGE:

				retw = lp->charge >= v;
				break;

			case IPT_DWISDN_CON:

				retw = 0;

				if(v & IPTCS_DWISN_OFFL) {

					if(!(lp->flags & ISDN_NET_CONNECTED) && 
						!lp->dialstate) {

						retw = 1;
					}
				}

				if(v & IPTCS_DWISN_ONL) {

					if((lp->flags & ISDN_NET_CONNECTED) && 
						!lp->dialstate) {

						retw = 1;
					}
				}

				if(v & IPTCS_DWISN_DIAL) {

					if((lp->flags & ISDN_NET_CONNECTED) && 
						lp->dialstate) {

						retw = 1;
					}
				}
					
				break;
			}

			if(not)
				retw = !retw;
		}
	}

	return(retw);
}


static int dwisdn_match(	const struct sk_buff *skb,
							const struct net_device *in,
							const struct net_device *out,
							const void *matchinfo,
							int offset,
							const void *hdr,
							u_int16_t datalen,
							int *hotdrop)
/************************************************************************
*************************************************************************/
{
	const IPTDWISDN_INFO *dw = matchinfo;

	if(dw == NULL)
		return(0);

	return(isdn_ipt_dwisdn(in,out,dw,skb));
}


static unsigned int DWISDN_target(
			struct sk_buff **pskb,
			unsigned int hooknum,
			const struct net_device *in,
			const struct net_device *out,
			const void *info,
			void *userinfo)                             
{
	unsigned int retw = IPT_CONTINUE;

	if(info != NULL) {

		int shl = 0;
		const IPTDWISDN_INFO *dw  = (IPTDWISDN_INFO *)info;
		isdn_net_local *lp = dwisdn_get_lp(out);
		struct sk_buff *p = (pskb != NULL) ? *pskb : NULL;

		for(shl = 0;shl < IPTDWISDN_MAXOPS && shl < dw->parcount; shl++) {

			enum tiptdwisdn inst = dw->inst[shl];
			u_long v = dw->value[shl];
			int not;

			if((not = (inst & TIPT_DWISDN_NOT)))
				inst &= ~TIPT_DWISDN_NOT;

			switch(inst) {

			default:

				printk(KERN_DEBUG
				"ipt_DWISDN instruction 0x%X unknown\n",inst);
				break;

			case TIPT_DWISDN_HUPRESET:

				if(lp != NULL)
					lp->huptimer = 0;

				break;

			case TIPT_DWISDN_DIALMODE:

				if(lp != NULL) {

					/* first all bits off */
					lp->flags &= ~ISDN_NET_DIALMODE_MASK;

					switch(v) {
					case 1:	lp->flags |= ISDN_NET_DM_AUTO;		break;
					case 2:	lp->flags |= ISDN_NET_DM_MANUAL;	break;
					case 3:	lp->flags |= ISDN_NET_DM_OFF;		break;
					}
				}

				break;

			case TIPT_DWISDN_HANGUP:

				if(lp != NULL && (lp->flags & ISDN_NET_CONNECTED))
					isdn_net_hangup(&lp->netdev->dev);

				break;

			case TIPT_DWISDN_DIAL:

				if(lp != NULL && !(lp->flags & ISDN_NET_CONNECTED))
					isdn_net_force_dial_lp(lp);

				break;

			case TIPT_DWISDN_IDEV:

				lp = dwisdn_get_lp((not) ? out : in);
				break;

			case TIPT_DWISDN_CLEAR:

				v = 0;

				v |= (1lu << ISDN_SKB_BIT_NF_NO_RS_TX);
				v |= (1lu << ISDN_SKB_BIT_NF_S_UNREACH);


				if(p != NULL && (p->isdn_skb_bits & v)) {

					p->isdn_skb_bits &= ~v;
					p->nfcache |= NFC_ALTERED;
				}

				break;

			case TIPT_DWISDN_SET:

				v = (1lu << ISDN_SKB_BIT_NF_NO_RS_TX);

				if(p != NULL && (p->isdn_skb_bits & v) != v) {

					p->isdn_skb_bits |= v;
					p->nfcache |= NFC_ALTERED;
				}

				break;

			case TIPT_DWISDN_UNREACH:

				v = (1lu << ISDN_SKB_BIT_NF_S_UNREACH);

				if(p != NULL && (p->isdn_skb_bits & v) != v) {

					p->isdn_skb_bits |= v;
					p->nfcache |= NFC_ALTERED;
				}

				break;
			}
		}
	}

	return(retw);
}

static int dwisdn_checkentry(	const char *tablename,
								const struct ipt_ip *ip,
								void *matchinfo,
								unsigned int matchsize,
								unsigned int hook_mask)
{
	const IPTDWISDN_INFO *dw = matchinfo;

	if (matchsize != IPT_ALIGN(sizeof(IPTDWISDN_INFO))) {

		printk(KERN_WARNING
		"ipt_dwisdn: sizeof(IPTDWISDN_INFO) wrong (I think wrong Version)\n");

		return 0;
	}

	if(dw != NULL && dw->revision > IPTDWISDN_REVISION) {

		printk(KERN_WARNING
		"ipt_dwisdn: iptables-revison > kernel-revision (%hu/%hu)\n",
			dw->revision,IPTDWISDN_REVISION);

		return 0;
	}

	return(1);
}


static int DWISDN_checkentry(	const char *tablename,
	   							const struct ipt_entry *e,
           						void *targinfo,
           						unsigned int targinfosize,
           						unsigned int hook_mask)
{
	const IPTDWISDN_INFO *dt = targinfo;

	if (targinfosize != IPT_ALIGN(sizeof(IPTDWISDN_INFO))) {

		printk(KERN_WARNING
		"ipt_DWISDN: sizeof(IPTDWISDN_INFO) wrong (I think wrong Version)\n");

		return 0;
	}

	if(dt != NULL && dt->revision > IPTDWISDN_REVISION) {

		printk(KERN_WARNING
		"ipt_DWISDN: iptables-revison > kernel-revision (%hu/%hu)\n",
			dt->revision,IPTDWISDN_REVISION);

		return 0;
	}

	return 1;
}



static unsigned int ISDNDIAL_target(
							struct sk_buff **pskb,
							unsigned int hooknum,
							const struct net_device *in,
							const struct net_device *out,
							const void *targinfo,
							void *userinfo)
/************************************************************************
*************************************************************************/
{
	isdn_net_local *lp = dwisdn_get_lp(out);
	struct sk_buff *skb = (pskb != NULL) ? *pskb : NULL;

	//printk(KERN_DEBUG"ISDNDIAL called 0x%lx\n",jiffies);
	//isdn_net_log_skb_dwabc(skb,lp,"ISDNDIAL");

	if(	lp != NULL						&&
		skb != NULL						&&
		!(skb->isdn_skb_bits & (1lu << ISDN_SKB_BIT_NF_DIALLOOP)) 	&& 
		(lp->dw_abc_flags & ISDN_DW_ABC_FLAG_DYNADDR)) {

		if(!(lp->flags & ISDN_NET_CONNECTED)) {

			lp->dw_abc_addr_ready = 0;

#ifdef CONFIG_ISDN_WITH_ABC_UDP_CHECK
			if(!(lp->dw_abc_flags & ISDN_DW_ABC_FLAG_NO_UDP_CHECK)) {

				if(dw_abc_udp_test(skb,&lp->netdev->dev))
					return(NF_DROP);
			}
#endif
	
			if(isdn_auto_dial_helper(lp,skb,1) < 0)
				return(NF_DROP);
		
		} else if(lp->dialstate) lp->dw_abc_addr_ready = 0;

		if(!lp->dw_abc_addr_ready) {

			if(skb_queue_len(&lp->dw_abc_nfq) > 256) {

				if(dev->net_verbose)
					isdn_net_log_skb_dwabc(skb,lp,
						"ISDNDIAL dropping: to many queued frames");

				return(NF_DROP);
			}

			if(dev->net_verbose > 2)
				isdn_net_log_skb_dwabc(skb,lp,"ISDNDIAL add frame");

			skb->nfcache |= NFC_ALTERED;
			skb_queue_tail(&lp->dw_abc_nfq,skb);
			return(NF_STOLEN);
		}

		if(dwisdn_nfw_rw_sock(lp,skb)) {

			dw_nfw_dlink(lp,skb,"ISDNDIAL: saddr rewrite failed");
			return(NF_DROP);
		}
	}

	return(IPT_CONTINUE);
}




static int ISDNDIAL_checkentry(	const char *tablename,
	   							const struct ipt_entry *e,
           						void *targinfo,
           						unsigned int targinfosize,
           						unsigned int hook_mask)
{
	int r = 1;
	
	if(strcmp(tablename,"nat")) {

		printk(KERN_DEBUG
			"ISDNDIAL only in nat table allowed (iptables -t nat)\n");
		r = 0;
	}

	if(hook_mask & ~(1 << NF_IP_POST_ROUTING)) {

		printk(KERN_DEBUG
			"ISDNDIAL only in HOOK POSTROUTING allowed\n");
		r = 0;
	}

	return(r);
}


static struct ipt_target ipt_DWISDN = {

	{ NULL, NULL },
	"DWISDN",
	DWISDN_target,
	DWISDN_checkentry,
	NULL, THIS_MODULE,
};


static struct ipt_match ipt_dwisdn = { 

	{ NULL, NULL },
	"dwisdn", 
	dwisdn_match,
	dwisdn_checkentry,
	NULL, THIS_MODULE
};

static struct ipt_target ipt_ISDNDIAL = { 

	{ NULL, NULL },
	"ISDNDIAL", 
	ISDNDIAL_target,
	ISDNDIAL_checkentry,
	NULL, THIS_MODULE
};


static void	dwabcnetfilter_init(void) 
{ 
	int r = ipt_register_match(&ipt_dwisdn); 

	if(!r)
		printk(KERN_WARNING "ipt_dwisdn: isdn-ipv4-netfilter installed\n");
	else 
		printk(KERN_WARNING
			"ipt_dwisdn: isdn-ipv4-netfilter install failed (%d)\n",r);

	r = ipt_register_target(&ipt_DWISDN); 

	if(!r)
		printk(KERN_WARNING "ipt_DWISDN: isdn-ipv4-netfilter installed\n");
	else 
		printk(KERN_WARNING
			"ipt_DWISDN: isdn-ipv4-netfilter install failed (%d)\n",r);

	r = ipt_register_target(&ipt_ISDNDIAL); 

	if(!r)
		printk(KERN_WARNING "ipt_ISDNDIAL: isdn-ipv4-netfilter installed\n");
	else 
		printk(KERN_WARNING
			"ipt_ISDNDIAL: isdn-ipv4-netfilter install failed (%d)\n",r);
}


static void	dwabcnetfilter_fini(void) 
{ 
	ipt_unregister_match(&ipt_dwisdn);
	ipt_unregister_target(&ipt_DWISDN);
	ipt_unregister_target(&ipt_ISDNDIAL);
}

#else
void dwisdn_nfw_send(isdn_net_local *lp,int drop_only) { return; }
#endif
#endif
