/* 
 *   Creation Date: <2002/05/22 22:41:40 samuel>
 *   Time-stamp: <2003/09/02 22:53:44 samuel>
 *   
 *	<iface.c>
 *	
 *	Support of various ethernet interfaces
 *   
 *   Copyright (C) 1999-2003 Samuel Rydh (samuel@ibrium.se)
 *   
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   as published by the Free Software Foundation
 *   
 */

#include "mol_config.h"
#include <sys/ioctl.h>
#include <asm/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <linux/netlink.h>
#include <linux/if.h>

#include "res_manager.h"
#include "verbose.h"
#include "enet.h"

SET_VERBOSE_NAME("enet");

#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
#define HAS_TUN
#include <linux/if_tun.h>
#endif

/* From sheep.c */
#define SIOC_MOL_GET_IPFILTER		SIOCDEVPRIVATE
#define SIOC_MOL_SET_IPFILTER		(SIOCDEVPRIVATE + 1)

static char sheep_virtual_hw_addr[6] = {0xFE, 0xFD, 0xDE, 0xAD, 0xBE, 0xEF };
#define TAP_VIRTUAL_HW_ADDR 		0xDEADBEEF
#define TUN_VIRTUAL_HW_ADDR 		0x0DEADBEE

#define TUNSETUP_RES			"tunconfig"

enum{ f_tap=1, f_sheep=2, f_tun=4, f_driver_id_mask=7 };


/************************************************************************/
/*	Misc								*/
/************************************************************************/

static int
check_netdev( char *ifname )
{
	struct ifreq ifr;
	int fd, ret=-1;
	
	if( (fd = socket( AF_INET, SOCK_DGRAM, 0 )) < 0 )
		return -1;

	memset( &ifr, 0, sizeof(ifr) );
	strncpy( ifr.ifr_name, ifname, sizeof(ifr.ifr_name) );
	if( ioctl( fd, SIOCGIFFLAGS, &ifr ) < 0 ){
		perrorm("SIOCGIFFLAGS");
		goto out;
	}
	if( !(ifr.ifr_flags & IFF_RUNNING ) ){
		printm("---> The network interface '%s' is not configured!\n", ifname);
		goto out;
	}
	if( (ifr.ifr_flags & IFF_NOARP ) ){
		printm("WARNING: Turning on ARP for device '%s'.\n", ifname);
		ifr.ifr_flags &= ~IFF_NOARP;
		if( ioctl( fd, SIOCSIFFLAGS, &ifr ) < 0 ) {
			perrorm("SIOCSIFFLAGS");
			goto out;
		}
	}
	ret = 0;
 out:
	close(fd);
	return ret;
}

static void
common_iface_open( enet_iface_t *is, char *drv_str, char *intf_name, int fd )
{
	snprintf( is->drv_name, sizeof(is->drv_name), "%s-<%s>", drv_str, intf_name );
	strncpy0( is->iface_name, intf_name, sizeof(is->iface_name));
	is->fd = fd;
}

static void
generic_close( enet_iface_t *is )
{
	close( is->fd );
	is->fd = -1;
}


/************************************************************************/
/*	SHEEP Packet Drivers						*/
/************************************************************************/

static int
sheep_open( enet_iface_t *is, char *intf_name )
{
	int fd;
	
	/* verify that the device is up and running */
	if( check_netdev(intf_name) )
		return 1;
	
	/* open sheep_net device */
	if( (fd=open("/dev/sheep_net", O_RDWR | O_NONBLOCK )) < 0 ) {
		printm("-----> Can't open /dev/sheep_net, please check module is present !\n");
		return 1;
	}

	/* attach to selected Ethernet card */
	if (ioctl(fd, SIOCSIFLINK, intf_name) < 0) {
		printm("-----> Can't attach to interface <%s>\n", intf_name);
		goto err;
	}

	/* get/set ethernet address */
	if( ioctl(fd, SIOCSIFADDR, sheep_virtual_hw_addr ) < 0 )
		printm("----> An old version of the sheep_net kernel module is probably running!\n");
	ioctl(fd, SIOCGIFADDR, is->eth_addr);

	/* set device specific variables */ 
	is->packet_pad = 0;

	common_iface_open( is, "sheep", intf_name, fd );
	return 0;
err:
	close(fd);
	return 1;
}

static int
sheep_add_multicast( enet_iface_t *is, char *addr )
{
	if( ioctl(is->fd, SIOCADDMULTI, addr) < 0 ) {
		LOG("sheep_add_multicast failed\n");
		return -1;
	}
	return 0;
}

static int 
sheep_del_multicast( enet_iface_t *is, char *addr )
{
	if( ioctl(is->fd, SIOCDELMULTI, addr) < 0 ) {
		LOG("sheep_del_multicast failed\n");
		return -1;
	}
	return 0;
}

static int
sheep_load_save_state( enet_iface_t *is, enet_iface_t *load_is, int index, int loading )
{
	/* XXX: Multicast addresses are not handled */
	if( loading ){
		if( ioctl( is->fd, SIOC_MOL_SET_IPFILTER, load_is->ipfilter ) < 0 )
			perrorm("SIOC_MOL_SET_IPFILTER");
	} else {
		if( ioctl( is->fd, SIOC_MOL_GET_IPFILTER, &is->ipfilter ) < 0 )
			perrorm("SIOC_MOL_GET_IPFILTER");
	}
	return 0;
}

static packet_driver_t sheep_pd = {
	name:			"sheep",
	packet_driver_id: 	f_sheep,
	open: 			sheep_open,
	close:			generic_close,
	add_multicast:		sheep_add_multicast,
	del_multicast:		sheep_del_multicast,
	load_save_state:	sheep_load_save_state
};


