/*
 * IPVS         Application module
 *
 * Version:     $Id: ip_vs_app.c,v 1.8 2001/03/18 15:01:45 wensong Exp $
 *
 * Authors:     Wensong Zhang <wensong@linuxvirtualserver.org>
 *
 *              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; either version
 *              2 of the License, or (at your option) any later version.
 *
 * Most code here is taken from ip_masq_app.c in kernel 2.2. The difference
 * is that ip_vs_app module handles the reverse direction (incoming requests
 * and outgoing responses). The ip_vs_app modules are only used for VS/NAT.
 *
 *		IP_MASQ_APP application masquerading module
 *
 * Author:	Juan Jose Ciarlante, <jjciarla@raiz.uncu.edu.ar>
 *
 */

#ifdef MODULE
#define EXPORT_SYMTAB
#endif

#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/init.h>
#include <net/protocol.h>
#include <net/tcp.h>
#include <net/udp.h>
#include <asm/system.h>
#include <linux/stat.h>
#include <linux/proc_fs.h>

#include "ip_vs.h"
//#include <net/ip_vs.h>

#define IP_VS_APP_TAB_SIZE  16          /* must be power of 2 */

#define IP_VS_APP_HASH(proto, port) ((port^proto) & (IP_VS_APP_TAB_SIZE-1))
#define IP_VS_APP_TYPE(proto, port) (proto<<16 | port)
#define IP_VS_APP_PORT(type)        (type & 0xffff)
#define IP_VS_APP_PROTO(type)       ((type>>16) & 0x00ff)


EXPORT_SYMBOL(register_ip_vs_app);
EXPORT_SYMBOL(unregister_ip_vs_app);


/*
 * 	will hold ipvs app. hashed list heads
 */
static struct list_head ip_vs_app_base[IP_VS_APP_TAB_SIZE];

/* lock for ip_vs_app table */
static rwlock_t __ip_vs_app_lock = RW_LOCK_UNLOCKED;


/*
 * 	ip_vs_app registration routine
 *	port: host byte order.
 */
int register_ip_vs_app(struct ip_vs_app *vapp,
                       unsigned short proto, __u16 port)
{
        unsigned hash;

        if (!vapp) {
                IP_VS_ERR("register_ip_vs_app(): NULL arg\n");
                return -EINVAL;
        }

        MOD_INC_USE_COUNT;
        
        vapp->type = IP_VS_APP_TYPE(proto, port);
        hash = IP_VS_APP_HASH(proto, port);

        write_lock_bh(&__ip_vs_app_lock);
        list_add(&vapp->n_list, &ip_vs_app_base[hash]);
        write_unlock_bh(&__ip_vs_app_lock);

        return 0;
}


/*
 * 	ip_vs_app unregistration routine.
 */
int unregister_ip_vs_app(struct ip_vs_app *vapp)
{
        if (!vapp) {
                IP_VS_ERR("unregister_ip_vs_app(): NULL arg\n");
                return -EINVAL;
        }

#if 0000
        /*
         * only allow unregistration if it has no referrer.
         */
        if (atomic_read(&vapp->refcnt) > 0)  {
                IP_VS_ERR("unregister_ip_vs_app(): has %d referrers. failed\n",
                          atomic_read(&vapp->refcnt));
                return -EINVAL;
        }
#endif
        
        write_lock_bh(&__ip_vs_app_lock);
        list_del(&vapp->n_list);
        write_unlock_bh(&__ip_vs_app_lock);
        
        MOD_DEC_USE_COUNT;

        return 0;
}


/*
 *	get ip_vs_app object by its proto and port (net byte order).
 */
static struct ip_vs_app * ip_vs_app_get(unsigned short proto, __u16 port)
{
        struct list_head *e;
        struct ip_vs_app *vapp;
        unsigned hash;
        unsigned type;

        port = ntohs(port);
        type = IP_VS_APP_TYPE(proto, port);
        hash = IP_VS_APP_HASH(proto, port);

        read_lock_bh(&__ip_vs_app_lock);
        
        list_for_each(e, &ip_vs_app_base[hash]) {
                vapp = list_entry(e, struct ip_vs_app, n_list);

                /*
                 * Test and MOD_INC_USE_COUNT atomically
                 */
                if (!try_inc_mod_count(vapp->module)) {
                        /*
                         * This application module is just deleted
                         */
                        continue;
                }
                if (type == vapp->type) {
                        read_unlock_bh(&__ip_vs_app_lock);
                        return vapp;
                }
                
                __MOD_DEC_USE_COUNT(vapp->module);
        }

