/*
 *  Public Release 3
 *
 *  $Id: krt_ifread_ioctl.c,v 1.9 1999/02/15 17:25:48 chopps Exp $
 */

/*
 * ------------------------------------------------------------------------
 *
 * Copyright (c) 1996 The Regents of the University of Michigan
 * All Rights Reserved
 *
 * License to use, copy, modify, and distribute this software and its
 * documentation can be obtained from Merit at the University of Michigan.
 *
 *      Merit GateDaemon Project
 *      4251 Plymouth Road, Suite C
 *      Ann Arbor, MI 48105
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE REGENTS OF THE
 * UNIVERSITY OF MICHIGAN AND MERIT DO NOT WARRANT THAT THE
 * FUNCTIONS CONTAINED IN THE SOFTWARE WILL MEET LICENSEE'S REQUIREMENTS OR
 * THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. The Regents of the
 * University of Michigan and Merit shall not be liable for
 * any special, indirect, incidental or consequential damages with respect
 * to any claim by Licensee or any third party arising from use of the
 * software. GateDaemon was originated and developed through release 3.0
 * by Cornell University and its collaborators.
 *
 * Please forward bug fixes, enhancements and questions to the
 * gated mailing list: gated-people@gated.merit.edu.
 *
*/

#define INCLUDE_IOCTL
#define INCLUDE_IF

#ifdef  PROTO_INET
#include "inet.h"
#endif  /* PROTO_INET */

#include "include.h"
#include "krt.h"
#include "krt_var.h"


/* per Stevens vol 1, loopback mtu is 1536 */
#define LOOPBACK_MTU  1536
#define POINTOPOINT_MTU 256
#define ETHER_MTU     1500
#define DEFAULT_MTU    256

#ifndef __P
#define __P(x) x
#endif

/*
 * Prototypes
 */
static int	addr_size __P((struct ifreq *));
static void	get_interface_address_info __P((task *, struct sockaddr *,
		    if_info *, if_link *, char *, int));
static void	get_interface_parameter_info __P((task *, if_info *, char *,
		    int));
static int	read_interface_list __P((task *, struct ifconf *, int));



/*
 * get the length of the socket
 */
static int
addr_size(ifrp)
	struct ifreq *ifrp;
{

#ifdef HAVE_SOCKADDR_SA_LEN
	return (max(sizeof(struct sockaddr), ifrp->ifr_addr.sa_len));
#else	/* !HAVE_SOCKADDR_SA_LEN */
	switch (ifrp->ifr_addr.sa_family) {
#ifdef IPV6
	case AF_INET6:
		return (sizeof(struct sockaddr_in6));
		break;
#endif	/* IPV6 */
	case AF_INET:
	default:
		return (sizeof(struct sockaddr));
		break;
	}
#endif	/* !HAVE_SOCKADDR_SA_LEN */
}

/*
 * Read the interfaces installed on the system using kernel ioctl call.
 */
static int
read_interface_list(task_ptr, ifcp, sockfd)
	task *task_ptr;
	struct ifconf *ifcp;
	int sockfd;
{
	int multiplier, lastlen, errcode;

	lastlen = multiplier = 0;

	/*
	 * let's read the interface info from the kernel using ioctl with
	 * SIOCGIFCONF request. This is a loop because not all implementations
	 * of ioctl will return an error if we don't allocate a large enough
	 * buffer. The trick around this is do the ioctl and save the length
	 * returned, then do the ioctl again with a larger buffer. If the
	 * lengths are the same we have all the data else we increase the
	 * size of the buffer and try again.
	 */
	for (;;) {
		/*
		 * allocate a task buffer and put buffer pointer
		 * and length in ioctl structure.
		 */
		task_alloc_send(task_ptr, task_pagesize << multiplier);
		ifcp->ifc_len = task_send_buffer_len;
		ifcp->ifc_buf = task_send_buffer;

#if defined(SUNOS5_0) || defined(HPSTREAMS)
		NON_INTR(errcode, ioctl(sockfd, SIOCGIFCONF, ifcp));
#else
		errcode = task_ioctl(sockfd, (u_long) SIOCGIFCONF, ifcp,
		    ifcp->ifc_len); 
#endif
		if (errcode < 0) {
			if (errno != EINVAL || lastlen != 0) {
				/*
				 * we got problems, cannot do successful
				 * ioctl call
				 */
				return (0);
			}
		} else if (ifcp->ifc_len == lastlen) {
			/*
			 * length is same so last time so we got
			 * it all, break out of loop
			 */
			return (1);
		}

		/*
		 * either first time or we got a different
		 * length, or buffer must not have been
		 * big enough let's try it again with a
		 * larger buffer
		 */
		lastlen = ifcp->ifc_len;
		multiplier++;
	}
}



