#define __KERNEL__
#define MODULE

#include <linux/module.h>  /* can't do without it */ 
#include <linux/version.h> /* and this too */ 

#include <linux/kernel.h>

#include <linux/types.h>
#include <linux/errno.h>   /* return values, like EINVAL */
#include <linux/poll.h>    /* copy_from_user */
#include <sys/syscall.h>
#include <linux/socket.h>
#include <linux/net.h>
#include <linux/slab.h>    /* kmalloc() */
#include <linux/vmalloc.h> /* vmalloc() */
#include <linux/in.h>      /* struct sockaddr_in */
#include <linux/in6.h>     /* struct in6_addr */
// #include <sys/types.h>

MODULE_AUTHOR("Arnout Engelen");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Overloads the muxed socket system call (sys_socketcall) to translate any IPv4-calls to IPv6 transparently to the application");

#define MAX_SOCK_ADDR   128 /* same as in net/socket.c */
typedef uint32_t in_addr_t; /* same as in drivers/net/bonding.c */

extern void *sys_call_table[];
asmlinkage long (*original_socketcall)(int, unsigned long *);

struct socket *sockfd_lookup(int fd, int *err)
{
        struct file *file;
        struct inode *inode;
        struct socket *sock;

        if (!(file = fget(fd)))
        {
                *err = -EBADF;
                return NULL;
        }

        inode = file->f_dentry->d_inode;
        if (!inode->i_sock || !(sock = socki_lookup(inode)))
        {
                *err = -ENOTSOCK;
                fput(file);
                return NULL;
        }

        if (sock->file != file) {
                printk(KERN_ERR "socki_lookup: socket file changed!\n");
                sock->file = file;
        }
        return sock;
}

/* 
 * input:  binary IPv4 address in network byte order
 * output: binary IPv6 address in network byte order
 */
asmlinkage struct in6_addr v4addr2v6addr (in_addr_t from)
{
	/* TODO is GFP_ATOMIC really needed? - may we make this static? */
	struct in6_addr * retval = (struct in6_addr *) kmalloc (sizeof(struct in6_addr), GFP_KERNEL); 

	/* TODO remove this when the printk below is removed */
	// char * buffer = (char *) kmalloc (8*4+7+1, GFP_ATOMIC);

	/* TODO make this configurable */
	retval->s6_addr[0] = 0xfe;
	retval->s6_addr[1] = 0xc0;
	retval->s6_addr[2] = 0;
	retval->s6_addr[3] = 0;
	retval->s6_addr[4] = 0;
	retval->s6_addr[5] = 0;
	retval->s6_addr[6] = 0xff;
	retval->s6_addr[7] = 0xff;
	retval->s6_addr[8] = 0;
	retval->s6_addr[9] = 0;
	retval->s6_addr[10] = 0;
	retval->s6_addr[11] = 0;
	/* TODO check endianness */
	retval->s6_addr[12] = 0xd8;
	retval->s6_addr[13] = 0xef;
	retval->s6_addr[14] = 0x39;
	retval->s6_addr[15] = 0x65;

	//retval->s6_addr[12] = *((uint8_t *)&from);
	//retval->s6_addr[13] = *((uint8_t *)&from + 1);
	//retval->s6_addr[14] = *((uint8_t *)&from + 2);
	//retval->s6_addr[15] = *((uint8_t *)&from + 3);

	/* inet_ntop in the kernel is a hassle :) */
	//printk("Address: %s\n", inet_ntop(AF_INET6, retval, buffer, ));
	
	return *retval;
}

asmlinkage long sys_sendtov6(int fd, void * buff, size_t len, unsigned flags,
		struct sockaddr *addr, int addr_len)
{
        struct socket *sock;
        //char address[MAX_SOCK_ADDR];
        int err;
        struct msghdr msg;
        struct iovec iov;

        sock = sockfd_lookup(fd, &err);
        if (!sock)
                goto out;
        iov.iov_base=buff;
        iov.iov_len=len;
        msg.msg_name=NULL;
        msg.msg_iov=&iov;
        msg.msg_iovlen=1;
        msg.msg_control=NULL;
        msg.msg_controllen=0;
        msg.msg_namelen=0;
        if(addr)
        {
		/*
                err = move_addr_to_kernel(addr, addr_len, address);
                if (err < 0)
                        goto out_put;
                msg.msg_name=address;
                msg.msg_namelen=addr_len;
		*/
		msg.msg_name=addr;
		msg.msg_namelen=addr_len;
        }
	
