/* $Id: if.c,v 1.2.10.1 2004/02/29 15:25:09 bfernhomberg Exp $
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/stream.h>
#include <sys/kmem.h>
#include <sys/dlpi.h>
#include <sys/cmn_err.h>
#include <sys/errno.h>
#include <sys/byteorder.h>
#include <sys/ethernet.h>
#include <sys/ddi.h>
#include <net/if.h>
#include <netinet/arp.h>

#ifdef STDC_HEADERS
#include <strings.h>
#else
#include <string.h>
#endif

#include <netatalk/at.h>
#include <netatalk/aarp.h>

#include "if.h"
#include "rt.h"
#include "ioc.h"

static struct atif_data	*interfaces = NULL;

    struct atif_data *
if_primary()
{
    return( interfaces );
}

    struct atif_data *
if_alloc( queue_t *q )
{
    struct atif_data	*aid;

    if (( aid = kmem_zalloc( sizeof( struct atif_data ), KM_SLEEP )) == NULL ) {
	return( NULL );
    }
    aid->aid_q = q;
    aid->aid_state = DL_UNATTACHED;

    return( aid );
}

/*
 * Name an interface, insert it in our list of interfaces.  If this is the
 * first interface, create the loopback interface.  If it's not the first
 * interfaces, keep the first interface the same, i.e. the first configured
 * interface should be the primary interface.
 */
    int
if_name( struct atif_data *aid, char *name, ulong ppa )
{
    sprintf( aid->aid_name, "%s%ld", name, ppa );

    if ( interfaces == NULL ) {		/* create fake loopback */
	if (( interfaces = if_alloc( NULL )) == NULL ) {
	    return( ENOMEM );
	}
	strcpy( interfaces->aid_name, "lo0" );
	interfaces->aid_state = DL_IDLE;
	bzero( interfaces->aid_hwaddr, sizeof( interfaces->aid_hwaddr ));
	interfaces->aid_flags = AIDF_LOOPBACK;
	interfaces->aid_c.c_type = 0;

	aid->aid_next = interfaces;
	aid->aid_next->aid_prev = aid;
	interfaces = aid;
    } else {
	aid->aid_next = interfaces->aid_next;
	aid->aid_prev = interfaces;
	aid->aid_next->aid_prev = aid;
	interfaces->aid_next = aid;
    }

    aarp_init( aid );
    return( 0 );
}

    void
if_free( struct atif_data *aid )
{
    if ( aid->aid_c.c_type != 0 ) {
	cmn_err( CE_NOTE, "if_free context %x\n", aid->aid_c.c_type );
	return;
    }

    aarp_clean( aid );

    if ( aid->aid_next != NULL ) {
	aid->aid_next->aid_prev = aid->aid_prev;
    }
    if ( aid->aid_prev != NULL ) {
	aid->aid_prev->aid_next = aid->aid_next;
    }
    if ( aid == interfaces ) {
	interfaces = aid->aid_next;
    }
    kmem_free( aid, sizeof( struct atif_data ));

    if ( interfaces != NULL && interfaces->aid_next == NULL ) {
	kmem_free( interfaces, sizeof( struct atif_data ));
	interfaces = NULL;
    }
    return;
}

    int
if_getaddr( char *name, struct sockaddr_at *sat )
{
    struct atif_data	*aid;

    for ( aid = interfaces; aid != NULL; aid = aid->aid_next ) {
	if ( strcmp( name, aid->aid_name ) == 0 ) {
	    break;
	}
    }
    if ( aid == NULL ) {
	return( EADDRNOTAVAIL );
    }

    bcopy( &aid->aid_sat, sat, sizeof( struct sockaddr_at ));
    return( 0 );
}

    int
if_addmulti( queue_t *q, mblk_t *m, char *name, struct sockaddr *sa )
{
    struct atif_data	*aid;

    for ( aid = interfaces; aid != NULL; aid = aid->aid_next ) {
	if ( strcmp( name, aid->aid_name ) == 0 ) {
	    break;
	}
    }
    if ( aid == NULL ) {
	return( EADDRNOTAVAIL );
    }

    if ( aid->aid_c.c_type != 0 ) {
	cmn_err( CE_NOTE, "if_addmulti context %x\n", aid->aid_c.c_type );
	return( EINVAL );
    }

    aid->aid_c.c_type = SIOCADDMULTI;
    aid->aid_c.c_u.u_multi.um_q = q;
    aid->aid_c.c_u.u_multi.um_m = m;
    dl_enabmulti_req( WR( aid->aid_q ), sa->sa_data );

    return( 0 );
}

    void
