summaryrefslogtreecommitdiff
path: root/linux/net/ipsec/ipsec_rcv.c
diff options
context:
space:
mode:
Diffstat (limited to 'linux/net/ipsec/ipsec_rcv.c')
-rw-r--r--linux/net/ipsec/ipsec_rcv.c2204
1 files changed, 2204 insertions, 0 deletions
diff --git a/linux/net/ipsec/ipsec_rcv.c b/linux/net/ipsec/ipsec_rcv.c
new file mode 100644
index 000000000..4df839fe2
--- /dev/null
+++ b/linux/net/ipsec/ipsec_rcv.c
@@ -0,0 +1,2204 @@
+/*
+ * 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