Logo Search packages:      
Sourcecode: strongswan version File versions  Download package

ipsec_rcv.c

/*
 * receive code
 * Copyright (C) 1996, 1997  John Ioannidis.
 * Copyright (C) 1998, 1999, 2000, 2001  Richard Guy Briggs.
 *
 * 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.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * 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.
 */

char ipsec_rcv_c_version[] = "RCSID $Id: ipsec_rcv.c,v 1.5 2005/04/10 21:38:32 as Exp $";

#include <linux/config.h>
#include <linux/version.h>

#define __NO_VERSION__
#include <linux/module.h>
#include <linux/kernel.h> /* printk() */

#include "freeswan/ipsec_param.h"

#ifdef MALLOC_SLAB
# include <linux/slab.h> /* kmalloc() */
#else /* MALLOC_SLAB */
# include <linux/malloc.h> /* kmalloc() */
#endif /* MALLOC_SLAB */
#include <linux/errno.h>  /* error codes */
#include <linux/types.h>  /* size_t */
#include <linux/interrupt.h> /* mark_bh */

#include <linux/netdevice.h>  /* struct device, and other headers */
#include <linux/etherdevice.h>      /* eth_type_trans */
#include <linux/ip.h>         /* struct iphdr */
#include <linux/skbuff.h>
#include <freeswan.h>
#ifdef SPINLOCK
# ifdef SPINLOCK_23
#  include <linux/spinlock.h> /* *lock* */
# else /* SPINLOCK_23 */
#  include <asm/spinlock.h> /* *lock* */
# endif /* SPINLOCK_23 */
#endif /* SPINLOCK */
#ifdef NET_21
# include <asm/uaccess.h>
# include <linux/in6.h>
# define proto_priv cb
#endif /* NET21 */
#include <asm/checksum.h>
#include <net/ip.h>

#include "freeswan/radij.h"
#include "freeswan/ipsec_encap.h"
#include "freeswan/ipsec_sa.h"

#include "freeswan/ipsec_radij.h"
#include "freeswan/ipsec_xform.h"
#include "freeswan/ipsec_tunnel.h"
#include "freeswan/ipsec_rcv.h"

#if defined(CONFIG_IPSEC_ESP) || defined(CONFIG_IPSEC_AH)
#include "freeswan/ipsec_ah.h"
#endif /* defined(CONFIG_IPSEC_ESP) || defined(CONFIG_IPSEC_AH) */

#ifdef CONFIG_IPSEC_ESP
#include "freeswan/ipsec_esp.h"
#endif /* !CONFIG_IPSEC_ESP */

#ifdef CONFIG_IPSEC_IPCOMP
#include "freeswan/ipcomp.h"
#endif /* CONFIG_IPSEC_COMP */

#include <pfkeyv2.h>
#include <pfkey.h>

#include "freeswan/ipsec_proto.h"
#include "freeswan/ipsec_alg.h"

#ifdef CONFIG_IPSEC_DEBUG
int debug_ah = 0;
int debug_esp = 0;
int debug_rcv = 0;
#endif /* CONFIG_IPSEC_DEBUG */

int sysctl_ipsec_inbound_policy_check = 1;

#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
#include <linux/udp.h>
#endif

#ifdef CONFIG_IPSEC_DEBUG
static void
rcv_dmp(char *s, caddr_t bb, int len)
{
      int i;
      unsigned char *b = bb;
  
      if (debug_rcv && sysctl_ipsec_debug_verbose) {
            printk(KERN_INFO "klips_debug:ipsec_tunnel_:dmp: "
                   "at %s, len=%d:",
                   s,
                   len);
            for (i=0; i < len; i++) {
                  if(!(i%16)){
                        printk("\nklips_debug:  ");
                  }
                  printk(" %02x", *b++);
            }
            printk("\n");
      }
}
#else /* CONFIG_IPSEC_DEBUG */
#define rcv_dmp(_x, _y, _z) 
#endif /* CONFIG_IPSEC_DEBUG */


#if defined(CONFIG_IPSEC_ESP) || defined(CONFIG_IPSEC_AH)
__u32 zeroes[AH_AMAX];
#endif /* defined(CONFIG_IPSEC_ESP) || defined(CONFIG_IPSEC_AH) */

/*
 * Check-replay-window routine, adapted from the original
 * by J. Hughes, from draft-ietf-ipsec-esp-des-md5-03.txt
 *
 *  This is a routine that implements a 64 packet window. This is intend-
 *  ed on being an implementation sample.
 */

DEBUG_NO_STATIC int
ipsec_checkreplaywindow(struct ipsec_sa*ipsp, __u32 seq)
{
      __u32 diff;

      if (ipsp->ips_replaywin == 0) /* replay shut off */
            return 1;
      if (seq == 0)
            return 0;         /* first == 0 or wrapped */

      /* new larger sequence number */
      if (seq > ipsp->ips_replaywin_lastseq) {
            return 1;         /* larger is good */
      }
      diff = ipsp->ips_replaywin_lastseq - seq;

      /* too old or wrapped */ /* if wrapped, kill off SA? */
      if (diff >= ipsp->ips_replaywin) {
            return 0;
      }
      /* this packet already seen */
      if (ipsp->ips_replaywin_bitmap & (1 << diff))
            return 0;
      return 1;               /* out of order but good */
}

DEBUG_NO_STATIC int
ipsec_updatereplaywindow(struct ipsec_sa*ipsp, __u32 seq)
{
      __u32 diff;

      if (ipsp->ips_replaywin == 0) /* replay shut off */
            return 1;
      if (seq == 0)
            return 0;         /* first == 0 or wrapped */

      /* new larger sequence number */
      if (seq > ipsp->ips_replaywin_lastseq) {
            diff = seq - ipsp->ips_replaywin_lastseq;

            /* In win, set bit for this pkt */
            if (diff < ipsp->ips_replaywin)
                  ipsp->ips_replaywin_bitmap =
                        (ipsp->ips_replaywin_bitmap << diff) | 1;
            else
                  /* This packet has way larger seq num */
                  ipsp->ips_replaywin_bitmap = 1;

            if(seq - ipsp->ips_replaywin_lastseq - 1 > ipsp->ips_replaywin_maxdiff) {
                  ipsp->ips_replaywin_maxdiff = seq - ipsp->ips_replaywin_lastseq - 1;
            }
            ipsp->ips_replaywin_lastseq = seq;
            return 1;         /* larger is good */
      }
      diff = ipsp->ips_replaywin_lastseq - seq;

      /* too old or wrapped */ /* if wrapped, kill off SA? */
      if (diff >= ipsp->ips_replaywin) {
/*
            if(seq < 0.25*max && ipsp->ips_replaywin_lastseq > 0.75*max) {
                  ipsec_sa_delchain(ipsp);
            }
*/
            return 0;
      }
      /* this packet already seen */
      if (ipsp->ips_replaywin_bitmap & (1 << diff))
            return 0;
      ipsp->ips_replaywin_bitmap |= (1 << diff);      /* mark as seen */
      return 1;               /* out of order but good */
}

#ifdef CONFIG_IPSEC_AUTH_HMAC_MD5
struct auth_alg ipsec_rcv_md5[]={
      {MD5Init, MD5Update, MD5Final, AHMD596_ALEN}
};

#endif /* CONFIG_IPSEC_AUTH_HMAC_MD5 */

#ifdef CONFIG_IPSEC_AUTH_HMAC_SHA1
struct auth_alg ipsec_rcv_sha1[]={
      {SHA1Init, SHA1Update, SHA1Final, AHSHA196_ALEN}
};
#endif /* CONFIG_IPSEC_AUTH_HMAC_MD5 */

enum ipsec_rcv_value {
      IPSEC_RCV_LASTPROTO=1,
      IPSEC_RCV_OK=0,
      IPSEC_RCV_BADPROTO=-1,
      IPSEC_RCV_BADLEN=-2,
      IPSEC_RCV_ESP_BADALG=-3,
      IPSEC_RCV_3DES_BADBLOCKING=-4,
      IPSEC_RCV_ESP_DECAPFAIL=-5,
      IPSEC_RCV_DECAPFAIL=-6,
      IPSEC_RCV_SAIDNOTFOUND=-7,
      IPSEC_RCV_IPCOMPALONE=-8,
      IPSEC_RCV_IPCOMPFAILED=-10,
      IPSEC_RCV_SAIDNOTLIVE=-11,
      IPSEC_RCV_FAILEDINBOUND=-12,
      IPSEC_RCV_LIFETIMEFAILED=-13,
      IPSEC_RCV_BADAUTH=-14,
      IPSEC_RCV_REPLAYFAILED=-15,
      IPSEC_RCV_AUTHFAILED=-16,
      IPSEC_RCV_REPLAYROLLED=-17,
      IPSEC_RCV_BAD_DECRYPT=-18
};

struct ipsec_rcv_state {
      struct sk_buff *skb;
      struct net_device_stats *stats;
      struct iphdr *ipp;
      struct ipsec_sa *ipsp;
      int len;
      int ilen;
      int authlen;
      int hard_header_len;
      int iphlen;
      struct auth_alg *authfuncs;
      struct sa_id said;
      char   sa[SATOA_BUF];
      size_t sa_len;
      __u8 next_header;
      __u8 hash[AH_AMAX];
      char ipsaddr_txt[ADDRTOA_BUF];
      char ipdaddr_txt[ADDRTOA_BUF];
      __u8 *octx;
      __u8 *ictx;
      int ictx_len;
      int octx_len;
      union {
            struct {
                  struct esphdr *espp;
            } espstuff;
            struct {
                  struct ahhdr *ahp;
            } ahstuff;
            struct {
                  struct ipcomphdr *compp;
            } ipcompstuff;
      } protostuff;
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
      __u16 natt_len;
      __u16 natt_sport;
      __u16 natt_dport;
      __u8 natt_type;
#endif      
};

struct xform_functions {
      enum ipsec_rcv_value (*checks)(struct ipsec_rcv_state *irs,
                               struct sk_buff *skb);
        enum ipsec_rcv_value (*decrypt)(struct ipsec_rcv_state *irs);

      enum ipsec_rcv_value (*setup_auth)(struct ipsec_rcv_state *irs,
                                 struct sk_buff *skb,
                                 __u32          *replay,
                                 unsigned char **authenticator);
      enum ipsec_rcv_value (*calc_auth)(struct ipsec_rcv_state *irs,
                              struct sk_buff *skb);
};

#ifdef CONFIG_IPSEC_ESP
enum ipsec_rcv_value
ipsec_rcv_esp_checks(struct ipsec_rcv_state *irs,
                 struct sk_buff *skb)
{
      __u8 proto;
      int len;    /* packet length */

      len = skb->len;
      proto = irs->ipp->protocol;

