/*
 *  Public Release 3
 *
 *  $Id: krt_ifread_ioctl.c,v 1.5 1998/09/09 21:05:20 swright 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


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

        /*	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.
        */ 
	while(1) {

		/* allocate a task buffer and put buffer pointer and length in ioctl structure. */
                task_alloc_send(task_ptr, task_pagesize << multiplier);
                ifc_buffer->ifc_len = task_send_buffer_len;
                ifc_buffer->ifc_buf =  task_send_buffer;

#if defined(SUNOS5_0) || defined(HPSTREAMS)
		NON_INTR(errcode, ioctl(sockfd, SIOCGIFCONF, ifc_buffer));
#else
                errcode = task_ioctl(sockfd, (u_long) SIOCGIFCONF,
                                (void_t) ifc_buffer, ifc_buffer->ifc_len); 
#endif

		if ( errcode < 0 ) {
			if ( errno != EINVAL || lastlen !=0)
				/* we got problems, cannot do successful ioctl call */
				return(0);
                }
                else {
                        if(ifc_buffer->ifc_len == lastlen )
                                /* length is same so last time so we got it all, break out of loop */
                                return(1);
                        else
                                /* 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 = ifc_buffer->ifc_len;
                }
                multiplier++;
        }
}



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

	/* copy default info into structure */
	if_info_ptr->ifi_link = if_link_ptr;

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

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

			/* copy the interface name into the ioctl buffer */
			strcpy(ifr_buffer.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(if_info_ptr->ifi_state, IFS_POINTOPOINT)) {

				if ( if_info_ptr->ifi_mtu == DEFAULT_MTU )
					if_info_ptr->ifi_mtu = POINTOPOINT_MTU;
				if (task_ioctl(sockfd, SIOCGIFDSTADDR, 
						(caddr_t) &ifr_buffer, sizeof (ifr_buffer)) < 0) {
					trace_log_tp(task_ptr, 0, LOG_ERR,
							("krt_ifread: %s: ioctl SIOCGIFDSTADDR: %m",
							ifr_buffer.ifr_name));
				}
				else {
					if ( (sockaddr_ptr = sock2gated(&ifr_buffer.ifr_dstaddr, 
							unix_socksize(&ifr_buffer.ifr_dstaddr))))	
						if_info_ptr->ifi_addr = sockaddr_ptr;
					 else
                                                trace_log_tp(task_ptr, 0, LOG_ERR,
                                                        ("krt_ifread: no destination address for %A (%s)",
                                                        if_info_ptr->ifi_addr_local, ifr_buffer.ifr_name));

				}	
			}
#endif


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


#ifdef	SIOCGIFNETMASK
			/* get the netmask address for the interface */
			if (task_ioctl(sockfd, SIOCGIFNETMASK, 
					(caddr_t) &ifr_buffer, sizeof (ifr_buffer)) < 0) {
				trace_log_tp(task_ptr, 0, LOG_ERR,
						("krt_ifread: %s: ioctl SIOCGIFNETMASK: %m",
						ifr_buffer.ifr_name));
			}
			else  {
				/* build a netmask from kernel info */
				if( (sockaddr_ptr = sock2gated(&ifr_buffer.ifr_addr,
                                                          unix_socksize(&ifr_buffer.ifr_addr))))
					if_info_ptr->ifi_netmask = mask_locate(sockaddr_ptr);
				else
                                        trace_log_tp(task_ptr, 0, LOG_ERR,
                                                    ("krt_ifread: no network mask for %A (%s)",
                                                     if_info_ptr->ifi_addr_local, ifr_buffer.ifr_name));

			}
#endif
	}

}



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

	/* strip out : from name */
	if ( (cptr = strchr(name, ':')) != NULL )
		*cptr = 0;

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

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


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

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

}



int addr_size __PF1(ifr_buffer, struct ifreq *)  {

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



int krt_ifread __PF1(save_task_state, flag_t)
{
	struct ifconf ifc_buffer;
	struct ifreq  *ifr_buffer_ptr;
	struct _if_info if_info_buffer;
	if_link *if_plink = (if_link *) 0, *ifl_ptr;
	task *task_ptr = krt_task;
	char *ptr;
	int interface_count = 0, slen, test_bit_set = 0;
        static int sockfd = -1;
	int junk;

	/* 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_buffer, sockfd)  )  {
	    /* we got problems, cannot do successful ioctl call */
            trace_log_tp(task_ptr, 0, LOG_ERR, ("krt_ifread: ioctl SIOCGIFCONF: %m"));
            return errno;
	}
	else {		/* we got data */

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

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

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

			/* zero out temporary data structure for storing values from kernel */
			bzero( &if_info_buffer, sizeof (if_info_buffer));

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

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

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

			/* Have to have a physical interface to link to */
			if ( !(if_plink) || !(strncmp(if_plink->ifl_name, ifr_buffer_ptr->ifr_name, IFNAMSIZ) == 0 ) ) { 
				/* either no physical interface or the name doesn't match, let's add a new one */
				slen = strlen(ifr_buffer_ptr->ifr_name);
				ifl_ptr = ifl_locate_name(ifr_buffer_ptr->ifr_name, slen);

				if_plink = ifl_addup(task_ptr, ifl_ptr,
						interface_count, if_info_buffer.ifi_state, 
						if_info_buffer.ifi_metric, 
						if_info_buffer.ifi_mtu, ifr_buffer_ptr->ifr_name,
						slen, krt_lladdr(ifr_buffer_ptr)); 
	                        
				if (BIT_TEST(if_info_buffer.ifi_state, IFS_LOOPBACK)) {
       		                         /* Set the loopback flag and mtu for this physical interface */
               		                 BIT_SET(if_plink->ifl_state, IFS_LOOPBACK);
					 if_info_buffer.ifi_mtu = LOOPBACK_MTU;
                       		 }

			}

			/* read address information from kernel. */
			get_interface_address_info(task_ptr, &ifr_buffer_ptr->ifr_addr, 
					&if_info_buffer, if_plink, ifr_buffer_ptr->ifr_name, sockfd);


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

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

		}
				
	}

	if_conf_close(task_ptr, FALSE);

	return(1);
}
