/*
 * Copyright (C) 2004, 2005 Mike Wray <mike.wray@hp.com>
 *
 * 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.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free software Foundation, Inc.,
 * 59 Temple Place, suite 330, Boston, MA 02111-1307 USA
 *
 */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/uaccess.h>

#include <linux/init.h>

#include <linux/version.h>

#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/net.h>
#include <linux/in.h>
#include <linux/inet.h>

#include <net/ip.h>
#include <net/protocol.h>
#include <net/route.h>

#include <linux/if_ether.h>
#include <linux/icmp.h>

#include <asm/scatterlist.h>
#include <linux/crypto.h>
#include <linux/pfkeyv2.h>
#include <linux/random.h>

#include <esp.h>
#include <sa.h>
#include <sa_algorithm.h>
#include <tunnel.h>
#include <vnet.h>
#include <skb_util.h>
#include <skb_context.h>

static const int DEBUG_ICV = 0;

#define MODULE_NAME "IPSEC"
#define DEBUG 1
#undef DEBUG
#include "debug.h"

#ifndef CONFIG_CRYPTO_HMAC
#warning No esp transform - CONFIG_CRYPTO_HMAC not defined

int __init esp_module_init(void){
    return 0;
}

void __exit esp_module_exit(void){
}

#else

/* Outgoing packet:                            [ eth | ip | data ]
 * After etherip:        [ eth2 | ip2 |  ethip | eth | ip | data ]
 * After esp   :   [ eth2 | ip2 | esp | {ethip | eth | ip | data} | pad | icv ]
 *                                                        ^     +
 * The curly braces { ... } denote encryption.
 * The esp header includes the fixed esp headers and the iv (variable size).
 * The point marked ^ does not move. To the left is in the header, to the right
 * is in the frag. Remember that all outgoing skbs (from domains) have 1 frag.
 * Data after + is added by esp, using an extra frag.
 *
 * Incoming as above.
 * After decrypt:  [ eth2 | ip2 | esp |  ethip | eth | ip | data  | pad | icv ]
 * Trim tail:      [ eth2 | ip2 | esp |  ethip | eth | ip | data ]
 * Drop hdr:             [ eth2 | ip2 |  ethip | eth | ip | data ]
 *                                    ^
 * The point marked ^ does not move. Incoming skbs are linear (no frags).
 * The tail is trimmed by adjusting skb->tail and len.
 * The esp hdr is dropped by using memmove to move the headers and
 * adjusting the skb pointers.
 *
 * todo: Now this code is in linux we can't assume 1 frag for outbound skbs,
 * or (maybe) that memmove is safe on inbound.
 */

/** Round n up to a multiple of block.
 * If block is less than 2 does nothing.
 * Otherwise assume block is a power of 2.
 *
 * @param n to round up
 * @param block size to round to a multiple of
 * @return rounded value
 */
static inline int roundupto(int n, int block){
    if(block <= 1) return n;
    block--;
    return (n + block) & ~block;
}

/** Check if n is a multiple of block.
 * If block is less than 2 returns 1.
 * Otherwise assumes block is a power of 2.
 *
 * @param n to check
 * @param block block size
 * @return 1 if a multiple, 0 otherwise
 */
static inline int multipleof(int n, int block){
    if(block <= 1) return 1;
    block--;
    return !(n & block);
}

/** Convert from bits to bytes.
 *
 * @param n number of bits
 * @return number of bytes
 */
static inline int bits_to_bytes(int n){
    return n / 8;
}


/** Insert esp padding at the end of an skb.
 * Inserts padding bytes, number of padding bytes, protocol number.
 *
 * @param skb skb
 * @param offset offset from skb end to where padding should end
 * @param extra_n total amount of padding
 * @param protocol protocol number (from original ip hdr)
 * @return 0 on success, error code otherwise
 */