      /* XXX this will need to be 8 for IPv6 */
      if ((proto == IPPROTO_ESP) && ((len - irs->iphlen) % 4)) {
            printk("klips_error:ipsec_rcv: "
                   "got packet with content length = %d from %s -- should be on 4 octet boundary, packet dropped\n",
                   len - irs->iphlen,
                   irs->ipsaddr_txt);
            if(irs->stats) {
                  irs->stats->rx_errors++;
            }
            return IPSEC_RCV_BADLEN;
      }

      if(skb->len < (irs->hard_header_len + sizeof(struct iphdr) + sizeof(struct esphdr))) {
            KLIPS_PRINT(debug_rcv & DB_RX_INAU,
                      "klips_debug:ipsec_rcv: "
                      "runt esp packet of skb->len=%d received from %s, dropped.\n",
                      skb->len,
                      irs->ipsaddr_txt);
            if(irs->stats) {
                  irs->stats->rx_errors++;
            }
            return IPSEC_RCV_BADLEN;
      }

      irs->protostuff.espstuff.espp = (struct esphdr *)(skb->data + irs->iphlen);
      irs->said.spi = irs->protostuff.espstuff.espp->esp_spi;

      return IPSEC_RCV_OK;
}

enum ipsec_rcv_value
ipsec_rcv_esp_decrypt_setup(struct ipsec_rcv_state *irs,
                      struct sk_buff *skb,
                      __u32          *replay,
                      unsigned char **authenticator)
{
      struct esphdr *espp = irs->protostuff.espstuff.espp;

      KLIPS_PRINT(debug_rcv,
                "klips_debug:ipsec_rcv: "
                "packet from %s received with seq=%d (iv)=0x%08x%08x iplen=%d esplen=%d sa=%s\n",
                irs->ipsaddr_txt,
                (__u32)ntohl(espp->esp_rpl),
                (__u32)ntohl(*((__u32 *)(espp->esp_iv)    )),
                (__u32)ntohl(*((__u32 *)(espp->esp_iv) + 1)),
                irs->len,
                irs->ilen,
                irs->sa_len ? irs->sa : " (error)");

      *replay = ntohl(espp->esp_rpl);
      *authenticator = &(skb->data[irs->len - irs->authlen]);

      return IPSEC_RCV_OK;
}

enum ipsec_rcv_value
ipsec_rcv_esp_authcalc(struct ipsec_rcv_state *irs,
                   struct sk_buff *skb)
{
      struct auth_alg *aa;
      struct esphdr *espp = irs->protostuff.espstuff.espp;
      union {
            MD5_CTX           md5;
            SHA1_CTX    sha1;
      } tctx;

#ifdef CONFIG_IPSEC_ALG
      if (irs->ipsp->ips_alg_auth) {
            KLIPS_PRINT(debug_rcv,
                        "klips_debug:ipsec_rcv: "
                        "ipsec_alg hashing proto=%d... ",
                        irs->said.proto);
            if(irs->said.proto == IPPROTO_ESP) {
                  ipsec_alg_sa_esp_hash(irs->ipsp,
                              (caddr_t)espp, irs->ilen,
                              irs->hash, AHHMAC_HASHLEN);
                  return IPSEC_RCV_OK;
            }
            return IPSEC_RCV_BADPROTO;
      }
#endif
      aa = irs->authfuncs;

      /* copy the initialized keying material */
      memcpy(&tctx, irs->ictx, irs->ictx_len);

      (*aa->update)((void *)&tctx, (caddr_t)espp, irs->ilen);

      (*aa->final)(irs->hash, (void *)&tctx);

      memcpy(&tctx, irs->octx, irs->octx_len);

      (*aa->update)((void *)&tctx, irs->hash, aa->hashlen);
      (*aa->final)(irs->hash, (void *)&tctx);

      return IPSEC_RCV_OK;
}


enum ipsec_rcv_value
ipsec_rcv_esp_decrypt(struct ipsec_rcv_state *irs)
{
      struct ipsec_sa *ipsp = irs->ipsp;
      struct esphdr *espp = irs->protostuff.espstuff.espp;
      int esphlen = 0;
      __u8 *idat; /* pointer to content to be decrypted/authenticated */
#ifdef CONFIG_IPSEC_ENC_3DES
      __u32 iv[2];
#endif /* !CONFIG_IPSEC_ENC_3DES */
      int pad = 0, padlen;
      int badpad = 0;
      int i;
      struct sk_buff *skb;
#ifdef CONFIG_IPSEC_ALG
      struct ipsec_alg_enc *ixt_e=NULL;
#endif /* CONFIG_IPSEC_ALG */

      skb=irs->skb;

      idat = skb->data + irs->iphlen;

#ifdef CONFIG_IPSEC_ALG
      if ((ixt_e=ipsp->ips_alg_enc)) {
            esphlen = ESP_HEADER_LEN + ixt_e->ixt_ivlen/8;
            KLIPS_PRINT(debug_rcv,
                        "klips_debug:ipsec_rcv: "
                        "encalg=%d esphlen=%d\n",
                        ipsp->ips_encalg, esphlen);
      } else
#endif /* CONFIG_IPSEC_ALG */
      switch(ipsp->ips_encalg) {
#ifdef CONFIG_IPSEC_ENC_3DES
      case ESP_3DES:
            iv[0] = *((__u32 *)(espp->esp_iv)    );
            iv[1] = *((__u32 *)(espp->esp_iv) + 1);
            esphlen = sizeof(struct esphdr);
            break;
#endif /* !CONFIG_IPSEC_ENC_3DES */
      default:
            ipsp->ips_errs.ips_alg_errs += 1;
            if(irs->stats) {
                  irs->stats->rx_errors++;
            }
            return IPSEC_RCV_ESP_BADALG;
      }

      idat += esphlen;
      irs->ilen -= esphlen;

#ifdef CONFIG_IPSEC_ALG
      if (ixt_e)
      {
            if (ipsec_alg_esp_encrypt(ipsp,
                              idat, irs->ilen, espp->esp_iv,
                              IPSEC_ALG_DECRYPT) <= 0)
            {
                  printk("klips_error:ipsec_rcv: "
                              "got packet with esplen = %d "
                              "from %s -- should be on "
                              "ENC(%d) octet boundary, "
                              "packet dropped\n",
                              irs->ilen,
                              irs->ipsaddr_txt,
                              ipsp->ips_encalg);
                  if(irs->stats) {
                        irs->stats->rx_errors++;
                  }
                  return IPSEC_RCV_BAD_DECRYPT;
            }
      } else
#endif /* CONFIG_IPSEC_ALG */
      switch(ipsp->ips_encalg) {
#ifdef CONFIG_IPSEC_ENC_3DES
      case ESP_3DES:
            if ((irs->ilen) % 8) {
                  ipsp->ips_errs.ips_encsize_errs += 1;
                  printk("klips_error:ipsec_rcv: "
                         "got packet with esplen = %d from %s -- should be on 8 octet boundary, packet dropped\n",
                         irs->ilen,
                         irs->ipsaddr_txt);
                  if(irs->stats) {
                        irs->stats->rx_errors++;
                  }
                  return IPSEC_RCV_3DES_BADBLOCKING;
            }
            des_ede3_cbc_encrypt((des_cblock *)idat,
                             (des_cblock *)idat,
                             irs->ilen,
                             ((struct des_eks *)(ipsp->ips_key_e))[0].ks,
                             ((struct des_eks *)(ipsp->ips_key_e))[1].ks,
                             ((struct des_eks *)(ipsp->ips_key_e))[2].ks,
                             (des_cblock *)iv, 0);
            break;
#endif /* !CONFIG_IPSEC_ENC_3DES */
      }

      rcv_dmp("postdecrypt", skb->data, skb->len);

      irs->next_header = idat[irs->ilen - 1];
      padlen = idat[irs->ilen - 2];
      pad = padlen + 2 + irs->authlen;

      KLIPS_PRINT(debug_rcv & DB_RX_IPAD,
                "klips_debug:ipsec_rcv: "
                "padlen=%d, contents: 0x<offset>: 0x<value> 0x<value> ...\n",
                padlen);

      for (i = 1; i <= padlen; i++) {
            if((i % 16) == 1) {
                  KLIPS_PRINT(debug_rcv & DB_RX_IPAD,
                            "klips_debug:           %02x:",
                            i - 1);
            }
            KLIPS_PRINTMORE(debug_rcv & DB_RX_IPAD,
                        " %02x",
                        idat[irs->ilen - 2 - padlen + i - 1]);
            if(i != idat[irs->ilen - 2 - padlen + i - 1]) {
                  badpad = 1;
            }
            if((i % 16) == 0) {
                  KLIPS_PRINTMORE(debug_rcv & DB_RX_IPAD,
                              "\n");
            }
      }
      if((i % 16) != 1) {
            KLIPS_PRINTMORE(debug_rcv & DB_RX_IPAD,
                                    "\n");
      }
      if(badpad) {
            KLIPS_PRINT(debug_rcv & DB_RX_IPAD,
                      "klips_debug:ipsec_rcv: "
                      "warning, decrypted packet from %s has bad padding\n",
                      irs->ipsaddr_txt);
            KLIPS_PRINT(debug_rcv & DB_RX_IPAD,
                      "klips_debug:ipsec_rcv: "
                      "...may be bad decryption -- not dropped\n");
            ipsp->ips_errs.ips_encpad_errs += 1;
      }

      KLIPS_PRINT(debug_rcv & DB_RX_IPAD,
                "klips_debug:ipsec_rcv: "
                "packet decrypted from %s: next_header = %d, padding = %d\n",
                irs->ipsaddr_txt,
                irs->next_header,
                pad - 2 - irs->authlen);

      irs->ipp->tot_len = htons(ntohs(irs->ipp->tot_len) - (esphlen + pad));

      /*
       * move the IP header forward by the size of the ESP header, which
       * will remove the the ESP header from the packet.
       */
      memmove((void *)(skb->data + esphlen),
            (void *)(skb->data), irs->iphlen);

      rcv_dmp("esp postmove", skb->data, skb->len);

      /* skb_pull below, will move up by esphlen */

      /* XXX not clear how this can happen, as the message indicates */
      if(skb->len < esphlen) {
            printk(KERN_WARNING
                   "klips_error:ipsec_rcv: "
                   "tried to skb_pull esphlen=%d, %d available.  This should never happen, please report.\n",
                   esphlen, (int)(skb->len));
            return IPSEC_RCV_ESP_DECAPFAIL;
      }
      skb_pull(skb, esphlen);

      irs->ipp = (struct iphdr *)skb->data;

      rcv_dmp("esp postpull", skb->data, skb->len);

      /* now, trip off the padding from the end */
      KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
                "klips_debug:ipsec_rcv: "
                "trimming to %d.\n",
                irs->len - esphlen - pad);
      if(pad + esphlen <= irs->len) {
            skb_trim(skb, irs->len - esphlen - pad);
      } else {
            KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
                      "klips_debug:ipsec_rcv: "
                      "bogus packet, size is zero or negative, dropping.\n");
            return IPSEC_RCV_DECAPFAIL;
      }

      return IPSEC_RCV_OK;
}


