/*-
 * ===================================
 * HARP  |  Host ATM Research Platform
 * ===================================
 *
 *
 * This Host ATM Research Platform ("HARP") file (the "Software") is
 * made available by Network Computing Services, Inc. ("NetworkCS")
 * "AS IS".  NetworkCS does not provide maintenance, improvements or
 * support of any kind.
 *
 * NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED,
 * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE
 * SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE.
 * In no event shall NetworkCS be responsible for any damages, including
 * but not limited to consequential damages, arising from or relating to
 * any use of the Software or related support.
 *
 * Copyright 1994-1998 Network Computing Services, Inc.
 *
 * Copies of this Software may be made, however, the above copyright
 * notice must be reproduced on all copies.
 */

/*
 * IP Over ATM Support
 * -------------------
 *
 * Virtual Channel Manager
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/netatm/ipatm/ipatm_vcm.c,v 1.20 2006/01/22 01:07:11 rwatson Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/syslog.h>

#include <net/if.h>

#include <netinet/in.h>
#include <netinet/in_var.h>

#include <netatm/port.h>
#include <netatm/queue.h>
#include <netatm/atm.h>
#include <netatm/atm_sys.h>
#include <netatm/atm_sap.h>
#include <netatm/atm_cm.h>
#include <netatm/atm_if.h>
#include <netatm/atm_stack.h>
#include <netatm/atm_pcb.h>
#include <netatm/atm_var.h>

#include <netatm/ipatm/ipatm.h>
#include <netatm/ipatm/ipatm_var.h>
#include <netatm/ipatm/ipatm_serv.h>

#include <vm/uma.h>

Atm_attributes	ipatm_aal5llc = {
	NULL,			/* nif */
	CMAPI_CPCS,		/* api */
	0,			/* api_init */
	0,			/* headin */
	0,			/* headout */
	{			/* aal */
		T_ATM_PRESENT,
		ATM_AAL5
	},
	{			/* traffic */
		T_ATM_PRESENT,
		{
			{
				T_ATM_ABSENT,
				0,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_NO
			},
			{
				T_ATM_ABSENT,
				0,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_NO
			},
			T_YES
		},
	},
	{			/* bearer */
		T_ATM_PRESENT,
		{
			T_ATM_CLASS_X,
			T_ATM_NULL,
			T_ATM_NULL,
			T_NO,
			T_ATM_1_TO_1
		}
	},
	{			/* bhli */
		T_ATM_ABSENT
	},
	{			/* blli */
		T_ATM_PRESENT,
		T_ATM_ABSENT,
		{
			{
				T_ATM_SIMPLE_ID,
			},
			{
				T_ATM_ABSENT
			}
		}
	},
	{			/* llc */
		T_ATM_PRESENT,
		{
			T_ATM_LLC_SHARING,
			IPATM_LLC_LEN,
			IPATM_LLC_HDR
		}
	},
	{			/* called */
		T_ATM_PRESENT,
	},
	{			/* calling */
		T_ATM_ABSENT
	},
	{			/* qos */
		T_ATM_PRESENT,
		{
			T_ATM_NETWORK_CODING,
			{
				T_ATM_QOS_CLASS_0,
			},
			{
				T_ATM_QOS_CLASS_0
			}
		}
	},
	{			/* transit */
		T_ATM_ABSENT
	},
	{			/* cause */
		T_ATM_ABSENT
	}
};

Atm_attributes	ipatm_aal5null = {
	NULL,			/* nif */
	CMAPI_CPCS,		/* api */
	0,			/* api_init */
	sizeof(struct ifnet *),	/* headin */
	0,			/* headout */
	{			/* aal */
		T_ATM_PRESENT,
		ATM_AAL5
	},
	{			/* traffic */
		T_ATM_PRESENT,
		{
			{
				T_ATM_ABSENT,
				0,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_NO
			},
			{
				T_ATM_ABSENT,
				0,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_NO
			},
			T_YES
		},
	},
	{			/* bearer */
		T_ATM_PRESENT,
		{
			T_ATM_CLASS_X,
			T_ATM_NULL,
			T_ATM_NULL,
			T_NO,
			T_ATM_1_TO_1
		}
	},
	{			/* bhli */
		T_ATM_ABSENT
	},
	{			/* blli */
		T_ATM_ABSENT,
		T_ATM_ABSENT
	},
	{			/* llc */
		T_ATM_ABSENT
	},
	{			/* called */
		T_ATM_PRESENT,
	},
	{			/* calling */
		T_ATM_ABSENT
	},
	{			/* qos */
		T_ATM_PRESENT,
		{
			T_ATM_NETWORK_CODING,
			{
				T_ATM_QOS_CLASS_0,
			},
			{
				T_ATM_QOS_CLASS_0
			}
		}
	},
	{			/* transit */
		T_ATM_ABSENT
	},
	{			/* cause */
		T_ATM_ABSENT
	}
};