        read_unlock_bh(&__ip_vs_app_lock);
        return NULL;
}


/*
 *	Bind ip_vs_conn to its ip_vs_app based on proto and dport,
 *	and call the ip_vs_app constructor.
 */
struct ip_vs_app * ip_vs_bind_app(struct ip_vs_conn *cp)
{
        struct ip_vs_app *vapp;

        /* no need to bind app if its forwarding method is not NAT */
        if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
                return NULL;

        if (cp->protocol != IPPROTO_TCP && cp->protocol != IPPROTO_UDP)
                return NULL;

        /*
         *	don't allow binding if already bound
         */
        if (cp->app != NULL) {
                IP_VS_ERR("ip_vs_bind_app(): "
                          "called for already bound object.\n");
                return cp->app;
        }
        
        vapp = ip_vs_app_get(cp->protocol, cp->vport);

        if (vapp != NULL) {
                /*  atomic_inc(&vapp->refcnt); */
                cp->app = vapp;
                
                if (vapp->init_conn)
                        vapp->init_conn(vapp, cp);
        }
        return vapp;
}


/*
 * 	Unbind cp from type object and call cp destructor (does not kfree()).
 */
int ip_vs_unbind_app(struct ip_vs_conn *cp)
{
        struct ip_vs_app *vapp = cp->app;

        if (cp->protocol != IPPROTO_TCP && cp->protocol != IPPROTO_UDP)
                return 0;

        if (vapp != NULL) {
                if (vapp->done_conn)
                        vapp->done_conn(vapp, cp);
                cp->app = NULL;
                /*  atomic_dec(&vapp->refcnt); */
                __MOD_DEC_USE_COUNT(vapp->module);
        }
        return (vapp != NULL);
}


/*
 *	Fixes th->seq based on ip_vs_seq info.
 */
static inline void vs_fix_seq(const struct ip_vs_seq *vseq, struct tcphdr *th)
{
        __u32 seq = ntohl(th->seq);

        /*
         * 	Adjust seq with delta-offset for all packets after
         * 	the most recent resized pkt seq and with previous_delta offset
         *	for all packets	before most recent resized pkt seq.
         */
        if (vseq->delta || vseq->previous_delta) {
                if(after(seq, vseq->init_seq)) {
                        th->seq = htonl(seq + vseq->delta);
                        IP_VS_DBG(9, "vs_fix_seq(): added delta (%d) to seq\n",
                                  vseq->delta);
                } else {
                        th->seq = htonl(seq + vseq->previous_delta);
                        IP_VS_DBG(9, "vs_fix_seq(): added previous_delta "
                                  "(%d) to seq\n", vseq->previous_delta);
                }
        }
}


/*
 *	Fixes th->ack_seq based on ip_vs_seq info.
 */
static inline void
vs_fix_ack_seq(const struct ip_vs_seq *vseq, struct tcphdr *th)
{
        __u32 ack_seq = ntohl(th->ack_seq);

        /*
         * Adjust ack_seq with delta-offset for
         * the packets AFTER most recent resized pkt has caused a shift
         * for packets before most recent resized pkt, use previous_delta
         */
        if (vseq->delta || vseq->previous_delta) {
                /* since ack_seq is the number of octet that is expected
                   to receive next, so compare it with init_seq+delta */
                if(after(ack_seq, vseq->init_seq+vseq->delta)) {
                        th->ack_seq = htonl(ack_seq - vseq->delta);
                        IP_VS_DBG(9, "vs_fix_ack_seq(): subtracted delta "
                                  "(%d) from ack_seq\n", vseq->delta);

                } else {
                        th->ack_seq = htonl(ack_seq - vseq->previous_delta);
                        IP_VS_DBG(9, "vs_fix_ack_seq(): subtracted "
                                  "previous_delta (%d) from ack_seq\n",
                                  vseq->previous_delta);
                }
        }
}


/*
 *	Updates ip_vs_seq if pkt has been resized
 *	Assumes already checked proto==IPPROTO_TCP and diff!=0.
 */
static inline void vs_seq_update(struct ip_vs_conn *cp, struct ip_vs_seq *vseq,
                                 unsigned flag, __u32 seq, int diff)
{
        if ( !(cp->flags & flag) || after(seq, vseq->init_seq)) {
                vseq->previous_delta = vseq->delta;
                vseq->delta += diff;
                vseq->init_seq = seq;
                cp->flags |= flag;
        }
}


