
/*
 * Virtual Source Router module. 
 * 
 * This is a virtual IP interface; it occupies an IP address so that 
 * packets destined to this "nonce" address stay here.
 *
 * For outgoing datagrams:
 *  > Routes must be set up so that packets destined to anywhere
 *    in the world are routed through us.
 *  > Associated with the interface is the first hop router.
 *  > When we get a packet, we add a source routing option to it
 *    and call ip_output with the SR in opts. 
 *  > Now, since the destination is a real router, we have a static
 *    route to it through a real interface, so the packet makes it out.
 *
 *
 * To set up the source route to be used, do a VSRCSETROUTE ioctl, 
 * using the follwoing format:
 *  { 131, length, 4, G1, G2, ..., GN, DST , 0} 
 * where length is the length of the option, and 0 is there as padding
 *
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/conf.h>

#include <net/if.h>
#include <net/netisr.h>
#include <net/route.h>

#ifdef	INET
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#endif

#ifdef NS
#include <netns/ns.h>
#include <netns/ns_if.h>
#endif

#include <netinet/in_pcb.h>
#include <netinet/ip_var.h>

#include "if_vsr.h"

#ifdef OPENPROMS
#include <sun/openprom.h>

int vsridentify(), vsrattach(), vsropen(), vsrclose(), vsrioctl();

struct  dev_ops fk_ops = 
{
	1,		/* revision */
	vsridentify,	/* ident */
	vsrattach,	/* attach */
	vsropen,	/* open */
	vsrclose,	/* close */
	NULL,		/* read */
	NULL,		/* write */
	NULL,		/* strategy */
	NULL,		/* dump */
	NULL,		/* psize */
	vsrioctl,	/* ioctl */
	NULL,		/* reset */
	NULL		/* mmap */
};
#else
  XXX -- driver not supported on non-4c/4m architectures yet - /ji
#endif

vsr_softc_t vsr_softc[MAXVSR];
int	vsrifs = 1;				/* can be patched */
int	vsroutput(), vsrifioctl();
static	int	vsrs_inited = 0;


vsrattach()
{
	register int i;
	register struct ifnet *ifp;

	if (vsrs_inited)
	{
		(void) printf("vsr: already initialized\n");
		return EBUSY;
	}
	vsrs_inited = 1;
	(void) printf("vsr: initializing\n");

	for (i=0; i<vsrifs; i++)
	{
		vsr_softc[i].vsr_opts = NULL;
		ifp = &vsr_softc[i].vsr_if;
		ifp->if_next = NULL;
		ifp->if_name = "vsr";
		ifp->if_unit = i;
		ifp->if_mtu = VSRMTU;
#if	MULTICAST
		ifp->if_flags = IFF_MULTICAST | IFF_NOARP;
#else	MULTICAST
		ifp->if_flags = IFF_NOARP;
#endif	MULTICAST
		ifp->if_ioctl = vsrifioctl;
		ifp->if_output = vsroutput;
		if_attach(ifp);
		printf("attached %s%d (%x)\n", ifp->if_name, ifp->if_unit, ifp);
	}
	return 0;
}


