/* getifname.c -- network interface handling
 * Copyright(C) 1999,2000,2001 Salvatore Sanfilippo <antirez@invece.org>
 * Copyright(C) 2001 by Nicolas Jombart <Nicolas.Jombart@hsc.fr>
 * This code is under the GPL license */

/* BSD support thanks to Nicolas Jombart <Nicolas.Jombart@hsc.fr> */

#include <stdio.h>		/* perror */
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>		/* struct sockaddr_in */
#include <arpa/inet.h>		/* inet_ntoa */
#include <net/if.h>
#include <unistd.h>		/* close */

#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
#include <stdlib.h>
#include <ifaddrs.h>
#include <net/route.h>
#include <net/if_dl.h> 
#endif /* defined(__*BSD__) */

#include "hping2.h"
#include "globals.h"

#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__linux__) && !defined(__sun__)
#error Sorry, interface code not implemented.
#endif

#ifdef __sun__
#include <sys/sockio.h>
#include <net/route.h>
#include <net/if_dl.h>
#endif

#if (defined __linux__) || (defined __sun__)
static int get_output_if(struct sockaddr_in *dest, struct sockaddr_in *ifip);
#endif

#if (defined OSTYPE_LINUX) || (defined __sun__)
int get_if_name(void)
{
	int fd;
	struct ifconf	ifc;
	struct ifreq	ibuf[16],
			ifr,
			*ifrp,
			*ifend;
	struct sockaddr_in sa;
	struct sockaddr_in output_if_addr;
	int known_output_if = 0;

	/* Try to get the output interface address according to
	 * the OS routing table */
	if (ifname[0] == '\0') {
		if (get_output_if(&remote, &output_if_addr) == 0) {
			known_output_if = 1;
			if (opt_debug)
				printf("Output interface address: %s\n",
					inet_ntoa(sa.sin_addr));
		} else {
			fprintf(stderr, "Warning: Unable to guess the output "
					"interface\n");
		}
	}

	if ( (fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		perror("[get_if_name] socket(AF_INET, SOCK_DGRAM, 0)");
		return -1;
	}

	memset(ibuf, 0, sizeof(struct ifreq)*16);
	ifc.ifc_len = sizeof ibuf;
	ifc.ifc_buf = (caddr_t) ibuf;

	/* gets interfaces list */
	if ( ioctl(fd, SIOCGIFCONF, (char*)&ifc) == -1 ||
	     ifc.ifc_len < sizeof(struct ifreq)		) {
		perror("[get_if_name] ioctl(SIOCGIFCONF)");
		close(fd);
		return -1;
	}

	/* ifrp points to buffer and ifend points to buffer's end */
	ifrp = ibuf;
	ifend = (struct ifreq*) ((char*)ibuf + ifc.ifc_len);

	for (; ifrp < ifend; ifrp++) {
		strncpy(ifr.ifr_name, ifrp->ifr_name, sizeof(ifr.ifr_name));

		if ( ioctl(fd, SIOCGIFFLAGS, (char*)&ifr) == -1) {
			if (opt_debug)
				perror("[get_if_name] ioctl(SIOCGIFFLAGS)");
			continue;
		}

		if (opt_debug)
			printf("if %s: ", ifr.ifr_name);

		/* Down interface? */
		if ( !(ifr.ifr_flags & IFF_UP) )
		{
			if (opt_debug)
				printf("DOWN\n");
			continue;
		}

		if (known_output_if) {
			/* Get the interface address */
			if (ioctl(fd, SIOCGIFADDR, (char*)&ifr) == -1) {
				perror("[get_if_name] ioctl(SIOCGIFADDR)");
				continue;
			}
			/* Copy it */
			memcpy(&sa, &ifr.ifr_addr,
				sizeof(struct sockaddr_in));
			/* Check if it is what we are locking for */
			if (sa.sin_addr.s_addr !=
			    output_if_addr.sin_addr.s_addr) {
				if (opt_debug)
					printf("The address doesn't match\n");
				continue;
			}
		} else if (ifname[0] != '\0' && !strstr(ifr.ifr_name, ifname)) {
			if (opt_debug)
				printf("Don't Match (but seems to be UP)\n");
			continue;
		}

		if (opt_debug)
			printf("OK\n");

		/* interface found, save if name */
		strncpy(ifname, ifr.ifr_name, 1024);

		/* get if address */
		if ( ioctl(fd, SIOCGIFADDR, (char*)&ifr) == -1) {
			perror("[get_if_name] ioctl(SIOCGIFADDR)");
			exit(1);
		}

		/* save if address */
		memcpy(&sa, &ifr.ifr_addr,
			sizeof(struct sockaddr_in));
		strncpy(ifstraddr, inet_ntoa(sa.sin_addr), 1024);

		/* get if mtu */
		if ( ioctl(fd, SIOCGIFMTU, (char*)&ifr) == -1) {
			perror("Warning: [get_if_name] ioctl(SIOCGIFMTU)");
			fprintf(stderr, "Using a fixed MTU of 1500\n");
			h_if_mtu = 1500;
		}
		else
		{
#ifdef __sun__
			/* somehow solaris is braidamaged in wrt ifr_mtu */
			h_if_mtu = ifr.ifr_metric;
#else
			h_if_mtu = ifr.ifr_mtu;
#endif
		}
		close(fd);
		return 0;
	}
	/* interface not found, use 'lo' */
	strncpy(ifname, "lo", 1024);
	strncpy(ifstraddr, "127.0.0.1", 1024);
	h_if_mtu = 1500;

	close(fd);
	return 0;
}

#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)

/* *BSD code written by Nicolas Jombart <ecureuil@hsc.fr> 
   Version: 4
*/

#define BUFLEN  (sizeof(struct rt_msghdr) + 512)

/* from Stevens' Unix Network Programming */
#define ROUNDUP(a, size) (((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a))
#define NEXT_SA(ap)     ap = (struct sockaddr *) \
        ((caddr_t) ap + (ap->sa_len ? ROUNDUP(ap->sa_len, sizeof (u_long)) : \
                                                                sizeof(u_long)))
static void
get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
{
        int             i;

        for (i = 0; i < RTAX_MAX; i++) {
                if (addrs & (1 << i)) {
                        rti_info[i] = sa;
                        NEXT_SA(sa);
                } else
                        rti_info[i] = NULL;
        }
}

/* get GW. return 1 if IP address of the gateway, 2 if interface address */
static int get_gw(char *target, char *address) {
int 		 	fd,pid,n;
char 		 	*buf;
struct rt_msghdr 	*rtm;
struct sockaddr_in 	*sin;
struct sockaddr		*sa,*rti_info[RTAX_MAX];

	/* bad hack if loopback interface */
	if(!strncmp(target,"127.0.0.1",9))
	{
		strncpy(address,"lo0\0",4);
		return 2;
	}

	/* try first to get interface from the kernel internal routing table */
	if( (fd = socket(PF_ROUTE,SOCK_RAW,0)) < 0) {
		perror("Can't open routing socket.\n");
		exit(1);
	}

	buf = (char *)calloc(1, BUFLEN);
	rtm = (struct rt_msghdr *) buf;
		
	/* data structure to talk with the kernel */
	rtm->rtm_msglen = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in);
	rtm->rtm_version = RTM_VERSION;
	rtm->rtm_type = RTM_GET;     
	rtm->rtm_addrs = RTA_DST;    /* socket address structure contains destination address */
	rtm->rtm_pid = pid = getpid();
	rtm->rtm_seq = 9999;

	sin = (struct sockaddr_in *) (rtm + 1);
	sin->sin_len = sizeof(struct sockaddr_in); sin->sin_family = AF_INET;
	inet_pton(AF_INET, target ,&sin->sin_addr);
	if (opt_debug) printf("adresse à pinger : %s\n",target);

	/* write the buffer and read response */
	write(fd, rtm, rtm->rtm_msglen);
	do { n = read(fd, rtm, BUFLEN); }
	while (rtm->rtm_type != RTM_GET || rtm->rtm_seq != 9999 || rtm->rtm_pid != pid);

	/* process the response */
	rtm = (struct rt_msghdr *) buf;
        sa = (struct sockaddr *)(rtm + 1);
	get_rtaddrs(rtm->rtm_addrs, sa, rti_info);

	if( (sa = rti_info[RTAX_GATEWAY]) != NULL && sa->sa_family == AF_LINK) 
	{
               	register struct sockaddr_dl      *sdl = (struct sockaddr_dl *) sa;
		if(opt_debug) {
			printf("AF_LINK len = %d type = %d\n",sdl->sdl_len, sdl->sdl_type);
               		printf("INDEX: %d\n",sdl->sdl_index);
			printf("Interface name : %s\n",if_indextoname(sdl->sdl_index,address));
		}
		if_indextoname(sdl->sdl_index,address);
		return 2;
	}
	
	if( (sa = rti_info[RTAX_GATEWAY]) != NULL && sa->sa_family == AF_INET) {
	char 	blah[24];
		sin = (struct sockaddr_in *) sa;
		inet_ntop(AF_INET, &sin->sin_addr, blah, sizeof(blah));
		if (opt_debug) printf("GW address: %s\n",blah);
		strncpy(address, blah, strlen(blah) + 1);
		return 1;
	}
	
	return 0;	/* error */
}