Atm_attributes	ipatm_aal4null = {
	NULL,			/* nif */
	CMAPI_CPCS,		/* api */
	0,			/* api_init */
	sizeof(struct ifnet *),	/* headin */
	0,			/* headout */
	{			/* aal */
		T_ATM_PRESENT,
		ATM_AAL3_4
	},
	{			/* traffic */
		T_ATM_PRESENT,
		{
			{
				T_ATM_ABSENT,
				0,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_NO
			},
			{
				T_ATM_ABSENT,
				0,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_ATM_ABSENT,
				T_NO
			},
			T_YES
		},
	},
	{			/* bearer */
		T_ATM_PRESENT,
		{
			T_ATM_CLASS_X,
			T_ATM_NULL,
			T_ATM_NULL,
			T_NO,
			T_ATM_1_TO_1
		}
	},
	{			/* bhli */
		T_ATM_ABSENT
	},
	{			/* blli */
		T_ATM_ABSENT,
		T_ATM_ABSENT
	},
	{			/* llc */
		T_ATM_ABSENT
	},
	{			/* called */
		T_ATM_PRESENT,
	},
	{			/* calling */
		T_ATM_ABSENT
	},
	{			/* qos */
		T_ATM_PRESENT,
		{
			T_ATM_NETWORK_CODING,
			{
				T_ATM_QOS_CLASS_0,
			},
			{
				T_ATM_QOS_CLASS_0
			}
		}
	},
	{			/* transit */
		T_ATM_ABSENT
	},
	{			/* cause */
		T_ATM_ABSENT
	}
};

static struct t_atm_cause	ipatm_cause = {
	T_ATM_ITU_CODING,
	T_ATM_LOC_USER,
	0,
	{0, 0, 0, 0}
};


/*
 * Open an IP PVC
 * 
 * This function will perform all actions necessary to activate a
 * PVC for IP usage.  In particular, it will allocate control blocks, 
 * open the PVC, initialize PVC stack, and initiate whatever ARP
 * procedures are required.
 *
 * Arguments:
 *	pvp	pointer to PVC parameter structure
 *	sivp	address to return pointer to IP PVC control block
 *
 * Returns:
 *	0 	PVC was successfully opened
 *	errno	open failed - reason indicated
 *
 */
