Files
strongswan-ext/linux/net/ipsec/ipsec_rcv.c
T
Martin Willi 997358a6c4 - import of strongswan-2.7.0
- applied patch for charon
2006-04-28 07:14:48 +00:00

2205 lines
60 KiB
C

/*
* receive code
* Copyright (C) 1996, 1997 John Ioannidis.
* Copyright (C) 1998, 1999, 2000, 2001 Richard Guy Briggs.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
char ipsec_rcv_c_version[] = "RCSID $Id: ipsec_rcv.c,v 1.5 2005/04/10 21:38:32 as Exp $";
#include <linux/config.h>
#include <linux/version.h>
#define __NO_VERSION__
#include <linux/module.h>
#include <linux/kernel.h> /* printk() */
#include "freeswan/ipsec_param.h"
#ifdef MALLOC_SLAB
# include <linux/slab.h> /* kmalloc() */
#else /* MALLOC_SLAB */
# include <linux/malloc.h> /* kmalloc() */
#endif /* MALLOC_SLAB */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/interrupt.h> /* mark_bh */
#include <linux/netdevice.h> /* struct device, and other headers */
#include <linux/etherdevice.h> /* eth_type_trans */
#include <linux/ip.h> /* struct iphdr */
#include <linux/skbuff.h>
#include <freeswan.h>
#ifdef SPINLOCK
# ifdef SPINLOCK_23
# include <linux/spinlock.h> /* *lock* */
# else /* SPINLOCK_23 */
# include <asm/spinlock.h> /* *lock* */
# endif /* SPINLOCK_23 */
#endif /* SPINLOCK */
#ifdef NET_21
# include <asm/uaccess.h>
# include <linux/in6.h>
# define proto_priv cb
#endif /* NET21 */
#include <asm/checksum.h>
#include <net/ip.h>
#include "freeswan/radij.h"
#include "freeswan/ipsec_encap.h"
#include "freeswan/ipsec_sa.h"
#include "freeswan/ipsec_radij.h"
#include "freeswan/ipsec_xform.h"
#include "freeswan/ipsec_tunnel.h"
#include "freeswan/ipsec_rcv.h"
#if defined(CONFIG_IPSEC_ESP) || defined(CONFIG_IPSEC_AH)
#include "freeswan/ipsec_ah.h"
#endif /* defined(CONFIG_IPSEC_ESP) || defined(CONFIG_IPSEC_AH) */
#ifdef CONFIG_IPSEC_ESP
#include "freeswan/ipsec_esp.h"
#endif /* !CONFIG_IPSEC_ESP */
#ifdef CONFIG_IPSEC_IPCOMP
#include "freeswan/ipcomp.h"
#endif /* CONFIG_IPSEC_COMP */
#include <pfkeyv2.h>
#include <pfkey.h>
#include "freeswan/ipsec_proto.h"
#include "freeswan/ipsec_alg.h"
#ifdef CONFIG_IPSEC_DEBUG
int debug_ah = 0;
int debug_esp = 0;
int debug_rcv = 0;
#endif /* CONFIG_IPSEC_DEBUG */
int sysctl_ipsec_inbound_policy_check = 1;
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
#include <linux/udp.h>
#endif
#ifdef CONFIG_IPSEC_DEBUG
static void
rcv_dmp(char *s, caddr_t bb, int len)
{
int i;
unsigned char *b = bb;
if (debug_rcv && sysctl_ipsec_debug_verbose) {
printk(KERN_INFO "klips_debug:ipsec_tunnel_:dmp: "
"at %s, len=%d:",
s,
len);
for (i=0; i < len; i++) {
if(!(i%16)){
printk("\nklips_debug: ");
}
printk(" %02x", *b++);
}
printk("\n");
}
}
#else /* CONFIG_IPSEC_DEBUG */
#define rcv_dmp(_x, _y, _z)
#endif /* CONFIG_IPSEC_DEBUG */
#if defined(CONFIG_IPSEC_ESP) || defined(CONFIG_IPSEC_AH)
__u32 zeroes[AH_AMAX];
#endif /* defined(CONFIG_IPSEC_ESP) || defined(CONFIG_IPSEC_AH) */
/*
* Check-replay-window routine, adapted from the original
* by J. Hughes, from draft-ietf-ipsec-esp-des-md5-03.txt
*
* This is a routine that implements a 64 packet window. This is intend-
* ed on being an implementation sample.
*/
DEBUG_NO_STATIC int
ipsec_checkreplaywindow(struct ipsec_sa*ipsp, __u32 seq)
{
__u32 diff;
if (ipsp->ips_replaywin == 0) /* replay shut off */
return 1;
if (seq == 0)
return 0; /* first == 0 or wrapped */
/* new larger sequence number */
if (seq > ipsp->ips_replaywin_lastseq) {
return 1; /* larger is good */
}
diff = ipsp->ips_replaywin_lastseq - seq;
/* too old or wrapped */ /* if wrapped, kill off SA? */
if (diff >= ipsp->ips_replaywin) {
return 0;
}
/* this packet already seen */
if (ipsp->ips_replaywin_bitmap & (1 << diff))
return 0;
return 1; /* out of order but good */
}
DEBUG_NO_STATIC int
ipsec_updatereplaywindow(struct ipsec_sa*ipsp, __u32 seq)
{
__u32 diff;
if (ipsp->ips_replaywin == 0) /* replay shut off */
return 1;
if (seq == 0)
return 0; /* first == 0 or wrapped */
/* new larger sequence number */
if (seq > ipsp->ips_replaywin_lastseq) {
diff = seq - ipsp->ips_replaywin_lastseq;
/* In win, set bit for this pkt */
if (diff < ipsp->ips_replaywin)
ipsp->ips_replaywin_bitmap =
(ipsp->ips_replaywin_bitmap << diff) | 1;
else
/* This packet has way larger seq num */
ipsp->ips_replaywin_bitmap = 1;
if(seq - ipsp->ips_replaywin_lastseq - 1 > ipsp->ips_replaywin_maxdiff) {
ipsp->ips_replaywin_maxdiff = seq - ipsp->ips_replaywin_lastseq - 1;
}
ipsp->ips_replaywin_lastseq = seq;
return 1; /* larger is good */
}
diff = ipsp->ips_replaywin_lastseq - seq;
/* too old or wrapped */ /* if wrapped, kill off SA? */
if (diff >= ipsp->ips_replaywin) {
/*
if(seq < 0.25*max && ipsp->ips_replaywin_lastseq > 0.75*max) {
ipsec_sa_delchain(ipsp);
}
*/
return 0;
}
/* this packet already seen */
if (ipsp->ips_replaywin_bitmap & (1 << diff))
return 0;
ipsp->ips_replaywin_bitmap |= (1 << diff); /* mark as seen */
return 1; /* out of order but good */
}
#ifdef CONFIG_IPSEC_AUTH_HMAC_MD5
struct auth_alg ipsec_rcv_md5[]={
{MD5Init, MD5Update, MD5Final, AHMD596_ALEN}
};
#endif /* CONFIG_IPSEC_AUTH_HMAC_MD5 */
#ifdef CONFIG_IPSEC_AUTH_HMAC_SHA1
struct auth_alg ipsec_rcv_sha1[]={
{SHA1Init, SHA1Update, SHA1Final, AHSHA196_ALEN}
};
#endif /* CONFIG_IPSEC_AUTH_HMAC_MD5 */
enum ipsec_rcv_value {
IPSEC_RCV_LASTPROTO=1,
IPSEC_RCV_OK=0,
IPSEC_RCV_BADPROTO=-1,
IPSEC_RCV_BADLEN=-2,
IPSEC_RCV_ESP_BADALG=-3,
IPSEC_RCV_3DES_BADBLOCKING=-4,
IPSEC_RCV_ESP_DECAPFAIL=-5,
IPSEC_RCV_DECAPFAIL=-6,
IPSEC_RCV_SAIDNOTFOUND=-7,
IPSEC_RCV_IPCOMPALONE=-8,
IPSEC_RCV_IPCOMPFAILED=-10,
IPSEC_RCV_SAIDNOTLIVE=-11,
IPSEC_RCV_FAILEDINBOUND=-12,
IPSEC_RCV_LIFETIMEFAILED=-13,
IPSEC_RCV_BADAUTH=-14,
IPSEC_RCV_REPLAYFAILED=-15,
IPSEC_RCV_AUTHFAILED=-16,
IPSEC_RCV_REPLAYROLLED=-17,
IPSEC_RCV_BAD_DECRYPT=-18
};
struct ipsec_rcv_state {
struct sk_buff *skb;
struct net_device_stats *stats;
struct iphdr *ipp;
struct ipsec_sa *ipsp;
int len;
int ilen;
int authlen;
int hard_header_len;
int iphlen;
struct auth_alg *authfuncs;
struct sa_id said;
char sa[SATOA_BUF];
size_t sa_len;
__u8 next_header;
__u8 hash[AH_AMAX];
char ipsaddr_txt[ADDRTOA_BUF];
char ipdaddr_txt[ADDRTOA_BUF];
__u8 *octx;
__u8 *ictx;
int ictx_len;
int octx_len;
union {
struct {
struct esphdr *espp;
} espstuff;
struct {
struct ahhdr *ahp;
} ahstuff;
struct {
struct ipcomphdr *compp;
} ipcompstuff;
} protostuff;
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
__u16 natt_len;
__u16 natt_sport;
__u16 natt_dport;
__u8 natt_type;
#endif
};
struct xform_functions {
enum ipsec_rcv_value (*checks)(struct ipsec_rcv_state *irs,
struct sk_buff *skb);
enum ipsec_rcv_value (*decrypt)(struct ipsec_rcv_state *irs);
enum ipsec_rcv_value (*setup_auth)(struct ipsec_rcv_state *irs,
struct sk_buff *skb,
__u32 *replay,
unsigned char **authenticator);
enum ipsec_rcv_value (*calc_auth)(struct ipsec_rcv_state *irs,
struct sk_buff *skb);
};
#ifdef CONFIG_IPSEC_ESP
enum ipsec_rcv_value
ipsec_rcv_esp_checks(struct ipsec_rcv_state *irs,
struct sk_buff *skb)
{
__u8 proto;
int len; /* packet length */
len = skb->len;
proto = irs->ipp->protocol;
/* XXX this will need to be 8 for IPv6 */
if ((proto == IPPROTO_ESP) && ((len - irs->iphlen) % 4)) {
printk("klips_error:ipsec_rcv: "
"got packet with content length = %d from %s -- should be on 4 octet boundary, packet dropped\n",
len - irs->iphlen,
irs->ipsaddr_txt);
if(irs->stats) {
irs->stats->rx_errors++;
}
return IPSEC_RCV_BADLEN;
}
if(skb->len < (irs->hard_header_len + sizeof(struct iphdr) + sizeof(struct esphdr))) {
KLIPS_PRINT(debug_rcv & DB_RX_INAU,
"klips_debug:ipsec_rcv: "
"runt esp packet of skb->len=%d received from %s, dropped.\n",
skb->len,
irs->ipsaddr_txt);
if(irs->stats) {
irs->stats->rx_errors++;
}
return IPSEC_RCV_BADLEN;
}
irs->protostuff.espstuff.espp = (struct esphdr *)(skb->data + irs->iphlen);
irs->said.spi = irs->protostuff.espstuff.espp->esp_spi;
return IPSEC_RCV_OK;
}
enum ipsec_rcv_value
ipsec_rcv_esp_decrypt_setup(struct ipsec_rcv_state *irs,
struct sk_buff *skb,
__u32 *replay,
unsigned char **authenticator)
{
struct esphdr *espp = irs->protostuff.espstuff.espp;
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"packet from %s received with seq=%d (iv)=0x%08x%08x iplen=%d esplen=%d sa=%s\n",
irs->ipsaddr_txt,
(__u32)ntohl(espp->esp_rpl),
(__u32)ntohl(*((__u32 *)(espp->esp_iv) )),
(__u32)ntohl(*((__u32 *)(espp->esp_iv) + 1)),
irs->len,
irs->ilen,
irs->sa_len ? irs->sa : " (error)");
*replay = ntohl(espp->esp_rpl);
*authenticator = &(skb->data[irs->len - irs->authlen]);
return IPSEC_RCV_OK;
}
enum ipsec_rcv_value
ipsec_rcv_esp_authcalc(struct ipsec_rcv_state *irs,
struct sk_buff *skb)
{
struct auth_alg *aa;
struct esphdr *espp = irs->protostuff.espstuff.espp;
union {
MD5_CTX md5;
SHA1_CTX sha1;
} tctx;
#ifdef CONFIG_IPSEC_ALG
if (irs->ipsp->ips_alg_auth) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"ipsec_alg hashing proto=%d... ",
irs->said.proto);
if(irs->said.proto == IPPROTO_ESP) {
ipsec_alg_sa_esp_hash(irs->ipsp,
(caddr_t)espp, irs->ilen,
irs->hash, AHHMAC_HASHLEN);
return IPSEC_RCV_OK;
}
return IPSEC_RCV_BADPROTO;
}
#endif
aa = irs->authfuncs;
/* copy the initialized keying material */
memcpy(&tctx, irs->ictx, irs->ictx_len);
(*aa->update)((void *)&tctx, (caddr_t)espp, irs->ilen);
(*aa->final)(irs->hash, (void *)&tctx);
memcpy(&tctx, irs->octx, irs->octx_len);
(*aa->update)((void *)&tctx, irs->hash, aa->hashlen);
(*aa->final)(irs->hash, (void *)&tctx);
return IPSEC_RCV_OK;
}
enum ipsec_rcv_value
ipsec_rcv_esp_decrypt(struct ipsec_rcv_state *irs)
{
struct ipsec_sa *ipsp = irs->ipsp;
struct esphdr *espp = irs->protostuff.espstuff.espp;
int esphlen = 0;
__u8 *idat; /* pointer to content to be decrypted/authenticated */
#ifdef CONFIG_IPSEC_ENC_3DES
__u32 iv[2];
#endif /* !CONFIG_IPSEC_ENC_3DES */
int pad = 0, padlen;
int badpad = 0;
int i;
struct sk_buff *skb;
#ifdef CONFIG_IPSEC_ALG
struct ipsec_alg_enc *ixt_e=NULL;
#endif /* CONFIG_IPSEC_ALG */
skb=irs->skb;
idat = skb->data + irs->iphlen;
#ifdef CONFIG_IPSEC_ALG
if ((ixt_e=ipsp->ips_alg_enc)) {
esphlen = ESP_HEADER_LEN + ixt_e->ixt_ivlen/8;
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"encalg=%d esphlen=%d\n",
ipsp->ips_encalg, esphlen);
} else
#endif /* CONFIG_IPSEC_ALG */
switch(ipsp->ips_encalg) {
#ifdef CONFIG_IPSEC_ENC_3DES
case ESP_3DES:
iv[0] = *((__u32 *)(espp->esp_iv) );
iv[1] = *((__u32 *)(espp->esp_iv) + 1);
esphlen = sizeof(struct esphdr);
break;
#endif /* !CONFIG_IPSEC_ENC_3DES */
default:
ipsp->ips_errs.ips_alg_errs += 1;
if(irs->stats) {
irs->stats->rx_errors++;
}
return IPSEC_RCV_ESP_BADALG;
}
idat += esphlen;
irs->ilen -= esphlen;
#ifdef CONFIG_IPSEC_ALG
if (ixt_e)
{
if (ipsec_alg_esp_encrypt(ipsp,
idat, irs->ilen, espp->esp_iv,
IPSEC_ALG_DECRYPT) <= 0)
{
printk("klips_error:ipsec_rcv: "
"got packet with esplen = %d "
"from %s -- should be on "
"ENC(%d) octet boundary, "
"packet dropped\n",
irs->ilen,
irs->ipsaddr_txt,
ipsp->ips_encalg);
if(irs->stats) {
irs->stats->rx_errors++;
}
return IPSEC_RCV_BAD_DECRYPT;
}
} else
#endif /* CONFIG_IPSEC_ALG */
switch(ipsp->ips_encalg) {
#ifdef CONFIG_IPSEC_ENC_3DES
case ESP_3DES:
if ((irs->ilen) % 8) {
ipsp->ips_errs.ips_encsize_errs += 1;
printk("klips_error:ipsec_rcv: "
"got packet with esplen = %d from %s -- should be on 8 octet boundary, packet dropped\n",
irs->ilen,
irs->ipsaddr_txt);
if(irs->stats) {
irs->stats->rx_errors++;
}
return IPSEC_RCV_3DES_BADBLOCKING;
}
des_ede3_cbc_encrypt((des_cblock *)idat,
(des_cblock *)idat,
irs->ilen,
((struct des_eks *)(ipsp->ips_key_e))[0].ks,
((struct des_eks *)(ipsp->ips_key_e))[1].ks,
((struct des_eks *)(ipsp->ips_key_e))[2].ks,
(des_cblock *)iv, 0);
break;
#endif /* !CONFIG_IPSEC_ENC_3DES */
}
rcv_dmp("postdecrypt", skb->data, skb->len);
irs->next_header = idat[irs->ilen - 1];
padlen = idat[irs->ilen - 2];
pad = padlen + 2 + irs->authlen;
KLIPS_PRINT(debug_rcv & DB_RX_IPAD,
"klips_debug:ipsec_rcv: "
"padlen=%d, contents: 0x<offset>: 0x<value> 0x<value> ...\n",
padlen);
for (i = 1; i <= padlen; i++) {
if((i % 16) == 1) {
KLIPS_PRINT(debug_rcv & DB_RX_IPAD,
"klips_debug: %02x:",
i - 1);
}
KLIPS_PRINTMORE(debug_rcv & DB_RX_IPAD,
" %02x",
idat[irs->ilen - 2 - padlen + i - 1]);
if(i != idat[irs->ilen - 2 - padlen + i - 1]) {
badpad = 1;
}
if((i % 16) == 0) {
KLIPS_PRINTMORE(debug_rcv & DB_RX_IPAD,
"\n");
}
}
if((i % 16) != 1) {
KLIPS_PRINTMORE(debug_rcv & DB_RX_IPAD,
"\n");
}
if(badpad) {
KLIPS_PRINT(debug_rcv & DB_RX_IPAD,
"klips_debug:ipsec_rcv: "
"warning, decrypted packet from %s has bad padding\n",
irs->ipsaddr_txt);
KLIPS_PRINT(debug_rcv & DB_RX_IPAD,
"klips_debug:ipsec_rcv: "
"...may be bad decryption -- not dropped\n");
ipsp->ips_errs.ips_encpad_errs += 1;
}
KLIPS_PRINT(debug_rcv & DB_RX_IPAD,
"klips_debug:ipsec_rcv: "
"packet decrypted from %s: next_header = %d, padding = %d\n",
irs->ipsaddr_txt,
irs->next_header,
pad - 2 - irs->authlen);
irs->ipp->tot_len = htons(ntohs(irs->ipp->tot_len) - (esphlen + pad));
/*
* move the IP header forward by the size of the ESP header, which
* will remove the the ESP header from the packet.
*/
memmove((void *)(skb->data + esphlen),
(void *)(skb->data), irs->iphlen);
rcv_dmp("esp postmove", skb->data, skb->len);
/* skb_pull below, will move up by esphlen */
/* XXX not clear how this can happen, as the message indicates */
if(skb->len < esphlen) {
printk(KERN_WARNING
"klips_error:ipsec_rcv: "
"tried to skb_pull esphlen=%d, %d available. This should never happen, please report.\n",
esphlen, (int)(skb->len));
return IPSEC_RCV_ESP_DECAPFAIL;
}
skb_pull(skb, esphlen);
irs->ipp = (struct iphdr *)skb->data;
rcv_dmp("esp postpull", skb->data, skb->len);
/* now, trip off the padding from the end */
KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
"klips_debug:ipsec_rcv: "
"trimming to %d.\n",
irs->len - esphlen - pad);
if(pad + esphlen <= irs->len) {
skb_trim(skb, irs->len - esphlen - pad);
} else {
KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
"klips_debug:ipsec_rcv: "
"bogus packet, size is zero or negative, dropping.\n");
return IPSEC_RCV_DECAPFAIL;
}
return IPSEC_RCV_OK;
}
struct xform_functions esp_rcv_funcs[]={
{ checks: ipsec_rcv_esp_checks,
setup_auth: ipsec_rcv_esp_decrypt_setup,
calc_auth: ipsec_rcv_esp_authcalc,
decrypt: ipsec_rcv_esp_decrypt,
},
};
#endif /* !CONFIG_IPSEC_ESP */
#ifdef CONFIG_IPSEC_AH
enum ipsec_rcv_value
ipsec_rcv_ah_checks(struct ipsec_rcv_state *irs,
struct sk_buff *skb)
{
int ahminlen;
ahminlen = irs->hard_header_len + sizeof(struct iphdr);
/* take care not to deref this pointer until we check the minlen though */
irs->protostuff.ahstuff.ahp = (struct ahhdr *) (skb->data + irs->iphlen);
if((skb->len < ahminlen+sizeof(struct ahhdr)) ||
(skb->len < ahminlen+(irs->protostuff.ahstuff.ahp->ah_hl << 2))) {
KLIPS_PRINT(debug_rcv & DB_RX_INAU,
"klips_debug:ipsec_rcv: "
"runt ah packet of skb->len=%d received from %s, dropped.\n",
skb->len,
irs->ipsaddr_txt);
if(irs->stats) {
irs->stats->rx_errors++;
}
return IPSEC_RCV_BADLEN;
}
irs->said.spi = irs->protostuff.ahstuff.ahp->ah_spi;
/* XXX we only support the one 12-byte authenticator for now */
if(irs->protostuff.ahstuff.ahp->ah_hl != ((AHHMAC_HASHLEN+AHHMAC_RPLLEN) >> 2)) {
KLIPS_PRINT(debug_rcv & DB_RX_INAU,
"klips_debug:ipsec_rcv: "
"bad authenticator length %ld, expected %lu from %s.\n",
(long)(irs->protostuff.ahstuff.ahp->ah_hl << 2),
(unsigned long) sizeof(struct ahhdr),
irs->ipsaddr_txt);
if(irs->stats) {
irs->stats->rx_errors++;
}
return IPSEC_RCV_BADLEN;
}
return IPSEC_RCV_OK;
}
enum ipsec_rcv_value
ipsec_rcv_ah_setup_auth(struct ipsec_rcv_state *irs,
struct sk_buff *skb,
__u32 *replay,
unsigned char **authenticator)
{
struct ahhdr *ahp = irs->protostuff.ahstuff.ahp;
*replay = ntohl(ahp->ah_rpl);
*authenticator = ahp->ah_data;
return IPSEC_RCV_OK;
}
enum ipsec_rcv_value
ipsec_rcv_ah_authcalc(struct ipsec_rcv_state *irs,
struct sk_buff *skb)
{
struct auth_alg *aa;
struct ahhdr *ahp = irs->protostuff.ahstuff.ahp;
union {
MD5_CTX md5;
SHA1_CTX sha1;
} tctx;
struct iphdr ipo;
int ahhlen;
aa = irs->authfuncs;
/* copy the initialized keying material */
memcpy(&tctx, irs->ictx, irs->ictx_len);
ipo = *irs->ipp;
ipo.tos = 0; /* mutable RFC 2402 3.3.3.1.1.1 */
ipo.frag_off = 0;
ipo.ttl = 0;
ipo.check = 0;
/* do the sanitized header */
(*aa->update)((void*)&tctx, (caddr_t)&ipo, sizeof(struct iphdr));
/* XXX we didn't do the options here! */
/* now do the AH header itself */
ahhlen = AH_BASIC_LEN + (ahp->ah_hl << 2);
(*aa->update)((void*)&tctx, (caddr_t)ahp, ahhlen - AHHMAC_HASHLEN);
/* now, do some zeroes */
(*aa->update)((void*)&tctx, (caddr_t)zeroes, AHHMAC_HASHLEN);
/* finally, do the packet contents themselves */
(*aa->update)((void*)&tctx,
(caddr_t)skb->data + irs->iphlen + ahhlen,
skb->len - irs->iphlen - ahhlen);
(*aa->final)(irs->hash, (void *)&tctx);
memcpy(&tctx, irs->octx, irs->octx_len);
(*aa->update)((void *)&tctx, irs->hash, aa->hashlen);
(*aa->final)(irs->hash, (void *)&tctx);
return IPSEC_RCV_OK;
}
enum ipsec_rcv_value
ipsec_rcv_ah_decap(struct ipsec_rcv_state *irs)
{
struct ahhdr *ahp = irs->protostuff.ahstuff.ahp;
struct sk_buff *skb;
int ahhlen;
skb=irs->skb;
ahhlen = AH_BASIC_LEN + (ahp->ah_hl << 2);
irs->ipp->tot_len = htons(ntohs(irs->ipp->tot_len) - ahhlen);
irs->next_header = ahp->ah_nh;
/*
* move the IP header forward by the size of the AH header, which
* will remove the the AH header from the packet.
*/
memmove((void *)(skb->data + ahhlen),
(void *)(skb->data), irs->iphlen);
rcv_dmp("ah postmove", skb->data, skb->len);
/* skb_pull below, will move up by ahhlen */
/* XXX not clear how this can happen, as the message indicates */
if(skb->len < ahhlen) {
printk(KERN_WARNING
"klips_error:ipsec_rcv: "
"tried to skb_pull ahhlen=%d, %d available. This should never happen, please report.\n",
ahhlen,
(int)(skb->len));
return IPSEC_RCV_DECAPFAIL;
}
skb_pull(skb, ahhlen);
irs->ipp = (struct iphdr *)skb->data;
rcv_dmp("ah postpull", skb->data, skb->len);
return IPSEC_RCV_OK;
}
struct xform_functions ah_rcv_funcs[]={
{ checks: ipsec_rcv_ah_checks,
setup_auth: ipsec_rcv_ah_setup_auth,
calc_auth: ipsec_rcv_ah_authcalc,
decrypt: ipsec_rcv_ah_decap,
},
};
#endif /* CONFIG_IPSEC_AH */
#ifdef CONFIG_IPSEC_IPCOMP
enum ipsec_rcv_value
ipsec_rcv_ipcomp_checks(struct ipsec_rcv_state *irs,
struct sk_buff *skb)
{
int ipcompminlen;
ipcompminlen = irs->hard_header_len + sizeof(struct iphdr);
if(skb->len < (ipcompminlen + sizeof(struct ipcomphdr))) {
KLIPS_PRINT(debug_rcv & DB_RX_INAU,
"klips_debug:ipsec_rcv: "
"runt comp packet of skb->len=%d received from %s, dropped.\n",
skb->len,
irs->ipsaddr_txt);
if(irs->stats) {
irs->stats->rx_errors++;
}
return IPSEC_RCV_BADLEN;
}
irs->protostuff.ipcompstuff.compp = (struct ipcomphdr *)(skb->data + irs->iphlen);
irs->said.spi = htonl((__u32)ntohs(irs->protostuff.ipcompstuff.compp->ipcomp_cpi));
return IPSEC_RCV_OK;
}
enum ipsec_rcv_value
ipsec_rcv_ipcomp_decomp(struct ipsec_rcv_state *irs)
{
unsigned int flags = 0;
struct ipsec_sa *ipsp = irs->ipsp;
struct sk_buff *skb;
skb=irs->skb;
rcv_dmp("ipcomp", skb->data, skb->len);
if(ipsp == NULL) {
return IPSEC_RCV_SAIDNOTFOUND;
}
#if 0
/* we want to check that this wasn't the first SA on the list, because
* we don't support bare IPCOMP, for unexplained reasons. MCR
*/
if (ipsp->ips_onext != NULL) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"Incoming packet with outer IPCOMP header SA:%s: not yet supported by KLIPS, dropped\n",
irs->sa_len ? irs->sa : " (error)");
if(irs->stats) {
irs->stats->rx_dropped++;
}
return IPSEC_RCV_IPCOMPALONE;
}
#endif
if(sysctl_ipsec_inbound_policy_check &&
((((ntohl(ipsp->ips_said.spi) & 0x0000ffff) != ntohl(irs->said.spi)) &&
(ipsp->ips_encalg != ntohl(irs->said.spi)) /* this is a workaround for peer non-compliance with rfc2393 */
))) {
char sa2[SATOA_BUF];
size_t sa_len2 = 0;
sa_len2 = satoa(ipsp->ips_said, 0, sa2, SATOA_BUF);
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"Incoming packet with SA(IPCA):%s does not match policy SA(IPCA):%s cpi=%04x cpi->spi=%08x spi=%08x, spi->cpi=%04x for SA grouping, dropped.\n",
irs->sa_len ? irs->sa : " (error)",
ipsp != NULL ? (sa_len2 ? sa2 : " (error)") : "NULL",
ntohs(irs->protostuff.ipcompstuff.compp->ipcomp_cpi),
(__u32)ntohl(irs->said.spi),
ipsp != NULL ? (__u32)ntohl((ipsp->ips_said.spi)) : 0,
ipsp != NULL ? (__u16)(ntohl(ipsp->ips_said.spi) & 0x0000ffff) : 0);
if(irs->stats) {
irs->stats->rx_dropped++;
}
return IPSEC_RCV_SAIDNOTFOUND;
}
ipsp->ips_comp_ratio_cbytes += ntohs(irs->ipp->tot_len);
irs->next_header = irs->protostuff.ipcompstuff.compp->ipcomp_nh;
skb = skb_decompress(skb, ipsp, &flags);
if (!skb || flags) {
spin_unlock(&tdb_lock);
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"skb_decompress() returned error flags=%x, dropped.\n",
flags);
if (irs->stats) {
if (flags)
irs->stats->rx_errors++;
else
irs->stats->rx_dropped++;
}
return IPSEC_RCV_IPCOMPFAILED;
}
/* make sure we update the pointer */
irs->skb = skb;
#ifdef NET_21
irs->ipp = skb->nh.iph;
#else /* NET_21 */
irs->ipp = skb->ip_hdr;
#endif /* NET_21 */
ipsp->ips_comp_ratio_dbytes += ntohs(irs->ipp->tot_len);
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"packet decompressed SA(IPCA):%s cpi->spi=%08x spi=%08x, spi->cpi=%04x, nh=%d.\n",
irs->sa_len ? irs->sa : " (error)",
(__u32)ntohl(irs->said.spi),
ipsp != NULL ? (__u32)ntohl((ipsp->ips_said.spi)) : 0,
ipsp != NULL ? (__u16)(ntohl(ipsp->ips_said.spi) & 0x0000ffff) : 0,
irs->next_header);
KLIPS_IP_PRINT(debug_rcv & DB_RX_PKTRX, irs->ipp);
return IPSEC_RCV_OK;
}
struct xform_functions ipcomp_rcv_funcs[]={
{checks: ipsec_rcv_ipcomp_checks,
decrypt: ipsec_rcv_ipcomp_decomp,
},
};
#endif /* CONFIG_IPSEC_IPCOMP */
enum ipsec_rcv_value
ipsec_rcv_decap_once(struct ipsec_rcv_state *irs)
{
int iphlen;
unsigned char *dat;
__u8 proto;
struct in_addr ipsaddr;
struct in_addr ipdaddr;
int replay = 0; /* replay value in AH or ESP packet */
struct ipsec_sa* ipsnext = NULL; /* next SA towards inside of packet */
struct xform_functions *proto_funcs;
struct ipsec_sa *newipsp;
struct iphdr *ipp;
struct sk_buff *skb;
#ifdef CONFIG_IPSEC_ALG
struct ipsec_alg_auth *ixt_a=NULL;
#endif /* CONFIG_IPSEC_ALG */
skb = irs->skb;
irs->len = skb->len;
dat = skb->data;
ipp = irs->ipp;
proto = ipp->protocol;
ipsaddr.s_addr = ipp->saddr;
addrtoa(ipsaddr, 0, irs->ipsaddr_txt, sizeof(irs->ipsaddr_txt));
ipdaddr.s_addr = ipp->daddr;
addrtoa(ipdaddr, 0, irs->ipdaddr_txt, sizeof(irs->ipdaddr_txt));
iphlen = ipp->ihl << 2;
irs->iphlen=iphlen;
ipp->check = 0; /* we know the sum is good */
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv_decap_once: "
"decap (%d) from %s -> %s\n",
proto, irs->ipsaddr_txt, irs->ipdaddr_txt);
switch(proto) {
#ifdef CONFIG_IPSEC_ESP
case IPPROTO_ESP:
proto_funcs = esp_rcv_funcs;
break;
#endif /* !CONFIG_IPSEC_ESP */
#ifdef CONFIG_IPSEC_AH
case IPPROTO_AH:
proto_funcs = ah_rcv_funcs;
break;
#endif /* !CONFIG_IPSEC_AH */
#ifdef CONFIG_IPSEC_IPCOMP
case IPPROTO_COMP:
proto_funcs = ipcomp_rcv_funcs;
break;
#endif /* !CONFIG_IPSEC_IPCOMP */
default:
if(irs->stats) {
irs->stats->rx_errors++;
}
return IPSEC_RCV_BADPROTO;
}
/*
* Find tunnel control block and (indirectly) call the
* appropriate tranform routine. The resulting sk_buf
* is a valid IP packet ready to go through input processing.
*/
irs->said.dst.s_addr = ipp->daddr;
if(proto_funcs->checks) {
enum ipsec_rcv_value retval = (*proto_funcs->checks)(irs, skb);
if(retval < 0) {
return retval;
}
}
irs->said.proto = proto;
irs->sa_len = satoa(irs->said, 0, irs->sa, SATOA_BUF);
if(irs->sa_len == 0) {
strcpy(irs->sa, "(error)");
}
newipsp = ipsec_sa_getbyid(&irs->said);
if (newipsp == NULL) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"no ipsec_sa for SA:%s: incoming packet with no SA dropped\n",
irs->sa_len ? irs->sa : " (error)");
if(irs->stats) {
irs->stats->rx_dropped++;
}
return IPSEC_RCV_SAIDNOTFOUND;
}
/* MCR - XXX this is bizarre. ipsec_sa_getbyid returned it, having incremented the refcount,
* why in the world would we decrement it here?
ipsec_sa_put(irs->ipsp);*/ /* incomplete */
/* If it is in larval state, drop the packet, we cannot process yet. */
if(newipsp->ips_state == SADB_SASTATE_LARVAL) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"ipsec_sa in larval state, cannot be used yet, dropping packet.\n");
if(irs->stats) {
irs->stats->rx_dropped++;
}
ipsec_sa_put(newipsp);
return IPSEC_RCV_SAIDNOTLIVE;
}
if(newipsp->ips_state == SADB_SASTATE_DEAD) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"ipsec_sa in dead state, cannot be used any more, dropping packet.\n");
if(irs->stats) {
irs->stats->rx_dropped++;
}
ipsec_sa_put(newipsp);
return IPSEC_RCV_SAIDNOTLIVE;
}
if(sysctl_ipsec_inbound_policy_check) {
if(irs->ipp->saddr != ((struct sockaddr_in*)(newipsp->ips_addr_s))->sin_addr.s_addr) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"SA:%s, src=%s of pkt does not agree with expected SA source address policy.\n",
irs->sa_len ? irs->sa : " (error)",
irs->ipsaddr_txt);
if(irs->stats) {
irs->stats->rx_dropped++;
}
ipsec_sa_put(newipsp);
return IPSEC_RCV_FAILEDINBOUND;
}
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"SA:%s, src=%s of pkt agrees with expected SA source address policy.\n",
irs->sa_len ? irs->sa : " (error)",
irs->ipsaddr_txt);
/*
* at this point, we have looked up a new SA, and we want to make sure that if this
* isn't the first SA in the list, that the previous SA actually points at this one.
*/
if(irs->ipsp) {
if(irs->ipsp->ips_inext != newipsp) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"unexpected SA:%s: does not agree with ips->inext policy, dropped\n",
irs->sa_len ? irs->sa : " (error)");
if(irs->stats) {
irs->stats->rx_dropped++;
}
ipsec_sa_put(newipsp);
return IPSEC_RCV_FAILEDINBOUND;
}
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"SA:%s grouping from previous SA is OK.\n",
irs->sa_len ? irs->sa : " (error)");
} else {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"SA:%s First SA in group.\n",
irs->sa_len ? irs->sa : " (error)");
}
/*
* previously, at this point, we checked if the back pointer from the new SA that
* we just found matched the back pointer. But, we won't do this check anymore,
* because we want to be able to nest SAs
*/
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"natt_type=%u tdbp->ips_natt_type=%u : %s\n",
irs->natt_type, newipsp->ips_natt_type,
(irs->natt_type==newipsp->ips_natt_type)?"ok":"bad");
if (irs->natt_type != newipsp->ips_natt_type) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"SA:%s does not agree with expected NAT-T policy.\n",
irs->sa_len ? irs->sa : " (error)");
if(irs->stats) {
irs->stats->rx_dropped++;
}
ipsec_sa_put(newipsp);
return IPSEC_RCV_FAILEDINBOUND;
}
#endif
}
/* okay, SA checks out, so free any previous SA, and record a new one */
if(irs->ipsp) {
ipsec_sa_put(irs->ipsp);
}
irs->ipsp=newipsp;
/* note that the outer code will free the irs->ipsp if there is an error */
/* now check the lifetimes */
if(ipsec_lifetime_check(&irs->ipsp->ips_life.ipl_bytes, "bytes", irs->sa,
ipsec_life_countbased, ipsec_incoming, irs->ipsp) == ipsec_life_harddied ||
ipsec_lifetime_check(&irs->ipsp->ips_life.ipl_addtime, "addtime",irs->sa,
ipsec_life_timebased, ipsec_incoming, irs->ipsp) == ipsec_life_harddied ||
ipsec_lifetime_check(&irs->ipsp->ips_life.ipl_addtime, "usetime",irs->sa,
ipsec_life_timebased, ipsec_incoming, irs->ipsp) == ipsec_life_harddied ||
ipsec_lifetime_check(&irs->ipsp->ips_life.ipl_packets, "packets",irs->sa,
ipsec_life_countbased, ipsec_incoming, irs->ipsp) == ipsec_life_harddied) {
ipsec_sa_delchain(irs->ipsp);
if(irs->stats) {
irs->stats->rx_dropped++;
}
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv_decap_once: "
"decap (%d) failed lifetime check\n",
proto);
return IPSEC_RCV_LIFETIMEFAILED;
}
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
if ((irs->natt_type) &&
( (irs->ipp->saddr != (((struct sockaddr_in*)(newipsp->ips_addr_s))->sin_addr.s_addr)) ||
(irs->natt_sport != newipsp->ips_natt_sport)
)) {
struct sockaddr sipaddr;
/** Advertise NAT-T addr change to pluto **/
sipaddr.sa_family = AF_INET;
((struct sockaddr_in*)&sipaddr)->sin_addr.s_addr = irs->ipp->saddr;
((struct sockaddr_in*)&sipaddr)->sin_port = htons(irs->natt_sport);
pfkey_nat_t_new_mapping(newipsp, &sipaddr, irs->natt_sport);
/**
* Then allow or block packet depending on
* sysctl_ipsec_inbound_policy_check.
*
* In all cases, pluto will update SA if new mapping is
* accepted.
*/
if (sysctl_ipsec_inbound_policy_check) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"SA:%s, src=%s:%u of pkt does not agree with expected "
"SA source address policy (pluto has been informed).\n",
irs->sa_len ? irs->sa : " (error)",
irs->ipsaddr_txt, irs->natt_sport);
if(irs->stats) {
irs->stats->rx_dropped++;
}
ipsec_sa_put(newipsp);
return IPSEC_RCV_FAILEDINBOUND;
}
}
#endif
irs->authfuncs=NULL;
/* authenticate, if required */
#ifdef CONFIG_IPSEC_ALG
if ((ixt_a=irs->ipsp->ips_alg_auth)) {
irs->authlen = AHHMAC_HASHLEN;
irs->authfuncs = NULL;
irs->ictx = NULL;
irs->octx = NULL;
irs->ictx_len = 0;
irs->octx_len = 0;
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"authalg=%d authlen=%d\n",
irs->ipsp->ips_authalg,
irs->authlen);
} else
#endif /* CONFIG_IPSEC_ALG */
switch(irs->ipsp->ips_authalg) {
#ifdef CONFIG_IPSEC_AUTH_HMAC_MD5
case AH_MD5:
irs->authlen = AHHMAC_HASHLEN;
irs->authfuncs = ipsec_rcv_md5;
irs->ictx = (void *)&((struct md5_ctx*)(irs->ipsp->ips_key_a))->ictx;
irs->octx = (void *)&((struct md5_ctx*)(irs->ipsp->ips_key_a))->octx;
irs->ictx_len = sizeof(((struct md5_ctx*)(irs->ipsp->ips_key_a))->ictx);
irs->octx_len = sizeof(((struct md5_ctx*)(irs->ipsp->ips_key_a))->octx);
break;
#endif /* CONFIG_IPSEC_AUTH_HMAC_MD5 */
#ifdef CONFIG_IPSEC_AUTH_HMAC_SHA1
case AH_SHA:
irs->authlen = AHHMAC_HASHLEN;
irs->authfuncs = ipsec_rcv_sha1;
irs->ictx = (void *)&((struct sha1_ctx*)(irs->ipsp->ips_key_a))->ictx;
irs->octx = (void *)&((struct sha1_ctx*)(irs->ipsp->ips_key_a))->octx;
irs->ictx_len = sizeof(((struct sha1_ctx*)(irs->ipsp->ips_key_a))->ictx);
irs->octx_len = sizeof(((struct sha1_ctx*)(irs->ipsp->ips_key_a))->octx);
break;
#endif /* CONFIG_IPSEC_AUTH_HMAC_SHA1 */
case AH_NONE:
irs->authlen = 0;
irs->authfuncs = NULL;
irs->ictx = NULL;
irs->octx = NULL;
irs->ictx_len = 0;
irs->octx_len = 0;
break;
default:
irs->ipsp->ips_errs.ips_alg_errs += 1;
if(irs->stats) {
irs->stats->rx_errors++;
}
return IPSEC_RCV_BADAUTH;
}
irs->ilen = irs->len - iphlen - irs->authlen;
if(irs->ilen <= 0) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"runt %s packet with no data, dropping.\n",
(proto == IPPROTO_ESP ? "esp" : "ah"));
if(irs->stats) {
irs->stats->rx_dropped++;
}
return IPSEC_RCV_BADLEN;
}
#ifdef CONFIG_IPSEC_ALG
if(irs->authfuncs || ixt_a) {
#else
if(irs->authfuncs) {
#endif
unsigned char *authenticator = NULL;
if(proto_funcs->setup_auth) {
enum ipsec_rcv_value retval
= (*proto_funcs->setup_auth)(irs, skb,
&replay,
&authenticator);
if(retval < 0) {
return retval;
}
}
if(!authenticator) {
irs->ipsp->ips_errs.ips_auth_errs += 1;
if(irs->stats) {
irs->stats->rx_dropped++;
}
return IPSEC_RCV_BADAUTH;
}
if(!ipsec_checkreplaywindow(irs->ipsp, replay)) {
irs->ipsp->ips_errs.ips_replaywin_errs += 1;
KLIPS_PRINT(debug_rcv & DB_RX_REPLAY,
"klips_debug:ipsec_rcv: "
"duplicate frame from %s, packet dropped\n",
irs->ipsaddr_txt);
if(irs->stats) {
irs->stats->rx_dropped++;
}
return IPSEC_RCV_REPLAYFAILED;
}
/*
* verify authenticator
*/
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"encalg = %d, authalg = %d.\n",
irs->ipsp->ips_encalg,
irs->ipsp->ips_authalg);
/* calculate authenticator */
if(proto_funcs->calc_auth == NULL) {
return IPSEC_RCV_BADAUTH;
}
(*proto_funcs->calc_auth)(irs, skb);
if (memcmp(irs->hash, authenticator, irs->authlen)) {
irs->ipsp->ips_errs.ips_auth_errs += 1;
KLIPS_PRINT(debug_rcv & DB_RX_INAU,
"klips_debug:ipsec_rcv: "
"auth failed on incoming packet from %s: hash=%08x%08x%08x auth=%08x%08x%08x, dropped\n",
irs->ipsaddr_txt,
ntohl(*(__u32*)&irs->hash[0]),
ntohl(*(__u32*)&irs->hash[4]),
ntohl(*(__u32*)&irs->hash[8]),
ntohl(*(__u32*)authenticator),
ntohl(*((__u32*)authenticator + 1)),
ntohl(*((__u32*)authenticator + 2)));
if(irs->stats) {
irs->stats->rx_dropped++;
}
return IPSEC_RCV_AUTHFAILED;
} else {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"authentication successful.\n");
}
/* Crypto hygiene: clear memory used to calculate autheticator.
* The length varies with the algorithm.
*/
memset(irs->hash, 0, irs->authlen);
/* If the sequence number == 0, expire SA, it had rolled */
if(irs->ipsp->ips_replaywin && !replay /* !irs->ipsp->ips_replaywin_lastseq */) {
ipsec_sa_delchain(irs->ipsp);
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"replay window counter rolled, expiring SA.\n");
if(irs->stats) {
irs->stats->rx_dropped++;
}
return IPSEC_RCV_REPLAYROLLED;
}
/* now update the replay counter */
if (!ipsec_updatereplaywindow(irs->ipsp, replay)) {
irs->ipsp->ips_errs.ips_replaywin_errs += 1;
KLIPS_PRINT(debug_rcv & DB_RX_REPLAY,
"klips_debug:ipsec_rcv: "
"duplicate frame from %s, packet dropped\n",
irs->ipsaddr_txt);
if(irs->stats) {
irs->stats->rx_dropped++;
}
return IPSEC_RCV_REPLAYROLLED;
}
}
if(proto_funcs->decrypt) {
enum ipsec_rcv_value retval =
(*proto_funcs->decrypt)(irs);
if(retval != IPSEC_RCV_OK) {
return retval;
}
}
/*
* Adjust pointers
*/
skb = irs->skb;
irs->len = skb->len;
dat = skb->data;
#ifdef NET_21
/* skb->h.ipiph=(struct iphdr *)skb->data; */
skb->nh.raw = skb->data;
skb->h.raw = skb->nh.raw + (skb->nh.iph->ihl << 2);
memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
#else /* NET_21 */
skb->h.iph=(struct iphdr *)skb->data;
skb->ip_hdr=(struct iphdr *)skb->data;
memset(skb->proto_priv, 0, sizeof(struct options));
#endif /* NET_21 */
ipp = (struct iphdr *)dat;
ipsaddr.s_addr = ipp->saddr;
addrtoa(ipsaddr, 0, irs->ipsaddr_txt, sizeof(irs->ipsaddr_txt));
ipdaddr.s_addr = ipp->daddr;
addrtoa(ipdaddr, 0, irs->ipdaddr_txt, sizeof(irs->ipdaddr_txt));
/*
* Discard the original ESP/AH header
*/
ipp->protocol = irs->next_header;
ipp->check = 0; /* NOTE: this will be included in checksum */
ipp->check = ip_fast_csum((unsigned char *)dat, iphlen >> 2);
KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
"klips_debug:ipsec_rcv: "
"after <%s%s%s>, SA:%s:\n",
IPS_XFORM_NAME(irs->ipsp),
irs->sa_len ? irs->sa : " (error)");
KLIPS_IP_PRINT(debug_rcv & DB_RX_PKTRX, ipp);
skb->protocol = htons(ETH_P_IP);
skb->ip_summed = 0;
ipsnext = irs->ipsp->ips_inext;
if(sysctl_ipsec_inbound_policy_check) {
if(ipsnext) {
if(
ipp->protocol != IPPROTO_AH
&& ipp->protocol != IPPROTO_ESP
#ifdef CONFIG_IPSEC_IPCOMP
&& ipp->protocol != IPPROTO_COMP
&& (ipsnext->ips_said.proto != IPPROTO_COMP
|| ipsnext->ips_inext)
#endif /* CONFIG_IPSEC_IPCOMP */
&& ipp->protocol != IPPROTO_IPIP
) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"packet with incomplete policy dropped, last successful SA:%s.\n",
irs->sa_len ? irs->sa : " (error)");
if(irs->stats) {
irs->stats->rx_dropped++;
}
return IPSEC_RCV_FAILEDINBOUND;
}
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"SA:%s, Another IPSEC header to process.\n",
irs->sa_len ? irs->sa : " (error)");
} else {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"No ips_inext from this SA:%s.\n",
irs->sa_len ? irs->sa : " (error)");
}
}
#ifdef CONFIG_IPSEC_IPCOMP
/* update ipcomp ratio counters, even if no ipcomp packet is present */
if (ipsnext
&& ipsnext->ips_said.proto == IPPROTO_COMP
&& ipp->protocol != IPPROTO_COMP) {
ipsnext->ips_comp_ratio_cbytes += ntohs(ipp->tot_len);
ipsnext->ips_comp_ratio_dbytes += ntohs(ipp->tot_len);
}
#endif /* CONFIG_IPSEC_IPCOMP */
irs->ipsp->ips_life.ipl_bytes.ipl_count += irs->len;
irs->ipsp->ips_life.ipl_bytes.ipl_last = irs->len;
if(!irs->ipsp->ips_life.ipl_usetime.ipl_count) {
irs->ipsp->ips_life.ipl_usetime.ipl_count = jiffies / HZ;
}
irs->ipsp->ips_life.ipl_usetime.ipl_last = jiffies / HZ;
irs->ipsp->ips_life.ipl_packets.ipl_count += 1;
#ifdef CONFIG_NETFILTER
if(proto == IPPROTO_ESP || proto == IPPROTO_AH) {
skb->nfmark = (skb->nfmark & (~(IPsecSAref2NFmark(IPSEC_SA_REF_MASK))))
| IPsecSAref2NFmark(IPsecSA2SAref(irs->ipsp));
KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
"klips_debug:ipsec_rcv: "
"%s SA sets skb->nfmark=0x%x.\n",
proto == IPPROTO_ESP ? "ESP" : "AH",
(unsigned)skb->nfmark);
}
#endif /* CONFIG_NETFILTER */
return IPSEC_RCV_OK;
}
int
#ifdef PROTO_HANDLER_SINGLE_PARM
ipsec_rcv(struct sk_buff *skb)
#else /* PROTO_HANDLER_SINGLE_PARM */
#ifdef NET_21
ipsec_rcv(struct sk_buff *skb, unsigned short xlen)
#else /* NET_21 */
ipsec_rcv(struct sk_buff *skb, struct device *dev, struct options *opt,
__u32 daddr_unused, unsigned short xlen, __u32 saddr,
int redo, struct inet_protocol *protocol)
#endif /* NET_21 */
#endif /* PROTO_HANDLER_SINGLE_PARM */
{
#ifdef NET_21
#ifdef CONFIG_IPSEC_DEBUG
struct device *dev = skb->dev;
#endif /* CONFIG_IPSEC_DEBUG */
#endif /* NET_21 */
unsigned char protoc;
struct iphdr *ipp;
#if defined(CONFIG_IPSEC_ESP) || defined(CONFIG_IPSEC_AH)
#endif /* defined(CONFIG_IPSEC_ESP) || defined(CONFIG_IPSEC_AH) */
struct ipsec_sa *ipsp = NULL;
struct net_device_stats *stats = NULL; /* This device's statistics */
struct device *ipsecdev = NULL, *prvdev;
struct ipsecpriv *prv;
char name[9];
int i;
struct in_addr ipsaddr;
struct in_addr ipdaddr;
struct ipsec_sa* ipsnext = NULL; /* next SA towards inside of packet */
struct ipsec_rcv_state irs;
/* Don't unlink in the middle of a turnaround */
MOD_INC_USE_COUNT;
memset(&irs, 0, sizeof(struct ipsec_rcv_state));
if (skb == NULL) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"NULL skb passed in.\n");
goto rcvleave;
}
if (skb->data == NULL) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"NULL skb->data passed in, packet is bogus, dropping.\n");
goto rcvleave;
}
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
if (skb->sk && skb->nh.iph && skb->nh.iph->protocol==IPPROTO_UDP) {
/**
* Packet comes from udp_queue_rcv_skb so it is already defrag,
* checksum verified, ... (ie safe to use)
*
* If the packet is not for us, return -1 and udp_queue_rcv_skb
* will continue to handle it (do not kfree skb !!).
*/
struct udp_opt *tp = &(skb->sk->tp_pinfo.af_udp);
struct iphdr *ip = (struct iphdr *)skb->nh.iph;
struct udphdr *udp = (struct udphdr *)((__u32 *)ip+ip->ihl);
__u8 *udpdata = (__u8 *)udp + sizeof(struct udphdr);
__u32 *udpdata32 = (__u32 *)udpdata;
irs.natt_sport = ntohs(udp->source);
irs.natt_dport = ntohs(udp->dest);
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"suspected ESPinUDP packet (NAT-Traversal) [%d].\n",
tp->esp_in_udp);
KLIPS_IP_PRINT(debug_rcv, ip);
if (udpdata < skb->tail) {
unsigned int len = skb->tail - udpdata;
if ((len==1) && (udpdata[0]==0xff)) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
/* not IPv6 compliant message */
"NAT-keepalive from %d.%d.%d.%d.\n", NIPQUAD(ip->saddr));
goto rcvleave;
}
else if ( (tp->esp_in_udp == ESPINUDP_WITH_NON_IKE) &&
(len > (2*sizeof(__u32) + sizeof(struct esphdr))) &&
(udpdata32[0]==0) && (udpdata32[1]==0) ) {
/* ESP Packet with Non-IKE header */
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"ESPinUDP pkt with Non-IKE - spi=0x%x\n",
udpdata32[2]);
irs.natt_type = ESPINUDP_WITH_NON_IKE;
irs.natt_len = sizeof(struct udphdr)+(2*sizeof(__u32));
}
else if ( (tp->esp_in_udp == ESPINUDP_WITH_NON_ESP) &&
(len > sizeof(struct esphdr)) &&
(udpdata32[0]!=0) ) {
/* ESP Packet without Non-ESP header */
irs.natt_type = ESPINUDP_WITH_NON_ESP;
irs.natt_len = sizeof(struct udphdr);
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"ESPinUDP pkt without Non-ESP - spi=0x%x\n",
udpdata32[0]);
}
else {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"IKE packet - not handled here\n");
MOD_DEC_USE_COUNT;
return -1;
}
}
else {
MOD_DEC_USE_COUNT;
return -1;
}
}
#endif
#ifdef IPH_is_SKB_PULLED
/* In Linux 2.4.4, the IP header has been skb_pull()ed before the
packet is passed to us. So we'll skb_push() to get back to it. */
if (skb->data == skb->h.raw) {
skb_push(skb, skb->h.raw - skb->nh.raw);
}
#endif /* IPH_is_SKB_PULLED */
/* dev->hard_header_len is unreliable and should not be used */
irs.hard_header_len = skb->mac.raw ? (skb->data - skb->mac.raw) : 0;
if((irs.hard_header_len < 0) || (irs.hard_header_len > skb_headroom(skb)))
irs.hard_header_len = 0;
#ifdef NET_21
/* if skb was cloned (most likely due to a packet sniffer such as
tcpdump being momentarily attached to the interface), make
a copy of our own to modify */
if(skb_cloned(skb)) {
/* include any mac header while copying.. */
if(skb_headroom(skb) < irs.hard_header_len) {
printk(KERN_WARNING "klips_error:ipsec_rcv: "
"tried to skb_push hhlen=%d, %d available. This should never happen, please report.\n",
irs.hard_header_len,
skb_headroom(skb));
goto rcvleave;
}
skb_push(skb, irs.hard_header_len);
if
#ifdef SKB_COW_NEW
(skb_cow(skb, skb_headroom(skb)) != 0)
#else /* SKB_COW_NEW */
((skb = skb_cow(skb, skb_headroom(skb))) == NULL)
#endif /* SKB_COW_NEW */
{
goto rcvleave;
}
if(skb->len < irs.hard_header_len) {
printk(KERN_WARNING "klips_error:ipsec_rcv: "
"tried to skb_pull hhlen=%d, %d available. This should never happen, please report.\n",
irs.hard_header_len,
skb->len);
goto rcvleave;
}
skb_pull(skb, irs.hard_header_len);
}
#endif /* NET_21 */
#if IP_FRAGMENT_LINEARIZE
/* In Linux 2.4.4, we may have to reassemble fragments. They are
not assembled automatically to save TCP from having to copy
twice.
*/
if (skb_is_nonlinear(skb)) {
if (skb_linearize(skb, GFP_ATOMIC) != 0) {
goto rcvleave;
}
}
#endif /* IP_FRAGMENT_LINEARIZE */
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
if (irs.natt_len) {
/**
* Now, we are sure packet is ESPinUDP. Remove natt_len bytes from
* packet and modify protocol to ESP.
*/
if (((unsigned char *)skb->data > (unsigned char *)skb->nh.iph) &&
((unsigned char *)skb->nh.iph > (unsigned char *)skb->head)) {
unsigned int _len = (unsigned char *)skb->data -
(unsigned char *)skb->nh.iph;
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: adjusting skb: skb_push(%u)\n",
_len);
skb_push(skb, _len);
}
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"removing %d bytes from ESPinUDP packet\n", irs.natt_len);
ipp = (struct iphdr *)skb->data;
irs.iphlen = ipp->ihl << 2;
ipp->tot_len = htons(ntohs(ipp->tot_len) - irs.natt_len);
if (skb->len < irs.iphlen + irs.natt_len) {
printk(KERN_WARNING
"klips_error:ipsec_rcv: "
"ESPinUDP packet is too small (%d < %d+%d). "
"This should never happen, please report.\n",
(int)(skb->len), irs.iphlen, irs.natt_len);
goto rcvleave;
}
memmove(skb->data + irs.natt_len, skb->data, irs.iphlen);
skb_pull(skb, irs.natt_len);
/* update nh.iph */
ipp = skb->nh.iph = (struct iphdr *)skb->data;
/* modify protocol */
ipp->protocol = IPPROTO_ESP;
skb->sk = NULL;
KLIPS_IP_PRINT(debug_rcv, skb->nh.iph);
}
#endif
ipp = skb->nh.iph;
ipsaddr.s_addr = ipp->saddr;
addrtoa(ipsaddr, 0, irs.ipsaddr_txt, sizeof(irs.ipsaddr_txt));
ipdaddr.s_addr = ipp->daddr;
addrtoa(ipdaddr, 0, irs.ipdaddr_txt, sizeof(irs.ipdaddr_txt));
irs.iphlen = ipp->ihl << 2;
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"<<< Info -- ");
KLIPS_PRINTMORE(debug_rcv && skb->dev, "skb->dev=%s ",
skb->dev->name ? skb->dev->name : "NULL");
KLIPS_PRINTMORE(debug_rcv && dev, "dev=%s ",
dev->name ? dev->name : "NULL");
KLIPS_PRINTMORE(debug_rcv, "\n");
KLIPS_PRINT(debug_rcv && !(skb->dev && dev && (skb->dev == dev)),
"klips_debug:ipsec_rcv: "
"Informational -- **if this happens, find out why** skb->dev:%s is not equal to dev:%s\n",
skb->dev ? (skb->dev->name ? skb->dev->name : "NULL") : "NULL",
dev ? (dev->name ? dev->name : "NULL") : "NULL");
protoc = ipp->protocol;
#ifndef NET_21
if((!protocol) || (protocol->protocol != protoc)) {
KLIPS_PRINT(debug_rcv & DB_RX_IPSA,
"klips_debug:ipsec_rcv: "
"protocol arg is NULL or unequal to the packet contents, this is odd, using value in packet.\n");
}
#endif /* !NET_21 */
if( (protoc != IPPROTO_AH) &&
#ifdef CONFIG_IPSEC_IPCOMP_disabled_until_we_register_IPCOMP_HANDLER
(protoc != IPPROTO_COMP) &&
#endif /* CONFIG_IPSEC_IPCOMP */
(protoc != IPPROTO_ESP) ) {
KLIPS_PRINT(debug_rcv & DB_RX_IPSA,
"klips_debug:ipsec_rcv: Why the hell is someone "
"passing me a non-ipsec protocol = %d packet? -- dropped.\n",
protoc);
goto rcvleave;
}
if(skb->dev) {
for(i = 0; i < IPSEC_NUM_IF; i++) {
sprintf(name, IPSEC_DEV_FORMAT, i);
if(!strcmp(name, skb->dev->name)) {
prv = (struct ipsecpriv *)(skb->dev->priv);
if(prv) {
stats = (struct net_device_stats *) &(prv->mystats);
}
ipsecdev = skb->dev;
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"Info -- pkt already proc'ed a group of ipsec headers, processing next group of ipsec headers.\n");
break;
}
if((ipsecdev = __ipsec_dev_get(name)) == NULL) {
KLIPS_PRINT(debug_rcv,
"klips_error:ipsec_rcv: "
"device %s does not exist\n",
name);
}
prv = ipsecdev ? (struct ipsecpriv *)(ipsecdev->priv) : NULL;
prvdev = prv ? (struct device *)(prv->dev) : NULL;
#if 0
KLIPS_PRINT(debug_rcv && prvdev,
"klips_debug:ipsec_rcv: "
"physical device for device %s is %s\n",
name,
prvdev->name);
#endif
if(prvdev && skb->dev &&
!strcmp(prvdev->name, skb->dev->name)) {
stats = prv ? ((struct net_device_stats *) &(prv->mystats)) : NULL;
skb->dev = ipsecdev;
KLIPS_PRINT(debug_rcv && prvdev,
"klips_debug:ipsec_rcv: "
"assigning packet ownership to virtual device %s from physical device %s.\n",
name, prvdev->name);
if(stats) {
stats->rx_packets++;
}
break;
}
}
} else {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"device supplied with skb is NULL\n");
}
if(stats == NULL) {
KLIPS_PRINT((debug_rcv),
"klips_error:ipsec_rcv: "
"packet received from physical I/F (%s) not connected to ipsec I/F. Cannot record stats. May not have SA for decoding. Is IPSEC traffic expected on this I/F? Check routing.\n",
skb->dev ? (skb->dev->name ? skb->dev->name : "NULL") : "NULL");
}
KLIPS_IP_PRINT(debug_rcv, ipp);
/* begin decapsulating loop here */
/*
The spinlock is to prevent any other process from
accessing or deleting the ipsec_sa hash table or any of the
ipsec_sa s while we are using and updating them.
This is not optimal, but was relatively straightforward
at the time. A better way to do it has been planned for
more than a year, to lock the hash table and put reference
counts on each ipsec_sa instead. This is not likely to happen
in KLIPS1 unless a volunteer contributes it, but will be
designed into KLIPS2.
*/
spin_lock(&tdb_lock);
/* set up for decap loop */
irs.stats= stats;
irs.ipp = ipp;
irs.ipsp = NULL;
irs.ilen = 0;
irs.authlen=0;
irs.authfuncs=NULL;
irs.skb = skb;
do {
int decap_stat;
decap_stat = ipsec_rcv_decap_once(&irs);
if(decap_stat != IPSEC_RCV_OK) {
spin_unlock(&tdb_lock);
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: decap_once failed: %d\n",
decap_stat);
goto rcvleave;
}
/* end decapsulation loop here */
} while( (irs.ipp->protocol == IPPROTO_ESP )
|| (irs.ipp->protocol == IPPROTO_AH )
#ifdef CONFIG_IPSEC_IPCOMP
|| (irs.ipp->protocol == IPPROTO_COMP)
#endif /* CONFIG_IPSEC_IPCOMP */
);
/* set up for decap loop */
ipp =irs.ipp;
ipsp =irs.ipsp;
ipsnext = ipsp->ips_inext;
skb = irs.skb;
/* if there is an IPCOMP, but we don't have an IPPROTO_COMP,
* then we can just skip it
*/
#ifdef CONFIG_IPSEC_IPCOMP
if(ipsnext && ipsnext->ips_said.proto == IPPROTO_COMP) {
ipsp = ipsnext;
ipsnext = ipsp->ips_inext;
}
#endif /* CONFIG_IPSEC_IPCOMP */
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
if ((irs.natt_type) && (ipp->protocol != IPPROTO_IPIP)) {
/**
* NAT-Traversal and Transport Mode:
* we need to correct TCP/UDP checksum
*
* If we've got NAT-OA, we can fix checksum without recalculation.
*/
__u32 natt_oa = ipsp->ips_natt_oa ?
((struct sockaddr_in*)(ipsp->ips_natt_oa))->sin_addr.s_addr : 0;
__u16 pkt_len = skb->tail - (unsigned char *)ipp;
__u16 data_len = pkt_len - (ipp->ihl << 2);
switch (ipp->protocol) {
case IPPROTO_TCP:
if (data_len >= sizeof(struct tcphdr)) {
struct tcphdr *tcp = (struct tcphdr *)((__u32 *)ipp+ipp->ihl);
if (natt_oa) {
__u32 buff[2] = { ~natt_oa, ipp->saddr };
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"NAT-T & TRANSPORT: "
"fix TCP checksum using NAT-OA\n");
tcp->check = csum_fold(
csum_partial((unsigned char *)buff, sizeof(buff),
tcp->check^0xffff));
}
else {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"NAT-T & TRANSPORT: recalc TCP checksum\n");
if (pkt_len > (ntohs(ipp->tot_len)))
data_len -= (pkt_len - ntohs(ipp->tot_len));
tcp->check = 0;
tcp->check = csum_tcpudp_magic(ipp->saddr, ipp->daddr,
data_len, IPPROTO_TCP,
csum_partial((unsigned char *)tcp, data_len, 0));
}
}
else {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"NAT-T & TRANSPORT: can't fix TCP checksum\n");
}
break;
case IPPROTO_UDP:
if (data_len >= sizeof(struct udphdr)) {
struct udphdr *udp = (struct udphdr *)((__u32 *)ipp+ipp->ihl);
if (udp->check == 0) {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"NAT-T & TRANSPORT: UDP checksum already 0\n");
}
else if (natt_oa) {
__u32 buff[2] = { ~natt_oa, ipp->saddr };
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"NAT-T & TRANSPORT: "
"fix UDP checksum using NAT-OA\n");
udp->check = csum_fold(
csum_partial((unsigned char *)buff, sizeof(buff),
udp->check^0xffff));
}
else {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"NAT-T & TRANSPORT: zero UDP checksum\n");
udp->check = 0;
}
}
else {
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"NAT-T & TRANSPORT: can't fix UDP checksum\n");
}
break;
default:
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"NAT-T & TRANSPORT: non TCP/UDP packet -- do nothing\n");
break;
}
}
#endif
/*
* XXX this needs to be locked from when it was first looked
* up in the decapsulation loop. Perhaps it is better to put
* the IPIP decap inside the loop.
*/
if(ipsnext) {
ipsp = ipsnext;
irs.sa_len = satoa(irs.said, 0, irs.sa, SATOA_BUF);
if(ipp->protocol != IPPROTO_IPIP) {
spin_unlock(&tdb_lock);
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"SA:%s, Hey! How did this get through? Dropped.\n",
irs.sa_len ? irs.sa : " (error)");
if(stats) {
stats->rx_dropped++;
}
goto rcvleave;
}
if(sysctl_ipsec_inbound_policy_check) {
if((ipsnext = ipsp->ips_inext)) {
char sa2[SATOA_BUF];
size_t sa_len2;
sa_len2 = satoa(ipsnext->ips_said, 0, sa2, SATOA_BUF);
spin_unlock(&tdb_lock);
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"unexpected SA:%s after IPIP SA:%s\n",
sa_len2 ? sa2 : " (error)",
irs.sa_len ? irs.sa : " (error)");
if(stats) {
stats->rx_dropped++;
}
goto rcvleave;
}
if(ipp->saddr != ((struct sockaddr_in*)(ipsp->ips_addr_s))->sin_addr.s_addr) {
spin_unlock(&tdb_lock);
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"SA:%s, src=%s of pkt does not agree with expected SA source address policy.\n",
irs.sa_len ? irs.sa : " (error)",
irs.ipsaddr_txt);
if(stats) {
stats->rx_dropped++;
}
goto rcvleave;
}
}
/*
* XXX this needs to be locked from when it was first looked
* up in the decapsulation loop. Perhaps it is better to put
* the IPIP decap inside the loop.
*/
ipsp->ips_life.ipl_bytes.ipl_count += skb->len;
ipsp->ips_life.ipl_bytes.ipl_last = skb->len;
if(!ipsp->ips_life.ipl_usetime.ipl_count) {
ipsp->ips_life.ipl_usetime.ipl_count = jiffies / HZ;
}
ipsp->ips_life.ipl_usetime.ipl_last = jiffies / HZ;
ipsp->ips_life.ipl_packets.ipl_count += 1;
if(skb->len < irs.iphlen) {
spin_unlock(&tdb_lock);
printk(KERN_WARNING "klips_debug:ipsec_rcv: "
"tried to skb_pull iphlen=%d, %d available. This should never happen, please report.\n",
irs.iphlen,
(int)(skb->len));
goto rcvleave;
}
skb_pull(skb, irs.iphlen);
#ifdef NET_21
skb->nh.raw = skb->data;
ipp = (struct iphdr *)skb->nh.raw;
skb->h.raw = skb->nh.raw + (skb->nh.iph->ihl << 2);
memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
#else /* NET_21 */
ipp = skb->ip_hdr = skb->h.iph = (struct iphdr *)skb->data;
memset(skb->proto_priv, 0, sizeof(struct options));
#endif /* NET_21 */
ipsaddr.s_addr = ipp->saddr;
addrtoa(ipsaddr, 0, irs.ipsaddr_txt, sizeof(irs.ipsaddr_txt));
ipdaddr.s_addr = ipp->daddr;
addrtoa(ipdaddr, 0, irs.ipdaddr_txt, sizeof(irs.ipdaddr_txt));
skb->protocol = htons(ETH_P_IP);
skb->ip_summed = 0;
KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
"klips_debug:ipsec_rcv: "
"IPIP tunnel stripped.\n");
KLIPS_IP_PRINT(debug_rcv & DB_RX_PKTRX, ipp);
if(sysctl_ipsec_inbound_policy_check
/*
Note: "xor" (^) logically replaces "not equal"
(!=) and "bitwise or" (|) logically replaces
"boolean or" (||). This is done to speed up
execution by doing only bitwise operations and
no branch operations
*/
&& (((ipp->saddr & ipsp->ips_mask_s.u.v4.sin_addr.s_addr)
^ ipsp->ips_flow_s.u.v4.sin_addr.s_addr)
| ((ipp->daddr & ipsp->ips_mask_d.u.v4.sin_addr.s_addr)
^ ipsp->ips_flow_d.u.v4.sin_addr.s_addr)) )
{
char sflow_txt[SUBNETTOA_BUF], dflow_txt[SUBNETTOA_BUF];
subnettoa(ipsp->ips_flow_s.u.v4.sin_addr,
ipsp->ips_mask_s.u.v4.sin_addr,
0, sflow_txt, sizeof(sflow_txt));
subnettoa(ipsp->ips_flow_d.u.v4.sin_addr,
ipsp->ips_mask_d.u.v4.sin_addr,
0, dflow_txt, sizeof(dflow_txt));
spin_unlock(&tdb_lock);
KLIPS_PRINT(debug_rcv,
"klips_debug:ipsec_rcv: "
"SA:%s, inner tunnel policy [%s -> %s] does not agree with pkt contents [%s -> %s].\n",
irs.sa_len ? irs.sa : " (error)",
sflow_txt,
dflow_txt,
irs.ipsaddr_txt,
irs.ipdaddr_txt);
if(stats) {
stats->rx_dropped++;
}
goto rcvleave;
}
#ifdef CONFIG_NETFILTER
skb->nfmark = (skb->nfmark & (~(IPsecSAref2NFmark(IPSEC_SA_REF_TABLE_MASK))))
| IPsecSAref2NFmark(IPsecSA2SAref(ipsp));
KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
"klips_debug:ipsec_rcv: "
"IPIP SA sets skb->nfmark=0x%x.\n",
(unsigned)skb->nfmark);
#endif /* CONFIG_NETFILTER */
}
spin_unlock(&tdb_lock);
#ifdef NET_21
if(stats) {
stats->rx_bytes += skb->len;
}
if(skb->dst) {
dst_release(skb->dst);
skb->dst = NULL;
}
skb->pkt_type = PACKET_HOST;
if(irs.hard_header_len &&
(skb->mac.raw != (skb->data - irs.hard_header_len)) &&
(irs.hard_header_len <= skb_headroom(skb))) {
/* copy back original MAC header */
memmove(skb->data - irs.hard_header_len, skb->mac.raw, irs.hard_header_len);
skb->mac.raw = skb->data - irs.hard_header_len;
}
#endif /* NET_21 */
#ifdef CONFIG_IPSEC_IPCOMP
if(ipp->protocol == IPPROTO_COMP) {
unsigned int flags = 0;
if(sysctl_ipsec_inbound_policy_check) {
KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
"klips_debug:ipsec_rcv: "
"inbound policy checking enabled, IPCOMP follows IPIP, dropped.\n");
if (stats) {
stats->rx_errors++;
}
goto rcvleave;
}
/*
XXX need a ipsec_sa for updating ratio counters but it is not
following policy anyways so it is not a priority
*/
skb = skb_decompress(skb, NULL, &flags);
if (!skb || flags) {
KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
"klips_debug:ipsec_rcv: "
"skb_decompress() returned error flags: %d, dropped.\n",
flags);
if (stats) {
stats->rx_errors++;
}
goto rcvleave;
}
}
#endif /* CONFIG_IPSEC_IPCOMP */
#ifdef SKB_RESET_NFCT
nf_conntrack_put(skb->nfct);
skb->nfct = NULL;
#ifdef CONFIG_NETFILTER_DEBUG
skb->nf_debug = 0;
#endif /* CONFIG_NETFILTER_DEBUG */
#endif /* SKB_RESET_NFCT */
KLIPS_PRINT(debug_rcv & DB_RX_PKTRX,
"klips_debug:ipsec_rcv: "
"netif_rx() called.\n");
netif_rx(skb);
MOD_DEC_USE_COUNT;
return(0);
rcvleave:
if(skb) {
ipsec_kfree_skb(skb);
}
MOD_DEC_USE_COUNT;
return(0);
}
struct inet_protocol ah_protocol =
{
ipsec_rcv, /* AH handler */
NULL, /* TUNNEL error control */
#ifdef NETDEV_25
1, /* no policy */
#else
0, /* next */
IPPROTO_AH, /* protocol ID */
0, /* copy */
NULL, /* data */
"AH" /* name */
#endif
};
struct inet_protocol esp_protocol =
{
ipsec_rcv, /* ESP handler */
NULL, /* TUNNEL error control */
#ifdef NETDEV_25
1, /* no policy */
#else
0, /* next */
IPPROTO_ESP, /* protocol ID */
0, /* copy */
NULL, /* data */
"ESP" /* name */
#endif
};
#if 0
/* We probably don't want to install a pure IPCOMP protocol handler, but
only want to handle IPCOMP if it is encapsulated inside an ESP payload
(which is already handled) */
#ifdef CONFIG_IPSEC_IPCOMP
struct inet_protocol comp_protocol =
{
ipsec_rcv, /* COMP handler */
NULL, /* COMP error control */
#ifdef NETDEV_25
1, /* no policy */
#else
0, /* next */
IPPROTO_COMP, /* protocol ID */
0, /* copy */
NULL, /* data */
"COMP" /* name */
#endif
};
#endif /* CONFIG_IPSEC_IPCOMP */
#endif