struct xform_functions esp_rcv_funcs[]={
      {     checks:         ipsec_rcv_esp_checks,
            setup_auth:     ipsec_rcv_esp_decrypt_setup,
            calc_auth:      ipsec_rcv_esp_authcalc,
            decrypt:        ipsec_rcv_esp_decrypt,
      },
};
#endif /* !CONFIG_IPSEC_ESP */

#ifdef CONFIG_IPSEC_AH
enum ipsec_rcv_value
ipsec_rcv_ah_checks(struct ipsec_rcv_state *irs,
                struct sk_buff *skb)
{
      int ahminlen;

      ahminlen = irs->hard_header_len + sizeof(struct iphdr);

      /* take care not to deref this pointer until we check the minlen though */
      irs->protostuff.ahstuff.ahp = (struct ahhdr *) (skb->data + irs->iphlen);

      if((skb->len < ahminlen+sizeof(struct ahhdr)) ||
         (skb->len < ahminlen+(irs->protostuff.ahstuff.ahp->ah_hl << 2))) {
            KLIPS_PRINT(debug_rcv & DB_RX_INAU,
                      "klips_debug:ipsec_rcv: "
                      "runt ah packet of skb->len=%d received from %s, dropped.\n",
                      skb->len,
                      irs->ipsaddr_txt);
            if(irs->stats) {
                  irs->stats->rx_errors++;
            }
            return IPSEC_RCV_BADLEN;
      }

      irs->said.spi = irs->protostuff.ahstuff.ahp->ah_spi;

      /* XXX we only support the one 12-byte authenticator for now */
      if(irs->protostuff.ahstuff.ahp->ah_hl != ((AHHMAC_HASHLEN+AHHMAC_RPLLEN) >> 2)) {
            KLIPS_PRINT(debug_rcv & DB_RX_INAU,
                      "klips_debug:ipsec_rcv: "
                      "bad authenticator length %ld, expected %lu from %s.\n",
                      (long)(irs->protostuff.ahstuff.ahp->ah_hl << 2),
                      (unsigned long) sizeof(struct ahhdr),
                      irs->ipsaddr_txt);
            if(irs->stats) {
                  irs->stats->rx_errors++;
            }
            return IPSEC_RCV_BADLEN;
      }

      return IPSEC_RCV_OK;
}


enum ipsec_rcv_value
ipsec_rcv_ah_setup_auth(struct ipsec_rcv_state *irs,
                  struct sk_buff *skb,
                  __u32          *replay,
                  unsigned char **authenticator)
{
      struct ahhdr *ahp = irs->protostuff.ahstuff.ahp;

      *replay = ntohl(ahp->ah_rpl);
      *authenticator = ahp->ah_data;

      return IPSEC_RCV_OK;
}

enum ipsec_rcv_value
ipsec_rcv_ah_authcalc(struct ipsec_rcv_state *irs,
                  struct sk_buff *skb)
{
      struct auth_alg *aa;
      struct ahhdr *ahp = irs->protostuff.ahstuff.ahp;
      union {
            MD5_CTX           md5;
            SHA1_CTX    sha1;
      } tctx;
      struct iphdr ipo;
      int ahhlen;

      aa = irs->authfuncs;

      /* copy the initialized keying material */
      memcpy(&tctx, irs->ictx, irs->ictx_len);

      ipo = *irs->ipp;
      ipo.tos = 0;      /* mutable RFC 2402 3.3.3.1.1.1 */
      ipo.frag_off = 0;
      ipo.ttl = 0;
      ipo.check = 0;


      /* do the sanitized header */
      (*aa->update)((void*)&tctx, (caddr_t)&ipo, sizeof(struct iphdr));

      /* XXX we didn't do the options here! */

      /* now do the AH header itself */
      ahhlen = AH_BASIC_LEN + (ahp->ah_hl << 2);
      (*aa->update)((void*)&tctx, (caddr_t)ahp,  ahhlen - AHHMAC_HASHLEN);

      /* now, do some zeroes */
      (*aa->update)((void*)&tctx, (caddr_t)zeroes,  AHHMAC_HASHLEN);

      /* finally, do the packet contents themselves */
      (*aa->update)((void*)&tctx,
                  (caddr_t)skb->data + irs->iphlen + ahhlen,
                  skb->len - irs->iphlen - ahhlen);

      (*aa->final)(irs->hash, (void *)&tctx);

      memcpy(&tctx, irs->octx, irs->octx_len);

      (*aa->update)((void *)&tctx, irs->hash, aa->hashlen);
      (*aa->final)(irs->hash, (void *)&tctx);

      return IPSEC_RCV_OK;
}

enum ipsec_rcv_value
ipsec_rcv_ah_decap(struct ipsec_rcv_state *irs)
{
      struct ahhdr *ahp = irs->protostuff.ahstuff.ahp;
      struct sk_buff *skb;
      int ahhlen;

      skb=irs->skb;

      ahhlen = AH_BASIC_LEN + (ahp->ah_hl << 2);

      irs->ipp->tot_len = htons(ntohs(irs->ipp->tot_len) - ahhlen);
      irs->next_header  = ahp->ah_nh;

      /*
       * move the IP header forward by the size of the AH header, which
       * will remove the the AH header from the packet.
       */
      memmove((void *)(skb->data + ahhlen),
            (void *)(skb->data), irs->iphlen);

      rcv_dmp("ah postmove", skb->data, skb->len);

      /* skb_pull below, will move up by ahhlen */

      /* XXX not clear how this can happen, as the message indicates */
      if(skb->len < ahhlen) {
            printk(KERN_WARNING
                   "klips_error:ipsec_rcv: "
                   "tried to skb_pull ahhlen=%d, %d available.  This should never happen, please report.\n",
                   ahhlen,
                   (int)(skb->len));
            return IPSEC_RCV_DECAPFAIL;
      }
      skb_pull(skb, ahhlen);

      irs->ipp = (struct iphdr *)skb->data;

      rcv_dmp("ah postpull", skb->data, skb->len);

      return IPSEC_RCV_OK;
}


struct xform_functions ah_rcv_funcs[]={
      {     checks:         ipsec_rcv_ah_checks,
            setup_auth:     ipsec_rcv_ah_setup_auth,
            calc_auth:      ipsec_rcv_ah_authcalc,
            decrypt:        ipsec_rcv_ah_decap,
      },
};

#endif /* CONFIG_IPSEC_AH */

#ifdef CONFIG_IPSEC_IPCOMP
enum ipsec_rcv_value
ipsec_rcv_ipcomp_checks(struct ipsec_rcv_state *irs,
                  struct sk_buff *skb)
{
      int ipcompminlen;

      ipcompminlen = irs->hard_header_len + sizeof(struct iphdr);

      if(skb->len < (ipcompminlen + sizeof(struct ipcomphdr))) {
            KLIPS_PRINT(debug_rcv & DB_RX_INAU,
                      "klips_debug:ipsec_rcv: "
                      "runt comp packet of skb->len=%d received from %s, dropped.\n",
                      skb->len,
                      irs->ipsaddr_txt);
            if(irs->stats) {
                  irs->stats->rx_errors++;
            }
            return IPSEC_RCV_BADLEN;
      }

      irs->protostuff.ipcompstuff.compp = (struct ipcomphdr *)(skb->data + irs->iphlen);
      irs->said.spi = htonl((__u32)ntohs(irs->protostuff.ipcompstuff.compp->ipcomp_cpi));
      return IPSEC_RCV_OK;
}

enum ipsec_rcv_value
ipsec_rcv_ipcomp_decomp(struct ipsec_rcv_state *irs)
{
      unsigned int flags = 0;
      struct ipsec_sa *ipsp = irs->ipsp;
      struct sk_buff *skb;

      skb=irs->skb;

      rcv_dmp("ipcomp", skb->data, skb->len);

      if(ipsp == NULL) {
            return IPSEC_RCV_SAIDNOTFOUND;
      }

#if 0
      /* we want to check that this wasn't the first SA on the list, because
       * we don't support bare IPCOMP, for unexplained reasons. MCR
       */
      if (ipsp->ips_onext != NULL) {
            KLIPS_PRINT(debug_rcv,
                      "klips_debug:ipsec_rcv: "
                      "Incoming packet with outer IPCOMP header SA:%s: not yet supported by KLIPS, dropped\n",
                      irs->sa_len ? irs->sa : " (error)");
            if(irs->stats) {
                  irs->stats->rx_dropped++;
            }

            return IPSEC_RCV_IPCOMPALONE;
      }
#endif

      if(sysctl_ipsec_inbound_policy_check &&
         ((((ntohl(ipsp->ips_said.spi) & 0x0000ffff) != ntohl(irs->said.spi)) &&
           (ipsp->ips_encalg != ntohl(irs->said.spi))   /* this is a workaround for peer non-compliance with rfc2393 */
                ))) {
            char sa2[SATOA_BUF];
            size_t sa_len2 = 0;

            sa_len2 = satoa(ipsp->ips_said, 0, sa2, SATOA_BUF);

            KLIPS_PRINT(debug_rcv,
                      "klips_debug:ipsec_rcv: "
                      "Incoming packet with SA(IPCA):%s does not match policy SA(IPCA):%s cpi=%04x cpi->spi=%08x spi=%08x, spi->cpi=%04x for SA grouping, dropped.\n",
                      irs->sa_len ? irs->sa : " (error)",
                      ipsp != NULL ? (sa_len2 ? sa2 : " (error)") : "NULL",
                      ntohs(irs->protostuff.ipcompstuff.compp->ipcomp_cpi),
                      (__u32)ntohl(irs->said.spi),
                      ipsp != NULL ? (__u32)ntohl((ipsp->ips_said.spi)) : 0,
                      ipsp != NULL ? (__u16)(ntohl(ipsp->ips_said.spi) & 0x0000ffff) : 0);
            if(irs->stats) {
                  irs->stats->rx_dropped++;
            }
            return IPSEC_RCV_SAIDNOTFOUND;
      }

      ipsp->ips_comp_ratio_cbytes += ntohs(irs->ipp->tot_len);
      irs->next_header = irs->protostuff.ipcompstuff.compp->ipcomp_nh;

      skb = skb_decompress(skb, ipsp, &flags);
      if (!skb || flags) {
            spin_unlock(&tdb_lock);
            KLIPS_PRINT(debug_rcv,
                      "klips_debug:ipsec_rcv: "
                      "skb_decompress() returned error flags=%x, dropped.\n",
                      flags);
            if (irs->stats) {
                  if (flags)
                        irs->stats->rx_errors++;
                  else
                        irs->stats->rx_dropped++;
            }
            return IPSEC_RCV_IPCOMPFAILED;
      }

      /* make sure we update the pointer */
      irs->skb = skb;
      
#ifdef NET_21
      irs->ipp = skb->nh.iph;
#else /* NET_21 */
      irs->ipp = skb->ip_hdr;
#endif /* NET_21 */

      ipsp->ips_comp_ratio_dbytes += ntohs(irs->ipp->tot_len);

      KLIPS_PRINT(debug_rcv,
                "klips_debug:ipsec_rcv: "
                "packet decompressed SA(IPCA):%s cpi->spi=%08x spi=%08x, spi->cpi=%04x, nh=%d.\n",
                irs->sa_len ? irs->sa : " (error)",
                (__u32)ntohl(irs->said.spi),
                ipsp != NULL ? (__u32)ntohl((ipsp->ips_said.spi)) : 0,
                ipsp != NULL ? (__u16)(ntohl(ipsp->ips_said.spi) & 0x0000ffff) : 0,
                irs->next_header);
      KLIPS_IP_PRINT(debug_rcv & DB_RX_PKTRX, irs->ipp);

      return IPSEC_RCV_OK;
}