int
ipatm_openpvc(struct ipatmpvc *pvp, struct ipvcc **sivp)
{
	struct ipvcc	*ivp = NULL;	/* XXX pacify gcc-3.1 */
	Atm_attributes	*ap;
	Atm_addr_pvc	*pvcp;
	struct atm_nif	*nip;
	struct ip_nif	*inp;
	int	s, err = 0;

	inp = pvp->ipp_ipnif;
	nip = inp->inf_nif;

	/*
	 * Make sure interface is ready to go
	 */
	if (inp->inf_state != IPNIF_ACTIVE) {
		err = ENETDOWN;
		goto done;
	}

	/*
	 * Validate fixed destination IP address
	 */
	if (pvp->ipp_dst.sin_addr.s_addr != INADDR_ANY) {
		if (in_broadcast(pvp->ipp_dst.sin_addr, ANIF2IFP(nip)) ||
		    IN_MULTICAST(ntohl(pvp->ipp_dst.sin_addr.s_addr)) ||
		    ipatm_chknif(pvp->ipp_dst.sin_addr, inp)) {
			err = EINVAL;
			goto done;
		}
	}

	/*
	 * Allocate IP VCC block
	 */
	ivp = uma_zalloc(ipatm_vc_zone, M_WAITOK);
	if (ivp == NULL) {
		err = ENOMEM;
		goto done;
	}

	/*
	 * Initialize the PVC
	 */
	ivp->iv_flags = IVF_PVC;
	if (pvp->ipp_encaps == ATM_ENC_LLC)
		ivp->iv_flags |= IVF_LLC;

	/*
	 * Fill out connection attributes
	 * Make a temporary copy of the attributes here so that we
	 * do not change the default attributes for SVCs. Otherwise this
	 * will give trouble in a mixed SVC/PVC case.
	 */
	ap = malloc(sizeof(*ap), M_TEMP, M_NOWAIT);
	if (ap == NULL) {
		err = ENOMEM;
		goto done;
	}
	if (pvp->ipp_aal == ATM_AAL5) {
		if (pvp->ipp_encaps == ATM_ENC_LLC)
			*ap = ipatm_aal5llc;
		else
			*ap = ipatm_aal5null;
	} else {
		*ap = ipatm_aal4null;
	}

	/*
	 * Build the ATM attributes
	 */
	ap->nif = nip;

        ap->bearer.v.traffic_type = pvp->ipp_traffic_type;
	switch(ap->bearer.v.traffic_type) {
	  case T_ATM_UBR:
	  case T_ATM_CBR:
		/*
		 * PCR=0 means `use up to the PIF's PCR'
		 */
		if (pvp->ipp_traffic.forward.PCR_all_traffic == 0)
			ap->traffic.v.forward.PCR_all_traffic =
				nip->nif_pif->pif_pcr;
		else
			ap->traffic.v.forward.PCR_all_traffic =
				pvp->ipp_traffic.forward.PCR_all_traffic;

		if (pvp->ipp_traffic.forward.PCR_high_priority == 0)
			ap->traffic.v.forward.PCR_high_priority =
				nip->nif_pif->pif_pcr;
		else
			ap->traffic.v.forward.PCR_high_priority =
				pvp->ipp_traffic.forward.PCR_high_priority;

		if (pvp->ipp_traffic.backward.PCR_all_traffic == 0)
			ap->traffic.v.backward.PCR_all_traffic =
				nip->nif_pif->pif_pcr;
		else
			ap->traffic.v.backward.PCR_all_traffic =
				pvp->ipp_traffic.backward.PCR_all_traffic;

		if (pvp->ipp_traffic.backward.PCR_high_priority == 0)
			ap->traffic.v.backward.PCR_high_priority =
				nip->nif_pif->pif_pcr;
		else
			ap->traffic.v.backward.PCR_high_priority =
				pvp->ipp_traffic.backward.PCR_high_priority;
		break;

	case T_ATM_VBR:
		ap->traffic.v.forward.PCR_all_traffic =
			pvp->ipp_traffic.forward.PCR_all_traffic;
		ap->traffic.v.forward.PCR_high_priority =
			pvp->ipp_traffic.forward.PCR_high_priority;
		ap->traffic.v.forward.SCR_all_traffic =
			pvp->ipp_traffic.forward.SCR_all_traffic;
		ap->traffic.v.forward.SCR_high_priority =
			pvp->ipp_traffic.forward.SCR_high_priority;
		ap->traffic.v.forward.MBS_all_traffic =
			pvp->ipp_traffic.forward.MBS_all_traffic;
		ap->traffic.v.forward.MBS_high_priority =
			pvp->ipp_traffic.forward.MBS_high_priority;

		ap->traffic.v.backward.PCR_all_traffic =
			pvp->ipp_traffic.backward.PCR_all_traffic;
		ap->traffic.v.backward.PCR_high_priority =
			pvp->ipp_traffic.backward.PCR_high_priority;
		ap->traffic.v.backward.SCR_all_traffic =
			pvp->ipp_traffic.backward.SCR_all_traffic;
		ap->traffic.v.backward.SCR_high_priority =
			pvp->ipp_traffic.backward.SCR_high_priority;
		ap->traffic.v.backward.MBS_all_traffic =
			pvp->ipp_traffic.backward.MBS_all_traffic;
		ap->traffic.v.backward.MBS_high_priority =
			pvp->ipp_traffic.backward.MBS_high_priority;
		break;

	case T_ATM_NULL:
		/*
		 * No traffic type
		 */
		/* FALLTHRU */
	default:
		ap->traffic.v.forward.PCR_all_traffic =
			nip->nif_pif->pif_pcr;
		ap->traffic.v.backward.PCR_all_traffic =
			nip->nif_pif->pif_pcr;
		break;
	}
	ap->called.addr.address_format = T_ATM_PVC_ADDR;
	ap->called.addr.address_length = sizeof(Atm_addr_pvc);
	pvcp = (Atm_addr_pvc *)ap->called.addr.address;
	ATM_PVC_SET_VPI(pvcp, pvp->ipp_vpi);
	ATM_PVC_SET_VCI(pvcp, pvp->ipp_vci);
	ap->called.subaddr.address_format = T_ATM_ABSENT;
	ap->called.subaddr.address_length = 0;

	/*
	 * Create PVC
	 */
	err = atm_cm_connect(&ipatm_endpt, ivp, ap, &ivp->iv_conn);
	if (err) {
		free(ap, M_TEMP);
		uma_zfree(ipatm_vc_zone, ivp);
		goto done;
	}

	/*
	 * Save PVC information and link in VCC
	 */
	/* ivp->iv_ = ap->headout; */
	free(ap, M_TEMP);

	/*
	 * Queue VCC onto its network interface
	 */
	s = splnet();
	ipatm_vccnt++;
	ENQUEUE(ivp, struct ipvcc, iv_elem, inp->inf_vcq);
	ivp->iv_ipnif = inp;
	(void) splx(s);

	/*
	 * Set destination IP address and IPVCC state
	 */
	if (pvp->ipp_dst.sin_addr.s_addr == INADDR_ANY) {
		/*
		 * Initiate ARP processing
		 */
		switch ((*inp->inf_serv->is_arp_pvcopen)(ivp)) {

		case MAP_PROCEEDING:
			/*
			 * Wait for answer
			 */
			ivp->iv_state = IPVCC_ACTIVE;
			break;

		case MAP_VALID:
			/*
			 * We've got our answer already
			 */
			ivp->iv_state = IPVCC_ACTIVE;
			ivp->iv_flags |= IVF_MAPOK;
			ivp->iv_dst.s_addr = ivp->iv_arpent->am_dstip.s_addr;
			break;

		case MAP_FAILED:
			/*
			 * Try again later
			 */
			ivp->iv_state = IPVCC_ACTPENT;
			IPVCC_TIMER(ivp, 1 * ATM_HZ);
			break;

		default:
			panic("ipatm_openpvc: invalid arp_pvcopen return");
		}

	} else {
		/*
		 * Use configured IP destination
		 */
		ivp->iv_dst.s_addr = pvp->ipp_dst.sin_addr.s_addr;
		ivp->iv_state = IPVCC_ACTIVE;
		ivp->iv_flags |= IVF_MAPOK;
	}

done:
	if (err)
		*sivp = NULL;
	else
		*sivp = ivp;
	return (err);
}