static int esp_sa_pad(struct sk_buff *skb, int offset, int extra_n,
                      unsigned char protocol){
    int err;
    char *data;
    int pad_n = extra_n - ESP_PAD_N;
    int i;
    char buf[extra_n];

    data = buf;
    for(i = 1; i <= pad_n; i++){
        *data++ = i;
    }
    *data++ = pad_n;
    *data++ = protocol;
    err = skb_put_bits(skb, skb->len - offset - extra_n, buf, extra_n);
    return err;
}

/** Encrypt skb. Skips esp header and iv.
 * Assumes skb->data points at esp header.
 *
 * @param esp esp state
 * @parm esph esp header
 * @param skb packet
 * @param head_n size of esp header and iv
 * @param iv_n size of iv
 * @param text_n size of ciphertext
 * @return 0 on success, error code otherwise
 */
static int esp_sa_encrypt(ESPState *esp, ESPHdr *esph, struct sk_buff *skb,
                   int head_n, int iv_n, int text_n){
    int err = 0;
    int sg_n = skb_shinfo(skb)->nr_frags + 1;
    struct scatterlist sg[sg_n];

    err = skb_scatterlist(skb, sg, &sg_n, head_n, text_n);
    if(err) goto exit;
    if(iv_n){
        crypto_cipher_set_iv(esp->cipher.tfm, esp->cipher.iv, iv_n);
    }
    crypto_cipher_encrypt(esp->cipher.tfm, sg, sg, text_n);
    if(iv_n){
        memcpy(esph->data, esp->cipher.iv, iv_n);
        crypto_cipher_get_iv(esp->cipher.tfm, esp->cipher.iv, iv_n);
    }
  exit:
    return err;
}

/** Decrypt skb. Skips esp header and iv.
 * Assumes skb->data points at esp header.
 *
 * @param esp esp state
 * @parm esph esp header
 * @param skb packet
 * @param head_n size of esp header and iv
 * @param iv_n size of iv
 * @param text_n size of ciphertext
 * @return 0 on success, error code otherwise
 */
static int esp_sa_decrypt(ESPState *esp, ESPHdr *esph, struct sk_buff *skb,
                   int head_n, int iv_n, int text_n){
    int err = 0;
    int sg_n = skb_shinfo(skb)->nr_frags + 1;
    struct scatterlist sg[sg_n];

    err = skb_scatterlist(skb, sg, &sg_n, head_n, text_n);
    if(err) goto exit;
    if(iv_n){
        crypto_cipher_set_iv(esp->cipher.tfm, esph->data, iv_n);
    }
    crypto_cipher_decrypt(esp->cipher.tfm, sg, sg, text_n);
  exit:
    return err;
}

/** Compute icv. Includes esp header, iv and ciphertext.
 * Assumes skb->data points at esp header.
 *
 * @param esp esp state
 * @param skb packet
 * @param digest_n number of bytes to digest
 * @param icv_n size of icv
 * @return 0 on success, error code otherwise
 */
static int esp_sa_digest(ESPState *esp, struct sk_buff *skb, int digest_n, int icv_n){
    int err = 0;
    u8 icv[icv_n];
    
    if(DEBUG_ICV){
        dprintf("> skb digest_n=%d icv_n=%d\n", digest_n, icv_n);
        skb_print_bits("esp", skb, 0, digest_n);
    }
    memset(icv, 0, icv_n);
    esp->digest.icv(esp, skb, 0, digest_n, icv);
    skb_put_bits(skb, digest_n, icv, icv_n);
    return err;
}

/** Check the icv and trim it from the skb tail.
 *
 * @param sa sa state
 * @param esp esp state
 * @param esph esp header
 * @param skb packet
 * @return 0 on success, error code otherwise
 */