struct xform_functions ipcomp_rcv_funcs[]={
      {checks:  ipsec_rcv_ipcomp_checks,
       decrypt: ipsec_rcv_ipcomp_decomp,
      },
};

#endif /* CONFIG_IPSEC_IPCOMP */

enum ipsec_rcv_value
ipsec_rcv_decap_once(struct ipsec_rcv_state *irs)
{
      int iphlen;
      unsigned char *dat;
      __u8 proto;
      struct in_addr ipsaddr;
      struct in_addr ipdaddr;
      int replay = 0;   /* replay value in AH or ESP packet */
      struct ipsec_sa* ipsnext = NULL;    /* next SA towards inside of packet */
      struct xform_functions *proto_funcs;
      struct ipsec_sa *newipsp;
      struct iphdr *ipp;
      struct sk_buff *skb;
#ifdef CONFIG_IPSEC_ALG
      struct ipsec_alg_auth *ixt_a=NULL;
#endif /* CONFIG_IPSEC_ALG */

      skb = irs->skb;
      irs->len = skb->len;
      dat = skb->data;
      ipp = irs->ipp;
      proto = ipp->protocol;
      ipsaddr.s_addr = ipp->saddr;
      addrtoa(ipsaddr, 0, irs->ipsaddr_txt, sizeof(irs->ipsaddr_txt));
      ipdaddr.s_addr = ipp->daddr;
      addrtoa(ipdaddr, 0, irs->ipdaddr_txt, sizeof(irs->ipdaddr_txt));

      iphlen = ipp->ihl << 2;
      irs->iphlen=iphlen;
      ipp->check = 0;               /* we know the sum is good */
      
      KLIPS_PRINT(debug_rcv,
                "klips_debug:ipsec_rcv_decap_once: "
                "decap (%d) from %s -> %s\n",
                proto, irs->ipsaddr_txt, irs->ipdaddr_txt);

      switch(proto) {
#ifdef CONFIG_IPSEC_ESP
      case IPPROTO_ESP:
            proto_funcs = esp_rcv_funcs;
            break;
#endif /* !CONFIG_IPSEC_ESP */

#ifdef CONFIG_IPSEC_AH
      case IPPROTO_AH:
            proto_funcs = ah_rcv_funcs;
            break;
#endif /* !CONFIG_IPSEC_AH */

#ifdef CONFIG_IPSEC_IPCOMP
      case IPPROTO_COMP:
            proto_funcs = ipcomp_rcv_funcs;
            break;
#endif /* !CONFIG_IPSEC_IPCOMP */
      default:
            if(irs->stats) {
                  irs->stats->rx_errors++;
            }
            return IPSEC_RCV_BADPROTO;
      }

      /*
       * Find tunnel control block and (indirectly) call the
       * appropriate tranform routine. The resulting sk_buf
       * is a valid IP packet ready to go through input processing.
       */

      irs->said.dst.s_addr = ipp->daddr;

      if(proto_funcs->checks) {
            enum ipsec_rcv_value retval = (*proto_funcs->checks)(irs, skb);

            if(retval < 0) {
                  return retval;
            }
      }

      irs->said.proto = proto;
      irs->sa_len = satoa(irs->said, 0, irs->sa, SATOA_BUF);
      if(irs->sa_len == 0) {
            strcpy(irs->sa, "(error)");
      }

      newipsp = ipsec_sa_getbyid(&irs->said);
      if (newipsp == NULL) {
            KLIPS_PRINT(debug_rcv,
                      "klips_debug:ipsec_rcv: "
                      "no ipsec_sa for SA:%s: incoming packet with no SA dropped\n",
                      irs->sa_len ? irs->sa : " (error)");
            if(irs->stats) {
                  irs->stats->rx_dropped++;
            }
            return IPSEC_RCV_SAIDNOTFOUND;
      }

      /* MCR - XXX this is bizarre. ipsec_sa_getbyid returned it, having incremented the refcount,
       * why in the world would we decrement it here?

       ipsec_sa_put(irs->ipsp);*/ /* incomplete */

      /* If it is in larval state, drop the packet, we cannot process yet. */
      if(newipsp->ips_state == SADB_SASTATE_LARVAL) {
            KLIPS_PRINT(debug_rcv,
                      "klips_debug:ipsec_rcv: "
                      "ipsec_sa in larval state, cannot be used yet, dropping packet.\n");
            if(irs->stats) {
                  irs->stats->rx_dropped++;
            }
            ipsec_sa_put(newipsp);
            return IPSEC_RCV_SAIDNOTLIVE;
      }

      if(newipsp->ips_state == SADB_SASTATE_DEAD) {
            KLIPS_PRINT(debug_rcv,
                      "klips_debug:ipsec_rcv: "
                      "ipsec_sa in dead state, cannot be used any more, dropping packet.\n");
            if(irs->stats) {
                  irs->stats->rx_dropped++;
            }
            ipsec_sa_put(newipsp);
            return IPSEC_RCV_SAIDNOTLIVE;
      }

      if(sysctl_ipsec_inbound_policy_check) {
            if(irs->ipp->saddr != ((struct sockaddr_in*)(newipsp->ips_addr_s))->sin_addr.s_addr) {
                  KLIPS_PRINT(debug_rcv,
                            "klips_debug:ipsec_rcv: "
                            "SA:%s, src=%s of pkt does not agree with expected SA source address policy.\n",
                            irs->sa_len ? irs->sa : " (error)",
                            irs->ipsaddr_txt);
                  if(irs->stats) {
                        irs->stats->rx_dropped++;
                  }
                  ipsec_sa_put(newipsp);
                  return IPSEC_RCV_FAILEDINBOUND;
            }

            KLIPS_PRINT(debug_rcv,
                      "klips_debug:ipsec_rcv: "
                      "SA:%s, src=%s of pkt agrees with expected SA source address policy.\n",
                      irs->sa_len ? irs->sa : " (error)",
                      irs->ipsaddr_txt);

            /*
             * at this point, we have looked up a new SA, and we want to make sure that if this
             * isn't the first SA in the list, that the previous SA actually points at this one.
             */
            if(irs->ipsp) {
                  if(irs->ipsp->ips_inext != newipsp) {
                        KLIPS_PRINT(debug_rcv,
                                  "klips_debug:ipsec_rcv: "
                                  "unexpected SA:%s: does not agree with ips->inext policy, dropped\n",
                                  irs->sa_len ? irs->sa : " (error)");
                        if(irs->stats) {
                              irs->stats->rx_dropped++;
                        }
                        ipsec_sa_put(newipsp);
                        return IPSEC_RCV_FAILEDINBOUND;
                  }
                  KLIPS_PRINT(debug_rcv,
                            "klips_debug:ipsec_rcv: "
                            "SA:%s grouping from previous SA is OK.\n",
                            irs->sa_len ? irs->sa : " (error)");
            } else {
                  KLIPS_PRINT(debug_rcv,
                            "klips_debug:ipsec_rcv: "
                            "SA:%s First SA in group.\n",
                            irs->sa_len ? irs->sa : " (error)");
            }

            /*
             * previously, at this point, we checked if the back pointer from the new SA that
             * we just found matched the back pointer. But, we won't do this check anymore,
             * because we want to be able to nest SAs
             */
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
            KLIPS_PRINT(debug_rcv,
                  "klips_debug:ipsec_rcv: "
                  "natt_type=%u tdbp->ips_natt_type=%u : %s\n",
                  irs->natt_type, newipsp->ips_natt_type,
                  (irs->natt_type==newipsp->ips_natt_type)?"ok":"bad");
            if (irs->natt_type != newipsp->ips_natt_type) {
                  KLIPS_PRINT(debug_rcv,
                            "klips_debug:ipsec_rcv: "
                            "SA:%s does not agree with expected NAT-T policy.\n",
                            irs->sa_len ? irs->sa : " (error)");
                  if(irs->stats) {
                        irs->stats->rx_dropped++;
                  }
                  ipsec_sa_put(newipsp);
                  return IPSEC_RCV_FAILEDINBOUND;
            }
#endif             
      }

      /* okay, SA checks out, so free any previous SA, and record a new one */

      if(irs->ipsp) {
            ipsec_sa_put(irs->ipsp);
      }
      irs->ipsp=newipsp;

      /* note that the outer code will free the irs->ipsp if there is an error */


      /* now check the lifetimes */
      if(ipsec_lifetime_check(&irs->ipsp->ips_life.ipl_bytes,   "bytes",  irs->sa,
                        ipsec_life_countbased, ipsec_incoming, irs->ipsp) == ipsec_life_harddied ||
         ipsec_lifetime_check(&irs->ipsp->ips_life.ipl_addtime, "addtime",irs->sa,
                        ipsec_life_timebased,  ipsec_incoming, irs->ipsp) == ipsec_life_harddied ||
         ipsec_lifetime_check(&irs->ipsp->ips_life.ipl_addtime, "usetime",irs->sa,
                        ipsec_life_timebased,  ipsec_incoming, irs->ipsp) == ipsec_life_harddied ||
         ipsec_lifetime_check(&irs->ipsp->ips_life.ipl_packets, "packets",irs->sa,
                        ipsec_life_countbased, ipsec_incoming, irs->ipsp) == ipsec_life_harddied) {
            ipsec_sa_delchain(irs->ipsp);
            if(irs->stats) {
                  irs->stats->rx_dropped++;
            }
            
            KLIPS_PRINT(debug_rcv,
                      "klips_debug:ipsec_rcv_decap_once: "
                      "decap (%d) failed lifetime check\n",
                      proto);

            return IPSEC_RCV_LIFETIMEFAILED;
      }

#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
      if ((irs->natt_type) &&
            ( (irs->ipp->saddr != (((struct sockaddr_in*)(newipsp->ips_addr_s))->sin_addr.s_addr)) ||
              (irs->natt_sport != newipsp->ips_natt_sport)
            )) {
            struct sockaddr sipaddr;
            /** Advertise NAT-T addr change to pluto **/
            sipaddr.sa_family = AF_INET;
            ((struct sockaddr_in*)&sipaddr)->sin_addr.s_addr = irs->ipp->saddr;
            ((struct sockaddr_in*)&sipaddr)->sin_port = htons(irs->natt_sport);
            pfkey_nat_t_new_mapping(newipsp, &sipaddr, irs->natt_sport);
            /**
             * Then allow or block packet depending on
             * sysctl_ipsec_inbound_policy_check.
             *
             * In all cases, pluto will update SA if new mapping is
             * accepted.
             */
            if (sysctl_ipsec_inbound_policy_check) {
                  KLIPS_PRINT(debug_rcv,
                        "klips_debug:ipsec_rcv: "
                        "SA:%s, src=%s:%u of pkt does not agree with expected "
                        "SA source address policy (pluto has been informed).\n",
                        irs->sa_len ? irs->sa : " (error)",
                        irs->ipsaddr_txt, irs->natt_sport);
                  if(irs->stats) {
                        irs->stats->rx_dropped++;
                  }
                  ipsec_sa_put(newipsp);
                  return IPSEC_RCV_FAILEDINBOUND;
            }
      }
#endif

      irs->authfuncs=NULL;
      /* authenticate, if required */
#ifdef CONFIG_IPSEC_ALG
      if ((ixt_a=irs->ipsp->ips_alg_auth)) {
            irs->authlen = AHHMAC_HASHLEN;
            irs->authfuncs = NULL;
            irs->ictx = NULL;
            irs->octx = NULL;
            irs->ictx_len = 0;
            irs->octx_len = 0;
            KLIPS_PRINT(debug_rcv,
                        "klips_debug:ipsec_rcv: "
                        "authalg=%d authlen=%d\n",
                        irs->ipsp->ips_authalg, 
                        irs->authlen);
      } else
#endif /* CONFIG_IPSEC_ALG */
      switch(irs->ipsp->ips_authalg) {
#ifdef CONFIG_IPSEC_AUTH_HMAC_MD5
      case AH_MD5:
            irs->authlen = AHHMAC_HASHLEN;
            irs->authfuncs = ipsec_rcv_md5;
            irs->ictx = (void *)&((struct md5_ctx*)(irs->ipsp->ips_key_a))->ictx;
            irs->octx = (void *)&((struct md5_ctx*)(irs->ipsp->ips_key_a))->octx;
            irs->ictx_len = sizeof(((struct md5_ctx*)(irs->ipsp->ips_key_a))->ictx);
            irs->octx_len = sizeof(((struct md5_ctx*)(irs->ipsp->ips_key_a))->octx);
            break;
#endif /* CONFIG_IPSEC_AUTH_HMAC_MD5 */
#ifdef CONFIG_IPSEC_AUTH_HMAC_SHA1
      case AH_SHA:
            irs->authlen = AHHMAC_HASHLEN;
            irs->authfuncs = ipsec_rcv_sha1;
            irs->ictx = (void *)&((struct sha1_ctx*)(irs->ipsp->ips_key_a))->ictx;
            irs->octx = (void *)&((struct sha1_ctx*)(irs->ipsp->ips_key_a))->octx;
            irs->ictx_len = sizeof(((struct sha1_ctx*)(irs->ipsp->ips_key_a))->ictx);
            irs->octx_len = sizeof(((struct sha1_ctx*)(irs->ipsp->ips_key_a))->octx);
            break;
#endif /* CONFIG_IPSEC_AUTH_HMAC_SHA1 */
      case AH_NONE:
            irs->authlen = 0;
            irs->authfuncs = NULL;
            irs->ictx = NULL;
            irs->octx = NULL;
            irs->ictx_len = 0;
            irs->octx_len = 0;

            break;
      default:
            irs->ipsp->ips_errs.ips_alg_errs += 1;
            if(irs->stats) {
                  irs->stats->rx_errors++;
            }
            return IPSEC_RCV_BADAUTH;
      }

      irs->ilen = irs->len - iphlen - irs->authlen;
      if(irs->ilen <= 0) {
        KLIPS_PRINT(debug_rcv,
                  "klips_debug:ipsec_rcv: "
                  "runt %s packet with no data, dropping.\n",
                  (proto == IPPROTO_ESP ? "esp" : "ah"));
        if(irs->stats) {
          irs->stats->rx_dropped++;
        }
        return IPSEC_RCV_BADLEN;
      }

#ifdef CONFIG_IPSEC_ALG
      if(irs->authfuncs || ixt_a) {
#else
      if(irs->authfuncs) {
#endif
            unsigned char *authenticator = NULL;

            if(proto_funcs->setup_auth) {
                  enum ipsec_rcv_value retval
                      = (*proto_funcs->setup_auth)(irs, skb,
                                           &replay,
                                           &authenticator);
                  if(retval < 0) {
                        return retval;
                  }
            }

            if(!authenticator) {
                  irs->ipsp->ips_errs.ips_auth_errs += 1;
                  if(irs->stats) {
                        irs->stats->rx_dropped++;
                  }
                  return IPSEC_RCV_BADAUTH;
            }

            if(!ipsec_checkreplaywindow(irs->ipsp, replay)) {
                  irs->ipsp->ips_errs.ips_replaywin_errs += 1;
                  KLIPS_PRINT(debug_rcv & DB_RX_REPLAY,
                            "klips_debug:ipsec_rcv: "
                            "duplicate frame from %s, packet dropped\n",
                            irs->ipsaddr_txt);
                  if(irs->stats) {
                        irs->stats->rx_dropped++;
                  }
                  return IPSEC_RCV_REPLAYFAILED;
            }

            /*
             * verify authenticator
             */

            KLIPS_PRINT(debug_rcv,
                      "klips_debug:ipsec_rcv: "
                      "encalg = %d, authalg = %d.\n",
                      irs->ipsp->ips_encalg,
                      irs->ipsp->ips_authalg);

            /* calculate authenticator */
            if(proto_funcs->calc_auth == NULL) {
                  return IPSEC_RCV_BADAUTH;
            }
            (*proto_funcs->calc_auth)(irs, skb);

            if (memcmp(irs->hash, authenticator, irs->authlen)) {
                  irs->ipsp->ips_errs.ips_auth_errs += 1;
                  KLIPS_PRINT(debug_rcv & DB_RX_INAU,
                            "klips_debug:ipsec_rcv: "
                            "auth failed on incoming packet from %s: hash=%08x%08x%08x auth=%08x%08x%08x, dropped\n",
                            irs->ipsaddr_txt,
                            ntohl(*(__u32*)&irs->hash[0]),
                            ntohl(*(__u32*)&irs->hash[4]),
                            ntohl(*(__u32*)&irs->hash[8]),
                            ntohl(*(__u32*)authenticator),
                            ntohl(*((__u32*)authenticator + 1)),
                            ntohl(*((__u32*)authenticator + 2)));
                  if(irs->stats) {
                        irs->stats->rx_dropped++;
                  }
                  return IPSEC_RCV_AUTHFAILED;
            } else {
                  KLIPS_PRINT(debug_rcv,
                            "klips_debug:ipsec_rcv: "
                            "authentication successful.\n");
            }

            /* Crypto hygiene: clear memory used to calculate autheticator.
             * The length varies with the algorithm.
             */
            memset(irs->hash, 0, irs->authlen);

            /* If the sequence number == 0, expire SA, it had rolled */
            if(irs->ipsp->ips_replaywin && !replay /* !irs->ipsp->ips_replaywin_lastseq */) {
                  ipsec_sa_delchain(irs->ipsp);
                  KLIPS_PRINT(debug_rcv,
                            "klips_debug:ipsec_rcv: "
                            "replay window counter rolled, expiring SA.\n");
                  if(irs->stats) {
                        irs->stats->rx_dropped++;
                  }
                  return IPSEC_RCV_REPLAYROLLED;
            }

            /* now update the replay counter */
            if (!ipsec_updatereplaywindow(irs->ipsp, replay)) {
                  irs->ipsp->ips_errs.ips_replaywin_errs += 1;
                  KLIPS_PRINT(debug_rcv & DB_RX_REPLAY,
                            "klips_debug:ipsec_rcv: "
                            "duplicate frame from %s, packet dropped\n",
                            irs->ipsaddr_txt);
                  if(irs->stats) {
                        irs->stats->rx_dropped++;
                  }
                  return IPSEC_RCV_REPLAYROLLED;
            }
      }

      if(proto_funcs->decrypt) {
            enum ipsec_rcv_value retval =
              (*proto_funcs->decrypt)(irs);

            if(retval != IPSEC_RCV_OK) {
                  return retval;
            }
      }

      /*
       *    Adjust pointers
       */
      skb = irs->skb;
      irs->len = skb->len;
      dat = skb->data;

#ifdef NET_21
/*          skb->h.ipiph=(struct iphdr *)skb->data; */
      skb->nh.raw = skb->data;
      skb->h.raw = skb->nh.raw + (skb->nh.iph->ihl << 2);

      memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
#else /* NET_21 */
      skb->h.iph=(struct iphdr *)skb->data;
      skb->ip_hdr=(struct iphdr *)skb->data;
      memset(skb->proto_priv, 0, sizeof(struct options));
#endif /* NET_21 */

      ipp = (struct iphdr *)dat;
      ipsaddr.s_addr = ipp->saddr;
      addrtoa(ipsaddr, 0, irs->ipsaddr_txt, sizeof(irs->ipsaddr_txt));
      ipdaddr.s_addr = ipp->daddr;
      addrtoa(ipdaddr, 0, irs->ipdaddr_txt, sizeof(irs->ipdaddr_txt));
      /*
       *    Discard the original ESP/AH header
       */
      ipp->protocol = irs->next_header;

      ipp->check = 0;   /* NOTE: this will be included in checksum */
      ipp->check = ip_fast_csum((unsigned char *)dat, iphlen >> 2);

      KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
                "klips_debug:ipsec_rcv: "
                "after <%s%s%s>, SA:%s:\n",
                IPS_XFORM_NAME(irs->ipsp),
                irs->sa_len ? irs->sa : " (error)");
      KLIPS_IP_PRINT(debug_rcv & DB_RX_PKTRX, ipp);

      skb->protocol = htons(ETH_P_IP);
      skb->ip_summed = 0;

      ipsnext = irs->ipsp->ips_inext;
      if(sysctl_ipsec_inbound_policy_check) {
            if(ipsnext) {
                  if(
                        ipp->protocol != IPPROTO_AH
                        && ipp->protocol != IPPROTO_ESP
#ifdef CONFIG_IPSEC_IPCOMP
                        && ipp->protocol != IPPROTO_COMP
                        && (ipsnext->ips_said.proto != IPPROTO_COMP
                            || ipsnext->ips_inext)
#endif /* CONFIG_IPSEC_IPCOMP */
                        && ipp->protocol != IPPROTO_IPIP
                        ) {
                        KLIPS_PRINT(debug_rcv,
                                  "klips_debug:ipsec_rcv: "
                                  "packet with incomplete policy dropped, last successful SA:%s.\n",
                                  irs->sa_len ? irs->sa : " (error)");
                        if(irs->stats) {
                              irs->stats->rx_dropped++;
                        }
                        return IPSEC_RCV_FAILEDINBOUND;
                  }
                  KLIPS_PRINT(debug_rcv,
                            "klips_debug:ipsec_rcv: "
                            "SA:%s, Another IPSEC header to process.\n",
                            irs->sa_len ? irs->sa : " (error)");
            } else {
                  KLIPS_PRINT(debug_rcv,
                            "klips_debug:ipsec_rcv: "
                            "No ips_inext from this SA:%s.\n",
                            irs->sa_len ? irs->sa : " (error)");
            }
      }

#ifdef CONFIG_IPSEC_IPCOMP
      /* update ipcomp ratio counters, even if no ipcomp packet is present */
      if (ipsnext
          && ipsnext->ips_said.proto == IPPROTO_COMP
          && ipp->protocol != IPPROTO_COMP) {
            ipsnext->ips_comp_ratio_cbytes += ntohs(ipp->tot_len);
            ipsnext->ips_comp_ratio_dbytes += ntohs(ipp->tot_len);
      }
#endif /* CONFIG_IPSEC_IPCOMP */

      irs->ipsp->ips_life.ipl_bytes.ipl_count += irs->len;
      irs->ipsp->ips_life.ipl_bytes.ipl_last   = irs->len;

      if(!irs->ipsp->ips_life.ipl_usetime.ipl_count) {
            irs->ipsp->ips_life.ipl_usetime.ipl_count = jiffies / HZ;
      }
      irs->ipsp->ips_life.ipl_usetime.ipl_last = jiffies / HZ;
      irs->ipsp->ips_life.ipl_packets.ipl_count += 1;

#ifdef CONFIG_NETFILTER
      if(proto == IPPROTO_ESP || proto == IPPROTO_AH) {
            skb->nfmark = (skb->nfmark & (~(IPsecSAref2NFmark(IPSEC_SA_REF_MASK))))
                  | IPsecSAref2NFmark(IPsecSA2SAref(irs->ipsp));
            KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
                      "klips_debug:ipsec_rcv: "
                      "%s SA sets skb->nfmark=0x%x.\n",
                      proto == IPPROTO_ESP ? "ESP" : "AH",
                      (unsigned)skb->nfmark);
      }
