/*
 * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 *
 * This file is not normally used.
 * It can be conditionally compiled in by defining RUN_ON_PUMA_WITHOUT_IFADDRS
 * in CFSocket.c. It is included mainly as sample code for people building
 * for other platforms that (like Puma) lack the getifaddrs() call.
 * NOTE: YOU CANNOT use this code to build an mDNSResponder daemon for Puma
 * that works just like the Jaguar one, because Puma lacks other necessary
 * functionality (like the LibInfo support to receive MIG messages from clients).

    Change History (most recent first):

$Log: CFSocketPuma.c,v $
Revision 1.4  2003/08/12 19:56:25  cheshire
Update to APSL 2.0

Revision 1.3  2003/07/02 21:19:51  cheshire
<rdar://problem/3313413> Update copyright notices, etc., in source code comments

Revision 1.2  2002/09/21 20:44:51  zarzycki
Added APSL info

Revision 1.1  2002/09/17 01:36:23  cheshire
Move Puma support to CFSocketPuma.c

 */

#include <sys/ioctl.h>
#include <sys/sockio.h>
#define ifaddrs ifa_info
#ifndef	ifa_broadaddr
#define	ifa_broadaddr	ifa_dstaddr	/* broadcast address interface */
#endif
#include <sys/cdefs.h>

/* Our own header for the programs that need interface configuration info.
   Include this file, instead of "unp.h". */

#define	IFA_NAME	16			/* same as IFNAMSIZ in <net/if.h> */
#define	IFA_HADDR	 8			/* allow for 64-bit EUI-64 in future */

struct ifa_info {
  char    ifa_name[IFA_NAME];	/* interface name, null terminated */
  u_char  ifa_haddr[IFA_HADDR];	/* hardware address */
  u_short ifa_hlen;				/* #bytes in hardware address: 0, 6, 8 */
  short   ifa_flags;			/* IFF_xxx constants from <net/if.h> */
  short   ifa_myflags;			/* our own IFI_xxx flags */
  struct sockaddr  *ifa_addr;	/* primary address */
  struct sockaddr  *ifa_brdaddr;/* broadcast address */
  struct sockaddr  *ifa_dstaddr;/* destination address */
  struct ifa_info  *ifa_next;	/* next of these structures */
};

#define	IFI_ALIAS	1			/* ifa_addr is an alias */

					/* function prototypes */
struct ifa_info	*get_ifa_info(int, int);
struct ifa_info	*Get_ifa_info(int, int);
void			 free_ifa_info(struct ifa_info *);

#define HAVE_SOCKADDR_SA_LEN	1

struct ifa_info *
get_ifa_info(int family, int doaliases)
{
	struct ifa_info		*ifi, *ifihead, **ifipnext;
	int					sockfd, len, lastlen, flags, myflags;
	char				*ptr, *buf, lastname[IFNAMSIZ], *cptr;
	struct ifconf		ifc;
	struct ifreq		*ifr, ifrcopy;
	struct sockaddr_in	*sinptr;

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);

	lastlen = 0;
	len = 100 * sizeof(struct ifreq);	/* initial buffer size guess */
	for ( ; ; ) {
		buf = (char *) malloc(len);
		ifc.ifc_len = len;
		ifc.ifc_buf = buf;
		if (ioctl(sockfd, SIOCGIFCONF, &ifc) < 0) {
			if (errno != EINVAL || lastlen != 0)
				debugf("ioctl error");
		} else {
			if (ifc.ifc_len == lastlen)
				break;		/* success, len has not changed */
			lastlen = ifc.ifc_len;
		}
		len += 10 * sizeof(struct ifreq);	/* increment */
		free(buf);
	}
	ifihead = NULL;
	ifipnext = &ifihead;
	lastname[0] = 0;
/* end get_ifa_info1 */

