/* ppp_if.c - Streams PPP top level module handles if_ and packetizing PPP packets.
    Copyright (C) 1990  Brad K. Clements, All Rights Reserved
    See copyright notice in Readme.streams
*/
#define	VJC	1
#include <sys/types.h>

#include "ppp.h"
#if NPPP > 0
#define	STREAMS	1

#define	PPP_STATS	1
#define	PPP_SNIT	/* pass packets to Streams Network Interface Tap */


#define	DEBUGS	1
#include <sys/param.h>
#include <sys/stream.h>
#include <sys/stropts.h>

#include <sys/user.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <net/if.h>
#include <net/netisr.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#ifdef	PPP_STATS
#include <sys/slip_var.h>
#define	INCR(comp)	++p->pii_stats.comp
#else
#define	INCR(comp)
#endif

#ifdef	VJC
#include "slcompress.h"
#endif
#include <sys/if_ppp.h>
#include <sys/ppp_str.h>
#ifdef	DEBUGS
#include <sys/syslog.h>
#define	DLOG(s,a)	if(ppp_if_debug) log(LOG_INFO, s, a)
int	ppp_if_debug=0;
#else
#define	DLOG(s)	{}
#endif

#ifdef PPP_SNIT
#include <net/nit_if.h>
#include <netinet/if_ether.h>
/* Use a fake link level header to make etherfind and tcpdump happy. */
static struct ether_header header = {{1}, {2}, ETHERTYPE_IP};
static struct nit_if nif = {(caddr_t)&header, sizeof(header), 0, 0};
#endif

static	int	ppp_if_open(), ppp_if_close(), ppp_if_rput(), ppp_if_wput(),
		ppp_if_wsrv(), ppp_if_rsrv();

static 	struct	module_info	minfo ={
	0xbad,"ppp_if",0, INFPSZ, 16384, 4096
};

static	struct	qinit	r_init = {
	ppp_if_rput, ppp_if_rsrv, ppp_if_open, ppp_if_close, NULL, &minfo, NULL
};
static	struct	qinit	w_init = {
	ppp_if_wput, ppp_if_wsrv, ppp_if_open, ppp_if_close, NULL, &minfo, NULL
};
struct	streamtab	ppp_ifinfo = {
	&r_init, &w_init, NULL, NULL, NULL
};

#ifdef	OLD
struct	ppp_if_info {
	int	pii_flags;
#define	PII_FLAGS_INUSE		0x1		/* in use by  a stream	*/
#define	PII_FLAGS_COMPAC	0x2
#define	PII_FLAGS_COMPPROT	0x4
#define	PII_FLAGS_ATTACHED	0x8		/* already if_attached	*/
#define	PII_FLAGS_VJC_ON	0x10		/* VJ TCP header compression enabled */
	struct	ifnet	pii_ifnet;
	queue_t		*pii_writeq;		/* used by ppp_output 	*/
#ifdef	VJC
	struct 	slcompress	pii_sc_comp;	/* vjc control buffer */
#endif
};
#endif

typedef	struct ppp_if_info	PII;

PII	pii[NPPP];

int	ppp_output(), ppp_ioctl();


int
ppp_attach(unit)
	int	unit;
{
	register struct ifnet *ifp = &pii[unit].pii_ifnet;

	ifp->if_name = "ppp";
	ifp->if_mtu  = PPP_MTU;
	ifp->if_flags = IFF_POINTOPOINT;
	ifp->if_unit  = unit;
	ifp->if_ioctl	= ppp_ioctl;
	ifp->if_output	= ppp_output;
	ifp->if_snd.ifq_maxlen = IFQ_MAXLEN;
	if_attach(ifp);
	pii[unit].pii_flags |= PII_FLAGS_ATTACHED;
}


static int
ppp_if_open(q, dev, flag, sflag)
	queue_t	*q;
	dev_t	dev;
	int	flag,
		sflag;