#endif /* CONFIG_NETFILTER */

      return IPSEC_RCV_OK;
}


int
#ifdef PROTO_HANDLER_SINGLE_PARM
ipsec_rcv(struct sk_buff *skb)
#else /* PROTO_HANDLER_SINGLE_PARM */
#ifdef NET_21
ipsec_rcv(struct sk_buff *skb, unsigned short xlen)
#else /* NET_21 */
ipsec_rcv(struct sk_buff *skb, struct device *dev, struct options *opt,
            __u32 daddr_unused, unsigned short xlen, __u32 saddr,
                           int redo, struct inet_protocol *protocol)
#endif /* NET_21 */
#endif /* PROTO_HANDLER_SINGLE_PARM */
{
#ifdef NET_21
#ifdef CONFIG_IPSEC_DEBUG
      struct device *dev = skb->dev;
#endif /* CONFIG_IPSEC_DEBUG */
#endif /* NET_21 */
      unsigned char protoc;
      struct iphdr *ipp;
#if defined(CONFIG_IPSEC_ESP) || defined(CONFIG_IPSEC_AH)
#endif /* defined(CONFIG_IPSEC_ESP) || defined(CONFIG_IPSEC_AH) */

      struct ipsec_sa *ipsp = NULL;
      struct net_device_stats *stats = NULL;          /* This device's statistics */
      struct device *ipsecdev = NULL, *prvdev;
      struct ipsecpriv *prv;
      char name[9];
      int i;
      struct in_addr ipsaddr;
      struct in_addr ipdaddr;

      struct ipsec_sa* ipsnext = NULL;    /* next SA towards inside of packet */
      struct ipsec_rcv_state irs;

      /* Don't unlink in the middle of a turnaround */
      MOD_INC_USE_COUNT;

      memset(&irs, 0, sizeof(struct ipsec_rcv_state));

      if (skb == NULL) {
            KLIPS_PRINT(debug_rcv,
                      "klips_debug:ipsec_rcv: "
                      "NULL skb passed in.\n");
            goto rcvleave;
      }

      if (skb->data == NULL) {
            KLIPS_PRINT(debug_rcv,
                      "klips_debug:ipsec_rcv: "
                      "NULL skb->data passed in, packet is bogus, dropping.\n");
            goto rcvleave;
      }

#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
      if (skb->sk && skb->nh.iph && skb->nh.iph->protocol==IPPROTO_UDP) {
            /**
             * Packet comes from udp_queue_rcv_skb so it is already defrag,
             * checksum verified, ... (ie safe to use)
             *
             * If the packet is not for us, return -1 and udp_queue_rcv_skb
             * will continue to handle it (do not kfree skb !!).
             */
            struct udp_opt *tp =  &(skb->sk->tp_pinfo.af_udp);
            struct iphdr *ip = (struct iphdr *)skb->nh.iph;
            struct udphdr *udp = (struct udphdr *)((__u32 *)ip+ip->ihl);
            __u8 *udpdata = (__u8 *)udp + sizeof(struct udphdr);
            __u32 *udpdata32 = (__u32 *)udpdata;

            irs.natt_sport = ntohs(udp->source);
            irs.natt_dport = ntohs(udp->dest);

            KLIPS_PRINT(debug_rcv,
                "klips_debug:ipsec_rcv: "
                "suspected ESPinUDP packet (NAT-Traversal) [%d].\n",
                  tp->esp_in_udp);
            KLIPS_IP_PRINT(debug_rcv, ip);

            if (udpdata < skb->tail) {
                  unsigned int len = skb->tail - udpdata;
                  if ((len==1) && (udpdata[0]==0xff)) {
                        KLIPS_PRINT(debug_rcv,
                            "klips_debug:ipsec_rcv: "
                              /* not IPv6 compliant message */
                            "NAT-keepalive from %d.%d.%d.%d.\n", NIPQUAD(ip->saddr));
                        goto rcvleave;
                  }
                  else if ( (tp->esp_in_udp == ESPINUDP_WITH_NON_IKE) &&
                        (len > (2*sizeof(__u32) + sizeof(struct esphdr))) &&
                        (udpdata32[0]==0) && (udpdata32[1]==0) ) {
                        /* ESP Packet with Non-IKE header */
                        KLIPS_PRINT(debug_rcv, 
                              "klips_debug:ipsec_rcv: "
                              "ESPinUDP pkt with Non-IKE - spi=0x%x\n",
                              udpdata32[2]);
                        irs.natt_type = ESPINUDP_WITH_NON_IKE;
                        irs.natt_len = sizeof(struct udphdr)+(2*sizeof(__u32));
                  }
                  else if ( (tp->esp_in_udp == ESPINUDP_WITH_NON_ESP) &&
                        (len > sizeof(struct esphdr)) &&
                        (udpdata32[0]!=0) ) {
                        /* ESP Packet without Non-ESP header */
                        irs.natt_type = ESPINUDP_WITH_NON_ESP;
                        irs.natt_len = sizeof(struct udphdr);
                        KLIPS_PRINT(debug_rcv, 
                              "klips_debug:ipsec_rcv: "
                              "ESPinUDP pkt without Non-ESP - spi=0x%x\n",
                              udpdata32[0]);
                  }
                  else {
                        KLIPS_PRINT(debug_rcv,
                            "klips_debug:ipsec_rcv: "
                              "IKE packet - not handled here\n");
                        MOD_DEC_USE_COUNT;
                        return -1;
                  }
            }
            else {
                  MOD_DEC_USE_COUNT;
                  return -1;
            }
      }
#endif

#ifdef IPH_is_SKB_PULLED
      /* In Linux 2.4.4, the IP header has been skb_pull()ed before the
         packet is passed to us. So we'll skb_push() to get back to it. */
      if (skb->data == skb->h.raw) {
            skb_push(skb, skb->h.raw - skb->nh.raw);
      }
#endif /* IPH_is_SKB_PULLED */

      /* dev->hard_header_len is unreliable and should not be used */
      irs.hard_header_len = skb->mac.raw ? (skb->data - skb->mac.raw) : 0;
      if((irs.hard_header_len < 0) || (irs.hard_header_len > skb_headroom(skb)))
            irs.hard_header_len = 0;

#ifdef NET_21
      /* if skb was cloned (most likely due to a packet sniffer such as
         tcpdump being momentarily attached to the interface), make
         a copy of our own to modify */
      if(skb_cloned(skb)) {
            /* include any mac header while copying.. */
            if(skb_headroom(skb) < irs.hard_header_len) {
                  printk(KERN_WARNING "klips_error:ipsec_rcv: "
                         "tried to skb_push hhlen=%d, %d available.  This should never happen, please report.\n",
                         irs.hard_header_len,
                         skb_headroom(skb));
                  goto rcvleave;
            }
            skb_push(skb, irs.hard_header_len);
            if
#ifdef SKB_COW_NEW
              (skb_cow(skb, skb_headroom(skb)) != 0)
#else /* SKB_COW_NEW */
              ((skb = skb_cow(skb, skb_headroom(skb))) == NULL)
#endif /* SKB_COW_NEW */
            {
                  goto rcvleave;
            }
            if(skb->len < irs.hard_header_len) {
                  printk(KERN_WARNING "klips_error:ipsec_rcv: "
                         "tried to skb_pull hhlen=%d, %d available.  This should never happen, please report.\n",
                         irs.hard_header_len,
                         skb->len);
                  goto rcvleave;
            }
            skb_pull(skb, irs.hard_header_len);
      }

#endif /* NET_21 */

#if IP_FRAGMENT_LINEARIZE
      /* In Linux 2.4.4, we may have to reassemble fragments. They are
         not assembled automatically to save TCP from having to copy
         twice.
      */
      if (skb_is_nonlinear(skb)) {
            if (skb_linearize(skb, GFP_ATOMIC) != 0) {
                  goto rcvleave;
            }
      }
#endif /* IP_FRAGMENT_LINEARIZE */

#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
      if (irs.natt_len) {
            /**
             * Now, we are sure packet is ESPinUDP. Remove natt_len bytes from
             * packet and modify protocol to ESP.
             */
            if (((unsigned char *)skb->data > (unsigned char *)skb->nh.iph) &&
                  ((unsigned char *)skb->nh.iph > (unsigned char *)skb->head)) {
                  unsigned int _len = (unsigned char *)skb->data -
                        (unsigned char *)skb->nh.iph;
                  KLIPS_PRINT(debug_rcv,
                        "klips_debug:ipsec_rcv: adjusting skb: skb_push(%u)\n",
                        _len);
                  skb_push(skb, _len);
            }
            KLIPS_PRINT(debug_rcv,
                "klips_debug:ipsec_rcv: "
                  "removing %d bytes from ESPinUDP packet\n", irs.natt_len);
            ipp = (struct iphdr *)skb->data;
            irs.iphlen = ipp->ihl << 2;
            ipp->tot_len = htons(ntohs(ipp->tot_len) - irs.natt_len);
            if (skb->len < irs.iphlen + irs.natt_len) {
                  printk(KERN_WARNING
                   "klips_error:ipsec_rcv: "
                   "ESPinUDP packet is too small (%d < %d+%d). "
                     "This should never happen, please report.\n",
                   (int)(skb->len), irs.iphlen, irs.natt_len);
                  goto rcvleave;
            }
            memmove(skb->data + irs.natt_len, skb->data, irs.iphlen);
            skb_pull(skb, irs.natt_len);

            /* update nh.iph */
            ipp = skb->nh.iph = (struct iphdr *)skb->data;

            /* modify protocol */
            ipp->protocol = IPPROTO_ESP;

            skb->sk = NULL;

            KLIPS_IP_PRINT(debug_rcv, skb->nh.iph);
      }
#endif

      ipp = skb->nh.iph;
      ipsaddr.s_addr = ipp->saddr;
      addrtoa(ipsaddr, 0, irs.ipsaddr_txt, sizeof(irs.ipsaddr_txt));
      ipdaddr.s_addr = ipp->daddr;
      addrtoa(ipdaddr, 0, irs.ipdaddr_txt, sizeof(irs.ipdaddr_txt));
      irs.iphlen = ipp->ihl << 2;

      KLIPS_PRINT(debug_rcv,
                "klips_debug:ipsec_rcv: "
                "<<< Info -- ");
      KLIPS_PRINTMORE(debug_rcv && skb->dev, "skb->dev=%s ",
                  skb->dev->name ? skb->dev->name : "NULL");
      KLIPS_PRINTMORE(debug_rcv && dev, "dev=%s ",
                  dev->name ? dev->name : "NULL");
      KLIPS_PRINTMORE(debug_rcv, "\n");

      KLIPS_PRINT(debug_rcv && !(skb->dev && dev && (skb->dev == dev)),
                "klips_debug:ipsec_rcv: "
                "Informational -- **if this happens, find out why** skb->dev:%s is not equal to dev:%s\n",
                skb->dev ? (skb->dev->name ? skb->dev->name : "NULL") : "NULL",
                dev ? (dev->name ? dev->name : "NULL") : "NULL");

      protoc = ipp->protocol;
#ifndef NET_21
      if((!protocol) || (protocol->protocol != protoc)) {
            KLIPS_PRINT(debug_rcv & DB_RX_IPSA,
                      "klips_debug:ipsec_rcv: "
                      "protocol arg is NULL or unequal to the packet contents, this is odd, using value in packet.\n");
      }
#endif /* !NET_21 */

      if( (protoc != IPPROTO_AH) &&
#ifdef CONFIG_IPSEC_IPCOMP_disabled_until_we_register_IPCOMP_HANDLER
          (protoc != IPPROTO_COMP) &&
#endif /* CONFIG_IPSEC_IPCOMP */
          (protoc != IPPROTO_ESP) ) {
            KLIPS_PRINT(debug_rcv & DB_RX_IPSA,
                      "klips_debug:ipsec_rcv: Why the hell is someone "
                      "passing me a non-ipsec protocol = %d packet? -- dropped.\n",
                      protoc);
            goto rcvleave;
      }

      if(skb->dev) {
            for(i = 0; i < IPSEC_NUM_IF; i++) {
                  sprintf(name, IPSEC_DEV_FORMAT, i);
                  if(!strcmp(name, skb->dev->name)) {
                        prv = (struct ipsecpriv *)(skb->dev->priv);
                        if(prv) {
                              stats = (struct net_device_stats *) &(prv->mystats);
                        }
                        ipsecdev = skb->dev;
                        KLIPS_PRINT(debug_rcv,
                                  "klips_debug:ipsec_rcv: "
                                  "Info -- pkt already proc'ed a group of ipsec headers, processing next group of ipsec headers.\n");
                        break;
                  }
                  if((ipsecdev = __ipsec_dev_get(name)) == NULL) {
                        KLIPS_PRINT(debug_rcv,
                                  "klips_error:ipsec_rcv: "
                                  "device %s does not exist\n",
                                  name);
                  }
                  prv = ipsecdev ? (struct ipsecpriv *)(ipsecdev->priv) : NULL;
                  prvdev = prv ? (struct device *)(prv->dev) : NULL;

#if 0
                  KLIPS_PRINT(debug_rcv && prvdev,
                            "klips_debug:ipsec_rcv: "
                            "physical device for device %s is %s\n",
                            name,
                            prvdev->name);
#endif
                  if(prvdev && skb->dev &&
                     !strcmp(prvdev->name, skb->dev->name)) {
                        stats = prv ? ((struct net_device_stats *) &(prv->mystats)) : NULL;
                        skb->dev = ipsecdev;
                        KLIPS_PRINT(debug_rcv && prvdev,
                                  "klips_debug:ipsec_rcv: "
                                  "assigning packet ownership to virtual device %s from physical device %s.\n",
                                  name, prvdev->name);
                        if(stats) {
                              stats->rx_packets++;
                        }
                        break;
                  }
            }
      } else {
            KLIPS_PRINT(debug_rcv,
                      "klips_debug:ipsec_rcv: "
                      "device supplied with skb is NULL\n");
      }

      if(stats == NULL) {
            KLIPS_PRINT((debug_rcv),
                      "klips_error:ipsec_rcv: "
                      "packet received from physical I/F (%s) not connected to ipsec I/F.  Cannot record stats.  May not have SA for decoding.  Is IPSEC traffic expected on this I/F?  Check routing.\n",
                      skb->dev ? (skb->dev->name ? skb->dev->name : "NULL") : "NULL");
      }
            
      KLIPS_IP_PRINT(debug_rcv, ipp);

      /* begin decapsulating loop here */

      /*
        The spinlock is to prevent any other process from
        accessing or deleting the ipsec_sa hash table or any of the
        ipsec_sa s while we are using and updating them.

        This is not optimal, but was relatively straightforward
        at the time.  A better way to do it has been planned for
        more than a year, to lock the hash table and put reference
        counts on each ipsec_sa instead.  This is not likely to happen
        in KLIPS1 unless a volunteer contributes it, but will be
        designed into KLIPS2.
      */
      spin_lock(&tdb_lock);

      /* set up for decap loop */
      irs.stats= stats;
      irs.ipp  = ipp;
      irs.ipsp = NULL;
      irs.ilen = 0;
      irs.authlen=0;
      irs.authfuncs=NULL;
      irs.skb = skb;

      do {
              int decap_stat;

              decap_stat = ipsec_rcv_decap_once(&irs);

            if(decap_stat != IPSEC_RCV_OK) {
                  spin_unlock(&tdb_lock);
                  KLIPS_PRINT(debug_rcv,
                            "klips_debug:ipsec_rcv: decap_once failed: %d\n",
                            decap_stat);
            
                  goto rcvleave;
            }
      /* end decapsulation loop here */
      } while(   (irs.ipp->protocol == IPPROTO_ESP )
            || (irs.ipp->protocol == IPPROTO_AH  )
#ifdef CONFIG_IPSEC_IPCOMP
            || (irs.ipp->protocol == IPPROTO_COMP)
#endif /* CONFIG_IPSEC_IPCOMP */
            );

      /* set up for decap loop */
      ipp  =irs.ipp;
      ipsp =irs.ipsp;
      ipsnext = ipsp->ips_inext;
      skb = irs.skb;

      /* if there is an IPCOMP, but we don't have an IPPROTO_COMP,
       * then we can just skip it
       */
#ifdef CONFIG_IPSEC_IPCOMP
      if(ipsnext && ipsnext->ips_said.proto == IPPROTO_COMP) {
            ipsp = ipsnext;
            ipsnext = ipsp->ips_inext;
      }
#endif /* CONFIG_IPSEC_IPCOMP */

#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
      if ((irs.natt_type) && (ipp->protocol != IPPROTO_IPIP)) {
            /**
             * NAT-Traversal and Transport Mode:
             *   we need to correct TCP/UDP checksum
             *
             * If we've got NAT-OA, we can fix checksum without recalculation.
             */
            __u32 natt_oa = ipsp->ips_natt_oa ?
                  ((struct sockaddr_in*)(ipsp->ips_natt_oa))->sin_addr.s_addr : 0;
            __u16 pkt_len = skb->tail - (unsigned char *)ipp;
            __u16 data_len = pkt_len - (ipp->ihl << 2);

            switch (ipp->protocol) {
                  case IPPROTO_TCP:
                        if (data_len >= sizeof(struct tcphdr)) {
                              struct tcphdr *tcp = (struct tcphdr *)((__u32 *)ipp+ipp->ihl);
                              if (natt_oa) {
                                    __u32 buff[2] = { ~natt_oa, ipp->saddr };
                                    KLIPS_PRINT(debug_rcv,
                                    "klips_debug:ipsec_rcv: "
                                          "NAT-T & TRANSPORT: "
                                          "fix TCP checksum using NAT-OA\n");
                                    tcp->check = csum_fold(
                                          csum_partial((unsigned char *)buff, sizeof(buff),
                                          tcp->check^0xffff));
                              }
                              else {
                                    KLIPS_PRINT(debug_rcv,
                                    "klips_debug:ipsec_rcv: "
                                          "NAT-T & TRANSPORT: recalc TCP checksum\n");
                                    if (pkt_len > (ntohs(ipp->tot_len)))
                                          data_len -= (pkt_len - ntohs(ipp->tot_len));
                                    tcp->check = 0;
                                    tcp->check = csum_tcpudp_magic(ipp->saddr, ipp->daddr,
                                          data_len, IPPROTO_TCP,
                                          csum_partial((unsigned char *)tcp, data_len, 0));
                              }
                        }
                        else {
                              KLIPS_PRINT(debug_rcv,
                              "klips_debug:ipsec_rcv: "
                                    "NAT-T & TRANSPORT: can't fix TCP checksum\n");
                        }
                        break;
                  case IPPROTO_UDP:
                        if (data_len >= sizeof(struct udphdr)) {
                              struct udphdr *udp = (struct udphdr *)((__u32 *)ipp+ipp->ihl);
                              if (udp->check == 0) {
                                    KLIPS_PRINT(debug_rcv,
                                    "klips_debug:ipsec_rcv: "
                                          "NAT-T & TRANSPORT: UDP checksum already 0\n");
                              }
                              else if (natt_oa) {
                                    __u32 buff[2] = { ~natt_oa, ipp->saddr };
                                    KLIPS_PRINT(debug_rcv,
                                    "klips_debug:ipsec_rcv: "
                                          "NAT-T & TRANSPORT: "
                                          "fix UDP checksum using NAT-OA\n");
                                    udp->check = csum_fold(
                                          csum_partial((unsigned char *)buff, sizeof(buff),
                                          udp->check^0xffff));
                              }
                              else {
                                    KLIPS_PRINT(debug_rcv,
                                    "klips_debug:ipsec_rcv: "
                                          "NAT-T & TRANSPORT: zero UDP checksum\n");
                                    udp->check = 0;
                              }
                        }
                        else {
                              KLIPS_PRINT(debug_rcv,
                              "klips_debug:ipsec_rcv: "
                                    "NAT-T & TRANSPORT: can't fix UDP checksum\n");
                        }
                        break;
                  default:
                        KLIPS_PRINT(debug_rcv,
                        "klips_debug:ipsec_rcv: "
                              "NAT-T & TRANSPORT: non TCP/UDP packet -- do nothing\n");
                        break;
            }
      }
#endif

      /*
       * XXX this needs to be locked from when it was first looked
       * up in the decapsulation loop.  Perhaps it is better to put
       * the IPIP decap inside the loop.
       */
      if(ipsnext) {
            ipsp = ipsnext;
            irs.sa_len = satoa(irs.said, 0, irs.sa, SATOA_BUF);
            if(ipp->protocol != IPPROTO_IPIP) {
                  spin_unlock(&tdb_lock);
                  KLIPS_PRINT(debug_rcv,
                            "klips_debug:ipsec_rcv: "
                            "SA:%s, Hey!  How did this get through?  Dropped.\n",
                            irs.sa_len ? irs.sa : " (error)");
                  if(stats) {
                        stats->rx_dropped++;
                  }
                  goto rcvleave;
            }
            if(sysctl_ipsec_inbound_policy_check) {
                  if((ipsnext = ipsp->ips_inext)) {
                        char sa2[SATOA_BUF];
                        size_t sa_len2;
                        sa_len2 = satoa(ipsnext->ips_said, 0, sa2, SATOA_BUF);
                        spin_unlock(&tdb_lock);
                        KLIPS_PRINT(debug_rcv,
                                  "klips_debug:ipsec_rcv: "
                                  "unexpected SA:%s after IPIP SA:%s\n",
                                  sa_len2 ? sa2 : " (error)",
                                  irs.sa_len ? irs.sa : " (error)");
                        if(stats) {
                              stats->rx_dropped++;
                        }
                        goto rcvleave;
                  }
                  if(ipp->saddr != ((struct sockaddr_in*)(ipsp->ips_addr_s))->sin_addr.s_addr) {
                        spin_unlock(&tdb_lock);
                        KLIPS_PRINT(debug_rcv,
                                  "klips_debug:ipsec_rcv: "
                                  "SA:%s, src=%s of pkt does not agree with expected SA source address policy.\n",
                                  irs.sa_len ? irs.sa : " (error)",
                                  irs.ipsaddr_txt);
                        if(stats) {
                              stats->rx_dropped++;
                        }
                        goto rcvleave;
                  }
            }

            /*
             * XXX this needs to be locked from when it was first looked
             * up in the decapsulation loop.  Perhaps it is better to put
             * the IPIP decap inside the loop.
             */
            ipsp->ips_life.ipl_bytes.ipl_count += skb->len;
            ipsp->ips_life.ipl_bytes.ipl_last   = skb->len;

            if(!ipsp->ips_life.ipl_usetime.ipl_count) {
                  ipsp->ips_life.ipl_usetime.ipl_count = jiffies / HZ;
            }
            ipsp->ips_life.ipl_usetime.ipl_last = jiffies / HZ;
            ipsp->ips_life.ipl_packets.ipl_count += 1;

            if(skb->len < irs.iphlen) {
                  spin_unlock(&tdb_lock);
                  printk(KERN_WARNING "klips_debug:ipsec_rcv: "
                         "tried to skb_pull iphlen=%d, %d available.  This should never happen, please report.\n",
                         irs.iphlen,
                         (int)(skb->len));

                  goto rcvleave;
            }
            skb_pull(skb, irs.iphlen);

#ifdef NET_21
            skb->nh.raw = skb->data;
            ipp = (struct iphdr *)skb->nh.raw;
            skb->h.raw = skb->nh.raw + (skb->nh.iph->ihl << 2);

            memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
#else /* NET_21 */
            ipp = skb->ip_hdr = skb->h.iph = (struct iphdr *)skb->data;

            memset(skb->proto_priv, 0, sizeof(struct options));
#endif /* NET_21 */
            ipsaddr.s_addr = ipp->saddr;
            addrtoa(ipsaddr, 0, irs.ipsaddr_txt, sizeof(irs.ipsaddr_txt));
            ipdaddr.s_addr = ipp->daddr;
            addrtoa(ipdaddr, 0, irs.ipdaddr_txt, sizeof(irs.ipdaddr_txt));

            skb->protocol = htons(ETH_P_IP);
            skb->ip_summed = 0;
            KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
                      "klips_debug:ipsec_rcv: "
                      "IPIP tunnel stripped.\n");
            KLIPS_IP_PRINT(debug_rcv & DB_RX_PKTRX, ipp);

            if(sysctl_ipsec_inbound_policy_check
               /*
                  Note: "xor" (^) logically replaces "not equal"
                  (!=) and "bitwise or" (|) logically replaces
                  "boolean or" (||).  This is done to speed up
                  execution by doing only bitwise operations and
                  no branch operations
               */
               && (((ipp->saddr & ipsp->ips_mask_s.u.v4.sin_addr.s_addr)
                            ^ ipsp->ips_flow_s.u.v4.sin_addr.s_addr)
                   | ((ipp->daddr & ipsp->ips_mask_d.u.v4.sin_addr.s_addr)
                              ^ ipsp->ips_flow_d.u.v4.sin_addr.s_addr)) )
            {
                  char sflow_txt[SUBNETTOA_BUF], dflow_txt[SUBNETTOA_BUF];

                  subnettoa(ipsp->ips_flow_s.u.v4.sin_addr,
                        ipsp->ips_mask_s.u.v4.sin_addr,
                        0, sflow_txt, sizeof(sflow_txt));
                  subnettoa(ipsp->ips_flow_d.u.v4.sin_addr,
                        ipsp->ips_mask_d.u.v4.sin_addr,
                        0, dflow_txt, sizeof(dflow_txt));
                  spin_unlock(&tdb_lock);
                  KLIPS_PRINT(debug_rcv,
                            "klips_debug:ipsec_rcv: "
                            "SA:%s, inner tunnel policy [%s -> %s] does not agree with pkt contents [%s -> %s].\n",
                            irs.sa_len ? irs.sa : " (error)",
                            sflow_txt,
                            dflow_txt,
                            irs.ipsaddr_txt,
                            irs.ipdaddr_txt);
                  if(stats) {
                        stats->rx_dropped++;
                  }
                  goto rcvleave;
            }
#ifdef CONFIG_NETFILTER
            skb->nfmark = (skb->nfmark & (~(IPsecSAref2NFmark(IPSEC_SA_REF_TABLE_MASK))))
                  | IPsecSAref2NFmark(IPsecSA2SAref(ipsp));
            KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
                      "klips_debug:ipsec_rcv: "
                      "IPIP SA sets skb->nfmark=0x%x.\n",
                      (unsigned)skb->nfmark);