        if (sock->file->f_flags & O_NONBLOCK)
                flags |= MSG_DONTWAIT;
        msg.msg_flags = flags;
        err = sock_sendmsg(sock, &msg, len);

out_put:
        sockfd_put(sock);
out:
        return err;
}

/*
 * It'd be nice to have the sockaddr_in6 static, to remove the
 * allocation overhead. However, this wouldn't be thread-safe anymore
 * if we did that :(.
 */
/*
int sendtov6 (int sockfd, const void *msg, int len, unsigned int flags,
		               const struct sockaddr *to, int tolen)
{
	struct sockaddr_in6 * tov6;

	if (to->sa_family != AF_INET)
		return sendto(sockfd, msg, len, flags, to, tolen);

	tov6 = (struct sockaddr_in6 *) calloc (1, sizeof(struct sockaddr_in6));

	// this is BSD-specific? (rfc2553 section 3.4)
	// tov6->sin6_len = sizeof(sockaddr_in6);
	
	tov6->sin6_family = AF_INET6;
	tov6->sin6_port = ((struct sockaddr_in *)to)->sin_port;
	tov6->sin6_addr = v4addr2v6addr(((struct sockaddr_in *)to)->sin_addr.s_addr);
	tov6->sin6_flowinfo = 0;
	//tov6->sin6_scope_id = ;
	
	return sendto(sockfd, msg, len, flags, (struct sockaddr *)tov6, sizeof(struct sockaddr_in6));
}
*/

/*
int recvfromv6 (int sockfd, void *msg, int len, unsigned int flags,
		               struct sockaddr *from, socklen_t * fromlen)
{
	struct sockaddr_in6 * fromv6;

	if (from->sa_family != AF_INET)
		return recvfrom (sockfd, msg, len, flags, from, fromlen);

	fromv6 = (struct sockaddr_in6 *) calloc (1, sizeof(struct sockaddr_in6));

	fromv6->sin6_family = AF_INET6;
	fromv6->sin6_port = ((struct sockaddr_in *)from)->sin_port;
	fromv6->sin6_addr = v4addr2v6addr(((struct sockaddr_in *)from)->sin_addr.s_addr);
	fromv6->sin6_flowinfo = 0;
	//fromv6->sin6_scope_id = ;
	
	// TODO fromv6 might change or something??
	return recvfrom(sockfd, msg, len, flags, (struct sockaddr *)fromv6, fromlen); 
}
*/

/*
 * Overloading the 'muxed' socket systemcalls, loosely based on the implementation of 2.4 net/socket.c
 */

/* Argument list sizes for sys_socketcall */
#define AL(x) ((x) * sizeof(unsigned long))
static unsigned char nargs[18]={AL(0),AL(3),AL(3),AL(3),AL(2),AL(3),
				AL(3),AL(3),AL(4),AL(4),AL(4),AL(6),
				AL(6),AL(2),AL(5),AL(5),AL(3),AL(3)};
#undef AL

/*
 *	System call vectors. 
 *
 *	Argument checking cleaned up. Saved 20% in size.
 *  This function doesn't need to set the kernel lock because
 *  it is set by the callees. 
 */