/*
 * Create an IP SVC
 * 
 * This function will initiate the creation of an IP SVC.  The IP VCC
 * control block will be initialized and, if required, we will initiate
 * ARP processing in order to resolve the destination's ATM address.  Once
 * the destination ATM address is known, ipatm_opensvc() will be called.
 *
 * Arguments:
 *	ifp	pointer to destination ifnet structure
 *	daf	destination address family type
 *	dst	pointer to destination address
 *	sivp	address to return pointer to IP SVC control block
 *
 * Returns:
 *	0 	SVC creation was successfully initiated
 *	errno	creation failed - reason indicated
 *
 */
int
ipatm_createsvc(struct ifnet *ifp, u_short daf, caddr_t dst,
    struct ipvcc **sivp)
{
	struct atm_nif	*nip = IFP2ANIF(ifp);
	struct ip_nif	*inp;
	struct ipvcc	*ivp = NULL;	/* XXX pacify gcc-3.1 */
	struct in_addr	*ip;
	Atm_addr	*atm;
	int	s, err = 0;

	/*
	 * Get IP interface and make sure its ready
	 */
	for (inp = ipatm_nif_head; inp; inp = inp->inf_next) {
		if (inp->inf_nif == nip)
			break;
	}
	if (inp == NULL) {
		err = ENXIO;
		goto done;
	}
	if (inp->inf_state != IPNIF_ACTIVE) {
		err = ENETDOWN;
		goto done;
	}

	/*
	 * Validate destination address
	 */
	if (daf == AF_INET) {
		/*
		 * Destination is IP address
		 */
		ip = (struct in_addr *)dst;
		atm = NULL;
		if (ip->s_addr == INADDR_ANY) {
			err = EADDRNOTAVAIL;
			goto done;
		}
	} else if (daf == AF_ATM) {
		/*
		 * Destination is ATM address
		 */
		atm = (Atm_addr *)dst;
		ip = NULL;
		if (atm->address_format == T_ATM_ABSENT) {
			err = EINVAL;
			goto done;
		}
	} else {
		err = EINVAL;
		goto done;
	}

	/*
	 * Make sure we have services provider and ARP support
	 */
	if ((inp->inf_serv == NULL) ||
	    (inp->inf_serv->is_arp_svcout == NULL)) {
		err = ENETDOWN;
		goto done;
	}

	/*
	 * Allocate IP VCC
	 * May be called from timeout - don't wait.
	 */
	ivp = uma_zalloc(ipatm_vc_zone, M_NOWAIT);
	if (ivp == NULL) {
		err = ENOMEM;
		goto done;
	}

	/*
	 * Initialize SVC
	 */
	ivp->iv_flags = IVF_SVC;
	ivp->iv_ipnif = inp;