#ifdef	SunOS4
vsrdetach()
{
	struct rtentry *rt;
	struct ifnet *ifp, **p;
	struct in_ifaddr *ia, **iap;
	struct mbuf **mp, *m;
	int i, j, s, k = 0;

	s = spl5();
	for (i = 0; i < vsrifs; i++) {
		ifp = &vsr_softc[i].vsr_if;
#ifdef	DEBUGVSR
		printf("vsrdet i %d ifp %x\n", i, ifp);
#endif
		if_down(ifp);
		for (j = 0; j < RTHASHSIZ; j++) {
			for (mp = &rthost[j]; m = *mp; ) {
				rt = mtod(m, struct rtentry *);
				if (rt->rt_ifp == ifp)
				{
					if (rt->rt_refcnt)
						goto busy;
					*mp = m->m_next;
					m->m_next = NULL;
					(void) m_free(m);
				} else
					mp = &m->m_next;
			}
			for (mp = &rtnet[j]; m = *mp; ) {
				rt = mtod(m, struct rtentry *);
				if (rt->rt_ifp == ifp)
				{
					if (rt->rt_refcnt)
						goto busy;
					*mp = m->m_next;
					m->m_next = NULL;
					(void) m_free(m);
				} else
					mp = &m->m_next;
			}
		}

		for (iap = &in_ifaddr; ia = *iap; )
			if (ia->ia_ifp == ifp)
				*iap = ia->ia_next;
			else
				iap = &ia->ia_next;
		iap = (struct in_ifaddr **)&ifp->if_addrlist;
		while (ia = *iap) {
			*iap = ia->ia_next;
			(void) m_free(dtom(ia));
		}
		for (p = &ifnet; *p; p = &(*p)->if_next)
			if (*p == ifp) {
				*p = ifp->if_next;
				break;
			}
		if (m = dtom(vsr_softc[i].vsr_opts)) {
			(void) m_free(dtom(vsr_softc[i].vsr_opts));
			vsr_softc[i].vsr_opts = NULL;
		}
		k++;
	}
	if (k == vsrifs)
		vsrs_inited = 0;
busy:
	(void) splx(s);
	return (k == vsrifs) ? 0 : EBUSY;
}
#endif


vsroutput(ifp, m0, dst)
	struct ifnet *ifp;
	register struct mbuf *m0;
	struct sockaddr *dst;
{
	int s;
	register struct ifqueue *ifq;
	struct mbuf *m;
	struct sockaddr_in *din;

	if (dst->sa_family != AF_INET)
	{
		(void) printf("%s%d: can't handle af%d\n", 
		       ifp->if_name, ifp->if_unit,
		       dst->sa_family);
		m_freem(m0);
		return (EAFNOSUPPORT);
	}

	din = (struct sockaddr_in *)dst;

	if (din->sin_addr.s_addr == IA_SIN(ifp->if_addrlist)->sin_addr.s_addr)
	{
#ifdef DEBUGVSR
		(void) printf("%s%d: looping\n", ifp->if_name, ifp->if_unit);
#endif		
		/*
		 * Place interface pointer before the data
		 * for the receiving protocol.
		 */
		if (m0->m_off <= MMAXOFF &&
		    m0->m_off >= MMINOFF + sizeof(struct ifnet *)) {
			m0->m_off -= sizeof(struct ifnet *);
			m0->m_len += sizeof(struct ifnet *);
		} else {
			MGET(m, M_DONTWAIT, MT_HEADER);
			if (m == (struct mbuf *)0)
			  return (ENOBUFS);
			m->m_off = MMINOFF;
			m->m_len = sizeof(struct ifnet *);
			m->m_next = m0;
			m0 = m;
		}
		*(mtod(m0, struct ifnet **)) = ifp;
		s = splimp();
		ifp->if_opackets++;
		ifq = &ipintrq;
		if (IF_QFULL(ifq)) {
			IF_DROP(ifq);
			m_freem(m0);
			splx(s);
			return (ENOBUFS);
		}
		IF_ENQUEUE(ifq, m0);
		schednetisr(NETISR_IP);
		ifp->if_ipackets++;
		(void) splx(s);
		return (0);
	}


	if (m = vsr_softc[ifp->if_unit].vsr_opts)
	{
		int iprt, lasthop;
		struct ip *ip;

		ip = mtod(m0, struct ip *);

		/*
		 * routing has been established. 
		 */
		
#ifdef DEBUGVSR
		(void) printf("vsroutput: adding routes...\n");
#endif
		s = splnet();
		lasthop = mtod(m, caddr_t)[5];
		
		bcopy(&(din->sin_addr.s_addr), mtod(m, caddr_t)+lasthop, 4);
		ip->ip_len = htons(ip->ip_len);
		
		iprt = ip_output(m0, m, (struct route *)0, IP_ALLOWBROADCAST);
		splx(s);
#ifdef DEBUGVSR
		(void) printf("vsroutput: ip_output returns %d\n", iprt);
#endif
		return iprt;

	}
	else
	  return EHOSTUNREACH;
}


/*
 * Process an ioctl request.
 */