/*
 *	Output pkt hook. Will call bound ip_vs_app specific function
 *	called by ip_vs_out(), assumes previously checked cp!=NULL
 *	returns (new - old) skb->len diff.
 */
int ip_vs_app_pkt_out(struct ip_vs_conn *cp, struct sk_buff **skb_p)
{
        struct ip_vs_app * vapp;
        int diff;
        struct iphdr *iph;
        struct tcphdr *th;
        __u32 seq;

        /*
         *	check if application module is bound to
         *	this ip_vs_conn.
         */
        if ( (vapp = cp->app) == NULL)
                return 0;

        iph = (*skb_p)->nh.iph;
        th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);

        /*
         *	Remember seq number in case this pkt gets resized
         */
        seq = ntohl(th->seq);

        /*
         *	Fix seq stuff if flagged as so.
         */
        if (cp->protocol == IPPROTO_TCP) {
                if (cp->flags & IP_VS_CONN_F_OUT_SEQ)
                        vs_fix_seq(&cp->out_seq, th);
                if (cp->flags & IP_VS_CONN_F_IN_SEQ)
                        vs_fix_ack_seq(&cp->in_seq, th);
        }
        
        /*
         *	Call private output hook function
         */
        if ( vapp->pkt_out == NULL )
                return 0;

        diff = vapp->pkt_out(vapp, cp, skb_p);

        /*
         *	Update ip_vs seq stuff if len has changed.
         */
        if (diff != 0 && cp->protocol == IPPROTO_TCP)
                vs_seq_update(cp, &cp->out_seq,
                              IP_VS_CONN_F_OUT_SEQ, seq, diff);

        return diff;
}


/*
 *	Input pkt hook. Will call bound ip_vs_app specific function
 *	called by ip_fw_demasquerade(), assumes previously checked cp!=NULL.
 *	returns (new - old) skb->len diff.
 */
int ip_vs_app_pkt_in(struct ip_vs_conn *cp, struct sk_buff **skb_p)
{
        struct ip_vs_app * vapp;
        int diff;
        struct iphdr *iph;
        struct tcphdr *th;
        __u32 seq;

        /*
         *	check if application module is bound to
         *	this ip_vs_conn.
         */
        if ( (vapp = cp->app) == NULL)
                return 0;

        iph = (*skb_p)->nh.iph;
        th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);

        /*
         *	Remember seq number in case this pkt gets resized
         */
        seq = ntohl(th->seq);

        /*
         *	Fix seq stuff if flagged as so.
         */
        if (cp->protocol == IPPROTO_TCP) {
                if (cp->flags & IP_VS_CONN_F_IN_SEQ)
                        vs_fix_seq(&cp->in_seq, th);
                if (cp->flags & IP_VS_CONN_F_OUT_SEQ)
                        vs_fix_ack_seq(&cp->out_seq, th);
        }
        
        /*
         *	Call private input hook function
         */
        if ( vapp->pkt_in == NULL )
                return 0;

        diff = vapp->pkt_in(vapp, cp, skb_p);

        /*
         *	Update ip_vs seq stuff if len has changed.
         */
        if (diff != 0 && cp->protocol == IPPROTO_TCP)
                vs_seq_update(cp, &cp->in_seq,
                              IP_VS_CONN_F_IN_SEQ, seq, diff);
        
        return diff;
}


/*
 *	/proc/net/ip_vs_app entry function
 */
static int ip_vs_app_getinfo(char *buffer, char **start, off_t offset,
			     int length)
{
        off_t pos=0;
        int len=0;
        char temp[64];
        int idx;
        struct ip_vs_app *vapp;
        struct list_head *e;

        pos = 64;
        if (pos > offset) {
                len += sprintf(buffer+len, "%-63s\n",
                               "prot port    usecnt name");
        }

        read_lock_bh(&__ip_vs_app_lock);
        for (idx=0 ; idx < IP_VS_APP_TAB_SIZE; idx++) {
                list_for_each (e, &ip_vs_app_base[idx]) {
                        vapp = list_entry(e, struct ip_vs_app, n_list);

                        pos += 64;
                        if (pos <= offset)
                                continue;
                        if (pos > offset+length) {
                                pos -= 64;
                                goto done;
                        }
                        sprintf(temp, "%-3s  %-7u %-6d %-17s",
                                ip_vs_proto_name(IP_VS_APP_PROTO(vapp->type)),
                                IP_VS_APP_PORT(vapp->type),
                                GET_USE_COUNT(vapp->module),
                                vapp->name);
                        len += sprintf(buffer+len, "%-63s\n", temp);
                }
        }
  done:
        read_unlock_bh(&__ip_vs_app_lock);

        *start = buffer+len-(pos-offset);       /* Start of wanted data */
        len = pos-offset;
        if (len > length)
                len = length;
        if (len < 0)
                len = 0;
	return len;
}