/* return interface informations :
   - from the specified (-I) interface
   - from the routing table
   - or at least from the first UP interface found
*/
int get_if_name(void)
{
	/* variable declarations */
        struct ifaddrs  	*ifap, *ifa;
        char             	current_if_name[24];
        char             	saved_ifname[24];
	int 			res;
	char 		 	gw[24];
#ifdef __NetBSD__
        int s;
        struct ifreq ifr;
#endif  /* __NetBSD__ */

        if (getifaddrs(&ifap) < 0)
                perror("getifaddrs");

	saved_ifname[0] = 0;

        if(ifname[0] == 0) {
		/* find gw and gw interface */
		res = get_gw(targetstraddr, gw);
		if(res == 1) {
			if(get_gw(gw, gw) == 2) strncpy(saved_ifname,gw,24);
		} else {
			strncpy(saved_ifname,gw,24);
		}
	}
	else {
		/* use the forced interface name */
		strncpy(saved_ifname,ifname,24);
	}

	/* get interface information */
        for (ifa = ifap; ifa; ifa = ifa->ifa_next) {

                if (opt_debug) printf("\nif %s: ", ifa->ifa_name);

                /* print if the data structure is null or not */
                if (ifa->ifa_data) {
                        if(opt_debug) printf("(struct DATA) "); }
                else
                        if(opt_debug) printf("(struct DATA is NULL) ");

                if (!(ifa->ifa_flags & IFF_UP)) {       /* if down */
                        if (opt_debug)
                                printf("DOWN");
                        continue; 
                }

                if ((ifa->ifa_flags & IFF_LOOPBACK)&&
                    (strncmp(saved_ifname,"lo0",3))) {  /* if loopback */
                        if (opt_debug)
                                printf("LOOPBACK, SKIPPED");
                        continue;
                }

                if (ifa->ifa_addr->sa_family == AF_LINK) {
                        if (opt_debug)
                                printf("AF_LINK ");
                        strncpy(ifname,ifa->ifa_name,1024);
                        strncpy(current_if_name,ifa->ifa_name,24);

#ifdef __NetBSD__
                        memset( &ifr, 0, sizeof(ifr));
                        strncpy(ifr.ifr_name, ifa->ifa_name, sizeof(ifr.ifr_name));
                        if (sizeof(ifr.ifr_addr) >= ifa->ifa_addr->sa_len)
                           memcpy(&ifr.ifr_addr, ifa->ifa_addr, ifa->ifa_addr->sa_len);
                        s = socket(PF_INET, SOCK_DGRAM, 0);
                        if (ioctl(s, SIOCGIFMTU, (caddr_t)&ifr) < 0) h_if_mtu = 0;
                        else h_if_mtu = ifr.ifr_mtu;
                        close(s);
#else

                        if( ifa->ifa_data )
                            h_if_mtu = ((struct if_data *)ifa->ifa_data)->ifi_mtu;
                        else {
                          h_if_mtu = 1500;
                            printf("Warning: fixing MTU to 1500 !\n");
                        }
#endif /* __NetBSD__ */
                        continue;
                }

                if (ifa->ifa_addr->sa_family == AF_INET6) {
                        if (opt_debug)
                                printf("AF_INET6 ");
                        continue;
                }

                if (ifa->ifa_addr->sa_family == AF_INET) {
                        if (opt_debug)
                                printf("AF_INET ");
                        if(strncmp(ifa->ifa_name,current_if_name,24))
                                continue; /* error */
                        if(opt_debug) printf("OK\n");
                        strncpy(ifstraddr,
                        inet_ntoa(((struct sockaddr_in *)ifa->ifa_addr)->sin_addr),1024);
                        if( (saved_ifname[0]==0) || (!strncmp(ifa->ifa_name,saved_ifname,24) )) 
				break; /* asked if found, of first UP interface */
                }

		/* interface not found, use 'lo' */
		strncpy(ifname, "lo0", 1024);
		strncpy(ifstraddr, "127.0.0.1", 1024);
		h_if_mtu = 1500;
	}

        freeifaddrs(ifap);
	return 0;
}