vsrifioctl(ifp, cmd, data)
	register struct ifnet *ifp;
	int cmd;
	caddr_t data;
{
	optarr_t oa;
	int error = 0, len;
	struct mbuf *m;

	switch (cmd) {

	      case SIOCSIFADDR:
		ifp->if_flags |= IFF_UP;
		/*
		 * Everything else is done at a higher level.
		 */
		break;

	      case VSRCSETROUTE:
		/*
		 * this cannot be called unless we patch sys/socket.c
		 */
#ifdef DEBUGVSR
		(void) printf("vsrifioctl: add source route\n");
#endif
		bcopy(data, oa, sizeof oa);
		if (oa[4] != 131)
		{
			(void) printf("vsrifioctl: invalid option %d\n", oa[0]);
			return EINVAL;
		}
		len = oa[5] + 5;
		if (len & 0x03)
		{
			(void) printf("vsrifioctl: length %d not multiple of 4\n", len);
			return EINVAL;
		}

		/* 
		 * XXX -- I should really be freeing or reusing 
		 * this; it's a memory leak.
		 */

		MGET(m, M_DONTWAIT, MT_SOOPTS);
		if (m == (struct mbuf *)0)
			return (ENOBUFS);
		m->m_off = MMINOFF;
		m->m_len = len;
		m->m_next = vsr_softc[ifp->if_unit].vsr_opts;

		bcopy(oa, mtod(m, char *), len);
		vsr_softc[ifp->if_unit].vsr_opts = m;
		break;

	default:
		error = EINVAL;
	}
	return (error);
}


int vsrioctl(dev, cmd, arg, mode)
dev_t dev;
int cmd;
caddr_t arg;
int mode;
{
	int unit, len, err;
	struct mbuf *m;
	optarr_t oa;

	unit = minor(dev);
	if ((unit < 0) || (unit >= MAXVSR))
		return ENXIO;
	if (!vsrs_inited && (err = vsrattach()))
		return err;
	switch (cmd)
	{
	      case VSRCSETROUTE:
#ifdef DEBUGVSR
		(void) printf("vsrioctl: add source route\n");
#endif
		bcopy(arg, oa, sizeof oa);
		if (oa[4] != 131)
		{
			(void) printf("vsrioctl: invalid option %d\n", oa[0]);
			return EINVAL;
		}
		len = oa[5] + 5;
		if (len & 0x03)
		{
			(void) printf("vsrifioctl: length %d not multiple of 4\n", len);
			return EINVAL;
		}
		if (len > 44)
		{
			(void) printf("vsrifioctl: too long\n");
			return EMSGSIZE;
		}

		/* 
		 * XXX -- I should really be freeing or reusing 
		 * this; it's a memory leak.
		 */
		/*
		 * reuse buffer already allocated
		 */

		if (!(m = vsr_softc[unit].vsr_opts))
		{
			MGET(m, M_DONTWAIT, MT_SOOPTS);
			if (m == (struct mbuf *)0)
				return (ENOBUFS);
			m->m_off = MMINOFF;
			m->m_len = len;
			m->m_next = (struct mbuf *)0;
			vsr_softc[unit].vsr_opts = m;
		}
		bcopy(oa, mtod(m, char *), len);
		return 0;
/*	      case VSRCDELROUTE:
#ifdef DEBUGVSR
		(void) printf("vsrioctl: delete source route\n");
#endif
		if (!(m = vsr_softc[unit].vsr_opts))
			return ENOENT;
		vsr_softc[unit].vsr_opts = NULL;
		(void) m_free(m);
		return 0;
*/
	      default:
		return EINVAL;
	}
}
 

vsridentify(s)
char *s;
{
	if (strcmp(s,"vsr") == 0)
		return 1;
	else
		return 0;
}


vsropen(dev, flag)
int dev, flag;
{
	int unit, err;

	(void) vsrattach();
	unit = minor(dev);
	if ((unit < 0) || (unit >= vsrifs ))
		return ENXIO;
	return 0;
}


vsrclose(dev, flag)
int dev, flag;
{
	return 0;
}