/************************************************************************/
/*	TAP Packet Driver						*/
/************************************************************************/

static int
tap_open( enet_iface_t *is, char *intf_name )
{
	struct sockaddr_nl nladdr;
	int fd, tapnum=0;
	char buf[16];
	
	if( intf_name ) {
		if( sscanf(intf_name, "tap%d", &tapnum)==1 ) {
			if( tapnum<0 || tapnum>15 ) {
				printf("Invalid tap device %s. Using tap0 instead\n", intf_name );
				intf_name = NULL;
				tapnum = 0;
			}
		} else {
			printm("Bad tapdevice interface '%s'\n", intf_name );
			printm("Using default tap device (tap0)\n");
			intf_name = NULL;
		}
	}
	if( !intf_name ) {
		sprintf( buf, "tap0" );
		intf_name = buf;
	}

	/* verify that the device is up and running */
	if( check_netdev( intf_name ) )
		return 1;

	if( (fd = socket( PF_NETLINK, SOCK_RAW, NETLINK_TAPBASE+tapnum )) < 0 ) {
		perrorm("socket");
		printm("Does the kernel lack netlink support (CONFIG_NETLINK)?\n");
		return 1;
	}
	memset( &nladdr, 0, sizeof(nladdr) );
	nladdr.nl_family = AF_NETLINK;
	nladdr.nl_groups = ~0;
	nladdr.nl_pid = TAP_VIRTUAL_HW_ADDR;
	/* nladdr.nl_pid = getpid(); */
	if( bind( fd, (struct sockaddr*)&nladdr, sizeof(nladdr) ) < 0 ) {
		perrorm("bind");
		close(fd);
		return 1;
	}

	is->eth_addr[0] = is->eth_addr[1] = 0;
	*(ulong*)&is->eth_addr[2] = TAP_VIRTUAL_HW_ADDR;
	/* *(ulong*)&is->eth_addr[2] = getpid(); */

	is->packet_pad = TAP_PACKET_PAD;

	common_iface_open( is, "tap", intf_name, fd );
	return 0;
}

static packet_driver_t tap_pd = {
	name:			"tap",
	packet_driver_id: 	f_tap,
	open: 			tap_open,
	close:			generic_close,
};


/************************************************************************/
/*	TUN/TAP Packet Driver						*/
/************************************************************************/

#ifdef HAS_TUN

static int
tun_open( enet_iface_t *is, char *intf_name )
{
	struct ifreq ifr;
	int fd;
	
	if( !intf_name )
		intf_name = "mol";

	/* allocate tun/tap device */ 
	if( (fd=open("/dev/net/tun", O_RDWR | O_NONBLOCK)) < 0 ) {
		perrorm("Failed to open /dev/net/tun");
		return 1;
	}
	fcntl( fd, F_SETFD, FD_CLOEXEC );

	memset( &ifr, 0, sizeof(ifr) );
	ifr.ifr_flags = IFF_TAP | IFF_NO_PI;

	strncpy(ifr.ifr_name, intf_name, IFNAMSIZ );
	if( ioctl(fd, TUNSETIFF, &ifr) < 0 ){
		perrorm("TUNSETIFF");
		goto out;
	}

	/* don't checksum */
	ioctl( fd, TUNSETNOCSUM, 1 );

	/* Configure device */
	script_exec( get_filename_res(TUNSETUP_RES), intf_name, "up" );
	
	/* set HW address */
	is->eth_addr[0] = is->eth_addr[1] = 0;
	*(ulong*)&is->eth_addr[2] = TUN_VIRTUAL_HW_ADDR;

	/* finish... */
	common_iface_open( is, "tun", intf_name, fd );
	return 0;

 out:
	close(fd);
	return 1;
}

static void
tun_close( enet_iface_t *is )
{
	script_exec( get_filename_res(TUNSETUP_RES), is->iface_name, "down" );

	close( is->fd );
	is->fd = -1;
}

static packet_driver_t tun_pd = {
	name:			"tun",
	packet_driver_id: 	f_tun,
	open: 			tun_open,
	close:			tun_close,
};

#endif /* HAS_TUN */


/************************************************************************/
/*	World interface							*/
/************************************************************************/

static packet_driver_t *packet_drivers[] = {
#ifdef HAS_TUN
	&tun_pd,
#endif
	&sheep_pd, 
	&tap_pd,
	NULL
};

static opt_entry_t netdev_opts[] = {
#ifdef HAS_TUN
	{"-tun",		f_tun },
#endif
	{"-tap",		f_tap },
	{"-sheep",		f_sheep },
	{NULL, 0 }
};

packet_driver_t *
find_packet_driver( char *res_name, int index )
{
	packet_driver_t **pd;
	int flags=0;
	char *str;
	
	if( !(str=get_str_res_ind( res_name, index, 0 )) )
		return NULL;

	flags = parse_res_options( res_name, index, 1, netdev_opts, "---> Invalid netdev flag");

	if( str[0] != 't' )
		flags &= ~f_tap;
	if( !(flags & (f_sheep | f_tap | f_tun)) ) 
		flags |= (str[0] == 't')? f_tap : f_sheep;

	for( pd=&packet_drivers[0]; *pd; pd++ )
		if( (**pd).packet_driver_id == (flags & f_driver_id_mask) )
			break;
	if( !*pd ) {
		printf("find_packet_driver: Error\n");
		return NULL;
	}
	return *pd;
}