static int esp_check_icv(SAState *sa, ESPState *esp, ESPHdr *esph, struct sk_buff *skb){
    int err = 0;
    int icv_n = esp->digest.icv_n;
    int digest_n = skb->len - icv_n;
    u8 icv_skb[icv_n];
    u8 icv_new[icv_n];

    dprintf(">\n");
    if(DEBUG_ICV){
        dprintf("> skb len=%d digest_n=%d icv_n=%d\n",
                skb->len, digest_n, icv_n);
        skb_print_bits("esp", skb, 0, skb->len);
    }
    if(skb_copy_bits(skb, digest_n, icv_skb, icv_n)){
        wprintf("> Error getting icv from skb\n");
        goto exit;
    }
    esp->digest.icv(esp, skb, 0, digest_n, icv_new);
    if(DEBUG_ICV){
        dprintf("> len=%d icv_n=%d", digest_n, icv_n);
        printk("\nskb="); buf_print(icv_skb, icv_n);
        printk("new="); buf_print(icv_new, icv_n);
    }
    if(unlikely(memcmp(icv_new, icv_skb, icv_n))){
        wprintf("> ICV check failed!\n");
        err = -EINVAL;
        sa->counts.integrity_failures++;
        goto exit;
    }
    skb_trim_tail(skb, icv_n);
  exit:
    dprintf("< err=%d\n", err);
    return err;
}

/** Send a packet via an ESP SA.
 *
 * @param sa SA state
 * @param skb packet to send
 * @param tunnel underlying tunnel
 * @return 0 on success, negative error code otherwise
 */
static int esp_sa_send(SAState *sa, struct sk_buff *skb, Tunnel *tunnel){
    int err = 0;
    int ip_n;           // Size of ip header.
    int plaintext_n;	// Size of plaintext.
    int ciphertext_n;   // Size of ciphertext (including padding).
    int extra_n;        // Extra bytes needed for ciphertext.
    int icv_n = 0;      // Size of integrity check value (icv).
    int iv_n = 0;       // Size of initialization vector (iv).
    int head_n;         // Size of esp header and iv.
    int tail_n;         // Size of esp trailer: padding and icv.
    ESPState  *esp;
    ESPHdr *esph;

    dprintf(">\n");
    esp = sa->data;
    ip_n = (skb->nh.iph->ihl << 2);
    // Assuming skb->data points at ethernet header, exclude ethernet
    // header and IP header.
    plaintext_n = skb->len - ETH_HLEN - ip_n;
    // Add size of padding fields.
    ciphertext_n = roundupto(plaintext_n + ESP_PAD_N, esp->cipher.block_n);
    if(esp->cipher.pad_n > 0){
        ciphertext_n = roundupto(ciphertext_n, esp->cipher.pad_n);
    }
    extra_n = ciphertext_n - plaintext_n;
    iv_n = esp->cipher.iv_n;
    icv_n = esp->digest.icv_n;
    dprintf("> len=%d plaintext=%d ciphertext=%d extra=%d\n",
            skb->len, plaintext_n, ciphertext_n, extra_n);
    dprintf("> iv=%d icv=%d\n", iv_n, icv_n);
    skb_print_bits("iv", skb, 0, skb->len);

    // Add headroom for esp header and iv, tailroom for the ciphertext
    // and icv.
    head_n = ESP_HDR_N + iv_n;
    tail_n = extra_n + icv_n;
    err = skb_make_room(&skb, skb, head_n, tail_n);
    if(err) goto exit;
    dprintf("> skb=%p\n", skb);
    // Move the headers up to make space for the esp header.  We can
    // use memmove() since all this data fits in the skb head.
    // todo: Can't assume this anymore?
    dprintf("> header push...\n");
    __skb_push(skb, head_n);
    if(0 && skb->mac.raw){
        dprintf("> skb->mac=%p\n", skb->mac.raw);
        dprintf("> ETH header pull...\n");
        memmove(skb->data, skb->mac.raw, ETH_HLEN);
        skb->mac.raw = skb->data; 
        __skb_pull(skb, ETH_HLEN);
    }
    dprintf("> IP header pull...\n");
    memmove(skb->data, skb->nh.raw, ip_n);
    skb->nh.raw = skb->data;
    __skb_pull(skb, ip_n);
    esph = (void*)skb->data;
    // Add spi and sequence number.
    esph->spi = sa->ident.spi;
    esph->seq = htonl(++sa->replay.send_seq);
    // Insert the padding bytes: extra bytes less the pad fields
    // themselves.
    dprintf("> esp_sa_pad ...\n");
    esp_sa_pad(skb, icv_n, extra_n, skb->nh.iph->protocol);
    if(sa->security & SA_CONF){
        dprintf("> esp_sa_encrypt...\n");
        err = esp_sa_encrypt(esp, esph, skb, head_n, iv_n, ciphertext_n);
        if(err) goto exit;
    }
    if(icv_n){
        dprintf("> esp_sa_digest...\n");
        err = esp_sa_digest(esp, skb, head_n + ciphertext_n, icv_n);
        if(err) goto exit;
    }
    dprintf("> IP header push...\n");
    __skb_push(skb, ip_n);
    if(0 && skb->mac.raw){
        dprintf("> ETH header push...\n");
        __skb_push(skb, ETH_HLEN);
    }
    // Fix ip header. Adjust length field, set protocol, zero
    // checksum.
    {
        // Total packet length (bytes).
        int tot_len = ntohs(skb->nh.iph->tot_len);
        tot_len += head_n;
        tot_len += tail_n;
        skb->nh.iph->protocol = IPPROTO_ESP;
        skb->nh.iph->tot_len  = htons(tot_len);
        skb->nh.iph->check    = 0;
    }
    err = Tunnel_send(tunnel, skb);
  exit:
    dprintf("< err=%d\n", err);
    return err;
}