	/*
	 * Get destination ATM address
	 */
	if (daf == AF_INET) {
		/*
		 * ARP is the way...
		 */
		ivp->iv_dst.s_addr = ip->s_addr;

		switch ((*inp->inf_serv->is_arp_svcout)(ivp, ip)) {

		case MAP_PROCEEDING:
			/*
			 * Wait for answer
			 */
			ivp->iv_state = IPVCC_PMAP;
			IPVCC_TIMER(ivp, IPATM_ARP_TIME);
			break;

		case MAP_VALID:
			/*
			 * We've got our answer already, so open SVC
			 */
			ivp->iv_flags |= IVF_MAPOK;
			err = ipatm_opensvc(ivp);
			if (err) {
				(*inp->inf_serv->is_arp_close)(ivp);
				uma_zfree(ipatm_vc_zone, ivp);
				goto done;
			}
			break;

		case MAP_FAILED:
			/*
			 * So sorry...come again
			 */
			uma_zfree(ipatm_vc_zone, ivp);
			err = ENETDOWN;
			goto done;

		default:
			panic("ipatm_createsvc: invalid arp_svcout return");
		}
	} else {
		/*
		 * We were given the ATM address, so open the SVC
		 *
		 * Create temporary arp map entry so that opensvc() works.
		 * Caller must set up a permanent entry immediately! (yuk)
		 */
		struct arpmap	map;

		ATM_ADDR_COPY(atm, &map.am_dstatm);
		map.am_dstatmsub.address_format = T_ATM_ABSENT;
		map.am_dstatmsub.address_length = 0;
		ivp->iv_arpent = &map;
		err = ipatm_opensvc(ivp);
		if (err) {
			uma_zfree(ipatm_vc_zone, ivp);
			goto done;
		}
		ivp->iv_arpent = NULL;
	}

	/*
	 * Queue VCC onto its network interface
	 */
	s = splnet();
	ipatm_vccnt++;
	ENQUEUE(ivp, struct ipvcc, iv_elem, inp->inf_vcq);
	(void) splx(s);

done:
	if (err)
		*sivp = NULL;
	else
		*sivp = ivp;
	return (err);
}


/*
 * Open an IP SVC
 * 
 * This function will continue the IP SVC creation process.  Here, we
 * will issue an SVC open to the signalling manager and then wait for
 * the final SVC setup results.
 *
 * Arguments:
 *	ivp	pointer to IP SVC to open
 *
 * Returns:
 *	0 	SVC open was successfully initiated
 *	errno	open failed - reason indicated
 *
 */
int
ipatm_opensvc(struct ipvcc *ivp)
{
	struct ip_nif	*inp = ivp->iv_ipnif;
	Atm_attributes	*ap;
	int	err = 0, i;

	/*
	 * Cancel possible arp timeout
	 */
	IPVCC_CANCEL(ivp);

	/*
	 * Fill out connection attributes
	 */
	i = ivp->iv_parmx;
	if (inp->inf_serv->is_vccparm[i].ivc_aal == ATM_AAL5) {
		if (inp->inf_serv->is_vccparm[i].ivc_encaps == ATM_ENC_LLC) {
			ap = &ipatm_aal5llc;
			ivp->iv_flags |= IVF_LLC;
		} else {
			ap = &ipatm_aal5null;
			ivp->iv_flags &= ~IVF_LLC;
		}
	} else {
		ap = &ipatm_aal4null;
		ivp->iv_flags &= ~IVF_LLC;
	}

	ap->nif = inp->inf_nif;
	ap->traffic.v.forward.PCR_all_traffic = inp->inf_nif->nif_pif->pif_pcr;
	ap->traffic.v.backward.PCR_all_traffic = inp->inf_nif->nif_pif->pif_pcr;

	ATM_ADDR_COPY(&ivp->iv_arpent->am_dstatm, &ap->called.addr);
	ATM_ADDR_COPY(&ivp->iv_arpent->am_dstatmsub, &ap->called.subaddr);

	/*
	 * Initiate SVC open
	 */
	err = atm_cm_connect(&ipatm_endpt, ivp, ap, &ivp->iv_conn);
	switch (err) {

	case EINPROGRESS:
		/*
		 * Call is progressing
		 */
		/* ivp->iv_ = ap->headout; */

		/*
		 * Now we just wait for a CALL_CONNECTED event
		 */
		ivp->iv_state = IPVCC_POPEN;
		IPVCC_TIMER(ivp, IPATM_SVC_TIME);
		err = 0;
		break;

	case 0:
		/*
		 * We've been hooked up with a shared VCC
		 */
		/* ivp->iv_ = ap->headout; */
		ipatm_activate(ivp);
		break;
	}

	return (err);
}


