1032 lines
27 KiB
C
1032 lines
27 KiB
C
/*
|
|
* Common routines for IPsec SA maintenance routines.
|
|
*
|
|
* Copyright (C) 1996, 1997 John Ioannidis.
|
|
* Copyright (C) 1998, 1999, 2000, 2001, 2002 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.
|
|
*
|
|
* RCSID $Id: ipsec_sa.c,v 1.3 2004/06/13 19:57:50 as Exp $
|
|
*
|
|
* This is the file formerly known as "ipsec_xform.h"
|
|
*
|
|
*/
|
|
|
|
#include <linux/config.h>
|
|
#include <linux/version.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/vmalloc.h> /* vmalloc() */
|
|
#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>
|
|
#endif
|
|
#include <asm/checksum.h>
|
|
#include <net/ip.h>
|
|
|
|
#include "freeswan/radij.h"
|
|
|
|
#include "freeswan/ipsec_stats.h"
|
|
#include "freeswan/ipsec_life.h"
|
|
#include "freeswan/ipsec_sa.h"
|
|
#include "freeswan/ipsec_xform.h"
|
|
|
|
#include "freeswan/ipsec_encap.h"
|
|
#include "freeswan/ipsec_radij.h"
|
|
#include "freeswan/ipsec_xform.h"
|
|
#include "freeswan/ipsec_ipe4.h"
|
|
#include "freeswan/ipsec_ah.h"
|
|
#include "freeswan/ipsec_esp.h"
|
|
|
|
#include <pfkeyv2.h>
|
|
#include <pfkey.h>
|
|
|
|
#include "freeswan/ipsec_proto.h"
|
|
#include "freeswan/ipsec_alg.h"
|
|
|
|
|
|
#ifdef CONFIG_IPSEC_DEBUG
|
|
int debug_xform = 0;
|
|
#endif /* CONFIG_IPSEC_DEBUG */
|
|
|
|
#define SENDERR(_x) do { error = -(_x); goto errlab; } while (0)
|
|
|
|
struct ipsec_sa *ipsec_sadb_hash[SADB_HASHMOD];
|
|
#ifdef SPINLOCK
|
|
spinlock_t tdb_lock = SPIN_LOCK_UNLOCKED;
|
|
#else /* SPINLOCK */
|
|
spinlock_t tdb_lock;
|
|
#endif /* SPINLOCK */
|
|
|
|
struct ipsec_sadb ipsec_sadb;
|
|
|
|
#if IPSEC_SA_REF_CODE
|
|
|
|
/* the sub table must be narrower (or equal) in bits than the variable type
|
|
in the main table to count the number of unused entries in it. */
|
|
typedef struct {
|
|
int testSizeOf_refSubTable :
|
|
((sizeof(IPsecRefTableUnusedCount) * 8) < IPSEC_SA_REF_SUBTABLE_IDX_WIDTH ? -1 : 1);
|
|
} dummy;
|
|
|
|
|
|
/* The field where the saref will be hosted in the skb must be wide enough to
|
|
accomodate the information it needs to store. */
|
|
typedef struct {
|
|
int testSizeOf_refField :
|
|
(IPSEC_SA_REF_HOST_FIELD_WIDTH < IPSEC_SA_REF_TABLE_IDX_WIDTH ? -1 : 1 );
|
|
} dummy2;
|
|
|
|
|
|
void
|
|
ipsec_SAtest(void)
|
|
{
|
|
IPsecSAref_t SAref = 258;
|
|
struct ipsec_sa ips;
|
|
ips.ips_ref = 772;
|
|
|
|
printk("klips_debug:ipsec_SAtest: "
|
|
"IPSEC_SA_REF_SUBTABLE_IDX_WIDTH=%u\n"
|
|
"IPSEC_SA_REF_MAINTABLE_NUM_ENTRIES=%u\n"
|
|
"IPSEC_SA_REF_SUBTABLE_NUM_ENTRIES=%u\n"
|
|
"IPSEC_SA_REF_HOST_FIELD_WIDTH=%lu\n"
|
|
"IPSEC_SA_REF_TABLE_MASK=%x\n"
|
|
"IPSEC_SA_REF_ENTRY_MASK=%x\n"
|
|
"IPsecSAref2table(%d)=%u\n"
|
|
"IPsecSAref2entry(%d)=%u\n"
|
|
"IPsecSAref2NFmark(%d)=%u\n"
|
|
"IPsecSAref2SA(%d)=%p\n"
|
|
"IPsecSA2SAref(%p)=%d\n"
|
|
,
|
|
IPSEC_SA_REF_SUBTABLE_IDX_WIDTH,
|
|
IPSEC_SA_REF_MAINTABLE_NUM_ENTRIES,
|
|
IPSEC_SA_REF_SUBTABLE_NUM_ENTRIES,
|
|
(unsigned long) IPSEC_SA_REF_HOST_FIELD_WIDTH,
|
|
IPSEC_SA_REF_TABLE_MASK,
|
|
IPSEC_SA_REF_ENTRY_MASK,
|
|
SAref, IPsecSAref2table(SAref),
|
|
SAref, IPsecSAref2entry(SAref),
|
|
SAref, IPsecSAref2NFmark(SAref),
|
|
SAref, IPsecSAref2SA(SAref),
|
|
(&ips), IPsecSA2SAref((&ips))
|
|
);
|
|
return;
|
|
}
|
|
|
|
int
|
|
ipsec_SAref_recycle(void)
|
|
{
|
|
int table;
|
|
int entry;
|
|
int error = 0;
|
|
|
|
ipsec_sadb.refFreeListHead = -1;
|
|
ipsec_sadb.refFreeListTail = -1;
|
|
|
|
if(ipsec_sadb.refFreeListCont == IPSEC_SA_REF_MAINTABLE_NUM_ENTRIES * IPSEC_SA_REF_SUBTABLE_NUM_ENTRIES) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_SAref_recycle: "
|
|
"end of table reached, continuing at start..\n");
|
|
ipsec_sadb.refFreeListCont = 0;
|
|
}
|
|
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_SAref_recycle: "
|
|
"recycling, continuing from SAref=%d (0p%p), table=%d, entry=%d.\n",
|
|
ipsec_sadb.refFreeListCont,
|
|
(ipsec_sadb.refTable[IPsecSAref2table(ipsec_sadb.refFreeListCont)] != NULL) ? IPsecSAref2SA(ipsec_sadb.refFreeListCont) : NULL,
|
|
IPsecSAref2table(ipsec_sadb.refFreeListCont),
|
|
IPsecSAref2entry(ipsec_sadb.refFreeListCont));
|
|
|
|
for(table = IPsecSAref2table(ipsec_sadb.refFreeListCont);
|
|
table < IPSEC_SA_REF_MAINTABLE_NUM_ENTRIES;
|
|
table++) {
|
|
if(ipsec_sadb.refTable[table] == NULL) {
|
|
error = ipsec_SArefSubTable_alloc(table);
|
|
if(error) {
|
|
return error;
|
|
}
|
|
}
|
|
for(entry = IPsecSAref2entry(ipsec_sadb.refFreeListCont);
|
|
entry < IPSEC_SA_REF_SUBTABLE_NUM_ENTRIES;
|
|
entry++) {
|
|
if(ipsec_sadb.refTable[table]->entry[entry] == NULL) {
|
|
ipsec_sadb.refFreeList[++ipsec_sadb.refFreeListTail] = IPsecSArefBuild(table, entry);
|
|
if(ipsec_sadb.refFreeListTail == (IPSEC_SA_REF_FREELIST_NUM_ENTRIES - 1)) {
|
|
ipsec_sadb.refFreeListHead = 0;
|
|
ipsec_sadb.refFreeListCont = ipsec_sadb.refFreeList[ipsec_sadb.refFreeListTail] + 1;
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_SAref_recycle: "
|
|
"SArefFreeList refilled.\n");
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ipsec_sadb.refFreeListTail == -1) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_SAref_recycle: "
|
|
"out of room in the SArefTable.\n");
|
|
|
|
return(-ENOSPC);
|
|
}
|
|
|
|
ipsec_sadb.refFreeListHead = 0;
|
|
ipsec_sadb.refFreeListCont = ipsec_sadb.refFreeList[ipsec_sadb.refFreeListTail] + 1;
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_SAref_recycle: "
|
|
"SArefFreeList partly refilled to %d of %d.\n",
|
|
ipsec_sadb.refFreeListTail,
|
|
IPSEC_SA_REF_FREELIST_NUM_ENTRIES);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ipsec_SArefSubTable_alloc(unsigned table)
|
|
{
|
|
unsigned entry;
|
|
struct IPsecSArefSubTable* SArefsub;
|
|
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_SArefSubTable_alloc: "
|
|
"allocating %lu bytes for table %u of %u.\n",
|
|
(unsigned long) (IPSEC_SA_REF_SUBTABLE_NUM_ENTRIES * sizeof(struct ipsec_sa *)),
|
|
table,
|
|
IPSEC_SA_REF_MAINTABLE_NUM_ENTRIES);
|
|
|
|
/* allocate another sub-table */
|
|
SArefsub = vmalloc(IPSEC_SA_REF_SUBTABLE_NUM_ENTRIES * sizeof(struct ipsec_sa *));
|
|
if(SArefsub == NULL) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_SArefSubTable_alloc: "
|
|
"error allocating memory for table %u of %u!\n",
|
|
table,
|
|
IPSEC_SA_REF_MAINTABLE_NUM_ENTRIES);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* add this sub-table to the main table */
|
|
ipsec_sadb.refTable[table] = SArefsub;
|
|
|
|
/* initialise each element to NULL */
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_SArefSubTable_alloc: "
|
|
"initialising %u elements (2 ^ %u) of table %u.\n",
|
|
IPSEC_SA_REF_SUBTABLE_NUM_ENTRIES,
|
|
IPSEC_SA_REF_SUBTABLE_IDX_WIDTH,
|
|
table);
|
|
for(entry = 0; entry < IPSEC_SA_REF_SUBTABLE_NUM_ENTRIES; entry++) {
|
|
SArefsub->entry[entry] = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* IPSEC_SA_REF_CODE */
|
|
|
|
int
|
|
ipsec_saref_freelist_init(void)
|
|
{
|
|
int i;
|
|
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_saref_freelist_init: "
|
|
"initialising %u elements of FreeList.\n",
|
|
IPSEC_SA_REF_FREELIST_NUM_ENTRIES);
|
|
|
|
for(i = 0; i < IPSEC_SA_REF_FREELIST_NUM_ENTRIES; i++) {
|
|
ipsec_sadb.refFreeList[i] = IPSEC_SAREF_NULL;
|
|
}
|
|
ipsec_sadb.refFreeListHead = -1;
|
|
ipsec_sadb.refFreeListCont = 0;
|
|
ipsec_sadb.refFreeListTail = -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ipsec_sadb_init(void)
|
|
{
|
|
int error = 0;
|
|
unsigned i;
|
|
|
|
for(i = 0; i < SADB_HASHMOD; i++) {
|
|
ipsec_sadb_hash[i] = NULL;
|
|
}
|
|
/* parts above are for the old style SADB hash table */
|
|
|
|
|
|
#if IPSEC_SA_REF_CODE
|
|
/* initialise SA reference table */
|
|
|
|
/* initialise the main table */
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sadb_init: "
|
|
"initialising main table of size %u (2 ^ %u).\n",
|
|
IPSEC_SA_REF_MAINTABLE_NUM_ENTRIES,
|
|
IPSEC_SA_REF_MAINTABLE_IDX_WIDTH);
|
|
{
|
|
unsigned table;
|
|
for(table = 0; table < IPSEC_SA_REF_MAINTABLE_NUM_ENTRIES; table++) {
|
|
ipsec_sadb.refTable[table] = NULL;
|
|
}
|
|
}
|
|
|
|
/* allocate the first sub-table */
|
|
error = ipsec_SArefSubTable_alloc(0);
|
|
if(error) {
|
|
return error;
|
|
}
|
|
|
|
error = ipsec_saref_freelist_init();
|
|
#endif /* IPSEC_SA_REF_CODE */
|
|
return error;
|
|
}
|
|
|
|
#if IPSEC_SA_REF_CODE
|
|
IPsecSAref_t
|
|
ipsec_SAref_alloc(int*error) /* pass in error var by pointer */
|
|
{
|
|
IPsecSAref_t SAref;
|
|
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_SAref_alloc: "
|
|
"SAref requested... head=%d, cont=%d, tail=%d, listsize=%d.\n",
|
|
ipsec_sadb.refFreeListHead,
|
|
ipsec_sadb.refFreeListCont,
|
|
ipsec_sadb.refFreeListTail,
|
|
IPSEC_SA_REF_FREELIST_NUM_ENTRIES);
|
|
|
|
if(ipsec_sadb.refFreeListHead == -1) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_SAref_alloc: "
|
|
"FreeList empty, recycling...\n");
|
|
*error = ipsec_SAref_recycle();
|
|
if(*error) {
|
|
return IPSEC_SAREF_NULL;
|
|
}
|
|
}
|
|
|
|
SAref = ipsec_sadb.refFreeList[ipsec_sadb.refFreeListHead];
|
|
if(SAref == IPSEC_SAREF_NULL) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_SAref_alloc: "
|
|
"unexpected error, refFreeListHead = %d points to invalid entry.\n",
|
|
ipsec_sadb.refFreeListHead);
|
|
*error = -ESPIPE;
|
|
return IPSEC_SAREF_NULL;
|
|
}
|
|
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_SAref_alloc: "
|
|
"allocating SAref=%d, table=%u, entry=%u of %u.\n",
|
|
SAref,
|
|
IPsecSAref2table(SAref),
|
|
IPsecSAref2entry(SAref),
|
|
IPSEC_SA_REF_MAINTABLE_NUM_ENTRIES * IPSEC_SA_REF_SUBTABLE_NUM_ENTRIES);
|
|
|
|
ipsec_sadb.refFreeList[ipsec_sadb.refFreeListHead] = IPSEC_SAREF_NULL;
|
|
ipsec_sadb.refFreeListHead++;
|
|
if(ipsec_sadb.refFreeListHead > ipsec_sadb.refFreeListTail) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_SAref_alloc: "
|
|
"last FreeList entry allocated, resetting list head to empty.\n");
|
|
ipsec_sadb.refFreeListHead = -1;
|
|
}
|
|
|
|
return SAref;
|
|
}
|
|
#endif /* IPSEC_SA_REF_CODE */
|
|
|
|
int
|
|
ipsec_sa_print(struct ipsec_sa *ips)
|
|
{
|
|
char sa[SATOA_BUF];
|
|
size_t sa_len;
|
|
|
|
printk(KERN_INFO "klips_debug: SA:");
|
|
if(ips == NULL) {
|
|
printk("NULL\n");
|
|
return -ENOENT;
|
|
}
|
|
printk(" ref=%d", ips->ips_ref);
|
|
printk(" refcount=%d", atomic_read(&ips->ips_refcount));
|
|
if(ips->ips_hnext != NULL) {
|
|
printk(" hnext=0p%p", ips->ips_hnext);
|
|
}
|
|
if(ips->ips_inext != NULL) {
|
|
printk(" inext=0p%p", ips->ips_inext);
|
|
}
|
|
if(ips->ips_onext != NULL) {
|
|
printk(" onext=0p%p", ips->ips_onext);
|
|
}
|
|
sa_len = satoa(ips->ips_said, 0, sa, SATOA_BUF);
|
|
printk(" said=%s", sa_len ? sa : " (error)");
|
|
if(ips->ips_seq) {
|
|
printk(" seq=%u", ips->ips_seq);
|
|
}
|
|
if(ips->ips_pid) {
|
|
printk(" pid=%u", ips->ips_pid);
|
|
}
|
|
if(ips->ips_authalg) {
|
|
printk(" authalg=%u", ips->ips_authalg);
|
|
}
|
|
if(ips->ips_encalg) {
|
|
printk(" encalg=%u", ips->ips_encalg);
|
|
}
|
|
printk(" XFORM=%s%s%s", IPS_XFORM_NAME(ips));
|
|
if(ips->ips_replaywin) {
|
|
printk(" ooowin=%u", ips->ips_replaywin);
|
|
}
|
|
if(ips->ips_flags) {
|
|
printk(" flags=%u", ips->ips_flags);
|
|
}
|
|
if(ips->ips_addr_s) {
|
|
char buf[SUBNETTOA_BUF];
|
|
addrtoa(((struct sockaddr_in*)(ips->ips_addr_s))->sin_addr,
|
|
0, buf, sizeof(buf));
|
|
printk(" src=%s", buf);
|
|
}
|
|
if(ips->ips_addr_d) {
|
|
char buf[SUBNETTOA_BUF];
|
|
addrtoa(((struct sockaddr_in*)(ips->ips_addr_s))->sin_addr,
|
|
0, buf, sizeof(buf));
|
|
printk(" dst=%s", buf);
|
|
}
|
|
if(ips->ips_addr_p) {
|
|
char buf[SUBNETTOA_BUF];
|
|
addrtoa(((struct sockaddr_in*)(ips->ips_addr_p))->sin_addr,
|
|
0, buf, sizeof(buf));
|
|
printk(" proxy=%s", buf);
|
|
}
|
|
if(ips->ips_key_bits_a) {
|
|
printk(" key_bits_a=%u", ips->ips_key_bits_a);
|
|
}
|
|
if(ips->ips_key_bits_e) {
|
|
printk(" key_bits_e=%u", ips->ips_key_bits_e);
|
|
}
|
|
|
|
printk("\n");
|
|
return 0;
|
|
}
|
|
|
|
struct ipsec_sa*
|
|
ipsec_sa_alloc(int*error) /* pass in error var by pointer */
|
|
{
|
|
struct ipsec_sa* ips;
|
|
|
|
if((ips = kmalloc(sizeof(*ips), GFP_ATOMIC) ) == NULL) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_alloc: "
|
|
"memory allocation error\n");
|
|
*error = -ENOMEM;
|
|
return NULL;
|
|
}
|
|
memset((caddr_t)ips, 0, sizeof(*ips));
|
|
#if IPSEC_SA_REF_CODE
|
|
ips->ips_ref = ipsec_SAref_alloc(error); /* pass in error return by pointer */
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_alloc: "
|
|
"allocated %lu bytes for ipsec_sa struct=0p%p ref=%d.\n",
|
|
(unsigned long) sizeof(*ips),
|
|
ips,
|
|
ips->ips_ref);
|
|
if(ips->ips_ref == IPSEC_SAREF_NULL) {
|
|
kfree(ips);
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_alloc: "
|
|
"SAref allocation error\n");
|
|
return NULL;
|
|
}
|
|
|
|
atomic_inc(&ips->ips_refcount);
|
|
IPsecSAref2SA(ips->ips_ref) = ips;
|
|
#endif /* IPSEC_SA_REF_CODE */
|
|
|
|
*error = 0;
|
|
return(ips);
|
|
}
|
|
|
|
int
|
|
ipsec_sa_free(struct ipsec_sa* ips)
|
|
{
|
|
return ipsec_sa_wipe(ips);
|
|
}
|
|
|
|
struct ipsec_sa *
|
|
ipsec_sa_getbyid(struct sa_id *said)
|
|
{
|
|
int hashval;
|
|
struct ipsec_sa *ips;
|
|
char sa[SATOA_BUF];
|
|
size_t sa_len;
|
|
|
|
if(said == NULL) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_error:ipsec_sa_getbyid: "
|
|
"null pointer passed in!\n");
|
|
return NULL;
|
|
}
|
|
|
|
sa_len = satoa(*said, 0, sa, SATOA_BUF);
|
|
|
|
hashval = (said->spi+said->dst.s_addr+said->proto) % SADB_HASHMOD;
|
|
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_getbyid: "
|
|
"linked entry in ipsec_sa table for hash=%d of SA:%s requested.\n",
|
|
hashval,
|
|
sa_len ? sa : " (error)");
|
|
|
|
if((ips = ipsec_sadb_hash[hashval]) == NULL) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_getbyid: "
|
|
"no entries in ipsec_sa table for hash=%d of SA:%s.\n",
|
|
hashval,
|
|
sa_len ? sa : " (error)");
|
|
return NULL;
|
|
}
|
|
|
|
for (; ips; ips = ips->ips_hnext) {
|
|
if ((ips->ips_said.spi == said->spi) &&
|
|
(ips->ips_said.dst.s_addr == said->dst.s_addr) &&
|
|
(ips->ips_said.proto == said->proto)) {
|
|
atomic_inc(&ips->ips_refcount);
|
|
return ips;
|
|
}
|
|
}
|
|
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_getbyid: "
|
|
"no entry in linked list for hash=%d of SA:%s.\n",
|
|
hashval,
|
|
sa_len ? sa : " (error)");
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
ipsec_sa_put(struct ipsec_sa *ips)
|
|
{
|
|
char sa[SATOA_BUF];
|
|
size_t sa_len;
|
|
|
|
if(ips == NULL) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_error:ipsec_sa_put: "
|
|
"null pointer passed in!\n");
|
|
return -1;
|
|
}
|
|
|
|
sa_len = satoa(ips->ips_said, 0, sa, SATOA_BUF);
|
|
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_put: "
|
|
"ipsec_sa SA:%s, ref:%d reference count decremented.\n",
|
|
sa_len ? sa : " (error)",
|
|
ips->ips_ref);
|
|
|
|
atomic_dec(&ips->ips_refcount);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
The ipsec_sa table better *NOT* be locked before it is handed in, or SMP locks will happen
|
|
*/
|
|
int
|
|
ipsec_sa_add(struct ipsec_sa *ips)
|
|
{
|
|
int error = 0;
|
|
unsigned int hashval;
|
|
|
|
if(ips == NULL) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_error:ipsec_sa_add: "
|
|
"null pointer passed in!\n");
|
|
return -ENODATA;
|
|
}
|
|
hashval = ((ips->ips_said.spi + ips->ips_said.dst.s_addr + ips->ips_said.proto) % SADB_HASHMOD);
|
|
|
|
atomic_inc(&ips->ips_refcount);
|
|
spin_lock_bh(&tdb_lock);
|
|
|
|
ips->ips_hnext = ipsec_sadb_hash[hashval];
|
|
ipsec_sadb_hash[hashval] = ips;
|
|
|
|
spin_unlock_bh(&tdb_lock);
|
|
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
The ipsec_sa table better be locked before it is handed in, or races might happen
|
|
*/
|
|
int
|
|
ipsec_sa_del(struct ipsec_sa *ips)
|
|
{
|
|
unsigned int hashval;
|
|
struct ipsec_sa *ipstp;
|
|
char sa[SATOA_BUF];
|
|
size_t sa_len;
|
|
|
|
if(ips == NULL) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_error:ipsec_sa_del: "
|
|
"null pointer passed in!\n");
|
|
return -ENODATA;
|
|
}
|
|
|
|
sa_len = satoa(ips->ips_said, 0, sa, SATOA_BUF);
|
|
if(ips->ips_inext || ips->ips_onext) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_error:ipsec_sa_del: "
|
|
"SA:%s still linked!\n",
|
|
sa_len ? sa : " (error)");
|
|
return -EMLINK;
|
|
}
|
|
|
|
hashval = ((ips->ips_said.spi + ips->ips_said.dst.s_addr + ips->ips_said.proto) % SADB_HASHMOD);
|
|
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_del: "
|
|
"deleting SA:%s, hashval=%d.\n",
|
|
sa_len ? sa : " (error)",
|
|
hashval);
|
|
if(ipsec_sadb_hash[hashval] == NULL) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_del: "
|
|
"no entries in ipsec_sa table for hash=%d of SA:%s.\n",
|
|
hashval,
|
|
sa_len ? sa : " (error)");
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (ips == ipsec_sadb_hash[hashval]) {
|
|
ipsec_sadb_hash[hashval] = ipsec_sadb_hash[hashval]->ips_hnext;
|
|
ips->ips_hnext = NULL;
|
|
atomic_dec(&ips->ips_refcount);
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_del: "
|
|
"successfully deleted first ipsec_sa in chain.\n");
|
|
return 0;
|
|
} else {
|
|
for (ipstp = ipsec_sadb_hash[hashval];
|
|
ipstp;
|
|
ipstp = ipstp->ips_hnext) {
|
|
if (ipstp->ips_hnext == ips) {
|
|
ipstp->ips_hnext = ips->ips_hnext;
|
|
ips->ips_hnext = NULL;
|
|
atomic_dec(&ips->ips_refcount);
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_del: "
|
|
"successfully deleted link in ipsec_sa chain.\n");
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_del: "
|
|
"no entries in linked list for hash=%d of SA:%s.\n",
|
|
hashval,
|
|
sa_len ? sa : " (error)");
|
|
return -ENOENT;
|
|
}
|
|
|
|
/*
|
|
The ipsec_sa table better be locked before it is handed in, or races
|
|
might happen
|
|
*/
|
|
int
|
|
ipsec_sa_delchain(struct ipsec_sa *ips)
|
|
{
|
|
struct ipsec_sa *ipsdel;
|
|
int error = 0;
|
|
char sa[SATOA_BUF];
|
|
size_t sa_len;
|
|
|
|
if(ips == NULL) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_error:ipsec_sa_delchain: "
|
|
"null pointer passed in!\n");
|
|
return -ENODATA;
|
|
}
|
|
|
|
sa_len = satoa(ips->ips_said, 0, sa, SATOA_BUF);
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_delchain: "
|
|
"passed SA:%s\n",
|
|
sa_len ? sa : " (error)");
|
|
while(ips->ips_onext != NULL) {
|
|
ips = ips->ips_onext;
|
|
}
|
|
|
|
while(ips) {
|
|
/* XXX send a pfkey message up to advise of deleted ipsec_sa */
|
|
sa_len = satoa(ips->ips_said, 0, sa, SATOA_BUF);
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_delchain: "
|
|
"unlinking and delting SA:%s",
|
|
sa_len ? sa : " (error)");
|
|
ipsdel = ips;
|
|
ips = ips->ips_inext;
|
|
if(ips != NULL) {
|
|
sa_len = satoa(ips->ips_said, 0, sa, SATOA_BUF);
|
|
KLIPS_PRINT(debug_xform,
|
|
", inext=%s",
|
|
sa_len ? sa : " (error)");
|
|
atomic_dec(&ipsdel->ips_refcount);
|
|
ipsdel->ips_inext = NULL;
|
|
atomic_dec(&ips->ips_refcount);
|
|
ips->ips_onext = NULL;
|
|
}
|
|
KLIPS_PRINT(debug_xform,
|
|
".\n");
|
|
if((error = ipsec_sa_del(ipsdel))) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_delchain: "
|
|
"ipsec_sa_del returned error %d.\n", -error);
|
|
return error;
|
|
}
|
|
if((error = ipsec_sa_wipe(ipsdel))) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_delchain: "
|
|
"ipsec_sa_wipe returned error %d.\n", -error);
|
|
return error;
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
int
|
|
ipsec_sadb_cleanup(__u8 proto)
|
|
{
|
|
unsigned i;
|
|
int error = 0;
|
|
struct ipsec_sa *ips, **ipsprev, *ipsdel;
|
|
char sa[SATOA_BUF];
|
|
size_t sa_len;
|
|
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sadb_cleanup: "
|
|
"cleaning up proto=%d.\n",
|
|
proto);
|
|
|
|
spin_lock_bh(&tdb_lock);
|
|
|
|
for (i = 0; i < SADB_HASHMOD; i++) {
|
|
ipsprev = &(ipsec_sadb_hash[i]);
|
|
ips = ipsec_sadb_hash[i];
|
|
if(ips != NULL) {
|
|
atomic_inc(&ips->ips_refcount);
|
|
}
|
|
for(; ips != NULL;) {
|
|
sa_len = satoa(ips->ips_said, 0, sa, SATOA_BUF);
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sadb_cleanup: "
|
|
"checking SA:%s, hash=%d, ref=%d",
|
|
sa_len ? sa : " (error)",
|
|
i,
|
|
ips->ips_ref);
|
|
ipsdel = ips;
|
|
ips = ipsdel->ips_hnext;
|
|
if(ips != NULL) {
|
|
atomic_inc(&ips->ips_refcount);
|
|
sa_len = satoa(ips->ips_said, 0, sa, SATOA_BUF);
|
|
KLIPS_PRINT(debug_xform,
|
|
", hnext=%s",
|
|
sa_len ? sa : " (error)");
|
|
}
|
|
if(*ipsprev != NULL) {
|
|
sa_len = satoa((*ipsprev)->ips_said, 0, sa, SATOA_BUF);
|
|
KLIPS_PRINT(debug_xform,
|
|
", *ipsprev=%s",
|
|
sa_len ? sa : " (error)");
|
|
if((*ipsprev)->ips_hnext) {
|
|
sa_len = satoa((*ipsprev)->ips_hnext->ips_said, 0, sa, SATOA_BUF);
|
|
KLIPS_PRINT(debug_xform,
|
|
", *ipsprev->ips_hnext=%s",
|
|
sa_len ? sa : " (error)");
|
|
}
|
|
}
|
|
KLIPS_PRINT(debug_xform,
|
|
".\n");
|
|
if(proto == 0 || (proto == ipsdel->ips_said.proto)) {
|
|
sa_len = satoa(ipsdel->ips_said, 0, sa, SATOA_BUF);
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sadb_cleanup: "
|
|
"deleting SA chain:%s.\n",
|
|
sa_len ? sa : " (error)");
|
|
if((error = ipsec_sa_delchain(ipsdel))) {
|
|
SENDERR(-error);
|
|
}
|
|
ipsprev = &(ipsec_sadb_hash[i]);
|
|
ips = ipsec_sadb_hash[i];
|
|
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sadb_cleanup: "
|
|
"deleted SA chain:%s",
|
|
sa_len ? sa : " (error)");
|
|
if(ips != NULL) {
|
|
sa_len = satoa(ips->ips_said, 0, sa, SATOA_BUF);
|
|
KLIPS_PRINT(debug_xform,
|
|
", ipsec_sadb_hash[%d]=%s",
|
|
i,
|
|
sa_len ? sa : " (error)");
|
|
}
|
|
if(*ipsprev != NULL) {
|
|
sa_len = satoa((*ipsprev)->ips_said, 0, sa, SATOA_BUF);
|
|
KLIPS_PRINT(debug_xform,
|
|
", *ipsprev=%s",
|
|
sa_len ? sa : " (error)");
|
|
if((*ipsprev)->ips_hnext != NULL) {
|
|
sa_len = satoa((*ipsprev)->ips_hnext->ips_said, 0, sa, SATOA_BUF);
|
|
KLIPS_PRINT(debug_xform,
|
|
", *ipsprev->ips_hnext=%s",
|
|
sa_len ? sa : " (error)");
|
|
}
|
|
}
|
|
KLIPS_PRINT(debug_xform,
|
|
".\n");
|
|
} else {
|
|
ipsprev = &ipsdel;
|
|
}
|
|
if(ipsdel != NULL) {
|
|
ipsec_sa_put(ipsdel);
|
|
}
|
|
}
|
|
}
|
|
errlab:
|
|
|
|
spin_unlock_bh(&tdb_lock);
|
|
|
|
|
|
#if IPSEC_SA_REF_CODE
|
|
/* clean up SA reference table */
|
|
|
|
/* go through the ref table and clean out all the SAs */
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sadb_cleanup: "
|
|
"removing SAref entries and tables.");
|
|
{
|
|
unsigned table, entry;
|
|
for(table = 0; table < IPSEC_SA_REF_MAINTABLE_NUM_ENTRIES; table++) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sadb_cleanup: "
|
|
"cleaning SAref table=%u.\n",
|
|
table);
|
|
if(ipsec_sadb.refTable[table] == NULL) {
|
|
printk("\n");
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sadb_cleanup: "
|
|
"cleaned %u used refTables.\n",
|
|
table);
|
|
break;
|
|
}
|
|
for(entry = 0; entry < IPSEC_SA_REF_SUBTABLE_NUM_ENTRIES; entry++) {
|
|
if(ipsec_sadb.refTable[table]->entry[entry] != NULL) {
|
|
ipsec_sa_delchain(ipsec_sadb.refTable[table]->entry[entry]);
|
|
ipsec_sadb.refTable[table]->entry[entry] = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif /* IPSEC_SA_REF_CODE */
|
|
|
|
return(error);
|
|
}
|
|
|
|
int
|
|
ipsec_sadb_free(void)
|
|
{
|
|
int error = 0;
|
|
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sadb_free: "
|
|
"freeing SArefTable memory.\n");
|
|
|
|
/* clean up SA reference table */
|
|
|
|
/* go through the ref table and clean out all the SAs if any are
|
|
left and free table memory */
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sadb_free: "
|
|
"removing SAref entries and tables.\n");
|
|
{
|
|
unsigned table, entry;
|
|
for(table = 0; table < IPSEC_SA_REF_MAINTABLE_NUM_ENTRIES; table++) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sadb_free: "
|
|
"removing SAref table=%u.\n",
|
|
table);
|
|
if(ipsec_sadb.refTable[table] == NULL) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sadb_free: "
|
|
"removed %u used refTables.\n",
|
|
table);
|
|
break;
|
|
}
|
|
for(entry = 0; entry < IPSEC_SA_REF_SUBTABLE_NUM_ENTRIES; entry++) {
|
|
if(ipsec_sadb.refTable[table]->entry[entry] != NULL) {
|
|
ipsec_sa_delchain(ipsec_sadb.refTable[table]->entry[entry]);
|
|
ipsec_sadb.refTable[table]->entry[entry] = NULL;
|
|
}
|
|
}
|
|
vfree(ipsec_sadb.refTable[table]);
|
|
ipsec_sadb.refTable[table] = NULL;
|
|
}
|
|
}
|
|
|
|
return(error);
|
|
}
|
|
|
|
int
|
|
ipsec_sa_wipe(struct ipsec_sa *ips)
|
|
{
|
|
if(ips == NULL) {
|
|
return -ENODATA;
|
|
}
|
|
|
|
/* if(atomic_dec_and_test(ips)) {
|
|
}; */
|
|
|
|
#if IPSEC_SA_REF_CODE
|
|
/* remove me from the SArefTable */
|
|
{
|
|
char sa[SATOA_BUF];
|
|
size_t sa_len;
|
|
sa_len = satoa(ips->ips_said, 0, sa, SATOA_BUF);
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_wipe: "
|
|
"removing SA=%s(0p%p), SAref=%d, table=%d(0p%p), entry=%d from the refTable.\n",
|
|
sa_len ? sa : " (error)",
|
|
ips,
|
|
ips->ips_ref,
|
|
IPsecSAref2table(IPsecSA2SAref(ips)),
|
|
ipsec_sadb.refTable[IPsecSAref2table(IPsecSA2SAref(ips))],
|
|
IPsecSAref2entry(IPsecSA2SAref(ips)));
|
|
}
|
|
if(ips->ips_ref == IPSEC_SAREF_NULL) {
|
|
KLIPS_PRINT(debug_xform,
|
|
"klips_debug:ipsec_sa_wipe: "
|
|
"why does this SA not have a valid SAref?.\n");
|
|
}
|
|
ipsec_sadb.refTable[IPsecSAref2table(IPsecSA2SAref(ips))]->entry[IPsecSAref2entry(IPsecSA2SAref(ips))] = NULL;
|
|
ips->ips_ref = IPSEC_SAREF_NULL;
|
|
ipsec_sa_put(ips);
|
|
#endif /* IPSEC_SA_REF_CODE */
|
|
|
|
/* paranoid clean up */
|
|
if(ips->ips_addr_s != NULL) {
|
|
memset((caddr_t)(ips->ips_addr_s), 0, ips->ips_addr_s_size);
|
|
kfree(ips->ips_addr_s);
|
|
}
|
|
ips->ips_addr_s = NULL;
|
|
|
|
if(ips->ips_addr_d != NULL) {
|
|
memset((caddr_t)(ips->ips_addr_d), 0, ips->ips_addr_d_size);
|
|
kfree(ips->ips_addr_d);
|
|
}
|
|
ips->ips_addr_d = NULL;
|
|
|
|
if(ips->ips_addr_p != NULL) {
|
|
memset((caddr_t)(ips->ips_addr_p), 0, ips->ips_addr_p_size);
|
|
kfree(ips->ips_addr_p);
|
|
}
|
|
ips->ips_addr_p = NULL;
|
|
|
|
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
|
|
if(ips->ips_natt_oa) {
|
|
memset((caddr_t)(ips->ips_natt_oa), 0, ips->ips_natt_oa_size);
|
|
kfree(ips->ips_natt_oa);
|
|
}
|
|
ips->ips_natt_oa = NULL;
|
|
#endif
|
|
|
|
if(ips->ips_key_a != NULL) {
|
|
memset((caddr_t)(ips->ips_key_a), 0, ips->ips_key_a_size);
|
|
kfree(ips->ips_key_a);
|
|
}
|
|
ips->ips_key_a = NULL;
|
|
|
|
if(ips->ips_key_e != NULL) {
|
|
#ifdef CONFIG_IPSEC_ALG
|
|
if (ips->ips_alg_enc&&ips->ips_alg_enc->ixt_e_destroy_key) {
|
|
ips->ips_alg_enc->ixt_e_destroy_key(ips->ips_alg_enc,
|
|
ips->ips_key_e);
|
|
} else {
|
|
#endif /* CONFIG_IPSEC_ALG */
|
|
memset((caddr_t)(ips->ips_key_e), 0, ips->ips_key_e_size);
|
|
kfree(ips->ips_key_e);
|
|
#ifdef CONFIG_IPSEC_ALG
|
|
}
|
|
#endif /* CONFIG_IPSEC_ALG */
|
|
}
|
|
ips->ips_key_e = NULL;
|
|
|
|
if(ips->ips_iv != NULL) {
|
|
memset((caddr_t)(ips->ips_iv), 0, ips->ips_iv_size);
|
|
kfree(ips->ips_iv);
|
|
}
|
|
ips->ips_iv = NULL;
|
|
|
|
if(ips->ips_ident_s.data != NULL) {
|
|
memset((caddr_t)(ips->ips_ident_s.data),
|
|
0,
|
|
ips->ips_ident_s.len * IPSEC_PFKEYv2_ALIGN - sizeof(struct sadb_ident));
|
|
kfree(ips->ips_ident_s.data);
|
|
}
|
|
ips->ips_ident_s.data = NULL;
|
|
|
|
if(ips->ips_ident_d.data != NULL) {
|
|
memset((caddr_t)(ips->ips_ident_d.data),
|
|
0,
|
|
ips->ips_ident_d.len * IPSEC_PFKEYv2_ALIGN - sizeof(struct sadb_ident));
|
|
kfree(ips->ips_ident_d.data);
|
|
}
|
|
ips->ips_ident_d.data = NULL;
|
|
|
|
#ifdef CONFIG_IPSEC_ALG
|
|
if (ips->ips_alg_enc||ips->ips_alg_auth) {
|
|
ipsec_alg_sa_wipe(ips);
|
|
}
|
|
#endif /* CONFIG_IPSEC_ALG */
|
|
|
|
memset((caddr_t)ips, 0, sizeof(*ips));
|
|
kfree(ips);
|
|
ips = NULL;
|
|
|
|
return 0;
|
|
}
|