diff options
Diffstat (limited to 'linux/net/ipsec/ipsec_rcv.c')
-rw-r--r-- | linux/net/ipsec/ipsec_rcv.c | 2204 |
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 |