#endif /* CONFIG_NETFILTER */
      }

      spin_unlock(&tdb_lock);

#ifdef NET_21
      if(stats) {
            stats->rx_bytes += skb->len;
      }
      if(skb->dst) {
            dst_release(skb->dst);
            skb->dst = NULL;
      }
      skb->pkt_type = PACKET_HOST;
      if(irs.hard_header_len &&
         (skb->mac.raw != (skb->data - irs.hard_header_len)) &&
         (irs.hard_header_len <= skb_headroom(skb))) {
            /* copy back original MAC header */
            memmove(skb->data - irs.hard_header_len, skb->mac.raw, irs.hard_header_len);
            skb->mac.raw = skb->data - irs.hard_header_len;
      }
#endif /* NET_21 */

#ifdef CONFIG_IPSEC_IPCOMP
      if(ipp->protocol == IPPROTO_COMP) {
            unsigned int flags = 0;

            if(sysctl_ipsec_inbound_policy_check) {
                  KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
                        "klips_debug:ipsec_rcv: "
                        "inbound policy checking enabled, IPCOMP follows IPIP, dropped.\n");
                  if (stats) {
                        stats->rx_errors++;
                  }
                  goto rcvleave;
            }
            /*
              XXX need a ipsec_sa for updating ratio counters but it is not
              following policy anyways so it is not a priority
            */
            skb = skb_decompress(skb, NULL, &flags);
            if (!skb || flags) {
                  KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
                        "klips_debug:ipsec_rcv: "
                        "skb_decompress() returned error flags: %d, dropped.\n",
                        flags);
                  if (stats) {
                        stats->rx_errors++;
                  }
                  goto rcvleave;
            }
      }