/*
 *	Replace a segment (of skb->data) with a new one.
 *	FIXME: Should re-use same skb if space available, this could
 *	       be done if n_len < o_len, unless some extra space
 *	       were already allocated at driver level :P .
 */
static struct sk_buff *skb_replace(struct sk_buff *skb, int pri, char *o_buf,
                                   int o_len, char *n_buf, int n_len)
{
        int maxsize, diff, o_offset;
        struct sk_buff *n_skb;
        int offset;

        maxsize = skb->truesize;
        diff = n_len - o_len;
        o_offset = o_buf - (char*) skb->data;

        if (maxsize <= n_len) {
                if (diff != 0) {
                        memcpy(skb->data + o_offset + n_len,o_buf + o_len,
                               skb->len - (o_offset + o_len));
                }

                memcpy(skb->data + o_offset, n_buf, n_len);

                n_skb    = skb;
                skb->len = n_len;
                skb->end = skb->head+n_len;
        } else {
                /*
                 * 	Sizes differ, make a copy.
                 *
                 *	FIXME: move this to core/sbuff.c:skb_grow()
                 */
                n_skb = alloc_skb(MAX_HEADER + skb->len + diff, pri);
                if (n_skb == NULL) {
                        IP_VS_ERR("skb_replace(): no room left (from %p)\n",
                                  __builtin_return_address(0));
                        return skb;

                }
                skb_reserve(n_skb, MAX_HEADER);
                skb_put(n_skb, skb->len + diff);

                /*
                 *	Copy as much data from the old skb as possible. Even
                 *	though we're only forwarding packets, we need stuff
                 *	like skb->protocol (PPP driver wants it).
                 */
                offset = n_skb->data - skb->data;
                n_skb->nh.raw = skb->nh.raw + offset;
                n_skb->h.raw = skb->h.raw + offset;
                n_skb->dev = skb->dev;
                n_skb->mac.raw = skb->mac.raw + offset;
                n_skb->pkt_type = skb->pkt_type;
                n_skb->protocol = skb->protocol;
                n_skb->ip_summed = skb->ip_summed;
                n_skb->dst = dst_clone(skb->dst);

                /*
                 * Copy pkt in new buffer
                 */
                memcpy(n_skb->data, skb->data, o_offset);
                memcpy(n_skb->data + o_offset, n_buf, n_len);
                memcpy(n_skb->data + o_offset + n_len, o_buf + o_len,
                       skb->len - (o_offset + o_len) );

                /*
                 * Problem, how to replace the new skb with old one,
                 * preferably inplace
                 */
                kfree_skb(skb);
        }

        return n_skb;
}


/*
 *	calls skb_replace() and update ip header if new skb was allocated
 */
struct sk_buff *ip_vs_skb_replace(struct sk_buff *skb, int pri, char *o_buf,
                                  int o_len, char *n_buf, int n_len)
{
        int diff;
        struct sk_buff *n_skb;
        unsigned skb_len;

	EnterFunction(9);

        diff = n_len - o_len;
        n_skb = skb_replace(skb, pri, o_buf, o_len, n_buf, n_len);
        skb_len = skb->len;

        if (diff)
        {
                struct iphdr *iph;
                IP_VS_DBG(9, "pkt resized for %d bytes (len=%d)\n",
                          diff, skb->len);
                /*
                 * 	update ip header
                 */
                iph = n_skb->nh.iph;
                iph->tot_len = htons(skb_len + diff);
                iph->check = 0;
                iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
        }
        
        LeaveFunction(9);
        return n_skb;
}


int ip_vs_app_init(void)
{
        int idx;

        for (idx=0 ; idx < IP_VS_APP_TAB_SIZE; idx++) {
                INIT_LIST_HEAD(&ip_vs_app_base[idx]);
        }
        
        /* we will replace it with proc_net_ipvs_create() soon */
        proc_net_create("ip_vs_app", 0, ip_vs_app_getinfo);
        return 0;
}

void ip_vs_app_cleanup(void)
{
        proc_net_remove("ip_vs_app");
}