/*
 * Retry an IP SVC Open
 * 
 * This function will attempt to retry a failed SVC open request.  The IP
 * interface service provider specifies a list of possible VCC parameters
 * for IP to use.  We will try each set of parameters in turn until either
 * an open succeeds or we reach the end of the list.
 * 
 * Arguments:
 *	ivp	pointer to IP SVC
 *
 * Returns:
 *	0 	SVC (re)open was successfully initiated
 *	else	retry failed
 *
 */
int
ipatm_retrysvc(struct ipvcc *ivp)
{
	struct ip_nif	*inp = ivp->iv_ipnif;

	/*
	 * If there isn't another set of vcc parameters to try, return
	 */
	if ((++ivp->iv_parmx >= IPATM_VCCPARMS) ||
	    (inp->inf_serv->is_vccparm[ivp->iv_parmx].ivc_aal == 0))
		return (1);

	/*
	 * Okay, now initiate open with a new set of parameters
	 */
	return (ipatm_opensvc(ivp));
}


/*
 * Finish IP SVC Activation
 * 
 * Arguments:
 *	ivp	pointer to IP SVC
 *
 * Returns:
 *	none
 *
 */
void
ipatm_activate(struct ipvcc *ivp)
{

	/*
	 * Connection is now active
	 */
	ivp->iv_state = IPVCC_ACTIVE;
	IPVCC_CANCEL(ivp);

	/*
	 * Tell ARP module that connection is active
	 */
	if ((*ivp->iv_ipnif->inf_serv->is_arp_svcact)(ivp)) {
		(void) ipatm_closevc(ivp, T_ATM_CAUSE_TEMPORARY_FAILURE);
		return;
	}

	/*
	 * Send any queued packet
	 */
	if ((ivp->iv_flags & IVF_MAPOK) && ivp->iv_queue) {
		struct sockaddr_in	sin;
		struct ifnet		*ifp;

		sin.sin_family = AF_INET;
		sin.sin_addr.s_addr = ivp->iv_dst.s_addr;
		ifp = (struct ifnet *)ivp->iv_ipnif->inf_nif;
		(void) ipatm_ifoutput(ifp, ivp->iv_queue, 
			(struct sockaddr *)&sin);
		ivp->iv_queue = NULL;
	}
}


/*
 * Process Incoming Calls
 * 
 * This function will receive control when an incoming call has been matched
 * to one of our registered listen parameter blocks.  Assuming the call passes
 * acceptance criteria and all required resources are available, we will
 * create an IP SVC and notify the connection manager of our decision.  We
 * will then await notification of the final SVC setup results.  If any
 * problems are encountered, we will just tell the connection manager to
 * reject the call.
 *
 * Called at splnet.
 *
 * Arguments:
 *	tok	owner's matched listening token
 *	cop	pointer to incoming call's connection block
 *	ap	pointer to incoming call's attributes
 *	tokp	pointer to location to store our connection token
 *
 * Returns:
 *	0	call is accepted
 *	errno	call rejected - reason indicated
 *
 */