/** Release an skb context.
 * Drops the refcount on the SA.
 *
 * @param context to free
 */
static void esp_context_free_fn(SkbContext *context){
    SAState *sa;
    if(!context) return;
    sa = context->data;
    if(!sa) return;
    context->data = NULL;
    SAState_decref(sa);
}   

/** Receive a packet via an ESP SA.
 * Does ESP receive processing (check icv, decrypt), strips
 * ESP header and re-receives.
 *
 * If return 1 the packet has been freed.
 * If return <= 0 the caller must free.
 *
 * @param sa SA
 * @param skb packet
 * @return >= 0 on success, negative protocol otherwise
 */
static int esp_sa_recv(SAState *sa, struct sk_buff *skb){
    int err = -EINVAL;
    int mine = 0;
    int vnet = 0; //todo: fixme - need to record skb vnet somewhere
    ESPState *esp;
    ESPHdr *esph;
    ESPPadding *pad;
    int block_n;	// Cipher blocksize.
    int icv_n;          // Size of integrity check value (icv).
    int iv_n;           // Size of initialization vector (iv).
    int text_n;         // Size of text (ciphertext or plaintext).
    int head_n;         // Size of esp header and iv.

    dprintf("> skb=%p\n", skb);
    // Assumes skb->data points at esp hdr.
    esph = (void*)skb->data;
    esp = sa->data;
    block_n = crypto_tfm_alg_blocksize(esp->cipher.tfm);
    icv_n = esp->digest.icv_n;
    iv_n = esp->cipher.iv_n;
    head_n = ESP_HDR_N + iv_n;
    text_n = skb->len - head_n - icv_n;
    if(text_n < ESP_PAD_N || !multipleof(text_n, block_n)){
        wprintf("> Invalid size: text_n=%d tfm:block_n=%d esp:block_n=%d\n",
                text_n, block_n, esp->cipher.block_n);
        goto exit;
    }
    if(icv_n){
        err = esp_check_icv(sa, esp, esph, skb);
        if(err) goto exit;
    }
    mine = 1;
    if(sa->security & SA_CONF){
        err = esp_sa_decrypt(esp, esph, skb, head_n, iv_n, text_n);
        if(err) goto exit;
    }
    // Strip esp header by moving the other headers down.
    //todo Maybe not safe to do this anymore.
    memmove(skb->mac.raw + head_n, skb->mac.raw, (skb->data - skb->mac.raw));
    skb->mac.raw += head_n;
    skb->nh.raw  += head_n;
    // Move skb->data back to ethernet header.
    // Do in 2 moves to ensure offsets are +ve,
    // since args to skb_pull/skb_push are unsigned.
    __skb_pull(skb, head_n);
    __skb_push(skb, skb->data - skb->mac.raw);
    // After this esph is invalid.
    esph = NULL;
    // Trim padding, restore protocol in IP header.
    pad = skb_trim_tail(skb, ESP_PAD_N);
    text_n -= ESP_PAD_N;
    if((pad->pad_n > 255) | (pad->pad_n > text_n)){
        wprintf("> Invalid padding: pad_n=%d text_n=%d\n", pad->pad_n, text_n);
        goto exit;
    }
    skb_trim_tail(skb, pad->pad_n);
    skb->nh.iph->protocol = pad->protocol;
    err = skb_push_context(skb, vnet, sa->ident.addr, IPPROTO_ESP,
                           sa, esp_context_free_fn);
    if(err) goto exit;
    // Increase sa refcount now the skb context refers to it.
    // Refcount is decreased by esp_context_free_fn.
    SAState_incref(sa);
    // Deliver skb to be received by network code.
    // Not safe to refer to the skb after this.
    // todo: return -skb->nh.iph->protocol instead?
    netif_rx(skb);
  exit:
    if(mine){
        if(err < 0){
            kfree_skb(skb);
        }
        err = 1;
    }
    dprintf("< skb=%p err=%d\n", skb, err);
    return err;
}