{
	register PII	*p;
	register int	x;
	int	s;

	if(!suser()) {
		u.u_error = EPERM;
		return(OPENFAIL);
	}
	s = splstr();
	if(!q->q_ptr) {
		for(x=0; x < NPPP; x++) {
			if(!(pii[x].pii_flags & PII_FLAGS_INUSE)) {	/* we can use it */
				p = &pii[x];
#ifdef	VJC
#ifdef	PPP_STATS
				bzero(&p->pii_stats,sizeof(p->pii_stats));
#endif
#ifdef	JUNK
				if(!p->pii_sc_comp) {
					p->pii_sc_comp = (struct slcompress *) kmem_alloc(sizeof(struct slcompress));
					if(!p->pii_sc_comp) {
						u.u_error = ENOSR;
						return(OPENFAIL);
					}
				}
#endif
				sl_compress_init(&p->pii_sc_comp);
#endif
				if(!(p->pii_flags & PII_FLAGS_ATTACHED))
					ppp_attach(x);			/* attach it */
				p->pii_writeq = WR(q);
				WR(q)->q_ptr = q->q_ptr = (caddr_t) p;	/* set write Q and read Q to point here */
				p->pii_ifnet.if_flags |= IFF_RUNNING;
				p->pii_flags = PII_FLAGS_INUSE|PII_FLAGS_ATTACHED;
				DLOG("ppp_if%d: open\n",x);
				splx(s);
				return(x);
			}
		}
	}
	else {
		p = (PII *) q->q_ptr;
#ifdef	VJC
#ifdef	PPP_STATS
		bzero(&p->pii_stats,sizeof(p->pii_stats));
#endif
#ifdef	JUNK
		if(!p->pii_sc_comp) {
			p->pii_sc_comp = (struct slcompress *) kmem_alloc(sizeof(struct slcompress));
			if(!p->pii_sc_comp) {
				u.u_error = ENOSR;
				return(OPENFAIL);
			}
		}
#endif
		sl_compress_init(&p->pii_sc_comp);
#endif
		DLOG("ppp_if%d: reopen\n", (p-pii)/sizeof(PII));
		if(!(p->pii_flags & PII_FLAGS_ATTACHED))
			ppp_attach(x);			/* attach it */
		p->pii_writeq = WR(q);
		WR(q)->q_ptr = q->q_ptr = (caddr_t) p;	/* set write Q and read Q to point here */
		p->pii_ifnet.if_flags |= IFF_RUNNING;
		p->pii_flags = PII_FLAGS_INUSE|PII_FLAGS_ATTACHED;
		splx(s);
		return(0);
	}
	splx(s);
	return(OPENFAIL);
}

static int
ppp_if_close(q)
	queue_t	*q;			/* queue info */
{
	PII	*p = (PII *) q->q_ptr;
	int	s;

	s = splimp();
	p->pii_flags &= ~PII_FLAGS_INUSE;
	if_down(&p->pii_ifnet);
	p->pii_ifnet.if_flags &= ~IFF_RUNNING;
#ifdef	VJC
#ifdef	JUNK
	if(p->pii_sc_comp)
		kmem_free(p->pii_sc_comp);
#endif
#endif
	DLOG("ppp_if%d: closed\n", (p-pii)/sizeof(PII));
	splx(s);
	return(0);			/* no work to be done */
}