int
ipatm_incoming(void *tok, Atm_connection *cop, Atm_attributes *ap,
    void **tokp)
{
	struct atm_nif	*nip = ap->nif;
	struct ip_nif	*inp;
	struct ipvcc	*ivp = NULL;
	int	err, cause;
	int	usellc = 0, mtu = ATM_NIF_MTU;

	/*
	 * Get IP interface and make sure its ready
	 */
	for (inp = ipatm_nif_head; inp; inp = inp->inf_next) {
		if (inp->inf_nif == nip)
			break;
	}
	if ((inp == NULL) || (inp->inf_state != IPNIF_ACTIVE)) {
		err = ENETUNREACH;
		cause = T_ATM_CAUSE_SERVICE_OR_OPTION_UNAVAILABLE;
		goto reject;
	}

	/*
	 * Make sure we have services provider and ARP support
	 */
	if ((inp->inf_serv == NULL) ||
	    (inp->inf_serv->is_arp_svcin == NULL)) {
		err = ENETUNREACH;
		cause = T_ATM_CAUSE_SERVICE_OR_OPTION_UNAVAILABLE;
		goto reject;
	}

	/*
	 * Check for LLC encapsulation
	 */
	if ((ap->blli.tag_l2 == T_ATM_PRESENT) &&
	    (ap->blli.v.layer_2_protocol.ID_type == T_ATM_SIMPLE_ID) &&
	    (ap->blli.v.layer_2_protocol.ID.simple_ID == T_ATM_BLLI2_I8802)) {
		usellc = 1;
		mtu += IPATM_LLC_LEN;
	}

	/*
	 * Verify requested MTU
	 */
	if (ap->aal.type == ATM_AAL5) {
		if ((ap->aal.v.aal5.forward_max_SDU_size > mtu) ||
		    (ap->aal.v.aal5.backward_max_SDU_size < mtu)) {
			err = ENETUNREACH;
			cause = T_ATM_CAUSE_AAL_PARAMETERS_NOT_SUPPORTED;
			goto reject;
		}
	} else {
		if ((ap->aal.v.aal4.forward_max_SDU_size > mtu) ||
		    (ap->aal.v.aal4.backward_max_SDU_size < mtu)) {
			err = ENETUNREACH;
			cause = T_ATM_CAUSE_AAL_PARAMETERS_NOT_SUPPORTED;
			goto reject;
		}
	}

	/*
	 * Allocate IP VCC
	 * May be called from timeout - don't wait.
	 */
	ivp = uma_zalloc(ipatm_vc_zone, M_NOWAIT);
	if (ivp == NULL) {
		err = ENOMEM;
		cause = T_ATM_CAUSE_UNSPECIFIED_RESOURCE_UNAVAILABLE;
		goto reject;
	}

	/*
	 * Initialize SVC
	 */
	ivp->iv_flags = IVF_SVC;
	ivp->iv_ipnif = inp;
	if (usellc)
		ivp->iv_flags |= IVF_LLC;

	/*
	 * Lookup ARP entry for destination
	 */
	switch ((*inp->inf_serv->is_arp_svcin)
			(ivp, &ap->calling.addr, &ap->calling.subaddr)) {

	case MAP_PROCEEDING:
		/*
		 * We'll be (hopefully) notified later
		 */
		break;

	case MAP_VALID:
		/*
		 * We've got our answer already
		 */
		ivp->iv_flags |= IVF_MAPOK;
		ivp->iv_dst.s_addr = ivp->iv_arpent->am_dstip.s_addr;
		break;

	case MAP_FAILED:
		/*
		 * So sorry...come again
		 */
		err = ENETUNREACH;
		cause = T_ATM_CAUSE_SERVICE_OR_OPTION_UNAVAILABLE;
		goto reject;

	default:
		panic("ipatm_incoming: invalid arp_svcin return");
	}

	/*
	 * Accept SVC connection
	 */
	ivp->iv_state = IPVCC_PACCEPT;

	/*
	 * Save VCC information
	 */
	ivp->iv_conn = cop;
	*tokp = ivp;
	/* ivp->iv_ = ap->headout; */

	/*
	 * Queue VCC onto its network interface
	 */
	ipatm_vccnt++;
	ENQUEUE(ivp, struct ipvcc, iv_elem, inp->inf_vcq);

	/*
	 * Wait for a CALL_CONNECTED event
	 */
	IPVCC_TIMER(ivp, IPATM_SVC_TIME);

	return (0);

reject:
	/*
	 * Clean up after call failure
	 */
	if (ivp) {
		(*inp->inf_serv->is_arp_close)(ivp);
		uma_zfree(ipatm_vc_zone, ivp);
	}
	ap->cause.tag = T_ATM_PRESENT;
	ap->cause.v = ipatm_cause;
	ap->cause.v.cause_value = cause;
	return (err);
}


/*
 * Close an IP VCC
 * 
 * This function will close an IP VCC (PVC or SVC), including notifying 
 * the signalling and ARP subsystems of the VCC's demise and cleaning 
 * up memory after ourselves.
 *
 * Arguments:
 *	ivp	pointer to VCC
 *	code	cause code
 *
 * Returns:
 *	0 	VCC successfully closed
 *	errno	close failed - reason indicated
 *
 */