/** Estimate the packet size for some data using ESP processing.    
 *
 * @param sa ESP SA
 * @param data_n data size
 * @return size after ESP processing
 */
static u32 esp_sa_size(SAState *sa, int data_n){
    // Even in transport mode have to round up to blocksize.
    // Have to add some padding for alignment even if pad_n is zero.
    ESPState *esp = sa->data;
    
    data_n = roundupto(data_n + ESP_PAD_N, esp->cipher.block_n);
    if(esp->cipher.pad_n > 0){
        data_n = roundupto(data_n, esp->cipher.pad_n);
    }
    data_n += esp->digest.icv_n;
    //data_n += esp->cipher.iv_n;
    data_n += ESP_HDR_N;
    return data_n;
}

/** Compute an icv using HMAC digest.
 *
 * @param esp ESP state
 * @param skb packet to digest
 * @param offset offset to start at
 * @param len number of bytes to digest
 * @param icv return parameter for ICV
 * @return 0 on success, negative error code otherwise
 */
static inline void esp_hmac_digest(ESPState *esp, struct sk_buff *skb,
                                   int offset, int len, u8 *icv){
    int err = 0;
    struct crypto_tfm *digest = esp->digest.tfm;
    char *icv_tmp = esp->digest.icv_tmp;
    int sg_n = skb_shinfo(skb)->nr_frags + 1;
    struct scatterlist sg[sg_n];

    dprintf("> offset=%d len=%d\n", offset, len);
    memset(icv, 0, esp->digest.icv_n);
    if(DEBUG_ICV){
        dprintf("> key len=%d\n", esp->digest.key_n);
        printk("\nkey=");
        buf_print(esp->digest.key,esp->digest.key_n); 
    }
    crypto_hmac_init(digest, esp->digest.key, &esp->digest.key_n);
    err = skb_scatterlist(skb, sg, &sg_n, offset, len);
    crypto_hmac_update(digest, sg, sg_n);
    crypto_hmac_final(digest, esp->digest.key, &esp->digest.key_n, icv_tmp);
    if(DEBUG_ICV){
        dprintf("> digest len=%d ", esp->digest.icv_n);
        printk("\nval=");
        buf_print(icv_tmp, esp->digest.icv_n);
    }
    memcpy(icv, icv_tmp, esp->digest.icv_n);
    dprintf("<\n");
}