/*
 * do ioctls to get address specific info, such as dest address,
 * broadcast address,and netmask.
 */ 
static void
get_interface_address_info(task_ptr, addr, ifip, iflp, name, sockfd)
	task *task_ptr;
	struct sockaddr *addr;
	struct _if_info *ifip;
	if_link *iflp;
	char *name;
	int sockfd;
{
	struct ifreq ifr;
	sockaddr_un *sap;

	/* copy default info into structure */
	ifip->ifi_link = iflp;

	/* ignore interfaces from undesired families */
	switch ( addr->sa_family)  {
#ifdef PROTO_INET
	case AF_INET:

		ifip->ifi_addr = sockdup(sock2gated(addr, unix_socksize(addr)));
		ifip->ifi_addr_local = ifip->ifi_addr;
		ifip->ifi_addr_broadcast = 0;
		ifip->ifi_netmask = 0;

		/* copy the interface name into the ioctl buffer */
		strcpy(ifr.ifr_name, name);

#ifdef SIOCGIFDSTADDR
		/*
		 * if we are p2p, let's set the mtu and get the address
		 * of the other side
		 */
		if (BIT_TEST(ifip->ifi_state, IFS_POINTOPOINT)) {
			if (ifip->ifi_mtu == DEFAULT_MTU)
				ifip->ifi_mtu = POINTOPOINT_MTU;

			if (task_ioctl(sockfd, SIOCGIFDSTADDR, &ifr,
			    sizeof(ifr)) < 0) {
				trace_log_tp(task_ptr, 0, LOG_ERR,
				    ("krt_ifread: %s: ioctl SIOCGIFDSTADDR: %m",
				    ifr.ifr_name));
			} else {
				sap = sock2gated(&ifr.ifr_dstaddr, 
				    unix_socksize(&ifr.ifr_dstaddr));
				if (sap) 
					ifip->ifi_addr = sockdup(sap);
				else {
					trace_log_tp(task_ptr, 0, LOG_ERR,
					    ("krt_ifread: no destination "
					    "address for %A (%s)",
					    ifip->ifi_addr_local,
					    ifr.ifr_name));
				}
			}	
		}
#endif /* SIOCGIFDSTADDR */
#ifdef SIOCGIFBRDADDR
		/*
		 * if we are a broadcast medium, set the mtu and get
		 * the broadcast address
		 */
		if (BIT_TEST(ifip->ifi_state, IFS_BROADCAST)) {
			if( ifip->ifi_mtu == DEFAULT_MTU)
				ifip->ifi_mtu = ETHER_MTU;

			if (task_ioctl(sockfd, SIOCGIFBRDADDR, &ifr,
			    sizeof(ifr)) < 0) {
				trace_log_tp(task_ptr, 0, LOG_ERR,
				    ("krt_ifread: %s: ioctl SIOCGIFBRDADDR: %m",
				    ifr.ifr_name));
			} else {
				sap = sock2gated(&ifr.ifr_broadaddr,
				    unix_socksize(&ifr.ifr_broadaddr));
				if (sap)
					ifip->ifi_addr_broadcast = sockdup(sap);
				else {
					trace_log_tp(task_ptr, 0, LOG_ERR, 
					    ("krt_ifread: no broadcast address"
					    " for %A (%s)",
					    ifip->ifi_addr_local,
					    ifr.ifr_name));
				}
			}
		}
#endif	/* SIOCGIFBRDADDR */
#ifdef	SIOCGIFNETMASK
		/*
		 * get the netmask address for the interface
		 */
		if (task_ioctl(sockfd, SIOCGIFNETMASK, &ifr, sizeof(ifr)) < 0) {
			trace_log_tp(task_ptr, 0, LOG_ERR,
			    ("krt_ifread: %s: ioctl SIOCGIFNETMASK: %m",
			    ifr.ifr_name));
		} else  {
			/* build a netmask from kernel info */
			sap = sock2gated(&ifr.ifr_addr,
			    unix_socksize(&ifr.ifr_addr));
			if (sap)
				ifip->ifi_netmask = mask_locate(sap);
			else {
				trace_log_tp(task_ptr, 0, LOG_ERR,
				    ("krt_ifread: no network mask for %A (%s)",
				    ifip->ifi_addr_local,
				    ifr.ifr_name));
			}
		}
#endif	/* SIOCGIFNETMASK */
#endif	/* PROTO_INET */
	}

}