static int
ppp_if_wput(q, mp)
	queue_t  *q;
	register mblk_t *mp;
{

	register struct iocblk	*i;
	register PII	*p;

	switch (mp->b_datap->db_type) {

		case 	M_FLUSH :
			if(*mp->b_rptr & FLUSHW)
				flushq(q, FLUSHDATA);
			putnext(q, mp);		/* send it along too */
			break;

		case	M_DATA :
			putq(q, mp);	/* queue it for my service routine */
			break;

		case	M_IOCTL :
			i = (struct iocblk *) mp->b_rptr;
			p =  (PII *) q->q_ptr;
			switch (i->ioc_cmd) {

				case SIOCSIFCOMPAC :	/* enable or disable AC compression */
					if(i->ioc_count != sizeof(u_char)) 
						goto passalong;
					DLOG("ppp_if: SIFCOMPAC %d\n",  *(u_char *) mp->b_cont->b_rptr);
					if( *(u_char *) mp->b_cont->b_rptr) 
						p->pii_flags |= PII_FLAGS_COMPAC;
					else
						p->pii_flags &= ~PII_FLAGS_COMPAC;
					goto passalong;
				case SIOCSIFCOMPPROT:	/* enable or disable PROT  compression */
					DLOG("ppp_if: SIFCOMPPROT %d\n",  *(u_char *) mp->b_cont->b_rptr);
					if(i->ioc_count != sizeof(u_char))
						goto passalong;
					if( *(u_char *) mp->b_cont->b_rptr) 
						p->pii_flags |= PII_FLAGS_COMPPROT;
					else
						p->pii_flags &= ~PII_FLAGS_COMPPROT;
					goto passalong;

				case SIOCSIFVJCOMP:	/* enable or disable VJ compression */
#ifdef	VJC
					if(i->ioc_count != sizeof(u_char)) 
						goto passalong;
					DLOG("ppp_if: SIFVJCOMP %d\n",  *(u_char *) mp->b_cont->b_rptr);
					if( *(u_char *) mp->b_cont->b_rptr) 
						p->pii_flags |= PII_FLAGS_VJC_ON;
					else
						p->pii_flags &= ~PII_FLAGS_VJC_ON;
					mp->b_datap->db_type = M_IOCACK;
#else
					mp->b_datap->db_type = M_IOCNAK;
					i->ioc_error = EINVAL;
					i->ioc_count = 0;
#endif
					qreply(q,mp);
					break;

				case SIOCGETU :	/* get unit number */
					if(mp->b_cont = allocb(sizeof(int), BPRI_MED)) {
						*(int *) mp->b_cont->b_wptr = p->pii_ifnet.if_unit;
						mp->b_cont->b_wptr += i->ioc_count  = sizeof(int);
						mp->b_datap->db_type = M_IOCACK;
						qreply(q,mp);
						break;
					}
					i->ioc_error = ENOSR;
					i->ioc_count = 0;
					mp->b_datap->db_type = M_IOCNAK;
					qreply(q, mp);
					break;
				default:		/* unknown IOCTL call */
passalong:;
					putnext(q,mp);	/* pass it along */
			}
			DLOG("ppp: Pii->flags 0x%x\n",p->pii_flags);
			break;	
		default :
			putnext(q,mp);	/* don't know what to do with this, so send it along*/
	}

}

static int
ppp_if_wsrv(q)
	queue_t	*q;
{
	register mblk_t *mp;
	register PII *p;

	p = (PII *) q->q_ptr;

	while((mp = getq(q)) != NULL) {
		/* we can only get M_DATA types into our Queue, due to our Put function */
		if(!canput(q->q_next)) {
			putbq(q, mp);
			return;
		}
		p->pii_ifnet.if_opackets++;	/* increment count of outgoing packets */
		INCR(sl_opackets);
		putnext(q, mp);	/* just pass it along, nothing to do in this direction */
	}	/* end while */

}



static int
ppp_if_rput(q, mp)
	queue_t *q;
	register mblk_t *mp;
{

	register u_char	*c;
	register PII	*p;

	switch (mp->b_datap->db_type) {

		case 	M_FLUSH :
			if(*mp->b_rptr & FLUSHR)
				flushq(q, FLUSHDATA);
			putnext(q, mp);		/* send it along too */
			break;

		case	M_DATA :
			putq(q, mp);	/* queue it for my service routine */
			break;

		case	M_CTL :		/* could be a message from below */
			p = (PII *) q->q_ptr;
			c = (u_char *) mp->b_rptr;
			switch  (c)	{
				case IF_INPUT_ERROR :
					p->pii_ifnet.if_ierrors++;
					INCR(sl_ierrors);
					DLOG("ppp_if: input error inc to %d\n", p->pii_ifnet.if_ierrors);
					break;
				case IF_OUTPUT_ERROR :
					p->pii_ifnet.if_oerrors++;
					INCR(sl_oerrors);
					DLOG("ppp_if: output error inc to %d\n", p->pii_ifnet.if_oerrors);
					break;
				default:;
					break;
			}
			freemsg(mp);
			/* putnext(q, mp); */
			break;
		default :
			putnext(q,mp);	/* don't know what to do with this, so send it along*/
	}
}