/** Finish up an esp state.
 * Releases the digest, cipher, iv and frees the state.
 *
 * @parma esp state
 */
static void esp_fini(ESPState *esp){
    if(!esp) return;
    if(esp->digest.tfm){
        crypto_free_tfm(esp->digest.tfm);
        esp->digest.tfm = NULL; 
    }
    if(esp->digest.icv_tmp){
        kfree(esp->digest.icv_tmp);
        esp->digest.icv_tmp = NULL;
    }
    if(esp->cipher.tfm){
        crypto_free_tfm(esp->cipher.tfm);
        esp->cipher.tfm = NULL;
    }
    if(esp->cipher.iv){
        kfree(esp->cipher.iv);
        esp->cipher.iv = NULL;
    }
    kfree(esp);
}

/** Release an ESP SA.
 *
 * @param sa ESO SA
 */
static void esp_sa_fini(SAState *sa){
    ESPState *esp;
    if(!sa) return;
    esp = sa->data;
    if(!esp) return;
    esp_fini(esp);
    sa->data = NULL;
}

/** Initialize the cipher for an ESP SA.
 *
 * @param sa ESP SA
 * @param esp ESP state
 * @return 0 on success, negative error code otherwise
 */
static int esp_cipher_init(SAState *sa, ESPState *esp){
    int err = 0; 
    SAAlgorithm *algo = NULL;
    int cipher_mode = CRYPTO_TFM_MODE_CBC;

    dprintf("> sa=%p esp=%p\n", sa, esp);
    dprintf("> cipher=%s\n", sa->cipher.name);
    algo = sa_cipher_by_name(sa->cipher.name);
    if(!algo){
        wprintf("> Cipher unavailable: %s\n", sa->cipher.name);
        err = -EINVAL;
        goto exit;
    }
    esp->cipher.key_n = roundupto(sa->cipher.bits, 8);
    // If cipher is null must use ECB because CBC algo does not support blocksize 1.
    if(strcmp(sa->cipher.name, "cipher_null")){
        cipher_mode = CRYPTO_TFM_MODE_ECB;
    }
    esp->cipher.tfm = crypto_alloc_tfm(sa->cipher.name, cipher_mode);
    if(!esp->cipher.tfm){
        err = -ENOMEM;
        goto exit;
    }
    esp->cipher.block_n = roundupto(crypto_tfm_alg_blocksize(esp->cipher.tfm), 4);
    esp->cipher.iv_n = crypto_tfm_alg_ivsize(esp->cipher.tfm);
    esp->cipher.pad_n = 0;
    if(esp->cipher.iv_n){
        esp->cipher.iv = kmalloc(esp->cipher.iv_n, GFP_KERNEL);
        get_random_bytes(esp->cipher.iv, esp->cipher.iv_n);
    }
    crypto_cipher_setkey(esp->cipher.tfm, esp->cipher.key, esp->cipher.key_n);
    err = 0;
  exit:
    dprintf("< err=%d\n", err);
    return err;
}

/** Initialize the digest for an ESP SA.
 *
 * @param sa ESP SA
 * @param esp ESP state
 * @return 0 on success, negative error code otherwise
 */