asmlinkage long sys_socketcallv6(int call, unsigned long *args)
{
	int err;

	unsigned long a[6];
	unsigned long a0,a1;

	if(call<1||call>SYS_RECVMSG)
		return -EINVAL;

	/* copy_from_user should be SMP safe. */
	if (copy_from_user(a, args, nargs[call]))
		return -EFAULT;
		
	a0=a[0];
	a1=a[1];
	
	switch(call) 
	{
		case SYS_SOCKET:
			if (a[0] == AF_INET)
			{
				printk("alternative socket() called for family AF_INET, ");
				switch(a[1])
				{
				case SOCK_STREAM:
					printk("protocol SOCK_STREAM. ignored.\n");
					break;
				case SOCK_DGRAM:
					printk("protocol SOCK_DGRAM. changed family to AF_INET6.\n");
					a[0] = AF_INET6;
					copy_to_user(args, a, sizeof(unsigned long));
					break;
				}
			}
			err = original_socketcall(call, args);
			break;
		case SYS_SENDTO:
		{
			// TODO check are all struct sockaddr's the same size?
			// TODO where do we kfree?
			struct sockaddr_in * to = (struct sockaddr_in *) kmalloc (sizeof(struct sockaddr_in), GFP_KERNEL);
			
			// a[4] points to userspace, to points to kernelspace
			if (copy_from_user(to, (struct sockaddr *)a[4], sizeof(struct sockaddr)))
				return -EFAULT;

			if (to->sin_family == AF_INET)
			{
				// tov6 points to kernelspace
				struct sockaddr_in6 * tov6;
				printk("alternative sendto() called for family AF_INET, creating ipv6 packet\n");
				tov6 = (struct sockaddr_in6 *) kmalloc (sizeof(struct sockaddr_in6), GFP_KERNEL);
				tov6->sin6_family = AF_INET6;
				tov6->sin6_port = to->sin_port;
				printk("Port: %u\n", tov6->sin6_port);
				printk("Port: %u\n", to->sin_port);
				tov6->sin6_addr = v4addr2v6addr(to->sin_addr.s_addr);
				tov6->sin6_flowinfo = 0;
				tov6->sin6_scope_id = 0; // ??
				
				//do copy_to_user here? 
				//args[4] = (unsigned long)tov6;
				//tov6 points to kernel space, a[4] points to kernel space
				//copy_to_user((struct sockaddr_in6*)a[4], tov6, sizeof(struct sockaddr_in6));
				printk("Sizes: %d, %d, %d\n", sizeof(struct sockaddr_in6), sizeof(struct sockaddr_in),  sizeof(struct sockaddr));

				// a points to kernel space, args point to user space
				//copy_to_user(args, a, 5 * sizeof(unsigned long)); // todo optimize
				err = sys_sendtov6(a0,(void *)a1, a[2], a[3],
					 (struct sockaddr *)tov6, a[5]);
			}
			else
			{
				err = original_socketcall(call, args);
			}

		}
		break;
		case SYS_RECVFROM:
			err = original_socketcall(call, args);
			//err = sys_recvfrom(a0, (void *)a1, a[2], a[3],
			//		   (struct sockaddr *)a[4], (int *)a[5]);
			break;
	/*	case SYS_BIND:
			err = sys_bind(a0,(struct sockaddr *)a1, a[2]);
			break;
		case SYS_CONNECT:
			err = sys_connect(a0, (struct sockaddr *)a1, a[2]);
			break;
		case SYS_LISTEN:
			err = sys_listen(a0,a1);
			break;
		case SYS_ACCEPT:
			err = sys_accept(a0,(struct sockaddr *)a1, (int *)a[2]);
			break;
		case SYS_GETSOCKNAME:
			err = sys_getsockname(a0,(struct sockaddr *)a1, (int *)a[2]);
			break;
		case SYS_GETPEERNAME:
			err = sys_getpeername(a0, (struct sockaddr *)a1, (int *)a[2]);
			break;
		case SYS_SOCKETPAIR:
			err = sys_socketpair(a0,a1, a[2], (int *)a[3]);
			break;
		case SYS_SEND:
			err = sys_send(a0, (void *)a1, a[2], a[3]);
			break;
		// remove SENDTO here
		case SYS_RECV:
			err = sys_recv(a0, (void *)a1, a[2], a[3]);
			break;
		// removed RECVFROM here
		case SYS_SHUTDOWN:
			err = sys_shutdown(a0,a1);
			break;
		case SYS_SETSOCKOPT:
			err = sys_setsockopt(a0, a1, a[2], (char *)a[3], a[4]);
			break;
		case SYS_GETSOCKOPT:
			err = sys_getsockopt(a0, a1, a[2], (char *)a[3], (int *)a[4]);
			break;
		case SYS_SENDMSG:
			err = sys_sendmsg(a0, (struct msghdr *) a1, a[2]);
			break;
		case SYS_RECVMSG:
			err = sys_recvmsg(a0, (struct msghdr *) a1, a[2]);
			break;
			*/
		default:
			return original_socketcall(call, args);
	}
	return err;
}

/*
 * will be called once before calling any other of the xxxv6-functions.
 *
 * corresponding to initsocketv6()
 */
int init_module ()
{
	printk("Initializing v4-to-v6 transparent syscall translation module\n");
	original_socketcall = sys_call_table[102];
	sys_call_table[102] = sys_socketcallv6;
	/*int i;
	for (i=0; i<sizeof(int); i++)
		sockfdv6[i] = -1; */
	return 0;
}

void cleanup_module ()
{
	printk("Cleaning up v4-to-v6 transparent syscall translation module\n");
	sys_call_table[102] = original_socketcall;
}