/* include get_ifa_info2 */
	for (ptr = buf; ptr < buf + ifc.ifc_len; ) {
		ifr = (struct ifreq *) ptr;

#ifdef	HAVE_SOCKADDR_SA_LEN
		len = MAX(sizeof(struct sockaddr), ifr->ifr_addr.sa_len);
#else
		switch (ifr->ifr_addr.sa_family) {
#ifdef	IPV6
		case AF_INET6:	
			len = sizeof(struct sockaddr_in6);
			break;
#endif
		case AF_INET:	
		default:	
			len = sizeof(struct sockaddr);
			break;
		}
#endif	/* HAVE_SOCKADDR_SA_LEN */
		ptr += sizeof(ifr->ifr_name) + len;	/* for next one in buffer */

		if (ifr->ifr_addr.sa_family != family)
			continue;	/* ignore if not desired address family */

		myflags = 0;
		if ( (cptr = strchr(ifr->ifr_name, ':')) != NULL)
			*cptr = 0;		/* replace colon will null */
		if (strncmp(lastname, ifr->ifr_name, IFNAMSIZ) == 0) {
			if (doaliases == 0)
				continue;	/* already processed this interface */
			myflags = IFI_ALIAS;
		}
		memcpy(lastname, ifr->ifr_name, IFNAMSIZ);

		ifrcopy = *ifr;
		ioctl(sockfd, SIOCGIFFLAGS, &ifrcopy);
		flags = ifrcopy.ifr_flags;
		if ((flags & IFF_UP) == 0)
			continue;	/* ignore if interface not up */

		ifi = (struct ifa_info *) calloc(1, sizeof(struct ifa_info));
		*ifipnext = ifi;			/* prev points to this new one */
		ifipnext = &ifi->ifa_next;	/* pointer to next one goes here */

		ifi->ifa_flags = flags;		/* IFF_xxx values */
		ifi->ifa_myflags = myflags;	/* IFI_xxx values */
		memcpy(ifi->ifa_name, ifr->ifr_name, IFA_NAME);
		ifi->ifa_name[IFA_NAME-1] = '\0';
/* end get_ifa_info2 */
/* include get_ifa_info3 */
		switch (ifr->ifr_addr.sa_family) {
		case AF_INET:
			sinptr = (struct sockaddr_in *) &ifr->ifr_addr;
			if (ifi->ifa_addr == NULL) {
				ifi->ifa_addr = (struct sockaddr *) calloc(1, sizeof(struct sockaddr_in));
				memcpy(ifi->ifa_addr, sinptr, sizeof(struct sockaddr_in));

#ifdef	SIOCGIFBRDADDR
				if (flags & IFF_BROADCAST) {
					ioctl(sockfd, SIOCGIFBRDADDR, &ifrcopy);
					sinptr = (struct sockaddr_in *) &ifrcopy.ifr_broadaddr;
					ifi->ifa_brdaddr = (struct sockaddr *) calloc(1, sizeof(struct sockaddr_in));
					memcpy(ifi->ifa_brdaddr, sinptr, sizeof(struct sockaddr_in));
				}
#endif

#ifdef	SIOCGIFDSTADDR
				if (flags & IFF_POINTOPOINT) {
					ioctl(sockfd, SIOCGIFDSTADDR, &ifrcopy);
					sinptr = (struct sockaddr_in *) &ifrcopy.ifr_dstaddr;
					ifi->ifa_dstaddr = (struct sockaddr *) calloc(1, sizeof(struct sockaddr_in));
					memcpy(ifi->ifa_dstaddr, sinptr, sizeof(struct sockaddr_in));
				}
#endif
			}
			break;

		default:
			break;
		}
	}
	free(buf);
	return(ifihead);	/* pointer to first structure in linked list */
}
/* end get_ifa_info3 */

/* include free_ifa_info */
mDNSlocal void freeifaddrs(struct ifa_info *ifihead)
{
	struct ifa_info	*ifi, *ifinext;

	for (ifi = ifihead; ifi != NULL; ifi = ifinext) {
		if (ifi->ifa_addr != NULL)
			free(ifi->ifa_addr);
		if (ifi->ifa_brdaddr != NULL)
			free(ifi->ifa_brdaddr);
		if (ifi->ifa_dstaddr != NULL)
			free(ifi->ifa_dstaddr);
		ifinext = ifi->ifa_next;	/* can't fetch ifa_next after free() */
		free(ifi);					/* the ifa_info{} itself */
	}
}
/* end free_ifa_info */

struct ifa_info *
Get_ifa_info(int family, int doaliases)
{
	struct ifa_info	*ifi;

	if ( (ifi = get_ifa_info(family, doaliases)) == NULL)
		debugf("get_ifa_info error");
	return(ifi);
}

mDNSlocal int getifaddrs(struct ifa_info **ifalist)
	{
	*ifalist = get_ifa_info(PF_INET, false);
	if( ifalist == nil )
		return -1;
	else
		return(0);
	}