static int esp_digest_init(SAState *sa, ESPState *esp){
    int err = 0;
    SAAlgorithm *algo = NULL;
    
    dprintf(">\n");
    esp->digest.key = sa->digest.key;
    esp->digest.key_n = bits_to_bytes(roundupto(sa->digest.bits, 8));
    esp->digest.tfm = crypto_alloc_tfm(sa->digest.name, 0);
    if(!esp->digest.tfm){
        err = -ENOMEM;
        goto exit;
    }
    algo = sa_digest_by_name(sa->digest.name);
    if(!algo){
        wprintf("> Digest unavailable: %s\n", sa->digest.name);
        err = -EINVAL;
        goto exit;
    }
    esp->digest.icv = esp_hmac_digest;
    esp->digest.icv_full_n = bits_to_bytes(algo->info.digest.icv_fullbits);
    esp->digest.icv_n = bits_to_bytes(algo->info.digest.icv_truncbits);
    
    if(esp->digest.icv_full_n != crypto_tfm_alg_digestsize(esp->digest.tfm)){
        err = -EINVAL;
        wprintf("> digest %s, size %u != %hu\n",
                sa->digest.name,
                crypto_tfm_alg_digestsize(esp->digest.tfm),
                esp->digest.icv_full_n);
        goto exit;
    }
    
    esp->digest.icv_tmp = kmalloc(esp->digest.icv_full_n, GFP_KERNEL);
    if(!esp->digest.icv_tmp){
        err = -ENOMEM;
        goto exit;
    }
  exit:
    dprintf("< err=%d\n", err);
    return err;
}

/** Initialize an ESP SA.
 *
 * @param sa ESP SA
 * @param args arguments
 * @return 0 on success, negative error code otherwise
 */
static int esp_sa_init(SAState *sa, void *args){
    int err = 0;
    ESPState *esp = NULL;
    
    dprintf("> sa=%p\n", sa);
    esp = kmalloc(sizeof(*esp), GFP_KERNEL);
    if(!esp){
        err = -ENOMEM;
        goto exit;
    }
    *esp = (ESPState){};
    err = esp_cipher_init(sa, esp);
    if(err) goto exit;
    err = esp_digest_init(sa, esp);
    if(err) goto exit;
    sa->data = esp;
  exit:
    if(err){
        if(esp) esp_fini(esp);
    }
    dprintf("< err=%d\n", err);
    return err;
}

/** SA type for ESP.
 */
static SAType esp_sa_type = {
    .name     = "ESP",
    .protocol = IPPROTO_ESP,
    .init     = esp_sa_init,
    .fini     = esp_sa_fini,
    .size     = esp_sa_size,
    .recv     = esp_sa_recv,
    .send     = esp_sa_send
};

/** Get the ESP header from a packet.
 *
 * @param skb packet
 * @param esph return parameter for header
 * @return 0 on success, negative error code otherwise
 */
static int esp_skb_header(struct sk_buff *skb, ESPHdr **esph){
    int err = 0;
    if(skb->len < ESP_HDR_N){
        err = -EINVAL;
        goto exit;
    }
    *esph = (ESPHdr*)skb->data;
  exit:
    return err;
}

/** Handle an incoming skb with ESP protocol.
 *
 * Lookup spi, if state found hand to the state.
 * If no state, check spi, if ok, create state and pass to it.
 * If spi not ok, drop.
 *
 * Return value convention for protocols:
 * >= 0 Protocol took the packet
 * < 0  A -ve protocol id the packet should be re-received as.
 *
 * So always return >=0 if we took the packet, even if we dropped it.
 * 
 * @param skb packet
 * @return 0 on sucess, negative protocol number otherwise
 */