#endif /* __*BSD__ */

/* used only by linux and solaris for now, but will be merged with the
 * BSD code. */
#if (defined __linux__) || (defined __sun__)
/* Try to obtain the IP address of the output interface according
 * to the OS routing table. Derived from R.Stevens */
int get_output_if(struct sockaddr_in *dest, struct sockaddr_in *ifip)
{
	int sock_rt, len, on=1;
	struct sockaddr_in iface_out;
 
	memset(&iface_out, 0, sizeof(iface_out));
	sock_rt = socket(AF_INET, SOCK_DGRAM, 0 );

	dest->sin_port = htons(11111);
	if (setsockopt(sock_rt, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on))
            == -1) {
		if (opt_debug)
			perror("[get_output_if] setsockopt(SOL_SOCKET, "
			       "SO_BROADCAST");
		close(sock_rt);
		return -1;
	}
  
	if (connect(sock_rt, (struct sockaddr*)dest, sizeof(struct sockaddr_in))
	    == -1 ) {
		if (opt_debug)
			perror("[get_output_if] connect");
		close(sock_rt);
		return -1;
	}

	len = sizeof(iface_out);
	if (getsockname(sock_rt, (struct sockaddr *)&iface_out, &len) == -1 ) {
		if (opt_debug)
			perror("[get_output_if] getsockname");
		close(sock_rt);
		return -1;
	}
	close(sock_rt);
	if (iface_out.sin_addr.s_addr == 0)
		return 1;
	memcpy(ifip, &iface_out, sizeof(struct sockaddr_in));
        return 0;
}
#endif