static int
ppp_if_rsrv(q)
	queue_t	*q;
{
	register mblk_t *mp,*m0;
#ifdef	VJC
	register mblk_t *mvjc;
	unsigned char *cp;

#endif
	register PII	*p;
	struct ifnet	**ifp;
	struct ppp_header	*ph;
	struct mbuf	*mb1, *mb2,*mbtail;
	int	len,xlen,count,s;
	u_char	*rptr;

	p = (PII *) q->q_ptr;

	while((mp = getq(q)) != NULL) {
		/* we can only get M_DATA types into our Queue, due to our Put function */
		m0 = mp;	/* remember first message block */
		ph = (struct ppp_header *) mp->b_rptr;
		/* assume ppp_header is completely in first block */
		if(mp->b_wptr - mp->b_rptr < sizeof(struct ppp_header)) {
			freemsg(mp);
			p->pii_ifnet.if_ierrors++;
			continue;
		}
#ifdef	VJC
		switch (ntohs(ph->ph_protocol)) {
			case PPP_IP :
				break;
			case PPP_VJC_COMP :
				if(p->pii_flags & PII_FLAGS_VJC_ON) {
					for(xlen=0, mvjc = mp; mvjc; mvjc = mvjc->b_cont)
						xlen += (mvjc->b_wptr - mvjc->b_rptr);
					xlen -= sizeof(struct ppp_header);
					if(!(mvjc = allocb(128, BPRI_MED))) {	/* get a 128 byte buffer for IP header*/
						putbq(q,mp);
						qenable(q);
						return;
					}
					mvjc->b_wptr += 128;
					linkb(mvjc,mp);
					if(!pullupmsg(mvjc,-1)) {	/* string em all together. ugh what a waste */
						freemsg(mvjc);
						continue;
					}
					cp = mvjc->b_rptr + 128 + sizeof(struct ppp_header);
					m0 = mp = mvjc;
					xlen = sl_uncompress_tcp(&cp,xlen, TYPE_COMPRESSED_TCP, &p->pii_sc_comp);
					if(!xlen) {
						DLOG("ppp: sl_uncompress failed on type Compressed",0);;
						goto reject;
					}
					mp->b_rptr = cp - sizeof(struct ppp_header);
					((struct ppp_header *) mp->b_rptr)->ph_protocol = htons(PPP_IP);
					break;
				}
					
			case PPP_VJC_UNCOMP :
				if(p->pii_flags & PII_FLAGS_VJC_ON) {
					cp = (unsigned char *) mp->b_rptr + sizeof(struct ppp_header);
					if(sl_uncompress_tcp(&cp, 1, TYPE_UNCOMPRESSED_TCP, &p->pii_sc_comp)) {
						((struct ppp_header *) mp->b_rptr)->ph_protocol = htons(PPP_IP);
						break;
					}
					DLOG("ppp: sl_uncompress failed on type Uncompresed\n",0);
reject:;
						freemsg(mp);
						continue;				
				}
			default :;
#endif
#ifndef	VJC
			if(ntohs(ph->ph_protocol) != PPP_IP) {
#endif
				DLOG("ppp: unknown protocol 0x%x\n",ntohs(ph->ph_protocol));
				INCR(sl_ipackets);
				if(!canput(q->q_next)) {
					putbq(q, mp);
					return;
				}
				putnext(q,mp);
				p->pii_ifnet.if_ipackets++;
				continue;
#ifndef	VJC
				}
#endif
		}
		len  = 0;
		mb1 = NULL;
		xlen = mp->b_wptr - (rptr = mp->b_rptr);
		while(mp) {
			if(len < 1) {
				MGET(mb2, M_DONTWAIT, MT_DATA);
				if(!mb2) {
					p->pii_ifnet.if_ierrors++;
					putbq(q,m0);
					qenable(q);
					if(mb1)
						m_freem(mb1);	/* discard what we've used already */
					return;
				}			/* if we couldn't get a buffer, put back the message and try later */
				len = MLEN;
				mb2->m_len = 0;
				if(mb1) {
					mbtail->m_next = mb2;
					mbtail = mb2;
				}
				else 
					mbtail = mb1 = mb2;
			}
			count = MIN(xlen, len);
			bcopy((char *) rptr, mtod(mb2, char *) + mb2->m_len, count);
#ifdef	PPP_STATS
			p->pii_stats.sl_ibytes += count;
#endif
			rptr += count;
			len -= count;
			xlen -= count;
			mb2->m_len += count;
			if(!xlen) {	/* move to the next mblk */
				mp = mp->b_cont;
				if(mp)
					xlen = mp->b_wptr - (rptr = mp->b_rptr);
			}
		}
#define	HADJ	(sizeof(struct ppp_header) - sizeof(struct ifnet *))
			/* note, HADJ >= 0 is assumed */

		ifp = (struct ifnet **) (mtod(mb1, u_char *) + HADJ);
		*ifp =  &p->pii_ifnet;	/* stick ifnet * in front of packet */
		mb1->m_off += HADJ;
		mb1->m_len -= HADJ;
		freemsg(m0);
#ifdef PPP_SNIT
		if (p->pii_ifnet.if_flags & IFF_PROMISC) {
			struct mbuf *m = mb1;

			len = 0;
			do {
				len += m->m_len;
			} while (m = m->m_next);
			nif.nif_bodylen = len - sizeof(struct ifnet *);
			mb1->m_off += sizeof(struct ifnet *);
			snit_intr(&p->pii_ifnet, mb1, &nif);
			mb1->m_off -= sizeof(struct ifnet *);
		}
#endif
		p->pii_ifnet.if_ipackets++;
		INCR(sl_ipackets);
		s = splimp();
		if (IF_QFULL(&ipintrq)) {
			IF_DROP(&ipintrq);
			p->pii_ifnet.if_ierrors++;
			m_freem(mb1);
		}
		else {
			IF_ENQUEUE(&ipintrq, mb1);
			schednetisr(NETISR_IP);
		}
		splx(s);
	}	/* end while */

}

