/* Copyright (C) 2010 Eric Hsiao <erichs0608@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

/* Kernel module implementing an IP set type: the service type */

#include <linux/module.h>
#include <net/ip.h>
#include <linux/skbuff.h>
#include <linux/errno.h>
#include <net/tcp.h>
#include <net/udp.h>
//#include <linux/skbuff_compat.h>

#include <linux/netfilter_ipv4/ip_set_service.h>

/* Returns 1 if the port is matched by the range, 0 otherwise */
static inline int
port_match(u_int16_t min, u_int16_t max, u_int16_t port)
{
	return (port >= min && port <= max);
}

static int
service_utest(struct ip_set *set, const void *data, u_int32_t size)
{
	struct ip_set_service *map = set->data;
	struct list_head *head = &map->head;
	const struct ip_set_req_service *req = data;
	struct list_head *pos;
	struct service_list *tmp;
	
	list_for_each(pos, head) {
		tmp = (struct service_list *) list_entry(pos, struct service_list, list);
		if (memcmp(&tmp->service, req, sizeof(struct ip_set_req_service)) == 0)
			return 1;
	}
	return 0;
}

static int
service_ktest(struct ip_set *set,
	      const struct sk_buff *skb,
	      const u_int32_t *flags)
{
    struct ip_set_service *map  = set->data;
    struct list_head      *head = &map->head;
    struct list_head      *pos;
    struct service_list   *tmp;

    struct iphdr *ip = ip_hdr(skb);
    u_int16_t    srcPort = 0;
    u_int16_t    dstPort = 0;

	int offset = ntohs(ip->frag_off) & IP_OFFSET;

	if ((ip->protocol == IPPROTO_TCP || ip->protocol == IPPROTO_UDP) && offset)
		return 0;

    if (ip->protocol == IPPROTO_TCP) {
        const struct tcphdr *tcph = (const void *)ip + ip_hdrlen(skb);

        srcPort = ntohs(tcph->source);
        dstPort = ntohs(tcph->dest);
    }
    else if (ip->protocol == IPPROTO_UDP) {
        const struct udphdr *udph = (const void *)ip + ip_hdrlen(skb);

        srcPort = ntohs(udph->source);
        dstPort = ntohs(udph->dest);
    }

    list_for_each(pos, head) {
        tmp = (struct service_list *) list_entry(pos, struct service_list, list);

        if ( tmp->service.protocol == ip->protocol ) 
        {
            if (ip->protocol == IPPROTO_TCP || ip->protocol == IPPROTO_UDP) 
            {
                if ( port_match(tmp->service.sptStart, tmp->service.sptEnd, srcPort) && 
                        port_match(tmp->service.dptStart, tmp->service.dptEnd, dstPort)  )
                    return 1;
            }
            else 
            {
                return 1;
            }
        }
    }

    return 0;
}

static int
service_uadd(struct ip_set *set, const void *data, u_int32_t size)
{
	struct ip_set_service *map = set->data;
	struct list_head *head = &map->head;
	const struct ip_set_req_service *req = data;
	struct list_head *pos;
	struct service_list *tmp;
	struct service_list *service_list;

		
	list_for_each(pos,head) {
		tmp = (struct service_list *) list_entry(pos, struct service_list, list);
		if (memcmp(&tmp->service, req, sizeof(struct ip_set_req_service)) == 0)
			return -EEXIST;
	}	
	
	service_list = kmalloc(sizeof(struct service_list), GFP_KERNEL);
	if (!service_list)
		return -ENOMEM;
	memcpy(&service_list->service, req, sizeof(struct ip_set_req_service));
	list_add(&service_list->list, head);
	map->size++;
	return 0;
}

static int
service_kadd(struct ip_set *set,
	     const struct sk_buff *skb,
	     const u_int32_t *flags)
{
	return 0;
}

static int
service_udel(struct ip_set *set, const void *data, u_int32_t size)
{
	struct ip_set_service *map = set->data;
	struct list_head *head = &map->head;
	const struct ip_set_req_service *req = data;
	struct list_head *pos, *q;
	struct service_list *tmp;
	
	list_for_each_safe(pos, q, head) {
		tmp = (struct service_list *) list_entry(pos, struct service_list, list);
		if (memcmp(&tmp->service, req, sizeof(struct ip_set_req_service)) == 0) {
			list_del(pos);
			kfree(tmp);
			map->size--;
			return 0;
		}
	}
	
	return -EEXIST;
}

static int
service_kdel(struct ip_set *set,
	     const struct sk_buff *skb,
	     const u_int32_t *flags)
{
	return 0;
}

static int
service_create(struct ip_set *set, const void *data, u_int32_t size)
{
	struct ip_set_service *map;
	//const struct ip_set_req_service_create *req = data;
	map= kmalloc(sizeof(struct ip_set_service) , GFP_KERNEL);
	if (!map)
		return -ENOMEM;
	map->size = 0;
	INIT_LIST_HEAD(&map->head);
	set->data = map;
	return 0;
}                 

static void
service_destroy(struct ip_set *set)
{
	struct ip_set_service *map = set->data;
	struct list_head *head = &map->head;
	struct list_head *pos, *q;
	struct service_list *tmp;
	
	list_for_each_safe(pos, q, head) {
		tmp = (struct service_list *) list_entry(pos, struct service_list, list);
		list_del(pos);
		kfree(tmp);
	}
	
	kfree(map);
	
	return;
}

static void
service_flush(struct ip_set *set)
{
	struct ip_set_service *map = set->data;
	struct list_head *head = &map->head;
	struct list_head *pos, *q;
	struct service_list *tmp;
	
	list_for_each_safe(pos, q, head) {
		tmp = (struct service_list *) list_entry(pos, struct service_list, list);
		list_del(pos);
		kfree(tmp);
	}
	
	map->size = 0;
	
	return;
}

static void
service_list_header(const struct ip_set *set, void *data)
{
	const struct ip_set_service *map = set->data;
	struct ip_set_req_service_create *header = data;
	
	header->size = map->size;
}

static int
service_list_members_size(const struct ip_set *set, char dont_align)
{
	struct ip_set_service *map = set->data;
	return map->size*sizeof(struct ip_set_req_service);
}

static void
service_list_members(const struct ip_set *set, void *data, char dont_align)
{
	struct ip_set_service *map = set->data;
	struct ip_set_req_service *d = data;
	struct list_head *head = &map->head;
	struct list_head *pos;
	struct service_list *tmp;
	
	list_for_each(pos,head) {
		tmp = (struct service_list *) list_entry(pos, struct service_list, list);
		memcpy(d, &tmp->service, sizeof(struct ip_set_req_service));
		d++;
	}
}

IP_SET_TYPE(service, IPSET_DATA_SINGLE)

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Eric Hsiao <erichs0608@gmail.com>");
MODULE_DESCRIPTION("service type of IP sets");

REGISTER_MODULE(service)