if_pickaddr( void *ptr )
{
    struct atif_data *aid = (struct atif_data*) ptr;

    if ( aid->aid_c.c_type != SIOCSIFADDR ) {
	cmn_err( CE_NOTE, "if_pickaddr context %x\n", aid->aid_c.c_type );
	return;
    }

    if ( aid->aid_flags & AIDF_PROBEFAILED ) {
	aid->aid_flags &= ~AIDF_PROBEFAILED;
	/* choose new address */
	for (;;) {
	    if ( aid->aid_c.c_u.u_addr.ua_nodecnt == 0 ) {
		/* if we've exausted all addresses, fail */
		if ( aid->aid_c.c_u.u_addr.ua_netcnt == 0 ) {
		    ioc_error_ack( aid->aid_c.c_u.u_addr.ua_q,
			    aid->aid_c.c_u.u_addr.ua_m, EADDRINUSE );
		    aid->aid_c.c_type = 0;
		    aid->aid_c.c_u.u_addr.ua_q = NULL;
		    aid->aid_c.c_u.u_addr.ua_m = NULL;
		    aid->aid_c.c_u.u_addr.ua_probecnt = 0;
		    aid->aid_c.c_u.u_addr.ua_netcnt = 0;
		    aid->aid_c.c_u.u_addr.ua_nodecnt = 0;
		} else {
		    aid->aid_c.c_u.u_addr.ua_nodecnt = 256;
		    aid->aid_c.c_u.u_addr.ua_netcnt--;
		    if ( ntohs(aid->aid_sat.sat_addr.s_net) >
			    ntohs(aid->aid_nr.nr_lastnet) ) {
			aid->aid_sat.sat_addr.s_net = aid->aid_nr.nr_firstnet;
		    } else
		      aid->aid_sat.sat_addr.s_net = 
			htons(ntohs(aid->aid_sat.sat_addr.s_net) + 1);
		}
	    } else {
		aid->aid_sat.sat_addr.s_node++;
		aid->aid_c.c_u.u_addr.ua_nodecnt--;
		if ( aid->aid_sat.sat_addr.s_node == 0 || 
			aid->aid_sat.sat_addr.s_node == 255 || 
			aid->aid_sat.sat_addr.s_node == 254 ) {
		    continue;
		}
		break;
	    }
	}
    }
    if ( aid->aid_c.c_u.u_addr.ua_probecnt-- <= 0 ) {
	aid->aid_flags &= ~AIDF_PROBING;
	/* worked, send ioctl reponse */
	ioc_ok_ack( aid->aid_c.c_u.u_addr.ua_q, aid->aid_c.c_u.u_addr.ua_m, 0 );
	aid->aid_c.c_type = 0;
	aid->aid_c.c_u.u_addr.ua_q = NULL;
	aid->aid_c.c_u.u_addr.ua_m = NULL;
	aid->aid_c.c_u.u_addr.ua_probecnt = 0;
	aid->aid_c.c_u.u_addr.ua_netcnt = 0;
	aid->aid_c.c_u.u_addr.ua_nodecnt = 0;
	return;
    }

    aarp_send( aid, AARPOP_PROBE, NULL,
	    aid->aid_sat.sat_addr.s_net, aid->aid_sat.sat_addr.s_node );
    qtimeout( aid->aid_q, if_pickaddr, (caddr_t)aid, hz / 5 );
}

    int