int
ipatm_closevc(struct ipvcc *ivp, int code)
{
	struct ip_nif	*inp = ivp->iv_ipnif;
	int	s, err;

	/*
	 * Make sure VCC hasn't been through here already
	 */
	switch (ivp->iv_state) {

	case IPVCC_FREE:
		return (EALREADY);
	}

	/*
	 * Reset lookup cache
	 */
	if (last_map_ipvcc == ivp) {
		last_map_ipvcc = NULL;
		last_map_ipdst = 0;
	}

	/*
	 * Tell ARP about SVCs and dynamic PVCs
	 */
	if (inp->inf_serv && 
	    ((ivp->iv_flags & IVF_SVC) || inp->inf_serv->is_arp_pvcopen)) {
		(*inp->inf_serv->is_arp_close)(ivp);
	}

	/*
	 * Free queued packets
	 */
	if (ivp->iv_queue)
		KB_FREEALL(ivp->iv_queue);

	/*
	 * Cancel any timers
	 */
	IPVCC_CANCEL(ivp);

	/*
	 * Close VCC
	 */
	switch (ivp->iv_state) {

	case IPVCC_PMAP:
		break;

	case IPVCC_POPEN:
	case IPVCC_PACCEPT:
	case IPVCC_ACTPENT:
	case IPVCC_ACTIVE:
		ipatm_cause.cause_value = code;
		err = atm_cm_release(ivp->iv_conn, &ipatm_cause);
		if (err) {
			log(LOG_ERR, 
				"ipatm_closevc: release fail: err=%d\n", err);
		}
		break;

	case IPVCC_CLOSED:
		break;

	default:
		log(LOG_ERR, 
			"ipatm_closevc: unknown state: ivp=%p, state=%d\n",
                       	ivp, ivp->iv_state);
	}

	/*
	 * Remove VCC from network i/f
	 */
	s = splnet();
	DEQUEUE(ivp, struct ipvcc, iv_elem, inp->inf_vcq);

	/*
	 * Reset state just to be sure
	 */
	ivp->iv_state = IPVCC_FREE;

	/*
	 * If ARP module is done with VCC too, then free it
	 */
	if (ivp->iv_arpconn == NULL)
		uma_zfree(ipatm_vc_zone, ivp);
	ipatm_vccnt--;
	(void) splx(s);

	return (0);
}


/*
 * Check if IP address is valid on a Network Interface
 * 
 * Checks whether the supplied IP address is allowed to be assigned to
 * the supplied IP network interface.
 *
 * Arguments:
 *	in	IP address
 *	inp	pointer to IP network interface
 *
 * Returns:
 *	0 - OK to assign
 *	1 - not valid to assign
 *
 */
int
ipatm_chknif(struct in_addr in, struct ip_nif *inp)
{
	struct in_ifaddr	*ia;
	u_long	i;

	/*
	 * Make sure there's an interface requested
	 */
	if (inp == NULL)
		return (1);

	/*
	 * Make sure we have an IP address
	 */
	i = ntohl(in.s_addr);
	if (i == 0)
		return (1);

	/*
	 * Make sure an interface address is set 
	 */
	ia = inp->inf_addr;
	if (ia == NULL)
		return (1);

	/*
	 * Make sure we're on the right subnet
	 */
	if ((i & ia->ia_subnetmask) != ia->ia_subnet)
		return (1);

	return (0);
}


/*
 * Map an IP Address to an IP VCC
 * 
 * Given a destination IP address, this function will return a pointer
 * to the appropriate output IP VCC to which to send the packet.
 * This is currently implemented using a one-behind cache containing the 
 * last successful mapping result.  If the cache lookup fails, then a
 * simple linear search of all IP VCCs on the destination network interface 
 * is performed.  This is obviously an area to look at for performance 
 * improvements.
 *
 * Arguments:
 *	dst	pointer to destination IP address
 *	nip	pointer to destination network interface
 *
 * Returns:
 *	addr 	pointer to located IP VCC
 *	0	no such mapping exists
 *
 */
struct ipvcc *
ipatm_iptovc(struct sockaddr_in *dst, struct atm_nif *nip)
{
	struct ip_nif	*inp;
	struct ipvcc	*ivp;
	u_long		dstip = dst->sin_addr.s_addr;
	int	s;

	/*
	 * Look in cache first
	 */
	if (last_map_ipdst == dstip)
		return (last_map_ipvcc);

	/*
	 * Oh well, we've got to search for it...first find the interface
	 */
	s = splnet();
	for (inp = ipatm_nif_head; inp; inp = inp->inf_next) {
		if (inp->inf_nif == nip)
			break;
	}
	if (inp == NULL) {
		(void) splx(s);
		return (NULL);
	}

	/*
	 * Now home in on the VCC
	 */
	for (ivp = Q_HEAD(inp->inf_vcq, struct ipvcc); ivp;
				ivp = Q_NEXT(ivp, struct ipvcc, iv_elem)) {
		if (ivp->iv_dst.s_addr == dstip)
			break;
	}

	/*
	 * Update lookup cache
	 */
	if (ivp) {
		last_map_ipdst = dstip;
		last_map_ipvcc = ivp;
	}
	(void) splx(s);

	return (ivp);
}