/* ifp output procedure */
int
ppp_output(ifp, m0, dst)
	struct ifnet *ifp;
	struct mbuf *m0;
	struct sockaddr *dst;
{
	register PII	*p = &pii[ifp->if_unit];
	struct mbuf	*m1;
	int	error,s, len;
	u_short	protocol;
#ifdef	VJC
	u_char	type;
#endif
	mblk_t	*mp;

	error = 0;
	if((ifp->if_flags & IFF_RUNNING) != IFF_RUNNING || !p->pii_writeq) {
		error = ENETDOWN;
		goto getout;
	}

	switch	(dst->sa_family) {
#ifdef	INET
		case AF_INET :
#ifdef PPP_SNIT
			if (ifp->if_flags & IFF_PROMISC) {
				struct mbuf *m = m0;

				len = 0;
				do {
					len += m->m_len;
				} while (m = m->m_next);
				nif.nif_bodylen = len;
				snit_intr(ifp, m0, &nif);
			}
#endif
			protocol = PPP_IP;
			break;
#endif
#ifdef	NS
		case AF_NS :
			protocol = PPP_XNS;
			break;
#endif
		default :;
			printf("ppp%d: af%d not supported\n",ifp->if_unit, dst->sa_family);
			error = EAFNOSUPPORT;
			goto getout;
	}
#ifdef	VJC
	if((protocol == PPP_IP) && (p->pii_flags & PII_FLAGS_VJC_ON)) {
		register struct ip *ip;
		ip = mtod(m0, struct ip *);
		if(ip->ip_p == IPPROTO_TCP) {
			type = sl_compress_tcp(m0, ip, &p->pii_sc_comp, 1);
			switch (type) {
		
				case TYPE_UNCOMPRESSED_TCP :
					protocol = PPP_VJC_UNCOMP;
					break;
				case TYPE_COMPRESSED_TCP :
					protocol = PPP_VJC_COMP;
					break;	
				default :;
			}
		}
	}
#endif
	len = 0;
	for(m1 = m0; m1; m1 = m1->m_next) 
		len += m1->m_len;
	if(!(p->pii_flags & PII_FLAGS_COMPAC))
		len += 2;		/* if we are not compac, then need 2 extra bytes */
	if(p->pii_flags & PII_FLAGS_COMPPROT)
		len += 1;
	else
		len += 2;		/* we never need to check the actual protocol, since its always
					   either PPP_IP,PPP_VJC_* or PPP_XNS
					*/