if_setaddr( queue_t *q, mblk_t *m, char *name, struct sockaddr_at *sat )
{
    struct atif_data	*aid;
    struct netrange	nr;
    ulong		time;

    for ( aid = interfaces; aid != NULL; aid = aid->aid_next ) {
	if ( strcmp( name, aid->aid_name ) == 0 ) {
	    break;
	}
    }
    if ( aid == NULL ) {
	return( EADDRNOTAVAIL );
    }

    if ( aid->aid_c.c_type != 0 ) {
	cmn_err( CE_NOTE, "if_setaddr context %x\n", aid->aid_c.c_type );
	return( EINVAL );
    }

    bcopy( sat->sat_zero, &nr, sizeof( struct netrange ));

    if ( aid->aid_flags & AIDF_LOOPBACK ) {
	aid->aid_sat = *sat;
	aid->aid_nr = nr;

	/* routes? */

	ioc_ok_ack( q, m, 0 );
	return( 0 );
    }

    drv_getparm( TIME, &time );
    if ( sat->sat_addr.s_net == ATADDR_ANYNET ) {
	if ( nr.nr_lastnet == nr.nr_firstnet ) {
	    sat->sat_addr.s_net = nr.nr_firstnet;
	} else {
	    sat->sat_addr.s_net = htons(ntohs(nr.nr_firstnet) + time %
		    (ntohs(nr.nr_lastnet) - ntohs(nr.nr_firstnet)));
	}
    } else {
	if ( ntohs( sat->sat_addr.s_net ) < ntohs( nr.nr_firstnet ) ||
		ntohs( sat->sat_addr.s_net ) > ntohs( nr.nr_lastnet )) {
	    return( EINVAL );
	}
    }

    if ( sat->sat_addr.s_node == ATADDR_ANYNODE ) {
	sat->sat_addr.s_node = time;
    }

    aid->aid_flags |= AIDF_PROBING;
    aid->aid_sat = *sat;
    aid->aid_nr = nr;

    aid->aid_c.c_type = SIOCSIFADDR;
    aid->aid_c.c_u.u_addr.ua_q = q;
    aid->aid_c.c_u.u_addr.ua_m = m;
    aid->aid_c.c_u.u_addr.ua_probecnt = 10;
    aid->aid_c.c_u.u_addr.ua_netcnt = ntohs(nr.nr_lastnet) - 
      ntohs(nr.nr_firstnet);
    aid->aid_c.c_u.u_addr.ua_nodecnt = 256;
    qtimeout( aid->aid_q, if_pickaddr, (caddr_t)aid, 0 );
    return( 0 );
}

/*
 * These three routines are all a big mess...
 */
    struct atif_data *
if_dest( struct atif_data *aid, struct sockaddr_at *sat )
{
    struct atif_data	*dest;

    for ( dest = interfaces; dest != NULL; dest = dest->aid_next ) {
	if ((( sat->sat_addr.s_net == 0 && aid == dest ) ||
		( ntohs(sat->sat_addr.s_net) >= 
		  ntohs(dest->aid_nr.nr_firstnet) &&
		ntohs(sat->sat_addr.s_net) <= 
		  ntohs(dest->aid_nr.nr_lastnet) )) &&
		( sat->sat_addr.s_node == dest->aid_sat.sat_addr.s_node ||
		sat->sat_addr.s_node == ATADDR_BCAST )) {
	    break;
	}
    }
    if ( dest == NULL ) {
	return( NULL );
    }
    return( dest );
}

    struct atif_data *
if_withaddr( struct sockaddr_at *sat )
{
    struct atif_data	*dest;

    for ( dest = interfaces; dest != NULL; dest = dest->aid_next ) {
	if ( sat->sat_addr.s_net == dest->aid_sat.sat_addr.s_net &&
		sat->sat_addr.s_node == dest->aid_sat.sat_addr.s_node ) {
	    break;
	}
    }
    return( dest );
}

    struct atif_data *
if_withnet( struct sockaddr_at *sat )
{
    struct atif_data	*dest;

    for ( dest = interfaces; dest != NULL; dest = dest->aid_next ) {
	if ( ntohs(sat->sat_addr.s_net) >= ntohs(dest->aid_nr.nr_firstnet) &&
		ntohs(sat->sat_addr.s_net) <= ntohs(dest->aid_nr.nr_lastnet)) {
	    break;
	}
    }
    return( dest );
}

    int
if_route( struct atif_data *aid, mblk_t *m, struct sockaddr_at *sat )
{
    struct sockaddr_at	gate;

    if ( sat->sat_addr.s_net == 0 ) {
	if ( sat->sat_addr.s_node == 0 ) {
	    aid = if_withaddr( sat );
	}
	if ( aid == NULL ) {
	    freemsg( m );
	    return( ENETUNREACH );
	}
	gate = *sat;
    } else {
	if ( rt_gate( sat, &gate ) < 0 ) {	/* no route */
	    gate = *sat;
	}
	if (( aid = if_withaddr( &gate )) == NULL ) {
	    if (( aid = if_withnet( &gate )) == NULL ) {
		freemsg( m );
		return( ENETUNREACH );
	    }
	}
    }

    if ( aid->aid_flags & AIDF_LOOPBACK ) {
	return( ddp_rput( aid, m ));
    } else {
	/* the aarp layer is expected to send broadcast packets appropriately */
	return( aarp_resolve( aid, m, &gate ));
    }
}