static int esp_protocol_recv(struct sk_buff *skb){
    int err = 0;
    const int eth_n = ETH_HLEN;
    int ip_n;
    ESPHdr *esph = NULL;
    SAState *sa = NULL;
    u32 addr;
    
    dprintf(">\n");
#ifdef DEBUG
    dprintf("> recv skb=\n"); 
    skb_print_bits(skb, 0, skb->len);
#endif
    ip_n = (skb->nh.iph->ihl << 2);
    if(skb->data == skb->mac.raw){
        // skb->data points at ethernet header.
        if (!pskb_may_pull(skb, eth_n + ip_n)){
            wprintf("> Malformed skb\n");
            err = -EINVAL;
            goto exit;
        }
        skb_pull(skb, eth_n + ip_n);
    }
    addr = skb->nh.iph->daddr;
    err = esp_skb_header(skb, &esph);
    if(err) goto exit;
    dprintf("> spi=%08x protocol=%d addr=" IPFMT "\n",
            esph->spi, IPPROTO_ESP, NIPQUAD(addr));
    sa = sa_table_lookup_spi(esph->spi, IPPROTO_ESP, addr);
    if(!sa){
        err = vnet_sa_create(esph->spi, IPPROTO_ESP, addr, &sa);
        if(err) goto exit;
    }
    //todo: Return a -ve protocol instead? See esp_sa_recv.
    err = SAState_recv(sa, skb);
  exit:
    if(sa) SAState_decref(sa);
    if(err <= 0){
        kfree_skb(skb);
        err = 0;
    }
    dprintf("< err=%d\n", err);
    return err;
}

/** Handle an ICMP error related to ESP.
 *
 * @param skb ICMP error packet
 * @param info
 */
static void esp_protocol_icmp_err(struct sk_buff *skb, u32 info){
    struct iphdr *iph = (struct iphdr*)skb->data;
    ESPHdr *esph;
    SAState *sa;
    
    dprintf("> ICMP error type=%d code=%d\n",
            skb->h.icmph->type, skb->h.icmph->code);
    if(skb->h.icmph->type != ICMP_DEST_UNREACH ||
       skb->h.icmph->code != ICMP_FRAG_NEEDED){
        return;
    }
    
    //todo: need to check skb has enough len to do this.
    esph = (ESPHdr*)(skb->data + (iph->ihl << 2));
    sa = sa_table_lookup_spi(esph->spi, IPPROTO_ESP, iph->daddr);
    if(!sa) return;
    wprintf("> ICMP unreachable on SA ESP spi=%08x addr=" IPFMT "\n",
            ntohl(esph->spi), NIPQUAD(iph->daddr));
    SAState_decref(sa);
}

//============================================================================
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
// Code for 2.6 kernel.

/** Protocol handler for ESP.
 */
static struct net_protocol esp_protocol = {
    .handler     = esp_protocol_recv,
    .err_handler = esp_protocol_icmp_err
};

static int esp_protocol_add(void){
    return inet_add_protocol(&esp_protocol, IPPROTO_ESP);
}

static int esp_protocol_del(void){
    return inet_del_protocol(&esp_protocol, IPPROTO_ESP);
}

//============================================================================
#else
//============================================================================
// Code for 2.4 kernel.

/** Protocol handler for ESP.
 */
static struct inet_protocol esp_protocol = {
    .name        = "ESP",
    .protocol    = IPPROTO_ESP,
    .handler     = esp_protocol_recv,
    .err_handler = esp_protocol_icmp_err
};

static int esp_protocol_add(void){
    inet_add_protocol(&esp_protocol);
    return 0;
}

static int esp_protocol_del(void){
    return inet_del_protocol(&esp_protocol);
}

#endif
//============================================================================


/** Initialize the ESP module.
 * Registers the ESP protocol and SA type.
 *
 * @return 0 on success, negative error code otherwise
 */
int __init esp_module_init(void){
    int err = 0;
    dprintf(">\n");
    err = SAType_add(&esp_sa_type);
    if(err < 0){
        eprintf("> Error adding esp sa type\n");
        goto exit;
    }
    esp_protocol_add();
  exit:
    dprintf("< err=%d\n", err);
    return err;
}

/** Finalize the ESP module.
 * Deregisters the ESP protocol and SA type.
 */
void __exit esp_module_exit(void){
    if(esp_protocol_del() < 0){
        eprintf("> Error removing esp protocol\n");
    }
    if(SAType_del(&esp_sa_type) < 0){
        eprintf("> Error removing esp sa type\n");
    }
}

#endif // CONFIG_CRYPTO_HMAC