	if(!(mp = allocb(len, BPRI_LO))) {
		error = ENOBUFS;
		goto getout;
	}
#ifdef	PPP_STATS
	p->pii_stats.sl_obytes += len;
#endif
	if(!(p->pii_flags & PII_FLAGS_COMPAC)) {
		*mp->b_wptr++ = PPP_ALLSTATIONS;
		*mp->b_wptr++ = PPP_UI;
	}
	if(!(p->pii_flags & PII_FLAGS_COMPPROT))
		*mp->b_wptr++ = 0;
	*mp->b_wptr++ = protocol & 0xff;
	for(m1 = m0; m1; m1 = m1->m_next) {		/* copy all data */
		bcopy(mtod(m1, char *), (char *) mp->b_wptr, m1->m_len);
		mp->b_wptr += m1->m_len;
	}
	s = splstr();
	putq(p->pii_writeq, mp);

	splx(s);
	p->pii_ifnet.if_opackets++;
	INCR(sl_opackets);
getout:;
	m_freem(m0);
	if(error) {
		INCR(sl_oerrors);
		p->pii_ifnet.if_oerrors++;
	}
	return(error);		/* gads, streams are great */
}

/*
 * if_ ioctl requests 
*/
ppp_ioctl(ifp, cmd, data)
	register struct ifnet *ifp;
	int	cmd;
	caddr_t	data;
{
	register struct ifaddr *ifa = (struct ifaddr *) data;
	register struct ifreq *ifr = (struct ifreq *) data;
	int	s = splimp(), error = 0;

	switch	(cmd)	{
		case SIOCSIFFLAGS :
			if (!ifr)	/* This happens every time IFF_PROMISC has been changed. */
				break;
			if(!suser()) {
				error = EPERM;
				break;
			}
			ifp->if_flags &= (IFF_CANTCHANGE);	/* clear the flags that can be cleared */
			ifp->if_flags |= (ifr->ifr_flags & ~IFF_CANTCHANGE); /* or in the flags that can
										be changed */
			break;
		case SIOCGIFFLAGS :
			ifr->ifr_flags = ifp->if_flags;
			break;
		case SIOCSIFADDR :
			if( ifa->ifa_addr.sa_family != AF_INET) 
				error = EAFNOSUPPORT;
			break;
		case SIOCSIFDSTADDR :
			if(ifa->ifa_addr.sa_family != AF_INET)
				error = EAFNOSUPPORT;
			break;

		case SIOCSIFMTU :
			if(!suser()) {
				error = EPERM;
				break;
			}
			if(ifr->ifr_mtu > (4096 - (sizeof(struct ppp_header) + sizeof(u_short)))) {
				error = EINVAL;
				break;
			}
			ifp->if_mtu = ifr->ifr_mtu;
			break;
		case SIOCGIFMTU :
			ifr->ifr_mtu = ifp->if_mtu;
			break;
		default :;
			error = EINVAL;
			break;
	}
	splx(s);
	return(error);
}

#endif