/*
 * Let's do some ioctls to get the interface flags, mtu, and metrics.
 */
static void
get_interface_parameter_info(task_ptr, ifip, name, sockfd)
	task *task_ptr;
	if_info *ifip;
	char *name;
	int sockfd;
{
	struct ifreq ifr;

	/* copy interface name to ioctl structure */
	strcpy(ifr.ifr_name, name);

	/*
	 * get the interface flags
	 */
#ifdef SIOCGIFFLAGS
	if (task_ioctl(sockfd, SIOCGIFFLAGS, &ifr, sizeof (ifr)) < 0) {
		trace_log_tp(task_ptr, 0, LOG_ERR,
		    ("krt_ifread: %s: ioctl SIOCGIFFLAGS: %m", ifr.ifr_name));
		ifip->ifi_state = 0;
	} else
		ifip->ifi_state = krt_if_flags(ifr.ifr_flags);
#else	/* !SIOCGIFFLAGS */
	ifip->ifi_state = 0;
#endif	/* !SIOCGIFFLAGS */

	/*
	 * get the interface MTU
	 */
#ifdef SIOCGIFMTU
	bzero((caddr_t)&ifr.ifr_ifru, sizeof(ifr.ifr_ifru));
	if (task_ioctl(sockfd, SIOCGIFMTU, &ifr, sizeof(ifr)) < 0) {
		trace_log_tp(task_ptr, 0, LOG_ERR, ("krt_ifread: %s:"
		    " ioctl SIOCGIFMTU: %m, Gated using default mtu",
		    ifr.ifr_name));
		ifip->ifi_mtu = DEFAULT_MTU;
	} else 
		ifip->ifi_mtu = ifr.KRT_IFR_MTU;
#else	/* !SIOCGIFMTU */
	ifip->ifi_mtu = DEFAULT_MTU;
#endif	/* !SIOCGIFMTU */

	/*
	 * get the interface metrics
	 */
#ifdef  SIOCGIFMETRIC     	    
	bzero ((caddr_t) &ifr.ifr_ifru, sizeof (ifr.ifr_ifru));
	if (task_ioctl(sockfd, SIOCGIFMETRIC, &ifr, sizeof(ifr)) < 0) {
 		trace_log_tp(task_ptr, 0, LOG_ERR,
		    ("krt_ifread: %s: ioctl SIOCGIFMETRIC: %m",
		    ifr.ifr_name));
		ifip->ifi_metric = 0;
 	} else
		ifip->ifi_metric = ifr.ifr_metric;
#else	/* !SIOCGIFMETRIC */
	ifip->ifi_metric = 0;
#endif	/* !SIOCGIFMETRIC */

}