#endif /* CONFIG_IPSEC_IPCOMP */

#ifdef SKB_RESET_NFCT
      nf_conntrack_put(skb->nfct);
      skb->nfct = NULL;
#ifdef CONFIG_NETFILTER_DEBUG
      skb->nf_debug = 0;
#endif /* CONFIG_NETFILTER_DEBUG */
#endif /* SKB_RESET_NFCT */
      KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
                "klips_debug:ipsec_rcv: "
                "netif_rx() called.\n");
      netif_rx(skb);

      MOD_DEC_USE_COUNT;
      return(0);

 rcvleave:
      if(skb) {
            ipsec_kfree_skb(skb);
      }

      MOD_DEC_USE_COUNT;
      return(0);
}

struct inet_protocol ah_protocol =
{
      ipsec_rcv,                    /* AH handler */
      NULL,                   /* TUNNEL error control */
#ifdef NETDEV_25
      1,                      /* no policy */
#else
      0,                      /* next */
      IPPROTO_AH,             /* protocol ID */
      0,                      /* copy */
      NULL,                   /* data */
      "AH"                    /* name */
#endif
};

struct inet_protocol esp_protocol =
{
      ipsec_rcv,              /* ESP handler          */
      NULL,                   /* TUNNEL error control */
#ifdef NETDEV_25
      1,                      /* no policy */
#else
      0,                      /* next */
      IPPROTO_ESP,                  /* protocol ID */
      0,                      /* copy */
      NULL,                   /* data */
      "ESP"                   /* name */
#endif
};

#if 0
/* We probably don't want to install a pure IPCOMP protocol handler, but
   only want to handle IPCOMP if it is encapsulated inside an ESP payload
   (which is already handled) */
#ifdef CONFIG_IPSEC_IPCOMP
struct inet_protocol comp_protocol =
{
      ipsec_rcv,              /* COMP handler         */
      NULL,                   /* COMP error control   */
#ifdef NETDEV_25
      1,                      /* no policy */
#else
      0,                      /* next */
      IPPROTO_COMP,                 /* protocol ID */
      0,                      /* copy */
      NULL,                   /* data */
      "COMP"                        /* name */
#endif
};
#endif /* CONFIG_IPSEC_IPCOMP */
#endif

Generated by  Doxygen 1.6.0   Back to index