int
krt_ifread __PF1(save_task_state, flag_t)
{
	static int sockfd = -1;

	struct ifconf ifc;
	if_info ifi;
	int count, slen, test_bit_set;
	if_link *piflp, *iflp;
	struct ifreq *ifrp;
	task *task_ptr;
	char *ptr;

	task_ptr = krt_task;
	piflp = 0;
	count = test_bit_set = 0;

	/*
	 * grab a socket for use with ioctl calls.
	 * Note task_get_socket checks for test mode so have to reset bit.
	 */
	if (sockfd == -1) {
		if (BIT_TEST(task_state, TASKS_TEST)) {
			test_bit_set = 1;
			BIT_RESET(task_state, TASKS_TEST);	
		}
		sockfd = task_floating_socket(task_ptr,
		    task_get_socket(task_ptr, AF_INET, SOCK_DGRAM, 0),
		    "krt_ifread_task");
		if (test_bit_set)
			BIT_SET(task_state, TASKS_TEST);
	}

	if (krt_task->task_socket < 0) {
		return (EBADF);
    	}

	/*
	 * read the interfaces from the kernel
	 */
	if (!read_interface_list(task_ptr, &ifc, sockfd)) {
		/* we got problems, cannot do successful ioctl call */
		trace_log_tp(task_ptr, 0, LOG_ERR,
		    ("krt_ifread: ioctl SIOCGIFCONF"));
		return (errno);
	}

	/* write our status to the log */
	trace_tp(task_ptr, TR_KRT_IFLIST, TRC_NL_BEFORE,
	    ("krt_iflist: SIOCGIFCONF returns %u bytes", ifc.ifc_len));

	/* set interface lists to known state, IFC_NOCHANGE */
	if_conf_open(task_ptr, TRUE);

	/* loop through all the data */
	for (ptr = ifc.ifc_buf; ptr < ifc.ifc_buf + ifc.ifc_len;) { 

		/* zero tmp data structure for storing values from kernel */
		bzero( &ifi, sizeof (ifi));

		/* get pointer to next interface */
		ifrp = (struct ifreq *) ptr;

		/* keep track of how many interfaces we have */
		count++;

		/* read interface specific info from kernel */
		get_interface_parameter_info(task_ptr, &ifi, ifrp->ifr_name,
		    sockfd);

		/*
		 * Have to have a physical interface to link to
		 * If no previous or previous name is different
		 *
		 * XXX the third check seems iffy, what if there is a new
		 * XXX different name of the same length as the
		 * XXX previous with a ':' in it
		 */
		if (!piflp
		    || (strncmp(piflp->ifl_name, ifrp->ifr_name, IFNAMSIZ)
		    && strchr(ifrp->ifr_name, ':') == NULL)) {
			/*
			 * either no physical interface or the name doesn't
			 * match, and it's not an alias
			 */
			slen = strlen(ifrp->ifr_name);
			iflp = ifl_locate_name(ifrp->ifr_name, slen);
			piflp = ifl_addup(task_ptr, iflp,
			    count, ifi.ifi_state, ifi.ifi_metric, ifi.ifi_mtu,
			    ifrp->ifr_name, slen, krt_lladdr(ifrp)); 

			if (BIT_TEST(ifi.ifi_state, IFS_LOOPBACK)) {
				/* set the loopback flag and mtu */
				BIT_SET(piflp->ifl_state, IFS_LOOPBACK);
				ifi.ifi_mtu = LOOPBACK_MTU;
			 }

		}

		/* read address information from kernel. */
		get_interface_address_info(task_ptr, &ifrp->ifr_addr, 
		    &ifi, piflp, ifrp->ifr_name, sockfd);


		/* Add the logical interface structure to the ifap list */
		if_conf_addaddr(task_ptr, &ifi);	

		/* all done with that address let's do it again */
		ptr += sizeof(ifrp->ifr_name) + addr_size(ifrp);
	}

	if_conf_close(task_ptr, FALSE);

	return(1);
}
