source "net/ipv6/Kconfig"
+source "net/xfrm/Kconfig"
+
source "net/sctp/Kconfig"
config ATM
# LLC has to be linked before the files in net/802/
obj-$(CONFIG_LLC) += llc/
obj-$(CONFIG_NET) += ethernet/ 802/ sched/ netlink/
-obj-$(CONFIG_INET) += ipv4/
+obj-$(CONFIG_INET) += ipv4/ xfrm/
obj-$(CONFIG_UNIX) += unix/
obj-$(CONFIG_IPV6) += ipv6/
obj-$(CONFIG_PACKET) += packet/
If unsure, say Y.
-config XFRM_USER
- tristate "IP: IPsec user configuration interface"
- ---help---
- Support for IPsec user configuration interface used
- by native Linux tools.
-
- If unsure, say Y.
-
source "net/ipv4/netfilter/Kconfig"
obj-$(CONFIG_INET_ESP) += esp.o
obj-$(CONFIG_IP_PNP) += ipconfig.o
obj-$(CONFIG_NETFILTER) += netfilter/
-obj-$(CONFIG_XFRM_USER) += xfrm_user.o
-obj-y += xfrm_policy.o xfrm4_policy.o xfrm_state.o xfrm4_state.o xfrm_input.o xfrm4_input.o xfrm_algo.o
+obj-y += xfrm4_policy.o xfrm4_state.o xfrm4_input.o
+++ /dev/null
-/*
- * xfrm algorithm interface
- *
- * Copyright (c) 2002 James Morris <jmorris@intercode.com.au>
- *
- * 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.
- */
-#include <linux/config.h>
-#include <linux/kernel.h>
-#include <linux/pfkeyv2.h>
-#include <net/xfrm.h>
-#if defined(CONFIG_INET_AH) || defined(CONFIG_INET_AH_MODULE) || defined(CONFIG_INET6_AH) || defined(CONFIG_INET6_AH_MODULE)
-#include <net/ah.h>
-#endif
-#if defined(CONFIG_INET_ESP) || defined(CONFIG_INET_ESP_MODULE) || defined(CONFIG_INET6_ESP) || defined(CONFIG_INET6_ESP_MODULE)
-#include <net/esp.h>
-#endif
-#include <asm/scatterlist.h>
-
-/*
- * Algorithms supported by IPsec. These entries contain properties which
- * are used in key negotiation and xfrm processing, and are used to verify
- * that instantiated crypto transforms have correct parameters for IPsec
- * purposes.
- */
-static struct xfrm_algo_desc aalg_list[] = {
-{
- .name = "digest_null",
-
- .uinfo = {
- .auth = {
- .icv_truncbits = 0,
- .icv_fullbits = 0,
- }
- },
-
- .desc = {
- .sadb_alg_id = SADB_X_AALG_NULL,
- .sadb_alg_ivlen = 0,
- .sadb_alg_minbits = 0,
- .sadb_alg_maxbits = 0
- }
-},
-{
- .name = "md5",
-
- .uinfo = {
- .auth = {
- .icv_truncbits = 96,
- .icv_fullbits = 128,
- }
- },
-
- .desc = {
- .sadb_alg_id = SADB_AALG_MD5HMAC,
- .sadb_alg_ivlen = 0,
- .sadb_alg_minbits = 128,
- .sadb_alg_maxbits = 128
- }
-},
-{
- .name = "sha1",
-
- .uinfo = {
- .auth = {
- .icv_truncbits = 96,
- .icv_fullbits = 160,
- }
- },
-
- .desc = {
- .sadb_alg_id = SADB_AALG_SHA1HMAC,
- .sadb_alg_ivlen = 0,
- .sadb_alg_minbits = 160,
- .sadb_alg_maxbits = 160
- }
-},
-{
- .name = "sha256",
-
- .uinfo = {
- .auth = {
- .icv_truncbits = 128,
- .icv_fullbits = 256,
- }
- },
-
- .desc = {
- .sadb_alg_id = SADB_X_AALG_SHA2_256HMAC,
- .sadb_alg_ivlen = 0,
- .sadb_alg_minbits = 256,
- .sadb_alg_maxbits = 256
- }
-},
-{
- .name = "ripemd160",
-
- .uinfo = {
- .auth = {
- .icv_truncbits = 96,
- .icv_fullbits = 160,
- }
- },
-
- .desc = {
- .sadb_alg_id = SADB_X_AALG_RIPEMD160HMAC,
- .sadb_alg_ivlen = 0,
- .sadb_alg_minbits = 160,
- .sadb_alg_maxbits = 160
- }
-},
-};
-
-static struct xfrm_algo_desc ealg_list[] = {
-{
- .name = "cipher_null",
-
- .uinfo = {
- .encr = {
- .blockbits = 8,
- .defkeybits = 0,
- }
- },
-
- .desc = {
- .sadb_alg_id = SADB_EALG_NULL,
- .sadb_alg_ivlen = 0,
- .sadb_alg_minbits = 0,
- .sadb_alg_maxbits = 0
- }
-},
-{
- .name = "des",
-
- .uinfo = {
- .encr = {
- .blockbits = 64,
- .defkeybits = 64,
- }
- },
-
- .desc = {
- .sadb_alg_id = SADB_EALG_DESCBC,
- .sadb_alg_ivlen = 8,
- .sadb_alg_minbits = 64,
- .sadb_alg_maxbits = 64
- }
-},
-{
- .name = "des3_ede",
-
- .uinfo = {
- .encr = {
- .blockbits = 64,
- .defkeybits = 192,
- }
- },
-
- .desc = {
- .sadb_alg_id = SADB_EALG_3DESCBC,
- .sadb_alg_ivlen = 8,
- .sadb_alg_minbits = 192,
- .sadb_alg_maxbits = 192
- }
-},
-{
- .name = "cast128",
-
- .uinfo = {
- .encr = {
- .blockbits = 64,
- .defkeybits = 128,
- }
- },
-
- .desc = {
- .sadb_alg_id = SADB_X_EALG_CASTCBC,
- .sadb_alg_ivlen = 8,
- .sadb_alg_minbits = 40,
- .sadb_alg_maxbits = 128
- }
-},
-{
- .name = "blowfish",
-
- .uinfo = {
- .encr = {
- .blockbits = 64,
- .defkeybits = 128,
- }
- },
-
- .desc = {
- .sadb_alg_id = SADB_X_EALG_BLOWFISHCBC,
- .sadb_alg_ivlen = 8,
- .sadb_alg_minbits = 40,
- .sadb_alg_maxbits = 448
- }
-},
-{
- .name = "aes",
-
- .uinfo = {
- .encr = {
- .blockbits = 128,
- .defkeybits = 128,
- }
- },
-
- .desc = {
- .sadb_alg_id = SADB_X_EALG_AESCBC,
- .sadb_alg_ivlen = 8,
- .sadb_alg_minbits = 128,
- .sadb_alg_maxbits = 256
- }
-},
-};
-
-static struct xfrm_algo_desc calg_list[] = {
-{
- .name = "deflate",
- .uinfo = {
- .comp = {
- .threshold = 90,
- }
- },
- .desc = { .sadb_alg_id = SADB_X_CALG_DEFLATE }
-},
-{
- .name = "lzs",
- .uinfo = {
- .comp = {
- .threshold = 90,
- }
- },
- .desc = { .sadb_alg_id = SADB_X_CALG_LZS }
-},
-{
- .name = "lzjh",
- .uinfo = {
- .comp = {
- .threshold = 50,
- }
- },
- .desc = { .sadb_alg_id = SADB_X_CALG_LZJH }
-},
-};
-
-static inline int aalg_entries(void)
-{
- return sizeof(aalg_list) / sizeof(aalg_list[0]);
-}
-
-static inline int ealg_entries(void)
-{
- return sizeof(ealg_list) / sizeof(ealg_list[0]);
-}
-
-static inline int calg_entries(void)
-{
- return sizeof(calg_list) / sizeof(calg_list[0]);
-}
-
-/* Todo: generic iterators */
-struct xfrm_algo_desc *xfrm_aalg_get_byid(int alg_id)
-{
- int i;
-
- for (i = 0; i < aalg_entries(); i++) {
- if (aalg_list[i].desc.sadb_alg_id == alg_id) {
- if (aalg_list[i].available)
- return &aalg_list[i];
- else
- break;
- }
- }
- return NULL;
-}
-
-struct xfrm_algo_desc *xfrm_ealg_get_byid(int alg_id)
-{
- int i;
-
- for (i = 0; i < ealg_entries(); i++) {
- if (ealg_list[i].desc.sadb_alg_id == alg_id) {
- if (ealg_list[i].available)
- return &ealg_list[i];
- else
- break;
- }
- }
- return NULL;
-}
-
-struct xfrm_algo_desc *xfrm_calg_get_byid(int alg_id)
-{
- int i;
-
- for (i = 0; i < calg_entries(); i++) {
- if (calg_list[i].desc.sadb_alg_id == alg_id) {
- if (calg_list[i].available)
- return &calg_list[i];
- else
- break;
- }
- }
- return NULL;
-}
-
-struct xfrm_algo_desc *xfrm_aalg_get_byname(char *name)
-{
- int i;
-
- if (!name)
- return NULL;
-
- for (i=0; i < aalg_entries(); i++) {
- if (strcmp(name, aalg_list[i].name) == 0) {
- if (aalg_list[i].available)
- return &aalg_list[i];
- else
- break;
- }
- }
- return NULL;
-}
-
-struct xfrm_algo_desc *xfrm_ealg_get_byname(char *name)
-{
- int i;
-
- if (!name)
- return NULL;
-
- for (i=0; i < ealg_entries(); i++) {
- if (strcmp(name, ealg_list[i].name) == 0) {
- if (ealg_list[i].available)
- return &ealg_list[i];
- else
- break;
- }
- }
- return NULL;
-}
-
-struct xfrm_algo_desc *xfrm_calg_get_byname(char *name)
-{
- int i;
-
- if (!name)
- return NULL;
-
- for (i=0; i < calg_entries(); i++) {
- if (strcmp(name, calg_list[i].name) == 0) {
- if (calg_list[i].available)
- return &calg_list[i];
- else
- break;
- }
- }
- return NULL;
-}
-
-struct xfrm_algo_desc *xfrm_aalg_get_byidx(unsigned int idx)
-{
- if (idx >= aalg_entries())
- return NULL;
-
- return &aalg_list[idx];
-}
-
-struct xfrm_algo_desc *xfrm_ealg_get_byidx(unsigned int idx)
-{
- if (idx >= ealg_entries())
- return NULL;
-
- return &ealg_list[idx];
-}
-
-struct xfrm_algo_desc *xfrm_calg_get_byidx(unsigned int idx)
-{
- if (idx >= calg_entries())
- return NULL;
-
- return &calg_list[idx];
-}
-
-/*
- * Probe for the availability of crypto algorithms, and set the available
- * flag for any algorithms found on the system. This is typically called by
- * pfkey during userspace SA add, update or register.
- */
-void xfrm_probe_algs(void)
-{
-#ifdef CONFIG_CRYPTO
- int i, status;
-
- BUG_ON(in_softirq());
-
- for (i = 0; i < aalg_entries(); i++) {
- status = crypto_alg_available(aalg_list[i].name, 0);
- if (aalg_list[i].available != status)
- aalg_list[i].available = status;
- }
-
- for (i = 0; i < ealg_entries(); i++) {
- status = crypto_alg_available(ealg_list[i].name, 0);
- if (ealg_list[i].available != status)
- ealg_list[i].available = status;
- }
-
- for (i = 0; i < calg_entries(); i++) {
- status = crypto_alg_available(calg_list[i].name, 0);
- if (calg_list[i].available != status)
- calg_list[i].available = status;
- }
-#endif
-}
-
-int xfrm_count_auth_supported(void)
-{
- int i, n;
-
- for (i = 0, n = 0; i < aalg_entries(); i++)
- if (aalg_list[i].available)
- n++;
- return n;
-}
-
-int xfrm_count_enc_supported(void)
-{
- int i, n;
-
- for (i = 0, n = 0; i < ealg_entries(); i++)
- if (ealg_list[i].available)
- n++;
- return n;
-}
-
-#if defined(CONFIG_INET_AH) || defined(CONFIG_INET_AH_MODULE) || defined(CONFIG_INET6_AH) || defined(CONFIG_INET6_AH_MODULE)
-void skb_ah_walk(const struct sk_buff *skb,
- struct crypto_tfm *tfm, icv_update_fn_t icv_update)
-{
- int offset = 0;
- int len = skb->len;
- int start = skb->len - skb->data_len;
- int i, copy = start - offset;
- struct scatterlist sg;
-
- /* Checksum header. */
- if (copy > 0) {
- if (copy > len)
- copy = len;
-
- sg.page = virt_to_page(skb->data + offset);
- sg.offset = (unsigned long)(skb->data + offset) % PAGE_SIZE;
- sg.length = copy;
-
- icv_update(tfm, &sg, 1);
-
- if ((len -= copy) == 0)
- return;
- offset += copy;
- }
-
- for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
- int end;
-
- BUG_TRAP(start <= offset + len);
-
- end = start + skb_shinfo(skb)->frags[i].size;
- if ((copy = end - offset) > 0) {
- skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
-
- if (copy > len)
- copy = len;
-
- sg.page = frag->page;
- sg.offset = frag->page_offset + offset-start;
- sg.length = copy;
-
- icv_update(tfm, &sg, 1);
-
- if (!(len -= copy))
- return;
- offset += copy;
- }
- start = end;
- }
-
- if (skb_shinfo(skb)->frag_list) {
- struct sk_buff *list = skb_shinfo(skb)->frag_list;
-
- for (; list; list = list->next) {
- int end;
-
- BUG_TRAP(start <= offset + len);
-
- end = start + list->len;
- if ((copy = end - offset) > 0) {
- if (copy > len)
- copy = len;
- skb_ah_walk(list, tfm, icv_update);
- if ((len -= copy) == 0)
- return;
- offset += copy;
- }
- start = end;
- }
- }
- if (len)
- BUG();
-}
-#endif
-
-#if defined(CONFIG_INET_ESP) || defined(CONFIG_INET_ESP_MODULE) || defined(CONFIG_INET6_ESP) || defined(CONFIG_INET6_ESP_MODULE)
-/* Move to common area: it is shared with AH. */
-
-void skb_icv_walk(const struct sk_buff *skb, struct crypto_tfm *tfm,
- int offset, int len, icv_update_fn_t icv_update)
-{
- int start = skb->len - skb->data_len;
- int i, copy = start - offset;
- struct scatterlist sg;
-
- /* Checksum header. */
- if (copy > 0) {
- if (copy > len)
- copy = len;
-
- sg.page = virt_to_page(skb->data + offset);
- sg.offset = (unsigned long)(skb->data + offset) % PAGE_SIZE;
- sg.length = copy;
-
- icv_update(tfm, &sg, 1);
-
- if ((len -= copy) == 0)
- return;
- offset += copy;
- }
-
- for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
- int end;
-
- BUG_TRAP(start <= offset + len);
-
- end = start + skb_shinfo(skb)->frags[i].size;
- if ((copy = end - offset) > 0) {
- skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
-
- if (copy > len)
- copy = len;
-
- sg.page = frag->page;
- sg.offset = frag->page_offset + offset-start;
- sg.length = copy;
-
- icv_update(tfm, &sg, 1);
-
- if (!(len -= copy))
- return;
- offset += copy;
- }
- start = end;
- }
-
- if (skb_shinfo(skb)->frag_list) {
- struct sk_buff *list = skb_shinfo(skb)->frag_list;
-
- for (; list; list = list->next) {
- int end;
-
- BUG_TRAP(start <= offset + len);
-
- end = start + list->len;
- if ((copy = end - offset) > 0) {
- if (copy > len)
- copy = len;
- skb_icv_walk(list, tfm, offset-start, copy, icv_update);
- if ((len -= copy) == 0)
- return;
- offset += copy;
- }
- start = end;
- }
- }
- if (len)
- BUG();
-}
-
-
-/* Looking generic it is not used in another places. */
-
-int
-skb_to_sgvec(struct sk_buff *skb, struct scatterlist *sg, int offset, int len)
-{
- int start = skb->len - skb->data_len;
- int i, copy = start - offset;
- int elt = 0;
-
- if (copy > 0) {
- if (copy > len)
- copy = len;
- sg[elt].page = virt_to_page(skb->data + offset);
- sg[elt].offset = (unsigned long)(skb->data + offset) % PAGE_SIZE;
- sg[elt].length = copy;
- elt++;
- if ((len -= copy) == 0)
- return elt;
- offset += copy;
- }
-
- for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
- int end;
-
- BUG_TRAP(start <= offset + len);
-
- end = start + skb_shinfo(skb)->frags[i].size;
- if ((copy = end - offset) > 0) {
- skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
-
- if (copy > len)
- copy = len;
- sg[elt].page = frag->page;
- sg[elt].offset = frag->page_offset+offset-start;
- sg[elt].length = copy;
- elt++;
- if (!(len -= copy))
- return elt;
- offset += copy;
- }
- start = end;
- }
-
- if (skb_shinfo(skb)->frag_list) {
- struct sk_buff *list = skb_shinfo(skb)->frag_list;
-
- for (; list; list = list->next) {
- int end;
-
- BUG_TRAP(start <= offset + len);
-
- end = start + list->len;
- if ((copy = end - offset) > 0) {
- if (copy > len)
- copy = len;
- elt += skb_to_sgvec(list, sg+elt, offset - start, copy);
- if ((len -= copy) == 0)
- return elt;
- offset += copy;
- }
- start = end;
- }
- }
- if (len)
- BUG();
- return elt;
-}
-
-/* Check that skb data bits are writable. If they are not, copy data
- * to newly created private area. If "tailbits" is given, make sure that
- * tailbits bytes beyond current end of skb are writable.
- *
- * Returns amount of elements of scatterlist to load for subsequent
- * transformations and pointer to writable trailer skb.
- */
-
-int skb_cow_data(struct sk_buff *skb, int tailbits, struct sk_buff **trailer)
-{
- int copyflag;
- int elt;
- struct sk_buff *skb1, **skb_p;
-
- /* If skb is cloned or its head is paged, reallocate
- * head pulling out all the pages (pages are considered not writable
- * at the moment even if they are anonymous).
- */
- if ((skb_cloned(skb) || skb_shinfo(skb)->nr_frags) &&
- __pskb_pull_tail(skb, skb_pagelen(skb)-skb_headlen(skb)) == NULL)
- return -ENOMEM;
-
- /* Easy case. Most of packets will go this way. */
- if (!skb_shinfo(skb)->frag_list) {
- /* A little of trouble, not enough of space for trailer.
- * This should not happen, when stack is tuned to generate
- * good frames. OK, on miss we reallocate and reserve even more
- * space, 128 bytes is fair. */
-
- if (skb_tailroom(skb) < tailbits &&
- pskb_expand_head(skb, 0, tailbits-skb_tailroom(skb)+128, GFP_ATOMIC))
- return -ENOMEM;
-
- /* Voila! */
- *trailer = skb;
- return 1;
- }
-
- /* Misery. We are in troubles, going to mincer fragments... */
-
- elt = 1;
- skb_p = &skb_shinfo(skb)->frag_list;
- copyflag = 0;
-
- while ((skb1 = *skb_p) != NULL) {
- int ntail = 0;
-
- /* The fragment is partially pulled by someone,
- * this can happen on input. Copy it and everything
- * after it. */
-
- if (skb_shared(skb1))
- copyflag = 1;
-
- /* If the skb is the last, worry about trailer. */
-
- if (skb1->next == NULL && tailbits) {
- if (skb_shinfo(skb1)->nr_frags ||
- skb_shinfo(skb1)->frag_list ||
- skb_tailroom(skb1) < tailbits)
- ntail = tailbits + 128;
- }
-
- if (copyflag ||
- skb_cloned(skb1) ||
- ntail ||
- skb_shinfo(skb1)->nr_frags ||
- skb_shinfo(skb1)->frag_list) {
- struct sk_buff *skb2;
-
- /* Fuck, we are miserable poor guys... */
- if (ntail == 0)
- skb2 = skb_copy(skb1, GFP_ATOMIC);
- else
- skb2 = skb_copy_expand(skb1,
- skb_headroom(skb1),
- ntail,
- GFP_ATOMIC);
- if (unlikely(skb2 == NULL))
- return -ENOMEM;
-
- if (skb1->sk)
- skb_set_owner_w(skb, skb1->sk);
-
- /* Looking around. Are we still alive?
- * OK, link new skb, drop old one */
-
- skb2->next = skb1->next;
- *skb_p = skb2;
- kfree_skb(skb1);
- skb1 = skb2;
- }
- elt++;
- *trailer = skb1;
- skb_p = &skb1->next;
- }
-
- return elt;
-}
-
-void *pskb_put(struct sk_buff *skb, struct sk_buff *tail, int len)
-{
- if (tail != skb) {
- skb->data_len += len;
- skb->len += len;
- }
- return skb_put(tail, len);
-}
-#endif
+++ /dev/null
-/*
- * xfrm_input.c
- *
- * Changes:
- * YOSHIFUJI Hideaki @USAGI
- * Split up af-specific portion
- *
- */
-
-#include <net/ip.h>
-#include <net/xfrm.h>
-
-void __secpath_destroy(struct sec_path *sp)
-{
- int i;
- for (i = 0; i < sp->len; i++)
- xfrm_state_put(sp->xvec[i]);
- kmem_cache_free(sp->pool, sp);
-}
-
-/* Fetch spi and seq frpm ipsec header */
-
-int xfrm_parse_spi(struct sk_buff *skb, u8 nexthdr, u32 *spi, u32 *seq)
-{
- int offset, offset_seq;
-
- switch (nexthdr) {
- case IPPROTO_AH:
- offset = offsetof(struct ip_auth_hdr, spi);
- offset_seq = offsetof(struct ip_auth_hdr, seq_no);
- break;
- case IPPROTO_ESP:
- offset = offsetof(struct ip_esp_hdr, spi);
- offset_seq = offsetof(struct ip_esp_hdr, seq_no);
- break;
- case IPPROTO_COMP:
- if (!pskb_may_pull(skb, 4))
- return -EINVAL;
- *spi = ntohl(ntohs(*(u16*)(skb->h.raw + 2)));
- *seq = 0;
- return 0;
- default:
- return 1;
- }
-
- if (!pskb_may_pull(skb, 16))
- return -EINVAL;
-
- *spi = *(u32*)(skb->h.raw + offset);
- *seq = *(u32*)(skb->h.raw + offset_seq);
- return 0;
-}
+++ /dev/null
-/*
- * xfrm_policy.c
- *
- * Changes:
- * Mitsuru KANDA @USAGI
- * Kazunori MIYAZAWA @USAGI
- * Kunihiro Ishiguro
- * IPv6 support
- * Kazunori MIYAZAWA @USAGI
- * YOSHIFUJI Hideaki
- * Split up af-specific portion
- *
- */
-
-#include <linux/config.h>
-#include <net/xfrm.h>
-#include <net/ip.h>
-
-DECLARE_MUTEX(xfrm_cfg_sem);
-
-static u32 xfrm_policy_genid;
-static rwlock_t xfrm_policy_lock = RW_LOCK_UNLOCKED;
-
-struct xfrm_policy *xfrm_policy_list[XFRM_POLICY_MAX*2];
-
-static rwlock_t xfrm_policy_afinfo_lock = RW_LOCK_UNLOCKED;
-static struct xfrm_policy_afinfo *xfrm_policy_afinfo[NPROTO];
-
-kmem_cache_t *xfrm_dst_cache;
-
-/* Limited flow cache. Its function now is to accelerate search for
- * policy rules.
- *
- * Flow cache is private to cpus, at the moment this is important
- * mostly for flows which do not match any rule, so that flow lookups
- * are absolultely cpu-local. When a rule exists we do some updates
- * to rule (refcnt, stats), so that locality is broken. Later this
- * can be repaired.
- */
-
-struct flow_entry
-{
- struct flow_entry *next;
- struct flowi fl;
- u8 dir;
- u32 genid;
- struct xfrm_policy *pol;
-};
-
-static kmem_cache_t *flow_cachep;
-
-struct flow_entry **flow_table;
-
-static int flow_lwm = 2*XFRM_FLOWCACHE_HASH_SIZE;
-static int flow_hwm = 4*XFRM_FLOWCACHE_HASH_SIZE;
-
-static int flow_number[NR_CPUS] __cacheline_aligned;
-
-#define flow_count(cpu) (flow_number[cpu])
-
-static void flow_cache_shrink(int cpu)
-{
- int i;
- struct flow_entry *fle, **flp;
- int shrink_to = flow_lwm/XFRM_FLOWCACHE_HASH_SIZE;
-
- for (i=0; i<XFRM_FLOWCACHE_HASH_SIZE; i++) {
- int k = 0;
- flp = &flow_table[cpu*XFRM_FLOWCACHE_HASH_SIZE+i];
- while ((fle=*flp) != NULL && k<shrink_to) {
- k++;
- flp = &fle->next;
- }
- while ((fle=*flp) != NULL) {
- *flp = fle->next;
- if (fle->pol)
- xfrm_pol_put(fle->pol);
- kmem_cache_free(flow_cachep, fle);
- }
- }
-}
-
-struct xfrm_policy *flow_lookup(int dir, struct flowi *fl,
- unsigned short family)
-{
- struct xfrm_policy *pol = NULL;
- struct flow_entry *fle;
- u32 hash;
- int cpu;
-
- hash = flow_hash(fl, family);
-
- local_bh_disable();
- cpu = smp_processor_id();
-
- for (fle = flow_table[cpu*XFRM_FLOWCACHE_HASH_SIZE+hash];
- fle; fle = fle->next) {
- if (memcmp(fl, &fle->fl, sizeof(fle->fl)) == 0 &&
- fle->dir == dir) {
- if (fle->genid == xfrm_policy_genid) {
- if ((pol = fle->pol) != NULL)
- atomic_inc(&pol->refcnt);
- local_bh_enable();
- return pol;
- }
- break;
- }
- }
-
- pol = xfrm_policy_lookup(dir, fl, family);
-
- if (fle) {
- /* Stale flow entry found. Update it. */
- fle->genid = xfrm_policy_genid;
-
- if (fle->pol)
- xfrm_pol_put(fle->pol);
- fle->pol = pol;
- if (pol)
- atomic_inc(&pol->refcnt);
- } else {
- if (flow_count(cpu) > flow_hwm)
- flow_cache_shrink(cpu);
-
- fle = kmem_cache_alloc(flow_cachep, SLAB_ATOMIC);
- if (fle) {
- flow_count(cpu)++;
- fle->fl = *fl;
- fle->genid = xfrm_policy_genid;
- fle->dir = dir;
- fle->pol = pol;
- if (pol)
- atomic_inc(&pol->refcnt);
- fle->next = flow_table[cpu*XFRM_FLOWCACHE_HASH_SIZE+hash];
- flow_table[cpu*XFRM_FLOWCACHE_HASH_SIZE+hash] = fle;
- }
- }
- local_bh_enable();
- return pol;
-}
-
-void __init flow_cache_init(void)
-{
- int order;
-
- flow_cachep = kmem_cache_create("flow_cache",
- sizeof(struct flow_entry),
- 0, SLAB_HWCACHE_ALIGN,
- NULL, NULL);
-
- if (!flow_cachep)
- panic("NET: failed to allocate flow cache slab\n");
-
- for (order = 0;
- (PAGE_SIZE<<order) < (NR_CPUS*sizeof(struct flow_entry *)*XFRM_FLOWCACHE_HASH_SIZE);
- order++)
- /* NOTHING */;
-
- flow_table = (struct flow_entry **)__get_free_pages(GFP_ATOMIC, order);
-
- if (!flow_table)
- panic("Failed to allocate flow cache hash table\n");
-
- memset(flow_table, 0, PAGE_SIZE<<order);
-}
-
-int xfrm_register_type(struct xfrm_type *type, unsigned short family)
-{
- struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
- struct xfrm_type_map *typemap;
- int err = 0;
-
- if (unlikely(afinfo == NULL))
- return -EAFNOSUPPORT;
- typemap = afinfo->type_map;
-
- write_lock(&typemap->lock);
- if (likely(typemap->map[type->proto] == NULL))
- typemap->map[type->proto] = type;
- else
- err = -EEXIST;
- write_unlock(&typemap->lock);
- xfrm_policy_put_afinfo(afinfo);
- return err;
-}
-
-int xfrm_unregister_type(struct xfrm_type *type, unsigned short family)
-{
- struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
- struct xfrm_type_map *typemap;
- int err = 0;
-
- if (unlikely(afinfo == NULL))
- return -EAFNOSUPPORT;
- typemap = afinfo->type_map;
-
- write_lock(&typemap->lock);
- if (unlikely(typemap->map[type->proto] != type))
- err = -ENOENT;
- else
- typemap->map[type->proto] = NULL;
- write_unlock(&typemap->lock);
- xfrm_policy_put_afinfo(afinfo);
- return err;
-}
-
-struct xfrm_type *xfrm_get_type(u8 proto, unsigned short family)
-{
- struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
- struct xfrm_type_map *typemap;
- struct xfrm_type *type;
-
- if (unlikely(afinfo == NULL))
- return NULL;
- typemap = afinfo->type_map;
-
- read_lock(&typemap->lock);
- type = typemap->map[proto];
- if (unlikely(type && !try_module_get(type->owner)))
- type = NULL;
- read_unlock(&typemap->lock);
- xfrm_policy_put_afinfo(afinfo);
- return type;
-}
-
-int xfrm_dst_lookup(struct xfrm_dst **dst, struct flowi *fl,
- unsigned short family)
-{
- struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
- int err = 0;
-
- if (unlikely(afinfo == NULL))
- return -EAFNOSUPPORT;
-
- if (likely(afinfo->dst_lookup != NULL))
- err = afinfo->dst_lookup(dst, fl);
- else
- err = -EINVAL;
- xfrm_policy_put_afinfo(afinfo);
- return err;
-}
-
-void xfrm_put_type(struct xfrm_type *type)
-{
- module_put(type->owner);
-}
-
-static inline unsigned long make_jiffies(long secs)
-{
- if (secs >= (MAX_SCHEDULE_TIMEOUT-1)/HZ)
- return MAX_SCHEDULE_TIMEOUT-1;
- else
- return secs*HZ;
-}
-
-static void xfrm_policy_timer(unsigned long data)
-{
- struct xfrm_policy *xp = (struct xfrm_policy*)data;
- unsigned long now = (unsigned long)xtime.tv_sec;
- long next = LONG_MAX;
- u32 index;
-
- if (xp->dead)
- goto out;
-
- if (xp->lft.hard_add_expires_seconds) {
- long tmo = xp->lft.hard_add_expires_seconds +
- xp->curlft.add_time - now;
- if (tmo <= 0)
- goto expired;
- if (tmo < next)
- next = tmo;
- }
- if (next != LONG_MAX &&
- !mod_timer(&xp->timer, jiffies + make_jiffies(next)))
- atomic_inc(&xp->refcnt);
-
-out:
- xfrm_pol_put(xp);
- return;
-
-expired:
- index = xp->index;
- xfrm_pol_put(xp);
-
- /* Not 100% correct. id can be recycled in theory */
- xp = xfrm_policy_byid(0, index, 1);
- if (xp) {
- xfrm_policy_kill(xp);
- xfrm_pol_put(xp);
- }
-}
-
-
-/* Allocate xfrm_policy. Not used here, it is supposed to be used by pfkeyv2
- * SPD calls.
- */
-
-struct xfrm_policy *xfrm_policy_alloc(int gfp)
-{
- struct xfrm_policy *policy;
-
- policy = kmalloc(sizeof(struct xfrm_policy), gfp);
-
- if (policy) {
- memset(policy, 0, sizeof(struct xfrm_policy));
- atomic_set(&policy->refcnt, 1);
- policy->lock = RW_LOCK_UNLOCKED;
- init_timer(&policy->timer);
- policy->timer.data = (unsigned long)policy;
- policy->timer.function = xfrm_policy_timer;
- }
- return policy;
-}
-
-/* Destroy xfrm_policy: descendant resources must be released to this moment. */
-
-void __xfrm_policy_destroy(struct xfrm_policy *policy)
-{
- if (!policy->dead)
- BUG();
-
- if (policy->bundles)
- BUG();
-
- if (del_timer(&policy->timer))
- BUG();
-
- kfree(policy);
-}
-
-/* Rule must be locked. Release descentant resources, announce
- * entry dead. The rule must be unlinked from lists to the moment.
- */
-
-void xfrm_policy_kill(struct xfrm_policy *policy)
-{
- struct dst_entry *dst;
-
- write_lock_bh(&policy->lock);
- if (policy->dead)
- goto out;
-
- policy->dead = 1;
-
- while ((dst = policy->bundles) != NULL) {
- policy->bundles = dst->next;
- dst_free(dst);
- }
-
- if (del_timer(&policy->timer))
- atomic_dec(&policy->refcnt);
-
-out:
- write_unlock_bh(&policy->lock);
-}
-
-/* Generate new index... KAME seems to generate them ordered by cost
- * of an absolute inpredictability of ordering of rules. This will not pass. */
-static u32 xfrm_gen_index(int dir)
-{
- u32 idx;
- struct xfrm_policy *p;
- static u32 idx_generator;
-
- for (;;) {
- idx = (idx_generator | dir);
- idx_generator += 8;
- if (idx == 0)
- idx = 8;
- for (p = xfrm_policy_list[dir]; p; p = p->next) {
- if (p->index == idx)
- break;
- }
- if (!p)
- return idx;
- }
-}
-
-int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl)
-{
- struct xfrm_policy *pol, **p;
-
- write_lock_bh(&xfrm_policy_lock);
- for (p = &xfrm_policy_list[dir]; (pol=*p)!=NULL; p = &pol->next) {
- if (memcmp(&policy->selector, &pol->selector, sizeof(pol->selector)) == 0) {
- if (excl) {
- write_unlock_bh(&xfrm_policy_lock);
- return -EEXIST;
- }
- break;
- }
- }
- atomic_inc(&policy->refcnt);
- policy->next = pol ? pol->next : NULL;
- *p = policy;
- xfrm_policy_genid++;
- policy->index = pol ? pol->index : xfrm_gen_index(dir);
- policy->curlft.add_time = (unsigned long)xtime.tv_sec;
- policy->curlft.use_time = 0;
- if (policy->lft.hard_add_expires_seconds &&
- !mod_timer(&policy->timer, jiffies + HZ))
- atomic_inc(&policy->refcnt);
- write_unlock_bh(&xfrm_policy_lock);
-
- if (pol) {
- atomic_dec(&pol->refcnt);
- xfrm_policy_kill(pol);
- xfrm_pol_put(pol);
- }
- return 0;
-}
-
-struct xfrm_policy *xfrm_policy_delete(int dir, struct xfrm_selector *sel)
-{
- struct xfrm_policy *pol, **p;
-
- write_lock_bh(&xfrm_policy_lock);
- for (p = &xfrm_policy_list[dir]; (pol=*p)!=NULL; p = &pol->next) {
- if (memcmp(sel, &pol->selector, sizeof(*sel)) == 0) {
- *p = pol->next;
- break;
- }
- }
- if (pol)
- xfrm_policy_genid++;
- write_unlock_bh(&xfrm_policy_lock);
- return pol;
-}
-
-struct xfrm_policy *xfrm_policy_byid(int dir, u32 id, int delete)
-{
- struct xfrm_policy *pol, **p;
-
- write_lock_bh(&xfrm_policy_lock);
- for (p = &xfrm_policy_list[id & 7]; (pol=*p)!=NULL; p = &pol->next) {
- if (pol->index == id) {
- if (delete)
- *p = pol->next;
- break;
- }
- }
- if (pol) {
- if (delete)
- xfrm_policy_genid++;
- else
- atomic_inc(&pol->refcnt);
- }
- write_unlock_bh(&xfrm_policy_lock);
- return pol;
-}
-
-void xfrm_policy_flush()
-{
- struct xfrm_policy *xp;
- int dir;
-
- write_lock_bh(&xfrm_policy_lock);
- for (dir = 0; dir < XFRM_POLICY_MAX; dir++) {
- while ((xp = xfrm_policy_list[dir]) != NULL) {
- xfrm_policy_list[dir] = xp->next;
- write_unlock_bh(&xfrm_policy_lock);
-
- xfrm_policy_kill(xp);
- xfrm_pol_put(xp);
-
- write_lock_bh(&xfrm_policy_lock);
- }
- }
- xfrm_policy_genid++;
- write_unlock_bh(&xfrm_policy_lock);
-}
-
-int xfrm_policy_walk(int (*func)(struct xfrm_policy *, int, int, void*),
- void *data)
-{
- struct xfrm_policy *xp;
- int dir;
- int count = 0;
- int error = 0;
-
- read_lock_bh(&xfrm_policy_lock);
- for (dir = 0; dir < 2*XFRM_POLICY_MAX; dir++) {
- for (xp = xfrm_policy_list[dir]; xp; xp = xp->next)
- count++;
- }
-
- if (count == 0) {
- error = -ENOENT;
- goto out;
- }
-
- for (dir = 0; dir < 2*XFRM_POLICY_MAX; dir++) {
- for (xp = xfrm_policy_list[dir]; xp; xp = xp->next) {
- error = func(xp, dir%XFRM_POLICY_MAX, --count, data);
- if (error)
- goto out;
- }
- }
-
-out:
- read_unlock_bh(&xfrm_policy_lock);
- return error;
-}
-
-
-/* Find policy to apply to this flow. */
-
-struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl,
- unsigned short family)
-{
- struct xfrm_policy *pol;
-
- read_lock_bh(&xfrm_policy_lock);
- for (pol = xfrm_policy_list[dir]; pol; pol = pol->next) {
- struct xfrm_selector *sel = &pol->selector;
- int match;
-
- if (pol->family != family)
- continue;
-
- match = xfrm_selector_match(sel, fl, family);
- if (match) {
- atomic_inc(&pol->refcnt);
- break;
- }
- }
- read_unlock_bh(&xfrm_policy_lock);
- return pol;
-}
-
-struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struct flowi *fl)
-{
- struct xfrm_policy *pol;
-
- read_lock_bh(&xfrm_policy_lock);
- if ((pol = sk->policy[dir]) != NULL) {
- int match;
-
- match = xfrm_selector_match(&pol->selector, fl, sk->family);
- if (match)
- atomic_inc(&pol->refcnt);
- else
- pol = NULL;
- }
- read_unlock_bh(&xfrm_policy_lock);
- return pol;
-}
-
-void xfrm_sk_policy_link(struct xfrm_policy *pol, int dir)
-{
- pol->next = xfrm_policy_list[XFRM_POLICY_MAX+dir];
- xfrm_policy_list[XFRM_POLICY_MAX+dir] = pol;
- atomic_inc(&pol->refcnt);
-}
-
-void xfrm_sk_policy_unlink(struct xfrm_policy *pol, int dir)
-{
- struct xfrm_policy **polp;
-
- for (polp = &xfrm_policy_list[XFRM_POLICY_MAX+dir];
- *polp != NULL; polp = &(*polp)->next) {
- if (*polp == pol) {
- *polp = pol->next;
- atomic_dec(&pol->refcnt);
- return;
- }
- }
-}
-
-int xfrm_sk_policy_insert(struct sock *sk, int dir, struct xfrm_policy *pol)
-{
- struct xfrm_policy *old_pol;
-
- write_lock_bh(&xfrm_policy_lock);
- old_pol = sk->policy[dir];
- sk->policy[dir] = pol;
- if (pol) {
- pol->curlft.add_time = (unsigned long)xtime.tv_sec;
- pol->index = xfrm_gen_index(XFRM_POLICY_MAX+dir);
- xfrm_sk_policy_link(pol, dir);
- }
- if (old_pol)
- xfrm_sk_policy_unlink(old_pol, dir);
- write_unlock_bh(&xfrm_policy_lock);
-
- if (old_pol) {
- xfrm_policy_kill(old_pol);
- xfrm_pol_put(old_pol);
- }
- return 0;
-}
-
-static struct xfrm_policy *clone_policy(struct xfrm_policy *old, int dir)
-{
- struct xfrm_policy *newp = xfrm_policy_alloc(GFP_ATOMIC);
-
- if (newp) {
- newp->selector = old->selector;
- newp->lft = old->lft;
- newp->curlft = old->curlft;
- newp->action = old->action;
- newp->flags = old->flags;
- newp->xfrm_nr = old->xfrm_nr;
- newp->index = old->index;
- memcpy(newp->xfrm_vec, old->xfrm_vec,
- newp->xfrm_nr*sizeof(struct xfrm_tmpl));
- write_lock_bh(&xfrm_policy_lock);
- xfrm_sk_policy_link(newp, dir);
- write_unlock_bh(&xfrm_policy_lock);
- }
- return newp;
-}
-
-int __xfrm_sk_clone_policy(struct sock *sk)
-{
- struct xfrm_policy *p0, *p1;
- p0 = sk->policy[0];
- p1 = sk->policy[1];
- sk->policy[0] = NULL;
- sk->policy[1] = NULL;
- if (p0 && (sk->policy[0] = clone_policy(p0, 0)) == NULL)
- return -ENOMEM;
- if (p1 && (sk->policy[1] = clone_policy(p1, 1)) == NULL)
- return -ENOMEM;
- return 0;
-}
-
-void __xfrm_sk_free_policy(struct xfrm_policy *pol, int dir)
-{
- write_lock_bh(&xfrm_policy_lock);
- xfrm_sk_policy_unlink(pol, dir);
- write_unlock_bh(&xfrm_policy_lock);
-
- xfrm_policy_kill(pol);
- xfrm_pol_put(pol);
-}
-
-/* Resolve list of templates for the flow, given policy. */
-
-static int
-xfrm_tmpl_resolve(struct xfrm_policy *policy, struct flowi *fl,
- struct xfrm_state **xfrm,
- unsigned short family)
-{
- int nx;
- int i, error;
- xfrm_address_t *daddr = xfrm_flowi_daddr(fl, family);
- xfrm_address_t *saddr = xfrm_flowi_saddr(fl, family);
-
- for (nx=0, i = 0; i < policy->xfrm_nr; i++) {
- struct xfrm_state *x;
- xfrm_address_t *remote = daddr;
- xfrm_address_t *local = saddr;
- struct xfrm_tmpl *tmpl = &policy->xfrm_vec[i];
-
- if (tmpl->mode) {
- remote = &tmpl->id.daddr;
- local = &tmpl->saddr;
- }
-
- x = xfrm_state_find(remote, local, fl, tmpl, policy, &error, family);
-
- if (x && x->km.state == XFRM_STATE_VALID) {
- xfrm[nx++] = x;
- daddr = remote;
- saddr = local;
- continue;
- }
- if (x) {
- error = (x->km.state == XFRM_STATE_ERROR ?
- -EINVAL : -EAGAIN);
- xfrm_state_put(x);
- }
-
- if (!tmpl->optional)
- goto fail;
- }
- return nx;
-
-fail:
- for (nx--; nx>=0; nx--)
- xfrm_state_put(xfrm[nx]);
- return error;
-}
-
-/* Check that the bundle accepts the flow and its components are
- * still valid.
- */
-
-static struct dst_entry *
-xfrm_find_bundle(struct flowi *fl, struct rtable *rt, struct xfrm_policy *policy, unsigned short family)
-{
- struct dst_entry *x;
- struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
- if (unlikely(afinfo == NULL))
- return ERR_PTR(-EINVAL);
- x = afinfo->find_bundle(fl, rt, policy);
- xfrm_policy_put_afinfo(afinfo);
- return x;
-}
-
-/* Allocate chain of dst_entry's, attach known xfrm's, calculate
- * all the metrics... Shortly, bundle a bundle.
- */
-
-static int
-xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx,
- struct flowi *fl, struct dst_entry **dst_p,
- unsigned short family)
-{
- int err;
- struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
- if (unlikely(afinfo == NULL))
- return -EINVAL;
- err = afinfo->bundle_create(policy, xfrm, nx, fl, dst_p);
- xfrm_policy_put_afinfo(afinfo);
- return err;
-}
-
-/* Main function: finds/creates a bundle for given flow.
- *
- * At the moment we eat a raw IP route. Mostly to speed up lookups
- * on interfaces with disabled IPsec.
- */
-int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
- struct sock *sk, int flags)
-{
- struct xfrm_policy *policy;
- struct xfrm_state *xfrm[XFRM_MAX_DEPTH];
- struct rtable *rt = (struct rtable*)*dst_p;
- struct dst_entry *dst;
- int nx = 0;
- int err;
- u32 genid;
- u16 family = (*dst_p)->ops->family;
-
- switch (family) {
- case AF_INET:
- if (!fl->fl4_src)
- fl->fl4_src = rt->rt_src;
- if (!fl->fl4_dst)
- fl->fl4_dst = rt->rt_dst;
- case AF_INET6:
- /* Still not clear... */
- default:
- /* nothing */;
- }
-
-restart:
- genid = xfrm_policy_genid;
- policy = NULL;
- if (sk && sk->policy[1])
- policy = xfrm_sk_policy_lookup(sk, XFRM_POLICY_OUT, fl);
-
- if (!policy) {
- /* To accelerate a bit... */
- if ((rt->u.dst.flags & DST_NOXFRM) || !xfrm_policy_list[XFRM_POLICY_OUT])
- return 0;
-
- policy = flow_lookup(XFRM_POLICY_OUT, fl, family);
- }
-
- if (!policy)
- return 0;
-
- policy->curlft.use_time = (unsigned long)xtime.tv_sec;
-
- switch (policy->action) {
- case XFRM_POLICY_BLOCK:
- /* Prohibit the flow */
- xfrm_pol_put(policy);
- return -EPERM;
-
- case XFRM_POLICY_ALLOW:
- if (policy->xfrm_nr == 0) {
- /* Flow passes not transformed. */
- xfrm_pol_put(policy);
- return 0;
- }
-
- /* Try to find matching bundle.
- *
- * LATER: help from flow cache. It is optional, this
- * is required only for output policy.
- */
- dst = xfrm_find_bundle(fl, rt, policy, family);
- if (IS_ERR(dst)) {
- xfrm_pol_put(policy);
- return PTR_ERR(dst);
- }
-
- if (dst)
- break;
-
- nx = xfrm_tmpl_resolve(policy, fl, xfrm, family);
-
- if (unlikely(nx<0)) {
- err = nx;
- if (err == -EAGAIN) {
- struct task_struct *tsk = current;
- DECLARE_WAITQUEUE(wait, tsk);
- if (!flags)
- goto error;
-
- __set_task_state(tsk, TASK_INTERRUPTIBLE);
- add_wait_queue(&km_waitq, &wait);
- err = xfrm_tmpl_resolve(policy, fl, xfrm, family);
- if (err == -EAGAIN)
- schedule();
- __set_task_state(tsk, TASK_RUNNING);
- remove_wait_queue(&km_waitq, &wait);
-
- if (err == -EAGAIN && signal_pending(current)) {
- err = -ERESTART;
- goto error;
- }
- if (err == -EAGAIN ||
- genid != xfrm_policy_genid)
- goto restart;
- }
- if (err)
- goto error;
- } else if (nx == 0) {
- /* Flow passes not transformed. */
- xfrm_pol_put(policy);
- return 0;
- }
-
- dst = &rt->u.dst;
- err = xfrm_bundle_create(policy, xfrm, nx, fl, &dst, family);
-
- if (unlikely(err)) {
- int i;
- for (i=0; i<nx; i++)
- xfrm_state_put(xfrm[i]);
- goto error;
- }
-
- write_lock_bh(&policy->lock);
- if (unlikely(policy->dead)) {
- /* Wow! While we worked on resolving, this
- * policy has gone. Retry. It is not paranoia,
- * we just cannot enlist new bundle to dead object.
- */
- write_unlock_bh(&policy->lock);
-
- xfrm_pol_put(policy);
- if (dst)
- dst_free(dst);
- goto restart;
- }
- dst->next = policy->bundles;
- policy->bundles = dst;
- dst_hold(dst);
- write_unlock_bh(&policy->lock);
- }
- *dst_p = dst;
- ip_rt_put(rt);
- xfrm_pol_put(policy);
- return 0;
-
-error:
- ip_rt_put(rt);
- xfrm_pol_put(policy);
- *dst_p = NULL;
- return err;
-}
-
-/* When skb is transformed back to its "native" form, we have to
- * check policy restrictions. At the moment we make this in maximally
- * stupid way. Shame on me. :-) Of course, connected sockets must
- * have policy cached at them.
- */
-
-static inline int
-xfrm_state_ok(struct xfrm_tmpl *tmpl, struct xfrm_state *x,
- unsigned short family)
-{
- return x->id.proto == tmpl->id.proto &&
- (x->id.spi == tmpl->id.spi || !tmpl->id.spi) &&
- x->props.mode == tmpl->mode &&
- (tmpl->aalgos & (1<<x->props.aalgo)) &&
- !(x->props.mode && xfrm_state_addr_cmp(tmpl, x, family));
-}
-
-static inline int
-xfrm_policy_ok(struct xfrm_tmpl *tmpl, struct sec_path *sp, int idx,
- unsigned short family)
-{
- for (; idx < sp->len; idx++) {
- if (xfrm_state_ok(tmpl, sp->xvec[idx], family))
- return ++idx;
- }
- return -1;
-}
-
-static int
-_decode_session(struct sk_buff *skb, struct flowi *fl, unsigned short family)
-{
- struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
-
- if (unlikely(afinfo == NULL))
- return -EAFNOSUPPORT;
-
- afinfo->decode_session(skb, fl);
- xfrm_policy_put_afinfo(afinfo);
- return 0;
-}
-
-int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
- unsigned short family)
-{
- struct xfrm_policy *pol;
- struct flowi fl;
-
- if (_decode_session(skb, &fl, family) < 0)
- return 0;
-
- /* First, check used SA against their selectors. */
- if (skb->sp) {
- int i;
-
- for (i=skb->sp->len-1; i>=0; i--) {
- if (!xfrm_selector_match(&skb->sp->xvec[i]->sel, &fl, family))
- return 0;
- }
- }
-
- pol = NULL;
- if (sk && sk->policy[dir])
- pol = xfrm_sk_policy_lookup(sk, dir, &fl);
-
- if (!pol)
- pol = flow_lookup(dir, &fl, family);
-
- if (!pol)
- return 1;
-
- pol->curlft.use_time = (unsigned long)xtime.tv_sec;
-
- if (pol->action == XFRM_POLICY_ALLOW) {
- if (pol->xfrm_nr != 0) {
- struct sec_path *sp;
- static struct sec_path dummy;
- int i, k;
-
- if ((sp = skb->sp) == NULL)
- sp = &dummy;
-
- /* For each tmpl search corresponding xfrm.
- * Order is _important_. Later we will implement
- * some barriers, but at the moment barriers
- * are implied between each two transformations.
- */
- for (i = pol->xfrm_nr-1, k = 0; i >= 0; i--) {
- k = xfrm_policy_ok(pol->xfrm_vec+i, sp, k, family);
- if (k < 0)
- goto reject;
- }
- }
- xfrm_pol_put(pol);
- return 1;
- }
-
-reject:
- xfrm_pol_put(pol);
- return 0;
-}
-
-int __xfrm_route_forward(struct sk_buff *skb, unsigned short family)
-{
- struct flowi fl;
-
- if (_decode_session(skb, &fl, family) < 0)
- return 0;
-
- return xfrm_lookup(&skb->dst, &fl, NULL, 0) == 0;
-}
-
-/* Optimize later using cookies and generation ids. */
-
-static struct dst_entry *xfrm_dst_check(struct dst_entry *dst, u32 cookie)
-{
- struct dst_entry *child = dst;
-
- while (child) {
- if (child->obsolete > 0 ||
- (child->xfrm && child->xfrm->km.state != XFRM_STATE_VALID)) {
- dst_release(dst);
- return NULL;
- }
- child = child->child;
- }
-
- return dst;
-}
-
-static void xfrm_dst_destroy(struct dst_entry *dst)
-{
- xfrm_state_put(dst->xfrm);
- dst->xfrm = NULL;
-}
-
-static void xfrm_link_failure(struct sk_buff *skb)
-{
- /* Impossible. Such dst must be popped before reaches point of failure. */
- return;
-}
-
-static struct dst_entry *xfrm_negative_advice(struct dst_entry *dst)
-{
- if (dst) {
- if (dst->obsolete) {
- dst_release(dst);
- dst = NULL;
- }
- }
- return dst;
-}
-
-static void __xfrm_garbage_collect(void)
-{
- int i;
- struct xfrm_policy *pol;
- struct dst_entry *dst, **dstp, *gc_list = NULL;
-
- read_lock_bh(&xfrm_policy_lock);
- for (i=0; i<2*XFRM_POLICY_MAX; i++) {
- for (pol = xfrm_policy_list[i]; pol; pol = pol->next) {
- write_lock(&pol->lock);
- dstp = &pol->bundles;
- while ((dst=*dstp) != NULL) {
- if (atomic_read(&dst->__refcnt) == 0) {
- *dstp = dst->next;
- dst->next = gc_list;
- gc_list = dst;
- } else {
- dstp = &dst->next;
- }
- }
- write_unlock(&pol->lock);
- }
- }
- read_unlock_bh(&xfrm_policy_lock);
-
- while (gc_list) {
- dst = gc_list;
- gc_list = dst->next;
- dst_free(dst);
- }
-}
-
-static int bundle_depends_on(struct dst_entry *dst, struct xfrm_state *x)
-{
- do {
- if (dst->xfrm == x)
- return 1;
- } while ((dst = dst->child) != NULL);
- return 0;
-}
-
-int xfrm_flush_bundles(struct xfrm_state *x)
-{
- int i;
- struct xfrm_policy *pol;
- struct dst_entry *dst, **dstp, *gc_list = NULL;
-
- read_lock_bh(&xfrm_policy_lock);
- for (i=0; i<2*XFRM_POLICY_MAX; i++) {
- for (pol = xfrm_policy_list[i]; pol; pol = pol->next) {
- write_lock(&pol->lock);
- dstp = &pol->bundles;
- while ((dst=*dstp) != NULL) {
- if (bundle_depends_on(dst, x)) {
- *dstp = dst->next;
- dst->next = gc_list;
- gc_list = dst;
- } else {
- dstp = &dst->next;
- }
- }
- write_unlock(&pol->lock);
- }
- }
- read_unlock_bh(&xfrm_policy_lock);
-
- while (gc_list) {
- dst = gc_list;
- gc_list = dst->next;
- dst_free(dst);
- }
-
- return 0;
-}
-
-/* Well... that's _TASK_. We need to scan through transformation
- * list and figure out what mss tcp should generate in order to
- * final datagram fit to mtu. Mama mia... :-)
- *
- * Apparently, some easy way exists, but we used to choose the most
- * bizarre ones. :-) So, raising Kalashnikov... tra-ta-ta.
- *
- * Consider this function as something like dark humour. :-)
- */
-static int xfrm_get_mss(struct dst_entry *dst, u32 mtu)
-{
- int res = mtu - dst->header_len;
-
- for (;;) {
- struct dst_entry *d = dst;
- int m = res;
-
- do {
- struct xfrm_state *x = d->xfrm;
- if (x) {
- spin_lock_bh(&x->lock);
- if (x->km.state == XFRM_STATE_VALID &&
- x->type && x->type->get_max_size)
- m = x->type->get_max_size(d->xfrm, m);
- else
- m += x->props.header_len;
- spin_unlock_bh(&x->lock);
- }
- } while ((d = d->child) != NULL);
-
- if (m <= mtu)
- break;
- res -= (m - mtu);
- if (res < 88)
- return mtu;
- }
-
- return res + dst->header_len;
-}
-
-int xfrm_policy_register_afinfo(struct xfrm_policy_afinfo *afinfo)
-{
- int err = 0;
- if (unlikely(afinfo == NULL))
- return -EINVAL;
- if (unlikely(afinfo->family >= NPROTO))
- return -EAFNOSUPPORT;
- write_lock(&xfrm_policy_afinfo_lock);
- if (unlikely(xfrm_policy_afinfo[afinfo->family] != NULL))
- err = -ENOBUFS;
- else {
- struct dst_ops *dst_ops = afinfo->dst_ops;
- if (likely(dst_ops->kmem_cachep == NULL))
- dst_ops->kmem_cachep = xfrm_dst_cache;
- if (likely(dst_ops->check == NULL))
- dst_ops->check = xfrm_dst_check;
- if (likely(dst_ops->destroy == NULL))
- dst_ops->destroy = xfrm_dst_destroy;
- if (likely(dst_ops->negative_advice == NULL))
- dst_ops->negative_advice = xfrm_negative_advice;
- if (likely(dst_ops->link_failure == NULL))
- dst_ops->link_failure = xfrm_link_failure;
- if (likely(dst_ops->get_mss == NULL))
- dst_ops->get_mss = xfrm_get_mss;
- if (likely(afinfo->garbage_collect == NULL))
- afinfo->garbage_collect = __xfrm_garbage_collect;
- xfrm_policy_afinfo[afinfo->family] = afinfo;
- }
- write_unlock(&xfrm_policy_afinfo_lock);
- return err;
-}
-
-int xfrm_policy_unregister_afinfo(struct xfrm_policy_afinfo *afinfo)
-{
- int err = 0;
- if (unlikely(afinfo == NULL))
- return -EINVAL;
- if (unlikely(afinfo->family >= NPROTO))
- return -EAFNOSUPPORT;
- write_lock(&xfrm_policy_afinfo_lock);
- if (likely(xfrm_policy_afinfo[afinfo->family] != NULL)) {
- if (unlikely(xfrm_policy_afinfo[afinfo->family] != afinfo))
- err = -EINVAL;
- else {
- struct dst_ops *dst_ops = afinfo->dst_ops;
- xfrm_policy_afinfo[afinfo->family] = NULL;
- dst_ops->kmem_cachep = NULL;
- dst_ops->check = NULL;
- dst_ops->destroy = NULL;
- dst_ops->negative_advice = NULL;
- dst_ops->link_failure = NULL;
- dst_ops->get_mss = NULL;
- afinfo->garbage_collect = NULL;
- }
- }
- write_unlock(&xfrm_policy_afinfo_lock);
- return err;
-}
-
-struct xfrm_policy_afinfo *xfrm_policy_get_afinfo(unsigned short family)
-{
- struct xfrm_policy_afinfo *afinfo;
- if (unlikely(family >= NPROTO))
- return NULL;
- read_lock(&xfrm_policy_afinfo_lock);
- afinfo = xfrm_policy_afinfo[family];
- if (likely(afinfo != NULL))
- read_lock(&afinfo->lock);
- read_unlock(&xfrm_policy_afinfo_lock);
- return afinfo;
-}
-
-void xfrm_policy_put_afinfo(struct xfrm_policy_afinfo *afinfo)
-{
- if (unlikely(afinfo == NULL))
- return;
- read_unlock(&afinfo->lock);
-}
-
-void __init xfrm_policy_init(void)
-{
- xfrm_dst_cache = kmem_cache_create("xfrm_dst_cache",
- sizeof(struct xfrm_dst),
- 0, SLAB_HWCACHE_ALIGN,
- NULL, NULL);
- if (!xfrm_dst_cache)
- panic("XFRM: failed to allocate xfrm_dst_cache\n");
-}
-
-void __init xfrm_init(void)
-{
- xfrm_state_init();
- flow_cache_init();
- xfrm_policy_init();
-}
-
+++ /dev/null
-/*
- * xfrm_state.c
- *
- * Changes:
- * Mitsuru KANDA @USAGI
- * Kazunori MIYAZAWA @USAGI
- * Kunihiro Ishiguro
- * IPv6 support
- * YOSHIFUJI Hideaki @USAGI
- * Split up af-specific functions
- *
- */
-
-#include <net/xfrm.h>
-#include <linux/pfkeyv2.h>
-#include <linux/ipsec.h>
-
-/* Each xfrm_state may be linked to two tables:
-
- 1. Hash table by (spi,daddr,ah/esp) to find SA by SPI. (input,ctl)
- 2. Hash table by daddr to find what SAs exist for given
- destination/tunnel endpoint. (output)
- */
-
-static spinlock_t xfrm_state_lock = SPIN_LOCK_UNLOCKED;
-
-/* Hash table to find appropriate SA towards given target (endpoint
- * of tunnel or destination of transport mode) allowed by selector.
- *
- * Main use is finding SA after policy selected tunnel or transport mode.
- * Also, it can be used by ah/esp icmp error handler to find offending SA.
- */
-static struct list_head xfrm_state_bydst[XFRM_DST_HSIZE];
-static struct list_head xfrm_state_byspi[XFRM_DST_HSIZE];
-
-DECLARE_WAIT_QUEUE_HEAD(km_waitq);
-
-static rwlock_t xfrm_state_afinfo_lock = RW_LOCK_UNLOCKED;
-static struct xfrm_state_afinfo *xfrm_state_afinfo[NPROTO];
-
-static void __xfrm_state_delete(struct xfrm_state *x);
-
-static inline unsigned long make_jiffies(long secs)
-{
- if (secs >= (MAX_SCHEDULE_TIMEOUT-1)/HZ)
- return MAX_SCHEDULE_TIMEOUT-1;
- else
- return secs*HZ;
-}
-
-static void xfrm_timer_handler(unsigned long data)
-{
- struct xfrm_state *x = (struct xfrm_state*)data;
- unsigned long now = (unsigned long)xtime.tv_sec;
- long next = LONG_MAX;
- int warn = 0;
-
- spin_lock(&x->lock);
- if (x->km.state == XFRM_STATE_DEAD)
- goto out;
- if (x->km.state == XFRM_STATE_EXPIRED)
- goto expired;
- if (x->lft.hard_add_expires_seconds) {
- long tmo = x->lft.hard_add_expires_seconds +
- x->curlft.add_time - now;
- if (tmo <= 0)
- goto expired;
- if (tmo < next)
- next = tmo;
- }
- if (x->lft.hard_use_expires_seconds && x->curlft.use_time) {
- long tmo = x->lft.hard_use_expires_seconds +
- x->curlft.use_time - now;
- if (tmo <= 0)
- goto expired;
- if (tmo < next)
- next = tmo;
- }
- if (x->km.dying)
- goto resched;
- if (x->lft.soft_add_expires_seconds) {
- long tmo = x->lft.soft_add_expires_seconds +
- x->curlft.add_time - now;
- if (tmo <= 0)
- warn = 1;
- else if (tmo < next)
- next = tmo;
- }
- if (x->lft.soft_use_expires_seconds && x->curlft.use_time) {
- long tmo = x->lft.soft_use_expires_seconds +
- x->curlft.use_time - now;
- if (tmo <= 0)
- warn = 1;
- else if (tmo < next)
- next = tmo;
- }
-
- if (warn)
- km_warn_expired(x);
-resched:
- if (next != LONG_MAX &&
- !mod_timer(&x->timer, jiffies + make_jiffies(next)))
- atomic_inc(&x->refcnt);
- goto out;
-
-expired:
- if (x->km.state == XFRM_STATE_ACQ && x->id.spi == 0) {
- x->km.state = XFRM_STATE_EXPIRED;
- wake_up(&km_waitq);
- next = 2;
- goto resched;
- }
- if (x->id.spi != 0)
- km_expired(x);
- __xfrm_state_delete(x);
-
-out:
- spin_unlock(&x->lock);
- xfrm_state_put(x);
-}
-
-struct xfrm_state *xfrm_state_alloc(void)
-{
- struct xfrm_state *x;
-
- x = kmalloc(sizeof(struct xfrm_state), GFP_ATOMIC);
-
- if (x) {
- memset(x, 0, sizeof(struct xfrm_state));
- atomic_set(&x->refcnt, 1);
- INIT_LIST_HEAD(&x->bydst);
- INIT_LIST_HEAD(&x->byspi);
- init_timer(&x->timer);
- x->timer.function = xfrm_timer_handler;
- x->timer.data = (unsigned long)x;
- x->curlft.add_time = (unsigned long)xtime.tv_sec;
- x->lft.soft_byte_limit = XFRM_INF;
- x->lft.soft_packet_limit = XFRM_INF;
- x->lft.hard_byte_limit = XFRM_INF;
- x->lft.hard_packet_limit = XFRM_INF;
- x->lock = SPIN_LOCK_UNLOCKED;
- }
- return x;
-}
-
-void __xfrm_state_destroy(struct xfrm_state *x)
-{
- BUG_TRAP(x->km.state == XFRM_STATE_DEAD);
- if (del_timer(&x->timer))
- BUG();
- if (x->aalg)
- kfree(x->aalg);
- if (x->ealg)
- kfree(x->ealg);
- if (x->calg)
- kfree(x->calg);
- if (x->type)
- xfrm_put_type(x->type);
- kfree(x);
-}
-
-static void __xfrm_state_delete(struct xfrm_state *x)
-{
- int kill = 0;
-
- if (x->km.state != XFRM_STATE_DEAD) {
- x->km.state = XFRM_STATE_DEAD;
- kill = 1;
- spin_lock(&xfrm_state_lock);
- list_del(&x->bydst);
- atomic_dec(&x->refcnt);
- if (x->id.spi) {
- list_del(&x->byspi);
- atomic_dec(&x->refcnt);
- }
- spin_unlock(&xfrm_state_lock);
- if (del_timer(&x->timer))
- atomic_dec(&x->refcnt);
- if (atomic_read(&x->refcnt) != 1)
- xfrm_flush_bundles(x);
- }
-
- if (kill && x->type)
- x->type->destructor(x);
- wake_up(&km_waitq);
-}
-
-void xfrm_state_delete(struct xfrm_state *x)
-{
- spin_lock_bh(&x->lock);
- __xfrm_state_delete(x);
- spin_unlock_bh(&x->lock);
-}
-
-void xfrm_state_flush(u8 proto)
-{
- int i;
- struct xfrm_state *x;
-
- spin_lock_bh(&xfrm_state_lock);
- for (i = 0; i < XFRM_DST_HSIZE; i++) {
-restart:
- list_for_each_entry(x, xfrm_state_bydst+i, bydst) {
- if (proto == IPSEC_PROTO_ANY || x->id.proto == proto) {
- atomic_inc(&x->refcnt);
- spin_unlock_bh(&xfrm_state_lock);
-
- xfrm_state_delete(x);
- xfrm_state_put(x);
-
- spin_lock_bh(&xfrm_state_lock);
- goto restart;
- }
- }
- }
- spin_unlock_bh(&xfrm_state_lock);
- wake_up(&km_waitq);
-}
-
-static int
-xfrm_init_tempsel(struct xfrm_state *x, struct flowi *fl,
- struct xfrm_tmpl *tmpl,
- xfrm_address_t *daddr, xfrm_address_t *saddr,
- unsigned short family)
-{
- struct xfrm_state_afinfo *afinfo = xfrm_state_get_afinfo(family);
- if (!afinfo)
- return -1;
- afinfo->init_tempsel(x, fl, tmpl, daddr, saddr);
- xfrm_state_put_afinfo(afinfo);
- return 0;
-}
-
-struct xfrm_state *
-xfrm_state_find(xfrm_address_t *daddr, xfrm_address_t *saddr,
- struct flowi *fl, struct xfrm_tmpl *tmpl,
- struct xfrm_policy *pol, int *err,
- unsigned short family)
-{
- unsigned h = xfrm_dst_hash(daddr, family);
- struct xfrm_state *x;
- int acquire_in_progress = 0;
- int error = 0;
- struct xfrm_state *best = NULL;
-
- spin_lock_bh(&xfrm_state_lock);
- list_for_each_entry(x, xfrm_state_bydst+h, bydst) {
- if (x->props.family == family &&
- x->props.reqid == tmpl->reqid &&
- xfrm_state_addr_check(x, daddr, saddr, family) &&
- tmpl->mode == x->props.mode &&
- tmpl->id.proto == x->id.proto) {
- /* Resolution logic:
- 1. There is a valid state with matching selector.
- Done.
- 2. Valid state with inappropriate selector. Skip.
-
- Entering area of "sysdeps".
-
- 3. If state is not valid, selector is temporary,
- it selects only session which triggered
- previous resolution. Key manager will do
- something to install a state with proper
- selector.
- */
- if (x->km.state == XFRM_STATE_VALID) {
- if (!xfrm_selector_match(&x->sel, fl, family))
- continue;
- if (!best ||
- best->km.dying > x->km.dying ||
- (best->km.dying == x->km.dying &&
- best->curlft.add_time < x->curlft.add_time))
- best = x;
- } else if (x->km.state == XFRM_STATE_ACQ) {
- acquire_in_progress = 1;
- } else if (x->km.state == XFRM_STATE_ERROR ||
- x->km.state == XFRM_STATE_EXPIRED) {
- if (xfrm_selector_match(&x->sel, fl, family))
- error = 1;
- }
- }
- }
-
- if (best) {
- atomic_inc(&best->refcnt);
- spin_unlock_bh(&xfrm_state_lock);
- return best;
- }
-
- x = NULL;
- if (!error && !acquire_in_progress &&
- ((x = xfrm_state_alloc()) != NULL)) {
- /* Initialize temporary selector matching only
- * to current session. */
- xfrm_init_tempsel(x, fl, tmpl, daddr, saddr, family);
-
- if (km_query(x, tmpl, pol) == 0) {
- x->km.state = XFRM_STATE_ACQ;
- list_add_tail(&x->bydst, xfrm_state_bydst+h);
- atomic_inc(&x->refcnt);
- if (x->id.spi) {
- h = xfrm_spi_hash(&x->id.daddr, x->id.spi, x->id.proto, family);
- list_add(&x->byspi, xfrm_state_byspi+h);
- atomic_inc(&x->refcnt);
- }
- x->lft.hard_add_expires_seconds = XFRM_ACQ_EXPIRES;
- atomic_inc(&x->refcnt);
- mod_timer(&x->timer, XFRM_ACQ_EXPIRES*HZ);
- } else {
- x->km.state = XFRM_STATE_DEAD;
- xfrm_state_put(x);
- x = NULL;
- error = 1;
- }
- }
- spin_unlock_bh(&xfrm_state_lock);
- if (!x)
- *err = acquire_in_progress ? -EAGAIN :
- (error ? -ESRCH : -ENOMEM);
- return x;
-}
-
-void xfrm_state_insert(struct xfrm_state *x)
-{
- unsigned h = xfrm_dst_hash(&x->id.daddr, x->props.family);
-
- spin_lock_bh(&xfrm_state_lock);
- list_add(&x->bydst, xfrm_state_bydst+h);
- atomic_inc(&x->refcnt);
-
- h = xfrm_spi_hash(&x->id.daddr, x->id.spi, x->id.proto, x->props.family);
-
- list_add(&x->byspi, xfrm_state_byspi+h);
- atomic_inc(&x->refcnt);
-
- if (!mod_timer(&x->timer, jiffies + HZ))
- atomic_inc(&x->refcnt);
-
- spin_unlock_bh(&xfrm_state_lock);
- wake_up(&km_waitq);
-}
-
-int xfrm_state_check_expire(struct xfrm_state *x)
-{
- if (!x->curlft.use_time)
- x->curlft.use_time = (unsigned long)xtime.tv_sec;
-
- if (x->km.state != XFRM_STATE_VALID)
- return -EINVAL;
-
- if (x->curlft.bytes >= x->lft.hard_byte_limit ||
- x->curlft.packets >= x->lft.hard_packet_limit) {
- km_expired(x);
- if (!mod_timer(&x->timer, jiffies + XFRM_ACQ_EXPIRES*HZ))
- atomic_inc(&x->refcnt);
- return -EINVAL;
- }
-
- if (!x->km.dying &&
- (x->curlft.bytes >= x->lft.soft_byte_limit ||
- x->curlft.packets >= x->lft.soft_packet_limit))
- km_warn_expired(x);
- return 0;
-}
-
-int xfrm_state_check_space(struct xfrm_state *x, struct sk_buff *skb)
-{
- int nhead = x->props.header_len + LL_RESERVED_SPACE(skb->dst->dev)
- - skb_headroom(skb);
-
- if (nhead > 0)
- return pskb_expand_head(skb, nhead, 0, GFP_ATOMIC);
-
- /* Check tail too... */
- return 0;
-}
-
-struct xfrm_state *
-xfrm_state_lookup(xfrm_address_t *daddr, u32 spi, u8 proto,
- unsigned short family)
-{
- struct xfrm_state *x;
- struct xfrm_state_afinfo *afinfo = xfrm_state_get_afinfo(family);
- if (!afinfo)
- return NULL;
-
- spin_lock_bh(&xfrm_state_lock);
- x = afinfo->state_lookup(daddr, spi, proto);
- spin_unlock_bh(&xfrm_state_lock);
- xfrm_state_put_afinfo(afinfo);
- return x;
-}
-
-struct xfrm_state *
-xfrm_find_acq(u8 mode, u16 reqid, u8 proto,
- xfrm_address_t *daddr, xfrm_address_t *saddr,
- int create, unsigned short family)
-{
- struct xfrm_state *x;
- struct xfrm_state_afinfo *afinfo = xfrm_state_get_afinfo(family);
- if (!afinfo)
- return NULL;
-
- spin_lock_bh(&xfrm_state_lock);
- x = afinfo->find_acq(mode, reqid, proto, daddr, saddr, create);
- spin_unlock_bh(&xfrm_state_lock);
- xfrm_state_put_afinfo(afinfo);
- return x;
-}
-
-/* Silly enough, but I'm lazy to build resolution list */
-
-struct xfrm_state * xfrm_find_acq_byseq(u32 seq)
-{
- int i;
- struct xfrm_state *x;
-
- spin_lock_bh(&xfrm_state_lock);
- for (i = 0; i < XFRM_DST_HSIZE; i++) {
- list_for_each_entry(x, xfrm_state_bydst+i, bydst) {
- if (x->km.seq == seq) {
- atomic_inc(&x->refcnt);
- spin_unlock_bh(&xfrm_state_lock);
- return x;
- }
- }
- }
- spin_unlock_bh(&xfrm_state_lock);
- return NULL;
-}
-
-u32 xfrm_get_acqseq(void)
-{
- u32 res;
- static u32 acqseq;
- static spinlock_t acqseq_lock = SPIN_LOCK_UNLOCKED;
-
- spin_lock_bh(&acqseq_lock);
- res = (++acqseq ? : ++acqseq);
- spin_unlock_bh(&acqseq_lock);
- return res;
-}
-
-void
-xfrm_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi)
-{
- u32 h;
- struct xfrm_state *x0;
-
- if (x->id.spi)
- return;
-
- if (minspi == maxspi) {
- x0 = xfrm_state_lookup(&x->id.daddr, minspi, x->id.proto, x->props.family);
- if (x0) {
- xfrm_state_put(x0);
- return;
- }
- x->id.spi = minspi;
- } else {
- u32 spi = 0;
- minspi = ntohl(minspi);
- maxspi = ntohl(maxspi);
- for (h=0; h<maxspi-minspi+1; h++) {
- spi = minspi + net_random()%(maxspi-minspi+1);
- x0 = xfrm_state_lookup(&x->id.daddr, minspi, x->id.proto, x->props.family);
- if (x0 == NULL)
- break;
- xfrm_state_put(x0);
- }
- x->id.spi = htonl(spi);
- }
- if (x->id.spi) {
- spin_lock_bh(&xfrm_state_lock);
- h = xfrm_spi_hash(&x->id.daddr, x->id.spi, x->id.proto, x->props.family);
- list_add(&x->byspi, xfrm_state_byspi+h);
- atomic_inc(&x->refcnt);
- spin_unlock_bh(&xfrm_state_lock);
- wake_up(&km_waitq);
- }
-}
-
-int xfrm_state_walk(u8 proto, int (*func)(struct xfrm_state *, int, void*),
- void *data)
-{
- int i;
- struct xfrm_state *x;
- int count = 0;
- int err = 0;
-
- spin_lock_bh(&xfrm_state_lock);
- for (i = 0; i < XFRM_DST_HSIZE; i++) {
- list_for_each_entry(x, xfrm_state_bydst+i, bydst) {
- if (proto == IPSEC_PROTO_ANY || x->id.proto == proto)
- count++;
- }
- }
- if (count == 0) {
- err = -ENOENT;
- goto out;
- }
-
- for (i = 0; i < XFRM_DST_HSIZE; i++) {
- list_for_each_entry(x, xfrm_state_bydst+i, bydst) {
- if (proto != IPSEC_PROTO_ANY && x->id.proto != proto)
- continue;
- err = func(x, --count, data);
- if (err)
- goto out;
- }
- }
-out:
- spin_unlock_bh(&xfrm_state_lock);
- return err;
-}
-
-
-int xfrm_replay_check(struct xfrm_state *x, u32 seq)
-{
- u32 diff;
-
- seq = ntohl(seq);
-
- if (unlikely(seq == 0))
- return -EINVAL;
-
- if (likely(seq > x->replay.seq))
- return 0;
-
- diff = x->replay.seq - seq;
- if (diff >= x->props.replay_window) {
- x->stats.replay_window++;
- return -EINVAL;
- }
-
- if (x->replay.bitmap & (1U << diff)) {
- x->stats.replay++;
- return -EINVAL;
- }
- return 0;
-}
-
-void xfrm_replay_advance(struct xfrm_state *x, u32 seq)
-{
- u32 diff;
-
- seq = ntohl(seq);
-
- if (seq > x->replay.seq) {
- diff = seq - x->replay.seq;
- if (diff < x->props.replay_window)
- x->replay.bitmap = ((x->replay.bitmap) << diff) | 1;
- else
- x->replay.bitmap = 1;
- x->replay.seq = seq;
- } else {
- diff = x->replay.seq - seq;
- x->replay.bitmap |= (1U << diff);
- }
-}
-
-int xfrm_check_selectors(struct xfrm_state **x, int n, struct flowi *fl)
-{
- int i;
-
- for (i=0; i<n; i++) {
- int match;
- match = xfrm_selector_match(&x[i]->sel, fl, x[i]->props.family);
- if (!match)
- return -EINVAL;
- }
- return 0;
-}
-
-static struct list_head xfrm_km_list = LIST_HEAD_INIT(xfrm_km_list);
-static rwlock_t xfrm_km_lock = RW_LOCK_UNLOCKED;
-
-void km_warn_expired(struct xfrm_state *x)
-{
- struct xfrm_mgr *km;
-
- x->km.dying = 1;
- read_lock(&xfrm_km_lock);
- list_for_each_entry(km, &xfrm_km_list, list)
- km->notify(x, 0);
- read_unlock(&xfrm_km_lock);
-}
-
-void km_expired(struct xfrm_state *x)
-{
- struct xfrm_mgr *km;
-
- x->km.state = XFRM_STATE_EXPIRED;
-
- read_lock(&xfrm_km_lock);
- list_for_each_entry(km, &xfrm_km_list, list)
- km->notify(x, 1);
- read_unlock(&xfrm_km_lock);
- wake_up(&km_waitq);
-}
-
-int km_query(struct xfrm_state *x, struct xfrm_tmpl *t, struct xfrm_policy *pol)
-{
- int err = -EINVAL;
- struct xfrm_mgr *km;
-
- read_lock(&xfrm_km_lock);
- list_for_each_entry(km, &xfrm_km_list, list) {
- err = km->acquire(x, t, pol, XFRM_POLICY_OUT);
- if (!err)
- break;
- }
- read_unlock(&xfrm_km_lock);
- return err;
-}
-
-int xfrm_user_policy(struct sock *sk, int optname, u8 *optval, int optlen)
-{
- int err;
- u8 *data;
- struct xfrm_mgr *km;
- struct xfrm_policy *pol = NULL;
-
- if (optlen <= 0 || optlen > PAGE_SIZE)
- return -EMSGSIZE;
-
- data = kmalloc(optlen, GFP_KERNEL);
- if (!data)
- return -ENOMEM;
-
- err = -EFAULT;
- if (copy_from_user(data, optval, optlen))
- goto out;
-
- err = -EINVAL;
- read_lock(&xfrm_km_lock);
- list_for_each_entry(km, &xfrm_km_list, list) {
- pol = km->compile_policy(sk->family, optname, data, optlen, &err);
- if (err >= 0)
- break;
- }
- read_unlock(&xfrm_km_lock);
-
- if (err >= 0) {
- xfrm_sk_policy_insert(sk, err, pol);
- err = 0;
- }
-
-out:
- kfree(data);
- return err;
-}
-
-int xfrm_register_km(struct xfrm_mgr *km)
-{
- write_lock_bh(&xfrm_km_lock);
- list_add_tail(&km->list, &xfrm_km_list);
- write_unlock_bh(&xfrm_km_lock);
- return 0;
-}
-
-int xfrm_unregister_km(struct xfrm_mgr *km)
-{
- write_lock_bh(&xfrm_km_lock);
- list_del(&km->list);
- write_unlock_bh(&xfrm_km_lock);
- return 0;
-}
-
-int xfrm_state_register_afinfo(struct xfrm_state_afinfo *afinfo)
-{
- int err = 0;
- if (unlikely(afinfo == NULL))
- return -EINVAL;
- if (unlikely(afinfo->family >= NPROTO))
- return -EAFNOSUPPORT;
- write_lock(&xfrm_state_afinfo_lock);
- if (unlikely(xfrm_state_afinfo[afinfo->family] != NULL))
- err = -ENOBUFS;
- else {
- afinfo->state_bydst = xfrm_state_bydst;
- afinfo->state_byspi = xfrm_state_byspi;
- xfrm_state_afinfo[afinfo->family] = afinfo;
- }
- write_unlock(&xfrm_state_afinfo_lock);
- return err;
-}
-
-int xfrm_state_unregister_afinfo(struct xfrm_state_afinfo *afinfo)
-{
- int err = 0;
- if (unlikely(afinfo == NULL))
- return -EINVAL;
- if (unlikely(afinfo->family >= NPROTO))
- return -EAFNOSUPPORT;
- write_lock(&xfrm_state_afinfo_lock);
- if (likely(xfrm_state_afinfo[afinfo->family] != NULL)) {
- if (unlikely(xfrm_state_afinfo[afinfo->family] != afinfo))
- err = -EINVAL;
- else {
- xfrm_state_afinfo[afinfo->family] = NULL;
- afinfo->state_byspi = NULL;
- afinfo->state_bydst = NULL;
- }
- }
- write_unlock(&xfrm_state_afinfo_lock);
- return err;
-}
-
-struct xfrm_state_afinfo *xfrm_state_get_afinfo(unsigned short family)
-{
- struct xfrm_state_afinfo *afinfo;
- if (unlikely(family >= NPROTO))
- return NULL;
- read_lock(&xfrm_state_afinfo_lock);
- afinfo = xfrm_state_afinfo[family];
- if (likely(afinfo != NULL))
- read_lock(&afinfo->lock);
- read_unlock(&xfrm_state_afinfo_lock);
- return afinfo;
-}
-
-void xfrm_state_put_afinfo(struct xfrm_state_afinfo *afinfo)
-{
- if (unlikely(afinfo == NULL))
- return;
- read_unlock(&afinfo->lock);
-}
-
-void __init xfrm_state_init(void)
-{
- int i;
-
- for (i=0; i<XFRM_DST_HSIZE; i++) {
- INIT_LIST_HEAD(&xfrm_state_bydst[i]);
- INIT_LIST_HEAD(&xfrm_state_byspi[i]);
- }
-}
-
+++ /dev/null
-/* xfrm_user.c: User interface to configure xfrm engine.
- *
- * Copyright (C) 2002 David S. Miller (davem@redhat.com)
- *
- * Changes:
- * Mitsuru KANDA @USAGI
- * Kazunori MIYAZAWA @USAGI
- * Kunihiro Ishiguro
- * IPv6 support
- *
- */
-
-#include <linux/module.h>
-#include <linux/kernel.h>
-#include <linux/types.h>
-#include <linux/slab.h>
-#include <linux/socket.h>
-#include <linux/string.h>
-#include <linux/net.h>
-#include <linux/skbuff.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <linux/pfkeyv2.h>
-#include <linux/ipsec.h>
-#include <linux/init.h>
-#include <linux/security.h>
-#include <net/sock.h>
-#include <net/xfrm.h>
-
-static struct sock *xfrm_nl;
-
-static int verify_one_alg(struct rtattr **xfrma, enum xfrm_attr_type_t type)
-{
- struct rtattr *rt = xfrma[type - 1];
- struct xfrm_algo *algp;
-
- if (!rt)
- return 0;
-
- if ((rt->rta_len - sizeof(*rt)) < sizeof(*algp))
- return -EINVAL;
-
- algp = RTA_DATA(rt);
- switch (type) {
- case XFRMA_ALG_AUTH:
- if (!algp->alg_key_len &&
- strcmp(algp->alg_name, "digest_null") != 0)
- return -EINVAL;
- break;
-
- case XFRMA_ALG_CRYPT:
- if (!algp->alg_key_len &&
- strcmp(algp->alg_name, "cipher_null") != 0)
- return -EINVAL;
- break;
-
- case XFRMA_ALG_COMP:
- /* Zero length keys are legal. */
- break;
-
- default:
- return -EINVAL;
- };
-
- algp->alg_name[CRYPTO_MAX_ALG_NAME - 1] = '\0';
- return 0;
-}
-
-static int verify_newsa_info(struct xfrm_usersa_info *p,
- struct rtattr **xfrma)
-{
- int err;
-
- err = -EINVAL;
- switch (p->family) {
- case AF_INET:
- break;
-
- case AF_INET6:
-#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
- break;
-#else
- err = -EAFNOSUPPORT;
- goto out;
-#endif
-
- default:
- goto out;
- };
-
- err = -EINVAL;
- switch (p->id.proto) {
- case IPPROTO_AH:
- if (!xfrma[XFRMA_ALG_AUTH-1] ||
- xfrma[XFRMA_ALG_CRYPT-1] ||
- xfrma[XFRMA_ALG_COMP-1])
- goto out;
- break;
-
- case IPPROTO_ESP:
- if ((!xfrma[XFRMA_ALG_AUTH-1] &&
- !xfrma[XFRMA_ALG_CRYPT-1]) ||
- xfrma[XFRMA_ALG_COMP-1])
- goto out;
- break;
-
- case IPPROTO_COMP:
- if (!xfrma[XFRMA_ALG_COMP-1] ||
- xfrma[XFRMA_ALG_AUTH-1] ||
- xfrma[XFRMA_ALG_CRYPT-1])
- goto out;
- break;
-
- default:
- goto out;
- };
-
- if ((err = verify_one_alg(xfrma, XFRMA_ALG_AUTH)))
- goto out;
- if ((err = verify_one_alg(xfrma, XFRMA_ALG_CRYPT)))
- goto out;
- if ((err = verify_one_alg(xfrma, XFRMA_ALG_COMP)))
- goto out;
-
- err = -EINVAL;
- switch (p->mode) {
- case 0:
- case 1:
- break;
-
- default:
- goto out;
- };
-
- err = 0;
-
-out:
- return err;
-}
-
-static int attach_one_algo(struct xfrm_algo **algpp, struct rtattr *u_arg)
-{
- struct rtattr *rta = u_arg;
- struct xfrm_algo *p, *ualg;
-
- if (!rta)
- return 0;
-
- ualg = RTA_DATA(rta);
- p = kmalloc(sizeof(*ualg) + ualg->alg_key_len, GFP_KERNEL);
- if (!p)
- return -ENOMEM;
-
- memcpy(p, ualg, sizeof(*ualg) + ualg->alg_key_len);
- *algpp = p;
- return 0;
-}
-
-static void copy_from_user_state(struct xfrm_state *x, struct xfrm_usersa_info *p)
-{
- memcpy(&x->id, &p->id, sizeof(x->id));
- memcpy(&x->sel, &p->sel, sizeof(x->sel));
- memcpy(&x->lft, &p->lft, sizeof(x->lft));
- x->props.mode = p->mode;
- x->props.replay_window = p->replay_window;
- x->props.reqid = p->reqid;
- x->props.family = p->family;
- x->props.saddr = x->sel.saddr;
-}
-
-static struct xfrm_state *xfrm_state_construct(struct xfrm_usersa_info *p,
- struct rtattr **xfrma,
- int *errp)
-{
- struct xfrm_state *x = xfrm_state_alloc();
- int err = -ENOMEM;
-
- if (!x)
- goto error_no_put;
-
- copy_from_user_state(x, p);
-
- if ((err = attach_one_algo(&x->aalg, xfrma[XFRMA_ALG_AUTH-1])))
- goto error;
- if ((err = attach_one_algo(&x->ealg, xfrma[XFRMA_ALG_CRYPT-1])))
- goto error;
- if ((err = attach_one_algo(&x->calg, xfrma[XFRMA_ALG_COMP-1])))
- goto error;
-
- err = -ENOENT;
- x->type = xfrm_get_type(x->id.proto, x->props.family);
- if (x->type == NULL)
- goto error;
-
- err = x->type->init_state(x, NULL);
- if (err)
- goto error;
-
- x->curlft.add_time = (unsigned long) xtime.tv_sec;
- x->km.state = XFRM_STATE_VALID;
- x->km.seq = p->seq;
-
- return x;
-
-error:
- xfrm_state_put(x);
-error_no_put:
- *errp = err;
- return NULL;
-}
-
-static int xfrm_add_sa(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
-{
- struct xfrm_usersa_info *p = NLMSG_DATA(nlh);
- struct xfrm_state *x, *x1;
- int err;
-
- err = verify_newsa_info(p, (struct rtattr **) xfrma);
- if (err)
- return err;
-
- x = xfrm_state_construct(p, (struct rtattr **) xfrma, &err);
- if (!x)
- return err;
-
- x1 = xfrm_state_lookup(&x->props.saddr, x->id.spi, x->id.proto, x->props.family);
- if (x1) {
- xfrm_state_put(x);
- xfrm_state_put(x1);
- return -EEXIST;
- }
-
- xfrm_state_insert(x);
-
- return 0;
-}
-
-static int xfrm_del_sa(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
-{
- struct xfrm_state *x;
- struct xfrm_usersa_id *p = NLMSG_DATA(nlh);
-
- x = xfrm_state_lookup(&p->saddr, p->spi, p->proto, p->family);
- if (x == NULL)
- return -ESRCH;
-
- xfrm_state_delete(x);
- xfrm_state_put(x);
-
- return 0;
-}
-
-static void copy_to_user_state(struct xfrm_state *x, struct xfrm_usersa_info *p)
-{
- memcpy(&p->id, &x->id, sizeof(p->id));
- memcpy(&p->sel, &x->sel, sizeof(p->sel));
- memcpy(&p->lft, &x->lft, sizeof(p->lft));
- memcpy(&p->curlft, &x->curlft, sizeof(p->curlft));
- memcpy(&p->stats, &x->stats, sizeof(p->stats));
- p->mode = x->props.mode;
- p->replay_window = x->props.replay_window;
- p->reqid = x->props.reqid;
- p->family = x->props.family;
- p->seq = x->km.seq;
-}
-
-struct xfrm_dump_info {
- struct sk_buff *in_skb;
- struct sk_buff *out_skb;
- u32 nlmsg_seq;
- int start_idx;
- int this_idx;
-};
-
-static int dump_one_state(struct xfrm_state *x, int count, void *ptr)
-{
- struct xfrm_dump_info *sp = ptr;
- struct sk_buff *in_skb = sp->in_skb;
- struct sk_buff *skb = sp->out_skb;
- struct xfrm_usersa_info *p;
- struct nlmsghdr *nlh;
- unsigned char *b = skb->tail;
-
- if (sp->this_idx < sp->start_idx)
- goto out;
-
- nlh = NLMSG_PUT(skb, NETLINK_CB(in_skb).pid,
- sp->nlmsg_seq,
- XFRM_MSG_NEWSA, sizeof(*p));
- nlh->nlmsg_flags = 0;
-
- p = NLMSG_DATA(nlh);
- copy_to_user_state(x, p);
-
- if (x->aalg)
- RTA_PUT(skb, XFRMA_ALG_AUTH,
- sizeof(*(x->aalg))+(x->aalg->alg_key_len+7)/8, x->aalg);
- if (x->ealg)
- RTA_PUT(skb, XFRMA_ALG_CRYPT,
- sizeof(*(x->ealg))+(x->ealg->alg_key_len+7)/8, x->ealg);
- if (x->calg)
- RTA_PUT(skb, XFRMA_ALG_COMP, sizeof(*(x->calg)), x->calg);
-
- nlh->nlmsg_len = skb->tail - b;
-out:
- sp->this_idx++;
- return 0;
-
-nlmsg_failure:
-rtattr_failure:
- skb_trim(skb, b - skb->data);
- return -1;
-}
-
-static int xfrm_dump_sa(struct sk_buff *skb, struct netlink_callback *cb)
-{
- struct xfrm_dump_info info;
-
- info.in_skb = cb->skb;
- info.out_skb = skb;
- info.nlmsg_seq = cb->nlh->nlmsg_seq;
- info.this_idx = 0;
- info.start_idx = cb->args[0];
- (void) xfrm_state_walk(IPSEC_PROTO_ANY, dump_one_state, &info);
- cb->args[0] = info.this_idx;
-
- return skb->len;
-}
-
-static struct sk_buff *xfrm_state_netlink(struct sk_buff *in_skb,
- struct xfrm_state *x, u32 seq)
-{
- struct xfrm_dump_info info;
- struct sk_buff *skb;
-
- skb = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC);
- if (!skb)
- return ERR_PTR(-ENOMEM);
-
- NETLINK_CB(skb).dst_pid = NETLINK_CB(in_skb).pid;
- info.in_skb = in_skb;
- info.out_skb = skb;
- info.nlmsg_seq = seq;
- info.this_idx = info.start_idx = 0;
-
- if (dump_one_state(x, 0, &info)) {
- kfree_skb(skb);
- return NULL;
- }
-
- return skb;
-}
-
-static int xfrm_get_sa(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
-{
- struct xfrm_usersa_id *p = NLMSG_DATA(nlh);
- struct xfrm_state *x;
- struct sk_buff *resp_skb;
- int err;
-
- x = xfrm_state_lookup(&p->saddr, p->spi, p->proto, p->family);
- err = -ESRCH;
- if (x == NULL)
- goto out_noput;
-
- resp_skb = xfrm_state_netlink(skb, x, nlh->nlmsg_seq);
- if (IS_ERR(resp_skb)) {
- err = PTR_ERR(resp_skb);
- } else {
- err = netlink_unicast(xfrm_nl, resp_skb,
- NETLINK_CB(skb).pid, MSG_DONTWAIT);
- }
- xfrm_state_put(x);
-out_noput:
- return err;
-}
-
-static int verify_userspi_info(struct xfrm_userspi_info *p)
-{
- switch (p->info.id.proto) {
- case IPPROTO_AH:
- case IPPROTO_ESP:
- break;
-
- case IPPROTO_COMP:
- /* IPCOMP spi is 16-bits. */
- if (p->min >= 0x10000 ||
- p->max >= 0x10000)
- return -EINVAL;
-
- default:
- return -EINVAL;
- };
-
- if (p->min > p->max)
- return -EINVAL;
-
- return 0;
-}
-
-static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
-{
- struct xfrm_state *x;
- struct xfrm_userspi_info *p;
- struct sk_buff *resp_skb;
- int err;
-
- p = NLMSG_DATA(nlh);
- err = verify_userspi_info(p);
- if (err)
- goto out_noput;
- x = xfrm_find_acq(p->info.mode, p->info.reqid, p->info.id.proto,
- &p->info.sel.daddr,
- &p->info.sel.saddr, 1,
- p->info.family);
- err = -ENOENT;
- if (x == NULL)
- goto out_noput;
-
- resp_skb = ERR_PTR(-ENOENT);
-
- spin_lock_bh(&x->lock);
- if (x->km.state != XFRM_STATE_DEAD) {
- xfrm_alloc_spi(x, p->min, p->max);
- if (x->id.spi)
- resp_skb = xfrm_state_netlink(skb, x, nlh->nlmsg_seq);
- }
- spin_unlock_bh(&x->lock);
-
- if (IS_ERR(resp_skb)) {
- err = PTR_ERR(resp_skb);
- goto out;
- }
-
- err = netlink_unicast(xfrm_nl, resp_skb,
- NETLINK_CB(skb).pid, MSG_DONTWAIT);
-
-out:
- xfrm_state_put(x);
-out_noput:
- return err;
-}
-
-static int verify_policy_dir(__u8 dir)
-{
- switch (dir) {
- case XFRM_POLICY_IN:
- case XFRM_POLICY_OUT:
- case XFRM_POLICY_FWD:
- break;
-
- default:
- return -EINVAL;
- };
-
- return 0;
-}
-
-static int verify_newpolicy_info(struct xfrm_userpolicy_info *p)
-{
- switch (p->share) {
- case XFRM_SHARE_ANY:
- case XFRM_SHARE_SESSION:
- case XFRM_SHARE_USER:
- case XFRM_SHARE_UNIQUE:
- break;
-
- default:
- return -EINVAL;
- };
-
- switch (p->action) {
- case XFRM_POLICY_ALLOW:
- case XFRM_POLICY_BLOCK:
- break;
-
- default:
- return -EINVAL;
- };
-
- switch (p->family) {
- case AF_INET:
- break;
-
- case AF_INET6:
-#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
- break;
-#else
- return -EAFNOSUPPORT;
-#endif
-
- default:
- return -EINVAL;
- };
-
- return verify_policy_dir(p->dir);
-}
-
-static void copy_templates(struct xfrm_policy *xp, struct xfrm_user_tmpl *ut,
- int nr)
-{
- int i;
-
- xp->xfrm_nr = nr;
- for (i = 0; i < nr; i++, ut++) {
- struct xfrm_tmpl *t = &xp->xfrm_vec[i];
-
- memcpy(&t->id, &ut->id, sizeof(struct xfrm_id));
- memcpy(&t->saddr, &ut->saddr,
- sizeof(xfrm_address_t));
- t->reqid = ut->reqid;
- t->mode = ut->mode;
- t->share = ut->share;
- t->optional = ut->optional;
- t->aalgos = ut->aalgos;
- t->ealgos = ut->ealgos;
- t->calgos = ut->calgos;
- }
-}
-
-static int copy_user_tmpl(struct xfrm_policy *pol, struct rtattr **xfrma)
-{
- struct rtattr *rt = xfrma[XFRMA_TMPL-1];
- struct xfrm_user_tmpl *utmpl;
- int nr;
-
- if (!rt) {
- pol->xfrm_nr = 0;
- } else {
- nr = (rt->rta_len - sizeof(*rt)) / sizeof(*utmpl);
-
- if (nr > XFRM_MAX_DEPTH)
- return -EINVAL;
-
- copy_templates(pol, RTA_DATA(rt), nr);
- }
- return 0;
-}
-
-static void copy_from_user_policy(struct xfrm_policy *xp, struct xfrm_userpolicy_info *p)
-{
- xp->priority = p->priority;
- xp->index = p->index;
- memcpy(&xp->selector, &p->sel, sizeof(xp->selector));
- memcpy(&xp->lft, &p->lft, sizeof(xp->lft));
- xp->action = p->action;
- xp->flags = p->flags;
- xp->family = p->family;
- /* XXX xp->share = p->share; */
-}
-
-static void copy_to_user_policy(struct xfrm_policy *xp, struct xfrm_userpolicy_info *p, int dir)
-{
- memcpy(&p->sel, &xp->selector, sizeof(p->sel));
- memcpy(&p->lft, &xp->lft, sizeof(p->lft));
- memcpy(&p->curlft, &xp->curlft, sizeof(p->curlft));
- p->priority = xp->priority;
- p->index = xp->index;
- p->family = xp->family;
- p->dir = dir;
- p->action = xp->action;
- p->flags = xp->flags;
- p->share = XFRM_SHARE_ANY; /* XXX xp->share */
-}
-
-static struct xfrm_policy *xfrm_policy_construct(struct xfrm_userpolicy_info *p, struct rtattr **xfrma, int *errp)
-{
- struct xfrm_policy *xp = xfrm_policy_alloc(GFP_KERNEL);
- int err;
-
- if (!xp) {
- *errp = -ENOMEM;
- return NULL;
- }
-
- copy_from_user_policy(xp, p);
- err = copy_user_tmpl(xp, xfrma);
- if (err) {
- *errp = err;
- kfree(xp);
- xp = NULL;
- }
-
- return xp;
-}
-
-static int xfrm_add_policy(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
-{
- struct xfrm_userpolicy_info *p = NLMSG_DATA(nlh);
- struct xfrm_policy *xp;
- int err;
-
- err = verify_newpolicy_info(p);
- if (err)
- return err;
-
- xp = xfrm_policy_construct(p, (struct rtattr **) xfrma, &err);
- if (!xp)
- return err;
-
- err = xfrm_policy_insert(p->dir, xp, 1);
- if (err) {
- kfree(xp);
- return err;
- }
-
- xfrm_pol_put(xp);
-
- return 0;
-}
-
-static int xfrm_del_policy(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
-{
- struct xfrm_policy *xp;
- struct xfrm_userpolicy_id *p;
- int err;
-
- p = NLMSG_DATA(nlh);
-
- err = verify_policy_dir(p->dir);
- if (err)
- return err;
-
- xp = xfrm_policy_delete(p->dir, &p->sel);
- if (xp == NULL)
- return -ENOENT;
- xfrm_policy_kill(xp);
- xfrm_pol_put(xp);
- return 0;
-}
-
-static int dump_one_policy(struct xfrm_policy *xp, int dir, int count, void *ptr)
-{
- struct xfrm_dump_info *sp = ptr;
- struct xfrm_userpolicy_info *p;
- struct sk_buff *in_skb = sp->in_skb;
- struct sk_buff *skb = sp->out_skb;
- struct nlmsghdr *nlh;
- unsigned char *b = skb->tail;
-
- if (sp->this_idx < sp->start_idx)
- goto out;
-
- nlh = NLMSG_PUT(skb, NETLINK_CB(in_skb).pid,
- sp->nlmsg_seq,
- XFRM_MSG_NEWPOLICY, sizeof(*p));
- p = NLMSG_DATA(nlh);
- nlh->nlmsg_flags = 0;
-
- copy_to_user_policy(xp, p, dir);
-
- if (xp->xfrm_nr) {
- struct xfrm_user_tmpl vec[XFRM_MAX_DEPTH];
- int i;
-
- for (i = 0; i < xp->xfrm_nr; i++) {
- struct xfrm_user_tmpl *up = &vec[i];
- struct xfrm_tmpl *kp = &xp->xfrm_vec[i];
-
- memcpy(&up->id, &kp->id, sizeof(up->id));
- memcpy(&up->saddr, &kp->saddr, sizeof(up->saddr));
- up->reqid = kp->reqid;
- up->mode = kp->mode;
- up->share = kp->share;
- up->optional = kp->optional;
- up->aalgos = kp->aalgos;
- up->ealgos = kp->ealgos;
- up->calgos = kp->calgos;
- }
- RTA_PUT(skb, XFRMA_TMPL,
- (sizeof(struct xfrm_user_tmpl) * xp->xfrm_nr),
- vec);
- }
-
- nlh->nlmsg_len = skb->tail - b;
-out:
- sp->this_idx++;
- return 0;
-
-nlmsg_failure:
-rtattr_failure:
- skb_trim(skb, b - skb->data);
- return -1;
-}
-
-static int xfrm_dump_policy(struct sk_buff *skb, struct netlink_callback *cb)
-{
- struct xfrm_dump_info info;
-
- info.in_skb = cb->skb;
- info.out_skb = skb;
- info.nlmsg_seq = cb->nlh->nlmsg_seq;
- info.this_idx = 0;
- info.start_idx = cb->args[0];
- (void) xfrm_policy_walk(dump_one_policy, &info);
- cb->args[0] = info.this_idx;
-
- return skb->len;
-}
-
-static struct sk_buff *xfrm_policy_netlink(struct sk_buff *in_skb,
- struct xfrm_policy *xp,
- int dir, u32 seq)
-{
- struct xfrm_dump_info info;
- struct sk_buff *skb;
-
- skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
- if (!skb)
- return ERR_PTR(-ENOMEM);
-
- NETLINK_CB(skb).dst_pid = NETLINK_CB(in_skb).pid;
- info.in_skb = in_skb;
- info.out_skb = skb;
- info.nlmsg_seq = seq;
- info.this_idx = info.start_idx = 0;
-
- if (dump_one_policy(xp, dir, 0, &info) < 0) {
- kfree_skb(skb);
- return NULL;
- }
-
- return skb;
-}
-
-static int xfrm_get_policy(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
-{
- struct xfrm_policy *xp;
- struct xfrm_userpolicy_id *p;
- struct sk_buff *resp_skb;
- int err;
-
- p = NLMSG_DATA(nlh);
- xp = xfrm_policy_byid(p->dir, p->index, 0);
- if (xp == NULL)
- return -ENOENT;
-
- resp_skb = xfrm_policy_netlink(skb, xp, p->dir, nlh->nlmsg_seq);
- if (IS_ERR(resp_skb)) {
- err = PTR_ERR(resp_skb);
- } else {
- err = netlink_unicast(xfrm_nl, resp_skb,
- NETLINK_CB(skb).pid, MSG_DONTWAIT);
- }
-
- xfrm_pol_put(xp);
-
- return err;
-}
-
-static const int xfrm_msg_min[(XFRM_MSG_MAX + 1 - XFRM_MSG_BASE)] = {
- NLMSG_LENGTH(sizeof(struct xfrm_usersa_info)), /* NEW SA */
- NLMSG_LENGTH(sizeof(struct xfrm_usersa_id)), /* DEL SA */
- NLMSG_LENGTH(sizeof(struct xfrm_usersa_id)), /* GET SA */
- NLMSG_LENGTH(sizeof(struct xfrm_userpolicy_info)),/* NEW POLICY */
- NLMSG_LENGTH(sizeof(struct xfrm_userpolicy_id)), /* DEL POLICY */
- NLMSG_LENGTH(sizeof(struct xfrm_userpolicy_id)), /* GET POLICY */
- NLMSG_LENGTH(sizeof(struct xfrm_userspi_info)), /* ALLOC SPI */
- NLMSG_LENGTH(sizeof(struct xfrm_user_acquire)), /* ACQUIRE */
- NLMSG_LENGTH(sizeof(struct xfrm_user_expire)), /* EXPIRE */
-};
-
-static struct xfrm_link {
- int (*doit)(struct sk_buff *, struct nlmsghdr *, void **);
- int (*dump)(struct sk_buff *, struct netlink_callback *);
-} xfrm_dispatch[] = {
- { .doit = xfrm_add_sa, },
- { .doit = xfrm_del_sa, },
- {
- .doit = xfrm_get_sa,
- .dump = xfrm_dump_sa,
- },
- { .doit = xfrm_add_policy },
- { .doit = xfrm_del_policy },
- {
- .doit = xfrm_get_policy,
- .dump = xfrm_dump_policy,
- },
- { .doit = xfrm_alloc_userspi },
-};
-
-static int xfrm_done(struct netlink_callback *cb)
-{
- return 0;
-}
-
-static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, int *errp)
-{
- struct rtattr *xfrma[XFRMA_MAX];
- struct xfrm_link *link;
- int type, min_len;
-
- if (!(nlh->nlmsg_flags & NLM_F_REQUEST))
- return 0;
-
- type = nlh->nlmsg_type;
-
- /* A control message: ignore them */
- if (type < XFRM_MSG_BASE)
- return 0;
-
- /* Unknown message: reply with EINVAL */
- if (type > XFRM_MSG_MAX)
- goto err_einval;
-
- type -= XFRM_MSG_BASE;
- link = &xfrm_dispatch[type];
-
- /* All operations require privileges, even GET */
- if (security_netlink_recv(skb)) {
- *errp = -EPERM;
- return -1;
- }
-
- if ((type == 2 || type == 5) && (nlh->nlmsg_flags & NLM_F_DUMP)) {
- u32 rlen;
-
- if (link->dump == NULL)
- goto err_einval;
-
- if ((*errp = netlink_dump_start(xfrm_nl, skb, nlh,
- link->dump,
- xfrm_done)) != 0) {
- return -1;
- }
- rlen = NLMSG_ALIGN(nlh->nlmsg_len);
- if (rlen > skb->len)
- rlen = skb->len;
- skb_pull(skb, rlen);
- return -1;
- }
-
- memset(xfrma, 0, sizeof(xfrma));
-
- if (nlh->nlmsg_len < (min_len = xfrm_msg_min[type]))
- goto err_einval;
-
- if (nlh->nlmsg_len > min_len) {
- int attrlen = nlh->nlmsg_len - NLMSG_ALIGN(min_len);
- struct rtattr *attr = (void *) nlh + NLMSG_ALIGN(min_len);
-
- while (RTA_OK(attr, attrlen)) {
- unsigned short flavor = attr->rta_type;
- if (flavor) {
- if (flavor > XFRMA_MAX)
- goto err_einval;
- xfrma[flavor - 1] = attr;
- }
- attr = RTA_NEXT(attr, attrlen);
- }
- }
-
- if (link->doit == NULL)
- goto err_einval;
- *errp = link->doit(skb, nlh, (void **) &xfrma);
-
- return *errp;
-
-err_einval:
- *errp = -EINVAL;
- return -1;
-}
-
-static int xfrm_user_rcv_skb(struct sk_buff *skb)
-{
- int err;
- struct nlmsghdr *nlh;
-
- while (skb->len >= NLMSG_SPACE(0)) {
- u32 rlen;
-
- nlh = (struct nlmsghdr *) skb->data;
- if (nlh->nlmsg_len < sizeof(*nlh) ||
- skb->len < nlh->nlmsg_len)
- return 0;
- rlen = NLMSG_ALIGN(nlh->nlmsg_len);
- if (rlen > skb->len)
- rlen = skb->len;
- if (xfrm_user_rcv_msg(skb, nlh, &err)) {
- if (err == 0)
- return -1;
- netlink_ack(skb, nlh, err);
- } else if (nlh->nlmsg_flags & NLM_F_ACK)
- netlink_ack(skb, nlh, 0);
- skb_pull(skb, rlen);
- }
-
- return 0;
-}
-
-static void xfrm_netlink_rcv(struct sock *sk, int len)
-{
- do {
- struct sk_buff *skb;
-
- down(&xfrm_cfg_sem);
-
- while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
- if (xfrm_user_rcv_skb(skb)) {
- if (skb->len)
- skb_queue_head(&sk->receive_queue, skb);
- else
- kfree_skb(skb);
- break;
- }
- kfree_skb(skb);
- }
-
- up(&xfrm_cfg_sem);
-
- } while (xfrm_nl && xfrm_nl->receive_queue.qlen);
-}
-
-static int build_expire(struct sk_buff *skb, struct xfrm_state *x, int hard)
-{
- struct xfrm_user_expire *ue;
- struct nlmsghdr *nlh;
- unsigned char *b = skb->tail;
-
- nlh = NLMSG_PUT(skb, 0, 0, XFRM_MSG_EXPIRE,
- sizeof(*ue));
- ue = NLMSG_DATA(nlh);
- nlh->nlmsg_flags = 0;
-
- copy_to_user_state(x, &ue->state);
- ue->hard = (hard != 0) ? 1 : 0;
-
- nlh->nlmsg_len = skb->tail - b;
- return skb->len;
-
-nlmsg_failure:
- skb_trim(skb, b - skb->data);
- return -1;
-}
-
-static int xfrm_send_notify(struct xfrm_state *x, int hard)
-{
- struct sk_buff *skb;
-
- skb = alloc_skb(sizeof(struct xfrm_user_expire) + 16, GFP_ATOMIC);
- if (skb == NULL)
- return -ENOMEM;
-
- if (build_expire(skb, x, hard) < 0)
- BUG();
-
- NETLINK_CB(skb).dst_groups = XFRMGRP_EXPIRE;
-
- return netlink_broadcast(xfrm_nl, skb, 0, XFRMGRP_EXPIRE, GFP_ATOMIC);
-}
-
-static int build_acquire(struct sk_buff *skb, struct xfrm_state *x,
- struct xfrm_tmpl *xt, struct xfrm_policy *xp,
- int dir)
-{
- struct xfrm_user_acquire *ua;
- struct nlmsghdr *nlh;
- unsigned char *b = skb->tail;
- __u32 seq = xfrm_get_acqseq();
-
- nlh = NLMSG_PUT(skb, 0, 0, XFRM_MSG_ACQUIRE,
- sizeof(*ua));
- ua = NLMSG_DATA(nlh);
- nlh->nlmsg_flags = 0;
-
- memcpy(&ua->id, &x->id, sizeof(ua->id));
- memcpy(&ua->saddr, &x->props.saddr, sizeof(ua->saddr));
- copy_to_user_policy(xp, &ua->policy, dir);
- ua->aalgos = xt->aalgos;
- ua->ealgos = xt->ealgos;
- ua->calgos = xt->calgos;
- ua->seq = x->km.seq = seq;
-
- nlh->nlmsg_len = skb->tail - b;
- return skb->len;
-
-nlmsg_failure:
- skb_trim(skb, b - skb->data);
- return -1;
-}
-
-static int xfrm_send_acquire(struct xfrm_state *x, struct xfrm_tmpl *xt,
- struct xfrm_policy *xp, int dir)
-{
- struct sk_buff *skb;
-
- skb = alloc_skb(sizeof(struct xfrm_user_acquire) + 16, GFP_ATOMIC);
- if (skb == NULL)
- return -ENOMEM;
-
- if (build_acquire(skb, x, xt, xp, dir) < 0)
- BUG();
-
- NETLINK_CB(skb).dst_groups = XFRMGRP_ACQUIRE;
-
- return netlink_broadcast(xfrm_nl, skb, 0, XFRMGRP_ACQUIRE, GFP_ATOMIC);
-}
-
-/* User gives us xfrm_user_policy_info followed by an array of 0
- * or more templates.
- */
-struct xfrm_policy *xfrm_compile_policy(u16 family, int opt,
- u8 *data, int len, int *dir)
-{
- struct xfrm_userpolicy_info *p = (struct xfrm_userpolicy_info *)data;
- struct xfrm_user_tmpl *ut = (struct xfrm_user_tmpl *) (p + 1);
- struct xfrm_policy *xp;
- int nr;
-
- switch (family) {
- case AF_INET:
- if (opt != IP_XFRM_POLICY) {
- *dir = -EOPNOTSUPP;
- return NULL;
- }
- break;
-#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
- case AF_INET6:
- if (opt != IPV6_XFRM_POLICY) {
- *dir = -EOPNOTSUPP;
- return NULL;
- }
- break;
-#endif
- default:
- *dir = -EINVAL;
- return NULL;
- }
-
- *dir = -EINVAL;
-
- if (len < sizeof(*p) ||
- verify_newpolicy_info(p))
- return NULL;
-
- nr = ((len - sizeof(*p)) / sizeof(*ut));
- if (nr > XFRM_MAX_DEPTH)
- return NULL;
-
- xp = xfrm_policy_alloc(GFP_KERNEL);
- if (xp == NULL) {
- *dir = -ENOBUFS;
- return NULL;
- }
-
- copy_from_user_policy(xp, p);
- copy_templates(xp, ut, nr);
-
- *dir = p->dir;
-
- return xp;
-}
-
-static struct xfrm_mgr netlink_mgr = {
- .id = "netlink",
- .notify = xfrm_send_notify,
- .acquire = xfrm_send_acquire,
- .compile_policy = xfrm_compile_policy,
-};
-
-static int __init xfrm_user_init(void)
-{
- printk(KERN_INFO "Initializing IPsec netlink socket\n");
-
- xfrm_nl = netlink_kernel_create(NETLINK_XFRM, xfrm_netlink_rcv);
- if (xfrm_nl == NULL)
- panic("xfrm_user_init: cannot initialize xfrm_nl\n");
-
-
- xfrm_register_km(&netlink_mgr);
-
- return 0;
-}
-
-static void __exit xfrm_user_exit(void)
-{
- xfrm_unregister_km(&netlink_mgr);
- sock_release(xfrm_nl->socket);
-}
-
-module_init(xfrm_user_init);
-module_exit(xfrm_user_exit);
--- /dev/null
+#
+# XFRM configuration
+#
+config XFRM_USER
+ tristate "IPsec user configuration interface"
+ depends on INET
+ ---help---
+ Support for IPsec user configuration interface used
+ by native Linux tools.
+
+ If unsure, say Y.
+
--- /dev/null
+#
+# Makefile for the XFRM subsystem.
+#
+
+obj-y := xfrm_policy.o xfrm_state.o xfrm_input.o xfrm_algo.o
+obj-$(CONFIG_XFRM_USER) += xfrm_user.o
+
--- /dev/null
+/*
+ * xfrm algorithm interface
+ *
+ * Copyright (c) 2002 James Morris <jmorris@intercode.com.au>
+ *
+ * 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.
+ */
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/pfkeyv2.h>
+#include <net/xfrm.h>
+#if defined(CONFIG_INET_AH) || defined(CONFIG_INET_AH_MODULE) || defined(CONFIG_INET6_AH) || defined(CONFIG_INET6_AH_MODULE)
+#include <net/ah.h>
+#endif
+#if defined(CONFIG_INET_ESP) || defined(CONFIG_INET_ESP_MODULE) || defined(CONFIG_INET6_ESP) || defined(CONFIG_INET6_ESP_MODULE)
+#include <net/esp.h>
+#endif
+#include <asm/scatterlist.h>
+
+/*
+ * Algorithms supported by IPsec. These entries contain properties which
+ * are used in key negotiation and xfrm processing, and are used to verify
+ * that instantiated crypto transforms have correct parameters for IPsec
+ * purposes.
+ */
+static struct xfrm_algo_desc aalg_list[] = {
+{
+ .name = "digest_null",
+
+ .uinfo = {
+ .auth = {
+ .icv_truncbits = 0,
+ .icv_fullbits = 0,
+ }
+ },
+
+ .desc = {
+ .sadb_alg_id = SADB_X_AALG_NULL,
+ .sadb_alg_ivlen = 0,
+ .sadb_alg_minbits = 0,
+ .sadb_alg_maxbits = 0
+ }
+},
+{
+ .name = "md5",
+
+ .uinfo = {
+ .auth = {
+ .icv_truncbits = 96,
+ .icv_fullbits = 128,
+ }
+ },
+
+ .desc = {
+ .sadb_alg_id = SADB_AALG_MD5HMAC,
+ .sadb_alg_ivlen = 0,
+ .sadb_alg_minbits = 128,
+ .sadb_alg_maxbits = 128
+ }
+},
+{
+ .name = "sha1",
+
+ .uinfo = {
+ .auth = {
+ .icv_truncbits = 96,
+ .icv_fullbits = 160,
+ }
+ },
+
+ .desc = {
+ .sadb_alg_id = SADB_AALG_SHA1HMAC,
+ .sadb_alg_ivlen = 0,
+ .sadb_alg_minbits = 160,
+ .sadb_alg_maxbits = 160
+ }
+},
+{
+ .name = "sha256",
+
+ .uinfo = {
+ .auth = {
+ .icv_truncbits = 128,
+ .icv_fullbits = 256,
+ }
+ },
+
+ .desc = {
+ .sadb_alg_id = SADB_X_AALG_SHA2_256HMAC,
+ .sadb_alg_ivlen = 0,
+ .sadb_alg_minbits = 256,
+ .sadb_alg_maxbits = 256
+ }
+},
+{
+ .name = "ripemd160",
+
+ .uinfo = {
+ .auth = {
+ .icv_truncbits = 96,
+ .icv_fullbits = 160,
+ }
+ },
+
+ .desc = {
+ .sadb_alg_id = SADB_X_AALG_RIPEMD160HMAC,
+ .sadb_alg_ivlen = 0,
+ .sadb_alg_minbits = 160,
+ .sadb_alg_maxbits = 160
+ }
+},
+};
+
+static struct xfrm_algo_desc ealg_list[] = {
+{
+ .name = "cipher_null",
+
+ .uinfo = {
+ .encr = {
+ .blockbits = 8,
+ .defkeybits = 0,
+ }
+ },
+
+ .desc = {
+ .sadb_alg_id = SADB_EALG_NULL,
+ .sadb_alg_ivlen = 0,
+ .sadb_alg_minbits = 0,
+ .sadb_alg_maxbits = 0
+ }
+},
+{
+ .name = "des",
+
+ .uinfo = {
+ .encr = {
+ .blockbits = 64,
+ .defkeybits = 64,
+ }
+ },
+
+ .desc = {
+ .sadb_alg_id = SADB_EALG_DESCBC,
+ .sadb_alg_ivlen = 8,
+ .sadb_alg_minbits = 64,
+ .sadb_alg_maxbits = 64
+ }
+},
+{
+ .name = "des3_ede",
+
+ .uinfo = {
+ .encr = {
+ .blockbits = 64,
+ .defkeybits = 192,
+ }
+ },
+
+ .desc = {
+ .sadb_alg_id = SADB_EALG_3DESCBC,
+ .sadb_alg_ivlen = 8,
+ .sadb_alg_minbits = 192,
+ .sadb_alg_maxbits = 192
+ }
+},
+{
+ .name = "cast128",
+
+ .uinfo = {
+ .encr = {
+ .blockbits = 64,
+ .defkeybits = 128,
+ }
+ },
+
+ .desc = {
+ .sadb_alg_id = SADB_X_EALG_CASTCBC,
+ .sadb_alg_ivlen = 8,
+ .sadb_alg_minbits = 40,
+ .sadb_alg_maxbits = 128
+ }
+},
+{
+ .name = "blowfish",
+
+ .uinfo = {
+ .encr = {
+ .blockbits = 64,
+ .defkeybits = 128,
+ }
+ },
+
+ .desc = {
+ .sadb_alg_id = SADB_X_EALG_BLOWFISHCBC,
+ .sadb_alg_ivlen = 8,
+ .sadb_alg_minbits = 40,
+ .sadb_alg_maxbits = 448
+ }
+},
+{
+ .name = "aes",
+
+ .uinfo = {
+ .encr = {
+ .blockbits = 128,
+ .defkeybits = 128,
+ }
+ },
+
+ .desc = {
+ .sadb_alg_id = SADB_X_EALG_AESCBC,
+ .sadb_alg_ivlen = 8,
+ .sadb_alg_minbits = 128,
+ .sadb_alg_maxbits = 256
+ }
+},
+};
+
+static struct xfrm_algo_desc calg_list[] = {
+{
+ .name = "deflate",
+ .uinfo = {
+ .comp = {
+ .threshold = 90,
+ }
+ },
+ .desc = { .sadb_alg_id = SADB_X_CALG_DEFLATE }
+},
+{
+ .name = "lzs",
+ .uinfo = {
+ .comp = {
+ .threshold = 90,
+ }
+ },
+ .desc = { .sadb_alg_id = SADB_X_CALG_LZS }
+},
+{
+ .name = "lzjh",
+ .uinfo = {
+ .comp = {
+ .threshold = 50,
+ }
+ },
+ .desc = { .sadb_alg_id = SADB_X_CALG_LZJH }
+},
+};
+
+static inline int aalg_entries(void)
+{
+ return sizeof(aalg_list) / sizeof(aalg_list[0]);
+}
+
+static inline int ealg_entries(void)
+{
+ return sizeof(ealg_list) / sizeof(ealg_list[0]);
+}
+
+static inline int calg_entries(void)
+{
+ return sizeof(calg_list) / sizeof(calg_list[0]);
+}
+
+/* Todo: generic iterators */
+struct xfrm_algo_desc *xfrm_aalg_get_byid(int alg_id)
+{
+ int i;
+
+ for (i = 0; i < aalg_entries(); i++) {
+ if (aalg_list[i].desc.sadb_alg_id == alg_id) {
+ if (aalg_list[i].available)
+ return &aalg_list[i];
+ else
+ break;
+ }
+ }
+ return NULL;
+}
+
+struct xfrm_algo_desc *xfrm_ealg_get_byid(int alg_id)
+{
+ int i;
+
+ for (i = 0; i < ealg_entries(); i++) {
+ if (ealg_list[i].desc.sadb_alg_id == alg_id) {
+ if (ealg_list[i].available)
+ return &ealg_list[i];
+ else
+ break;
+ }
+ }
+ return NULL;
+}
+
+struct xfrm_algo_desc *xfrm_calg_get_byid(int alg_id)
+{
+ int i;
+
+ for (i = 0; i < calg_entries(); i++) {
+ if (calg_list[i].desc.sadb_alg_id == alg_id) {
+ if (calg_list[i].available)
+ return &calg_list[i];
+ else
+ break;
+ }
+ }
+ return NULL;
+}
+
+struct xfrm_algo_desc *xfrm_aalg_get_byname(char *name)
+{
+ int i;
+
+ if (!name)
+ return NULL;
+
+ for (i=0; i < aalg_entries(); i++) {
+ if (strcmp(name, aalg_list[i].name) == 0) {
+ if (aalg_list[i].available)
+ return &aalg_list[i];
+ else
+ break;
+ }
+ }
+ return NULL;
+}
+
+struct xfrm_algo_desc *xfrm_ealg_get_byname(char *name)
+{
+ int i;
+
+ if (!name)
+ return NULL;
+
+ for (i=0; i < ealg_entries(); i++) {
+ if (strcmp(name, ealg_list[i].name) == 0) {
+ if (ealg_list[i].available)
+ return &ealg_list[i];
+ else
+ break;
+ }
+ }
+ return NULL;
+}
+
+struct xfrm_algo_desc *xfrm_calg_get_byname(char *name)
+{
+ int i;
+
+ if (!name)
+ return NULL;
+
+ for (i=0; i < calg_entries(); i++) {
+ if (strcmp(name, calg_list[i].name) == 0) {
+ if (calg_list[i].available)
+ return &calg_list[i];
+ else
+ break;
+ }
+ }
+ return NULL;
+}
+
+struct xfrm_algo_desc *xfrm_aalg_get_byidx(unsigned int idx)
+{
+ if (idx >= aalg_entries())
+ return NULL;
+
+ return &aalg_list[idx];
+}
+
+struct xfrm_algo_desc *xfrm_ealg_get_byidx(unsigned int idx)
+{
+ if (idx >= ealg_entries())
+ return NULL;
+
+ return &ealg_list[idx];
+}
+
+struct xfrm_algo_desc *xfrm_calg_get_byidx(unsigned int idx)
+{
+ if (idx >= calg_entries())
+ return NULL;
+
+ return &calg_list[idx];
+}
+
+/*
+ * Probe for the availability of crypto algorithms, and set the available
+ * flag for any algorithms found on the system. This is typically called by
+ * pfkey during userspace SA add, update or register.
+ */
+void xfrm_probe_algs(void)
+{
+#ifdef CONFIG_CRYPTO
+ int i, status;
+
+ BUG_ON(in_softirq());
+
+ for (i = 0; i < aalg_entries(); i++) {
+ status = crypto_alg_available(aalg_list[i].name, 0);
+ if (aalg_list[i].available != status)
+ aalg_list[i].available = status;
+ }
+
+ for (i = 0; i < ealg_entries(); i++) {
+ status = crypto_alg_available(ealg_list[i].name, 0);
+ if (ealg_list[i].available != status)
+ ealg_list[i].available = status;
+ }
+
+ for (i = 0; i < calg_entries(); i++) {
+ status = crypto_alg_available(calg_list[i].name, 0);
+ if (calg_list[i].available != status)
+ calg_list[i].available = status;
+ }
+#endif
+}
+
+int xfrm_count_auth_supported(void)
+{
+ int i, n;
+
+ for (i = 0, n = 0; i < aalg_entries(); i++)
+ if (aalg_list[i].available)
+ n++;
+ return n;
+}
+
+int xfrm_count_enc_supported(void)
+{
+ int i, n;
+
+ for (i = 0, n = 0; i < ealg_entries(); i++)
+ if (ealg_list[i].available)
+ n++;
+ return n;
+}
+
+#if defined(CONFIG_INET_AH) || defined(CONFIG_INET_AH_MODULE) || defined(CONFIG_INET6_AH) || defined(CONFIG_INET6_AH_MODULE)
+void skb_ah_walk(const struct sk_buff *skb,
+ struct crypto_tfm *tfm, icv_update_fn_t icv_update)
+{
+ int offset = 0;
+ int len = skb->len;
+ int start = skb->len - skb->data_len;
+ int i, copy = start - offset;
+ struct scatterlist sg;
+
+ /* Checksum header. */
+ if (copy > 0) {
+ if (copy > len)
+ copy = len;
+
+ sg.page = virt_to_page(skb->data + offset);
+ sg.offset = (unsigned long)(skb->data + offset) % PAGE_SIZE;
+ sg.length = copy;
+
+ icv_update(tfm, &sg, 1);
+
+ if ((len -= copy) == 0)
+ return;
+ offset += copy;
+ }
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ int end;
+
+ BUG_TRAP(start <= offset + len);
+
+ end = start + skb_shinfo(skb)->frags[i].size;
+ if ((copy = end - offset) > 0) {
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+ if (copy > len)
+ copy = len;
+
+ sg.page = frag->page;
+ sg.offset = frag->page_offset + offset-start;
+ sg.length = copy;
+
+ icv_update(tfm, &sg, 1);
+
+ if (!(len -= copy))
+ return;
+ offset += copy;
+ }
+ start = end;
+ }
+
+ if (skb_shinfo(skb)->frag_list) {
+ struct sk_buff *list = skb_shinfo(skb)->frag_list;
+
+ for (; list; list = list->next) {
+ int end;
+
+ BUG_TRAP(start <= offset + len);
+
+ end = start + list->len;
+ if ((copy = end - offset) > 0) {
+ if (copy > len)
+ copy = len;
+ skb_ah_walk(list, tfm, icv_update);
+ if ((len -= copy) == 0)
+ return;
+ offset += copy;
+ }
+ start = end;
+ }
+ }
+ if (len)
+ BUG();
+}
+#endif
+
+#if defined(CONFIG_INET_ESP) || defined(CONFIG_INET_ESP_MODULE) || defined(CONFIG_INET6_ESP) || defined(CONFIG_INET6_ESP_MODULE)
+/* Move to common area: it is shared with AH. */
+
+void skb_icv_walk(const struct sk_buff *skb, struct crypto_tfm *tfm,
+ int offset, int len, icv_update_fn_t icv_update)
+{
+ int start = skb->len - skb->data_len;
+ int i, copy = start - offset;
+ struct scatterlist sg;
+
+ /* Checksum header. */
+ if (copy > 0) {
+ if (copy > len)
+ copy = len;
+
+ sg.page = virt_to_page(skb->data + offset);
+ sg.offset = (unsigned long)(skb->data + offset) % PAGE_SIZE;
+ sg.length = copy;
+
+ icv_update(tfm, &sg, 1);
+
+ if ((len -= copy) == 0)
+ return;
+ offset += copy;
+ }
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ int end;
+
+ BUG_TRAP(start <= offset + len);
+
+ end = start + skb_shinfo(skb)->frags[i].size;
+ if ((copy = end - offset) > 0) {
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+ if (copy > len)
+ copy = len;
+
+ sg.page = frag->page;
+ sg.offset = frag->page_offset + offset-start;
+ sg.length = copy;
+
+ icv_update(tfm, &sg, 1);
+
+ if (!(len -= copy))
+ return;
+ offset += copy;
+ }
+ start = end;
+ }
+
+ if (skb_shinfo(skb)->frag_list) {
+ struct sk_buff *list = skb_shinfo(skb)->frag_list;
+
+ for (; list; list = list->next) {
+ int end;
+
+ BUG_TRAP(start <= offset + len);
+
+ end = start + list->len;
+ if ((copy = end - offset) > 0) {
+ if (copy > len)
+ copy = len;
+ skb_icv_walk(list, tfm, offset-start, copy, icv_update);
+ if ((len -= copy) == 0)
+ return;
+ offset += copy;
+ }
+ start = end;
+ }
+ }
+ if (len)
+ BUG();
+}
+
+
+/* Looking generic it is not used in another places. */
+
+int
+skb_to_sgvec(struct sk_buff *skb, struct scatterlist *sg, int offset, int len)
+{
+ int start = skb->len - skb->data_len;
+ int i, copy = start - offset;
+ int elt = 0;
+
+ if (copy > 0) {
+ if (copy > len)
+ copy = len;
+ sg[elt].page = virt_to_page(skb->data + offset);
+ sg[elt].offset = (unsigned long)(skb->data + offset) % PAGE_SIZE;
+ sg[elt].length = copy;
+ elt++;
+ if ((len -= copy) == 0)
+ return elt;
+ offset += copy;
+ }
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ int end;
+
+ BUG_TRAP(start <= offset + len);
+
+ end = start + skb_shinfo(skb)->frags[i].size;
+ if ((copy = end - offset) > 0) {
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+ if (copy > len)
+ copy = len;
+ sg[elt].page = frag->page;
+ sg[elt].offset = frag->page_offset+offset-start;
+ sg[elt].length = copy;
+ elt++;
+ if (!(len -= copy))
+ return elt;
+ offset += copy;
+ }
+ start = end;
+ }
+
+ if (skb_shinfo(skb)->frag_list) {
+ struct sk_buff *list = skb_shinfo(skb)->frag_list;
+
+ for (; list; list = list->next) {
+ int end;
+
+ BUG_TRAP(start <= offset + len);
+
+ end = start + list->len;
+ if ((copy = end - offset) > 0) {
+ if (copy > len)
+ copy = len;
+ elt += skb_to_sgvec(list, sg+elt, offset - start, copy);
+ if ((len -= copy) == 0)
+ return elt;
+ offset += copy;
+ }
+ start = end;
+ }
+ }
+ if (len)
+ BUG();
+ return elt;
+}
+
+/* Check that skb data bits are writable. If they are not, copy data
+ * to newly created private area. If "tailbits" is given, make sure that
+ * tailbits bytes beyond current end of skb are writable.
+ *
+ * Returns amount of elements of scatterlist to load for subsequent
+ * transformations and pointer to writable trailer skb.
+ */
+
+int skb_cow_data(struct sk_buff *skb, int tailbits, struct sk_buff **trailer)
+{
+ int copyflag;
+ int elt;
+ struct sk_buff *skb1, **skb_p;
+
+ /* If skb is cloned or its head is paged, reallocate
+ * head pulling out all the pages (pages are considered not writable
+ * at the moment even if they are anonymous).
+ */
+ if ((skb_cloned(skb) || skb_shinfo(skb)->nr_frags) &&
+ __pskb_pull_tail(skb, skb_pagelen(skb)-skb_headlen(skb)) == NULL)
+ return -ENOMEM;
+
+ /* Easy case. Most of packets will go this way. */
+ if (!skb_shinfo(skb)->frag_list) {
+ /* A little of trouble, not enough of space for trailer.
+ * This should not happen, when stack is tuned to generate
+ * good frames. OK, on miss we reallocate and reserve even more
+ * space, 128 bytes is fair. */
+
+ if (skb_tailroom(skb) < tailbits &&
+ pskb_expand_head(skb, 0, tailbits-skb_tailroom(skb)+128, GFP_ATOMIC))
+ return -ENOMEM;
+
+ /* Voila! */
+ *trailer = skb;
+ return 1;
+ }
+
+ /* Misery. We are in troubles, going to mincer fragments... */
+
+ elt = 1;
+ skb_p = &skb_shinfo(skb)->frag_list;
+ copyflag = 0;
+
+ while ((skb1 = *skb_p) != NULL) {
+ int ntail = 0;
+
+ /* The fragment is partially pulled by someone,
+ * this can happen on input. Copy it and everything
+ * after it. */
+
+ if (skb_shared(skb1))
+ copyflag = 1;
+
+ /* If the skb is the last, worry about trailer. */
+
+ if (skb1->next == NULL && tailbits) {
+ if (skb_shinfo(skb1)->nr_frags ||
+ skb_shinfo(skb1)->frag_list ||
+ skb_tailroom(skb1) < tailbits)
+ ntail = tailbits + 128;
+ }
+
+ if (copyflag ||
+ skb_cloned(skb1) ||
+ ntail ||
+ skb_shinfo(skb1)->nr_frags ||
+ skb_shinfo(skb1)->frag_list) {
+ struct sk_buff *skb2;
+
+ /* Fuck, we are miserable poor guys... */
+ if (ntail == 0)
+ skb2 = skb_copy(skb1, GFP_ATOMIC);
+ else
+ skb2 = skb_copy_expand(skb1,
+ skb_headroom(skb1),
+ ntail,
+ GFP_ATOMIC);
+ if (unlikely(skb2 == NULL))
+ return -ENOMEM;
+
+ if (skb1->sk)
+ skb_set_owner_w(skb, skb1->sk);
+
+ /* Looking around. Are we still alive?
+ * OK, link new skb, drop old one */
+
+ skb2->next = skb1->next;
+ *skb_p = skb2;
+ kfree_skb(skb1);
+ skb1 = skb2;
+ }
+ elt++;
+ *trailer = skb1;
+ skb_p = &skb1->next;
+ }
+
+ return elt;
+}
+
+void *pskb_put(struct sk_buff *skb, struct sk_buff *tail, int len)
+{
+ if (tail != skb) {
+ skb->data_len += len;
+ skb->len += len;
+ }
+ return skb_put(tail, len);
+}
+#endif
--- /dev/null
+/*
+ * xfrm_input.c
+ *
+ * Changes:
+ * YOSHIFUJI Hideaki @USAGI
+ * Split up af-specific portion
+ *
+ */
+
+#include <net/ip.h>
+#include <net/xfrm.h>
+
+void __secpath_destroy(struct sec_path *sp)
+{
+ int i;
+ for (i = 0; i < sp->len; i++)
+ xfrm_state_put(sp->xvec[i]);
+ kmem_cache_free(sp->pool, sp);
+}
+
+/* Fetch spi and seq frpm ipsec header */
+
+int xfrm_parse_spi(struct sk_buff *skb, u8 nexthdr, u32 *spi, u32 *seq)
+{
+ int offset, offset_seq;
+
+ switch (nexthdr) {
+ case IPPROTO_AH:
+ offset = offsetof(struct ip_auth_hdr, spi);
+ offset_seq = offsetof(struct ip_auth_hdr, seq_no);
+ break;
+ case IPPROTO_ESP:
+ offset = offsetof(struct ip_esp_hdr, spi);
+ offset_seq = offsetof(struct ip_esp_hdr, seq_no);
+ break;
+ case IPPROTO_COMP:
+ if (!pskb_may_pull(skb, 4))
+ return -EINVAL;
+ *spi = ntohl(ntohs(*(u16*)(skb->h.raw + 2)));
+ *seq = 0;
+ return 0;
+ default:
+ return 1;
+ }
+
+ if (!pskb_may_pull(skb, 16))
+ return -EINVAL;
+
+ *spi = *(u32*)(skb->h.raw + offset);
+ *seq = *(u32*)(skb->h.raw + offset_seq);
+ return 0;
+}
--- /dev/null
+/*
+ * xfrm_policy.c
+ *
+ * Changes:
+ * Mitsuru KANDA @USAGI
+ * Kazunori MIYAZAWA @USAGI
+ * Kunihiro Ishiguro
+ * IPv6 support
+ * Kazunori MIYAZAWA @USAGI
+ * YOSHIFUJI Hideaki
+ * Split up af-specific portion
+ *
+ */
+
+#include <linux/config.h>
+#include <net/xfrm.h>
+#include <net/ip.h>
+
+DECLARE_MUTEX(xfrm_cfg_sem);
+
+static u32 xfrm_policy_genid;
+static rwlock_t xfrm_policy_lock = RW_LOCK_UNLOCKED;
+
+struct xfrm_policy *xfrm_policy_list[XFRM_POLICY_MAX*2];
+
+static rwlock_t xfrm_policy_afinfo_lock = RW_LOCK_UNLOCKED;
+static struct xfrm_policy_afinfo *xfrm_policy_afinfo[NPROTO];
+
+kmem_cache_t *xfrm_dst_cache;
+
+/* Limited flow cache. Its function now is to accelerate search for
+ * policy rules.
+ *
+ * Flow cache is private to cpus, at the moment this is important
+ * mostly for flows which do not match any rule, so that flow lookups
+ * are absolultely cpu-local. When a rule exists we do some updates
+ * to rule (refcnt, stats), so that locality is broken. Later this
+ * can be repaired.
+ */
+
+struct flow_entry
+{
+ struct flow_entry *next;
+ struct flowi fl;
+ u8 dir;
+ u32 genid;
+ struct xfrm_policy *pol;
+};
+
+static kmem_cache_t *flow_cachep;
+
+struct flow_entry **flow_table;
+
+static int flow_lwm = 2*XFRM_FLOWCACHE_HASH_SIZE;
+static int flow_hwm = 4*XFRM_FLOWCACHE_HASH_SIZE;
+
+static int flow_number[NR_CPUS] __cacheline_aligned;
+
+#define flow_count(cpu) (flow_number[cpu])
+
+static void flow_cache_shrink(int cpu)
+{
+ int i;
+ struct flow_entry *fle, **flp;
+ int shrink_to = flow_lwm/XFRM_FLOWCACHE_HASH_SIZE;
+
+ for (i=0; i<XFRM_FLOWCACHE_HASH_SIZE; i++) {
+ int k = 0;
+ flp = &flow_table[cpu*XFRM_FLOWCACHE_HASH_SIZE+i];
+ while ((fle=*flp) != NULL && k<shrink_to) {
+ k++;
+ flp = &fle->next;
+ }
+ while ((fle=*flp) != NULL) {
+ *flp = fle->next;
+ if (fle->pol)
+ xfrm_pol_put(fle->pol);
+ kmem_cache_free(flow_cachep, fle);
+ }
+ }
+}
+
+struct xfrm_policy *flow_lookup(int dir, struct flowi *fl,
+ unsigned short family)
+{
+ struct xfrm_policy *pol = NULL;
+ struct flow_entry *fle;
+ u32 hash;
+ int cpu;
+
+ hash = flow_hash(fl, family);
+
+ local_bh_disable();
+ cpu = smp_processor_id();
+
+ for (fle = flow_table[cpu*XFRM_FLOWCACHE_HASH_SIZE+hash];
+ fle; fle = fle->next) {
+ if (memcmp(fl, &fle->fl, sizeof(fle->fl)) == 0 &&
+ fle->dir == dir) {
+ if (fle->genid == xfrm_policy_genid) {
+ if ((pol = fle->pol) != NULL)
+ atomic_inc(&pol->refcnt);
+ local_bh_enable();
+ return pol;
+ }
+ break;
+ }
+ }
+
+ pol = xfrm_policy_lookup(dir, fl, family);
+
+ if (fle) {
+ /* Stale flow entry found. Update it. */
+ fle->genid = xfrm_policy_genid;
+
+ if (fle->pol)
+ xfrm_pol_put(fle->pol);
+ fle->pol = pol;
+ if (pol)
+ atomic_inc(&pol->refcnt);
+ } else {
+ if (flow_count(cpu) > flow_hwm)
+ flow_cache_shrink(cpu);
+
+ fle = kmem_cache_alloc(flow_cachep, SLAB_ATOMIC);
+ if (fle) {
+ flow_count(cpu)++;
+ fle->fl = *fl;
+ fle->genid = xfrm_policy_genid;
+ fle->dir = dir;
+ fle->pol = pol;
+ if (pol)
+ atomic_inc(&pol->refcnt);
+ fle->next = flow_table[cpu*XFRM_FLOWCACHE_HASH_SIZE+hash];
+ flow_table[cpu*XFRM_FLOWCACHE_HASH_SIZE+hash] = fle;
+ }
+ }
+ local_bh_enable();
+ return pol;
+}
+
+void __init flow_cache_init(void)
+{
+ int order;
+
+ flow_cachep = kmem_cache_create("flow_cache",
+ sizeof(struct flow_entry),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+
+ if (!flow_cachep)
+ panic("NET: failed to allocate flow cache slab\n");
+
+ for (order = 0;
+ (PAGE_SIZE<<order) < (NR_CPUS*sizeof(struct flow_entry *)*XFRM_FLOWCACHE_HASH_SIZE);
+ order++)
+ /* NOTHING */;
+
+ flow_table = (struct flow_entry **)__get_free_pages(GFP_ATOMIC, order);
+
+ if (!flow_table)
+ panic("Failed to allocate flow cache hash table\n");
+
+ memset(flow_table, 0, PAGE_SIZE<<order);
+}
+
+int xfrm_register_type(struct xfrm_type *type, unsigned short family)
+{
+ struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
+ struct xfrm_type_map *typemap;
+ int err = 0;
+
+ if (unlikely(afinfo == NULL))
+ return -EAFNOSUPPORT;
+ typemap = afinfo->type_map;
+
+ write_lock(&typemap->lock);
+ if (likely(typemap->map[type->proto] == NULL))
+ typemap->map[type->proto] = type;
+ else
+ err = -EEXIST;
+ write_unlock(&typemap->lock);
+ xfrm_policy_put_afinfo(afinfo);
+ return err;
+}
+
+int xfrm_unregister_type(struct xfrm_type *type, unsigned short family)
+{
+ struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
+ struct xfrm_type_map *typemap;
+ int err = 0;
+
+ if (unlikely(afinfo == NULL))
+ return -EAFNOSUPPORT;
+ typemap = afinfo->type_map;
+
+ write_lock(&typemap->lock);
+ if (unlikely(typemap->map[type->proto] != type))
+ err = -ENOENT;
+ else
+ typemap->map[type->proto] = NULL;
+ write_unlock(&typemap->lock);
+ xfrm_policy_put_afinfo(afinfo);
+ return err;
+}
+
+struct xfrm_type *xfrm_get_type(u8 proto, unsigned short family)
+{
+ struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
+ struct xfrm_type_map *typemap;
+ struct xfrm_type *type;
+
+ if (unlikely(afinfo == NULL))
+ return NULL;
+ typemap = afinfo->type_map;
+
+ read_lock(&typemap->lock);
+ type = typemap->map[proto];
+ if (unlikely(type && !try_module_get(type->owner)))
+ type = NULL;
+ read_unlock(&typemap->lock);
+ xfrm_policy_put_afinfo(afinfo);
+ return type;
+}
+
+int xfrm_dst_lookup(struct xfrm_dst **dst, struct flowi *fl,
+ unsigned short family)
+{
+ struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
+ int err = 0;
+
+ if (unlikely(afinfo == NULL))
+ return -EAFNOSUPPORT;
+
+ if (likely(afinfo->dst_lookup != NULL))
+ err = afinfo->dst_lookup(dst, fl);
+ else
+ err = -EINVAL;
+ xfrm_policy_put_afinfo(afinfo);
+ return err;
+}
+
+void xfrm_put_type(struct xfrm_type *type)
+{
+ module_put(type->owner);
+}
+
+static inline unsigned long make_jiffies(long secs)
+{
+ if (secs >= (MAX_SCHEDULE_TIMEOUT-1)/HZ)
+ return MAX_SCHEDULE_TIMEOUT-1;
+ else
+ return secs*HZ;
+}
+
+static void xfrm_policy_timer(unsigned long data)
+{
+ struct xfrm_policy *xp = (struct xfrm_policy*)data;
+ unsigned long now = (unsigned long)xtime.tv_sec;
+ long next = LONG_MAX;
+ u32 index;
+
+ if (xp->dead)
+ goto out;
+
+ if (xp->lft.hard_add_expires_seconds) {
+ long tmo = xp->lft.hard_add_expires_seconds +
+ xp->curlft.add_time - now;
+ if (tmo <= 0)
+ goto expired;
+ if (tmo < next)
+ next = tmo;
+ }
+ if (next != LONG_MAX &&
+ !mod_timer(&xp->timer, jiffies + make_jiffies(next)))
+ atomic_inc(&xp->refcnt);
+
+out:
+ xfrm_pol_put(xp);
+ return;
+
+expired:
+ index = xp->index;
+ xfrm_pol_put(xp);
+
+ /* Not 100% correct. id can be recycled in theory */
+ xp = xfrm_policy_byid(0, index, 1);
+ if (xp) {
+ xfrm_policy_kill(xp);
+ xfrm_pol_put(xp);
+ }
+}
+
+
+/* Allocate xfrm_policy. Not used here, it is supposed to be used by pfkeyv2
+ * SPD calls.
+ */
+
+struct xfrm_policy *xfrm_policy_alloc(int gfp)
+{
+ struct xfrm_policy *policy;
+
+ policy = kmalloc(sizeof(struct xfrm_policy), gfp);
+
+ if (policy) {
+ memset(policy, 0, sizeof(struct xfrm_policy));
+ atomic_set(&policy->refcnt, 1);
+ policy->lock = RW_LOCK_UNLOCKED;
+ init_timer(&policy->timer);
+ policy->timer.data = (unsigned long)policy;
+ policy->timer.function = xfrm_policy_timer;
+ }
+ return policy;
+}
+
+/* Destroy xfrm_policy: descendant resources must be released to this moment. */
+
+void __xfrm_policy_destroy(struct xfrm_policy *policy)
+{
+ if (!policy->dead)
+ BUG();
+
+ if (policy->bundles)
+ BUG();
+
+ if (del_timer(&policy->timer))
+ BUG();
+
+ kfree(policy);
+}
+
+/* Rule must be locked. Release descentant resources, announce
+ * entry dead. The rule must be unlinked from lists to the moment.
+ */
+
+void xfrm_policy_kill(struct xfrm_policy *policy)
+{
+ struct dst_entry *dst;
+
+ write_lock_bh(&policy->lock);
+ if (policy->dead)
+ goto out;
+
+ policy->dead = 1;
+
+ while ((dst = policy->bundles) != NULL) {
+ policy->bundles = dst->next;
+ dst_free(dst);
+ }
+
+ if (del_timer(&policy->timer))
+ atomic_dec(&policy->refcnt);
+
+out:
+ write_unlock_bh(&policy->lock);
+}
+
+/* Generate new index... KAME seems to generate them ordered by cost
+ * of an absolute inpredictability of ordering of rules. This will not pass. */
+static u32 xfrm_gen_index(int dir)
+{
+ u32 idx;
+ struct xfrm_policy *p;
+ static u32 idx_generator;
+
+ for (;;) {
+ idx = (idx_generator | dir);
+ idx_generator += 8;
+ if (idx == 0)
+ idx = 8;
+ for (p = xfrm_policy_list[dir]; p; p = p->next) {
+ if (p->index == idx)
+ break;
+ }
+ if (!p)
+ return idx;
+ }
+}
+
+int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl)
+{
+ struct xfrm_policy *pol, **p;
+
+ write_lock_bh(&xfrm_policy_lock);
+ for (p = &xfrm_policy_list[dir]; (pol=*p)!=NULL; p = &pol->next) {
+ if (memcmp(&policy->selector, &pol->selector, sizeof(pol->selector)) == 0) {
+ if (excl) {
+ write_unlock_bh(&xfrm_policy_lock);
+ return -EEXIST;
+ }
+ break;
+ }
+ }
+ atomic_inc(&policy->refcnt);
+ policy->next = pol ? pol->next : NULL;
+ *p = policy;
+ xfrm_policy_genid++;
+ policy->index = pol ? pol->index : xfrm_gen_index(dir);
+ policy->curlft.add_time = (unsigned long)xtime.tv_sec;
+ policy->curlft.use_time = 0;
+ if (policy->lft.hard_add_expires_seconds &&
+ !mod_timer(&policy->timer, jiffies + HZ))
+ atomic_inc(&policy->refcnt);
+ write_unlock_bh(&xfrm_policy_lock);
+
+ if (pol) {
+ atomic_dec(&pol->refcnt);
+ xfrm_policy_kill(pol);
+ xfrm_pol_put(pol);
+ }
+ return 0;
+}
+
+struct xfrm_policy *xfrm_policy_delete(int dir, struct xfrm_selector *sel)
+{
+ struct xfrm_policy *pol, **p;
+
+ write_lock_bh(&xfrm_policy_lock);
+ for (p = &xfrm_policy_list[dir]; (pol=*p)!=NULL; p = &pol->next) {
+ if (memcmp(sel, &pol->selector, sizeof(*sel)) == 0) {
+ *p = pol->next;
+ break;
+ }
+ }
+ if (pol)
+ xfrm_policy_genid++;
+ write_unlock_bh(&xfrm_policy_lock);
+ return pol;
+}
+
+struct xfrm_policy *xfrm_policy_byid(int dir, u32 id, int delete)
+{
+ struct xfrm_policy *pol, **p;
+
+ write_lock_bh(&xfrm_policy_lock);
+ for (p = &xfrm_policy_list[id & 7]; (pol=*p)!=NULL; p = &pol->next) {
+ if (pol->index == id) {
+ if (delete)
+ *p = pol->next;
+ break;
+ }
+ }
+ if (pol) {
+ if (delete)
+ xfrm_policy_genid++;
+ else
+ atomic_inc(&pol->refcnt);
+ }
+ write_unlock_bh(&xfrm_policy_lock);
+ return pol;
+}
+
+void xfrm_policy_flush()
+{
+ struct xfrm_policy *xp;
+ int dir;
+
+ write_lock_bh(&xfrm_policy_lock);
+ for (dir = 0; dir < XFRM_POLICY_MAX; dir++) {
+ while ((xp = xfrm_policy_list[dir]) != NULL) {
+ xfrm_policy_list[dir] = xp->next;
+ write_unlock_bh(&xfrm_policy_lock);
+
+ xfrm_policy_kill(xp);
+ xfrm_pol_put(xp);
+
+ write_lock_bh(&xfrm_policy_lock);
+ }
+ }
+ xfrm_policy_genid++;
+ write_unlock_bh(&xfrm_policy_lock);
+}
+
+int xfrm_policy_walk(int (*func)(struct xfrm_policy *, int, int, void*),
+ void *data)
+{
+ struct xfrm_policy *xp;
+ int dir;
+ int count = 0;
+ int error = 0;
+
+ read_lock_bh(&xfrm_policy_lock);
+ for (dir = 0; dir < 2*XFRM_POLICY_MAX; dir++) {
+ for (xp = xfrm_policy_list[dir]; xp; xp = xp->next)
+ count++;
+ }
+
+ if (count == 0) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ for (dir = 0; dir < 2*XFRM_POLICY_MAX; dir++) {
+ for (xp = xfrm_policy_list[dir]; xp; xp = xp->next) {
+ error = func(xp, dir%XFRM_POLICY_MAX, --count, data);
+ if (error)
+ goto out;
+ }
+ }
+
+out:
+ read_unlock_bh(&xfrm_policy_lock);
+ return error;
+}
+
+
+/* Find policy to apply to this flow. */
+
+struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl,
+ unsigned short family)
+{
+ struct xfrm_policy *pol;
+
+ read_lock_bh(&xfrm_policy_lock);
+ for (pol = xfrm_policy_list[dir]; pol; pol = pol->next) {
+ struct xfrm_selector *sel = &pol->selector;
+ int match;
+
+ if (pol->family != family)
+ continue;
+
+ match = xfrm_selector_match(sel, fl, family);
+ if (match) {
+ atomic_inc(&pol->refcnt);
+ break;
+ }
+ }
+ read_unlock_bh(&xfrm_policy_lock);
+ return pol;
+}
+
+struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struct flowi *fl)
+{
+ struct xfrm_policy *pol;
+
+ read_lock_bh(&xfrm_policy_lock);
+ if ((pol = sk->policy[dir]) != NULL) {
+ int match;
+
+ match = xfrm_selector_match(&pol->selector, fl, sk->family);
+ if (match)
+ atomic_inc(&pol->refcnt);
+ else
+ pol = NULL;
+ }
+ read_unlock_bh(&xfrm_policy_lock);
+ return pol;
+}
+
+void xfrm_sk_policy_link(struct xfrm_policy *pol, int dir)
+{
+ pol->next = xfrm_policy_list[XFRM_POLICY_MAX+dir];
+ xfrm_policy_list[XFRM_POLICY_MAX+dir] = pol;
+ atomic_inc(&pol->refcnt);
+}
+
+void xfrm_sk_policy_unlink(struct xfrm_policy *pol, int dir)
+{
+ struct xfrm_policy **polp;
+
+ for (polp = &xfrm_policy_list[XFRM_POLICY_MAX+dir];
+ *polp != NULL; polp = &(*polp)->next) {
+ if (*polp == pol) {
+ *polp = pol->next;
+ atomic_dec(&pol->refcnt);
+ return;
+ }
+ }
+}
+
+int xfrm_sk_policy_insert(struct sock *sk, int dir, struct xfrm_policy *pol)
+{
+ struct xfrm_policy *old_pol;
+
+ write_lock_bh(&xfrm_policy_lock);
+ old_pol = sk->policy[dir];
+ sk->policy[dir] = pol;
+ if (pol) {
+ pol->curlft.add_time = (unsigned long)xtime.tv_sec;
+ pol->index = xfrm_gen_index(XFRM_POLICY_MAX+dir);
+ xfrm_sk_policy_link(pol, dir);
+ }
+ if (old_pol)
+ xfrm_sk_policy_unlink(old_pol, dir);
+ write_unlock_bh(&xfrm_policy_lock);
+
+ if (old_pol) {
+ xfrm_policy_kill(old_pol);
+ xfrm_pol_put(old_pol);
+ }
+ return 0;
+}
+
+static struct xfrm_policy *clone_policy(struct xfrm_policy *old, int dir)
+{
+ struct xfrm_policy *newp = xfrm_policy_alloc(GFP_ATOMIC);
+
+ if (newp) {
+ newp->selector = old->selector;
+ newp->lft = old->lft;
+ newp->curlft = old->curlft;
+ newp->action = old->action;
+ newp->flags = old->flags;
+ newp->xfrm_nr = old->xfrm_nr;
+ newp->index = old->index;
+ memcpy(newp->xfrm_vec, old->xfrm_vec,
+ newp->xfrm_nr*sizeof(struct xfrm_tmpl));
+ write_lock_bh(&xfrm_policy_lock);
+ xfrm_sk_policy_link(newp, dir);
+ write_unlock_bh(&xfrm_policy_lock);
+ }
+ return newp;
+}
+
+int __xfrm_sk_clone_policy(struct sock *sk)
+{
+ struct xfrm_policy *p0, *p1;
+ p0 = sk->policy[0];
+ p1 = sk->policy[1];
+ sk->policy[0] = NULL;
+ sk->policy[1] = NULL;
+ if (p0 && (sk->policy[0] = clone_policy(p0, 0)) == NULL)
+ return -ENOMEM;
+ if (p1 && (sk->policy[1] = clone_policy(p1, 1)) == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+void __xfrm_sk_free_policy(struct xfrm_policy *pol, int dir)
+{
+ write_lock_bh(&xfrm_policy_lock);
+ xfrm_sk_policy_unlink(pol, dir);
+ write_unlock_bh(&xfrm_policy_lock);
+
+ xfrm_policy_kill(pol);
+ xfrm_pol_put(pol);
+}
+
+/* Resolve list of templates for the flow, given policy. */
+
+static int
+xfrm_tmpl_resolve(struct xfrm_policy *policy, struct flowi *fl,
+ struct xfrm_state **xfrm,
+ unsigned short family)
+{
+ int nx;
+ int i, error;
+ xfrm_address_t *daddr = xfrm_flowi_daddr(fl, family);
+ xfrm_address_t *saddr = xfrm_flowi_saddr(fl, family);
+
+ for (nx=0, i = 0; i < policy->xfrm_nr; i++) {
+ struct xfrm_state *x;
+ xfrm_address_t *remote = daddr;
+ xfrm_address_t *local = saddr;
+ struct xfrm_tmpl *tmpl = &policy->xfrm_vec[i];
+
+ if (tmpl->mode) {
+ remote = &tmpl->id.daddr;
+ local = &tmpl->saddr;
+ }
+
+ x = xfrm_state_find(remote, local, fl, tmpl, policy, &error, family);
+
+ if (x && x->km.state == XFRM_STATE_VALID) {
+ xfrm[nx++] = x;
+ daddr = remote;
+ saddr = local;
+ continue;
+ }
+ if (x) {
+ error = (x->km.state == XFRM_STATE_ERROR ?
+ -EINVAL : -EAGAIN);
+ xfrm_state_put(x);
+ }
+
+ if (!tmpl->optional)
+ goto fail;
+ }
+ return nx;
+
+fail:
+ for (nx--; nx>=0; nx--)
+ xfrm_state_put(xfrm[nx]);
+ return error;
+}
+
+/* Check that the bundle accepts the flow and its components are
+ * still valid.
+ */
+
+static struct dst_entry *
+xfrm_find_bundle(struct flowi *fl, struct rtable *rt, struct xfrm_policy *policy, unsigned short family)
+{
+ struct dst_entry *x;
+ struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
+ if (unlikely(afinfo == NULL))
+ return ERR_PTR(-EINVAL);
+ x = afinfo->find_bundle(fl, rt, policy);
+ xfrm_policy_put_afinfo(afinfo);
+ return x;
+}
+
+/* Allocate chain of dst_entry's, attach known xfrm's, calculate
+ * all the metrics... Shortly, bundle a bundle.
+ */
+
+static int
+xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx,
+ struct flowi *fl, struct dst_entry **dst_p,
+ unsigned short family)
+{
+ int err;
+ struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
+ if (unlikely(afinfo == NULL))
+ return -EINVAL;
+ err = afinfo->bundle_create(policy, xfrm, nx, fl, dst_p);
+ xfrm_policy_put_afinfo(afinfo);
+ return err;
+}
+
+/* Main function: finds/creates a bundle for given flow.
+ *
+ * At the moment we eat a raw IP route. Mostly to speed up lookups
+ * on interfaces with disabled IPsec.
+ */
+int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
+ struct sock *sk, int flags)
+{
+ struct xfrm_policy *policy;
+ struct xfrm_state *xfrm[XFRM_MAX_DEPTH];
+ struct rtable *rt = (struct rtable*)*dst_p;
+ struct dst_entry *dst;
+ int nx = 0;
+ int err;
+ u32 genid;
+ u16 family = (*dst_p)->ops->family;
+
+ switch (family) {
+ case AF_INET:
+ if (!fl->fl4_src)
+ fl->fl4_src = rt->rt_src;
+ if (!fl->fl4_dst)
+ fl->fl4_dst = rt->rt_dst;
+ case AF_INET6:
+ /* Still not clear... */
+ default:
+ /* nothing */;
+ }
+
+restart:
+ genid = xfrm_policy_genid;
+ policy = NULL;
+ if (sk && sk->policy[1])
+ policy = xfrm_sk_policy_lookup(sk, XFRM_POLICY_OUT, fl);
+
+ if (!policy) {
+ /* To accelerate a bit... */
+ if ((rt->u.dst.flags & DST_NOXFRM) || !xfrm_policy_list[XFRM_POLICY_OUT])
+ return 0;
+
+ policy = flow_lookup(XFRM_POLICY_OUT, fl, family);
+ }
+
+ if (!policy)
+ return 0;
+
+ policy->curlft.use_time = (unsigned long)xtime.tv_sec;
+
+ switch (policy->action) {
+ case XFRM_POLICY_BLOCK:
+ /* Prohibit the flow */
+ xfrm_pol_put(policy);
+ return -EPERM;
+
+ case XFRM_POLICY_ALLOW:
+ if (policy->xfrm_nr == 0) {
+ /* Flow passes not transformed. */
+ xfrm_pol_put(policy);
+ return 0;
+ }
+
+ /* Try to find matching bundle.
+ *
+ * LATER: help from flow cache. It is optional, this
+ * is required only for output policy.
+ */
+ dst = xfrm_find_bundle(fl, rt, policy, family);
+ if (IS_ERR(dst)) {
+ xfrm_pol_put(policy);
+ return PTR_ERR(dst);
+ }
+
+ if (dst)
+ break;
+
+ nx = xfrm_tmpl_resolve(policy, fl, xfrm, family);
+
+ if (unlikely(nx<0)) {
+ err = nx;
+ if (err == -EAGAIN) {
+ struct task_struct *tsk = current;
+ DECLARE_WAITQUEUE(wait, tsk);
+ if (!flags)
+ goto error;
+
+ __set_task_state(tsk, TASK_INTERRUPTIBLE);
+ add_wait_queue(&km_waitq, &wait);
+ err = xfrm_tmpl_resolve(policy, fl, xfrm, family);
+ if (err == -EAGAIN)
+ schedule();
+ __set_task_state(tsk, TASK_RUNNING);
+ remove_wait_queue(&km_waitq, &wait);
+
+ if (err == -EAGAIN && signal_pending(current)) {
+ err = -ERESTART;
+ goto error;
+ }
+ if (err == -EAGAIN ||
+ genid != xfrm_policy_genid)
+ goto restart;
+ }
+ if (err)
+ goto error;
+ } else if (nx == 0) {
+ /* Flow passes not transformed. */
+ xfrm_pol_put(policy);
+ return 0;
+ }
+
+ dst = &rt->u.dst;
+ err = xfrm_bundle_create(policy, xfrm, nx, fl, &dst, family);
+
+ if (unlikely(err)) {
+ int i;
+ for (i=0; i<nx; i++)
+ xfrm_state_put(xfrm[i]);
+ goto error;
+ }
+
+ write_lock_bh(&policy->lock);
+ if (unlikely(policy->dead)) {
+ /* Wow! While we worked on resolving, this
+ * policy has gone. Retry. It is not paranoia,
+ * we just cannot enlist new bundle to dead object.
+ */
+ write_unlock_bh(&policy->lock);
+
+ xfrm_pol_put(policy);
+ if (dst)
+ dst_free(dst);
+ goto restart;
+ }
+ dst->next = policy->bundles;
+ policy->bundles = dst;
+ dst_hold(dst);
+ write_unlock_bh(&policy->lock);
+ }
+ *dst_p = dst;
+ ip_rt_put(rt);
+ xfrm_pol_put(policy);
+ return 0;
+
+error:
+ ip_rt_put(rt);
+ xfrm_pol_put(policy);
+ *dst_p = NULL;
+ return err;
+}
+
+/* When skb is transformed back to its "native" form, we have to
+ * check policy restrictions. At the moment we make this in maximally
+ * stupid way. Shame on me. :-) Of course, connected sockets must
+ * have policy cached at them.
+ */
+
+static inline int
+xfrm_state_ok(struct xfrm_tmpl *tmpl, struct xfrm_state *x,
+ unsigned short family)
+{
+ return x->id.proto == tmpl->id.proto &&
+ (x->id.spi == tmpl->id.spi || !tmpl->id.spi) &&
+ x->props.mode == tmpl->mode &&
+ (tmpl->aalgos & (1<<x->props.aalgo)) &&
+ !(x->props.mode && xfrm_state_addr_cmp(tmpl, x, family));
+}
+
+static inline int
+xfrm_policy_ok(struct xfrm_tmpl *tmpl, struct sec_path *sp, int idx,
+ unsigned short family)
+{
+ for (; idx < sp->len; idx++) {
+ if (xfrm_state_ok(tmpl, sp->xvec[idx], family))
+ return ++idx;
+ }
+ return -1;
+}
+
+static int
+_decode_session(struct sk_buff *skb, struct flowi *fl, unsigned short family)
+{
+ struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
+
+ if (unlikely(afinfo == NULL))
+ return -EAFNOSUPPORT;
+
+ afinfo->decode_session(skb, fl);
+ xfrm_policy_put_afinfo(afinfo);
+ return 0;
+}
+
+int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
+ unsigned short family)
+{
+ struct xfrm_policy *pol;
+ struct flowi fl;
+
+ if (_decode_session(skb, &fl, family) < 0)
+ return 0;
+
+ /* First, check used SA against their selectors. */
+ if (skb->sp) {
+ int i;
+
+ for (i=skb->sp->len-1; i>=0; i--) {
+ if (!xfrm_selector_match(&skb->sp->xvec[i]->sel, &fl, family))
+ return 0;
+ }
+ }
+
+ pol = NULL;
+ if (sk && sk->policy[dir])
+ pol = xfrm_sk_policy_lookup(sk, dir, &fl);
+
+ if (!pol)
+ pol = flow_lookup(dir, &fl, family);
+
+ if (!pol)
+ return 1;
+
+ pol->curlft.use_time = (unsigned long)xtime.tv_sec;
+
+ if (pol->action == XFRM_POLICY_ALLOW) {
+ if (pol->xfrm_nr != 0) {
+ struct sec_path *sp;
+ static struct sec_path dummy;
+ int i, k;
+
+ if ((sp = skb->sp) == NULL)
+ sp = &dummy;
+
+ /* For each tmpl search corresponding xfrm.
+ * Order is _important_. Later we will implement
+ * some barriers, but at the moment barriers
+ * are implied between each two transformations.
+ */
+ for (i = pol->xfrm_nr-1, k = 0; i >= 0; i--) {
+ k = xfrm_policy_ok(pol->xfrm_vec+i, sp, k, family);
+ if (k < 0)
+ goto reject;
+ }
+ }
+ xfrm_pol_put(pol);
+ return 1;
+ }
+
+reject:
+ xfrm_pol_put(pol);
+ return 0;
+}
+
+int __xfrm_route_forward(struct sk_buff *skb, unsigned short family)
+{
+ struct flowi fl;
+
+ if (_decode_session(skb, &fl, family) < 0)
+ return 0;
+
+ return xfrm_lookup(&skb->dst, &fl, NULL, 0) == 0;
+}
+
+/* Optimize later using cookies and generation ids. */
+
+static struct dst_entry *xfrm_dst_check(struct dst_entry *dst, u32 cookie)
+{
+ struct dst_entry *child = dst;
+
+ while (child) {
+ if (child->obsolete > 0 ||
+ (child->xfrm && child->xfrm->km.state != XFRM_STATE_VALID)) {
+ dst_release(dst);
+ return NULL;
+ }
+ child = child->child;
+ }
+
+ return dst;
+}
+
+static void xfrm_dst_destroy(struct dst_entry *dst)
+{
+ xfrm_state_put(dst->xfrm);
+ dst->xfrm = NULL;
+}
+
+static void xfrm_link_failure(struct sk_buff *skb)
+{
+ /* Impossible. Such dst must be popped before reaches point of failure. */
+ return;
+}
+
+static struct dst_entry *xfrm_negative_advice(struct dst_entry *dst)
+{
+ if (dst) {
+ if (dst->obsolete) {
+ dst_release(dst);
+ dst = NULL;
+ }
+ }
+ return dst;
+}
+
+static void __xfrm_garbage_collect(void)
+{
+ int i;
+ struct xfrm_policy *pol;
+ struct dst_entry *dst, **dstp, *gc_list = NULL;
+
+ read_lock_bh(&xfrm_policy_lock);
+ for (i=0; i<2*XFRM_POLICY_MAX; i++) {
+ for (pol = xfrm_policy_list[i]; pol; pol = pol->next) {
+ write_lock(&pol->lock);
+ dstp = &pol->bundles;
+ while ((dst=*dstp) != NULL) {
+ if (atomic_read(&dst->__refcnt) == 0) {
+ *dstp = dst->next;
+ dst->next = gc_list;
+ gc_list = dst;
+ } else {
+ dstp = &dst->next;
+ }
+ }
+ write_unlock(&pol->lock);
+ }
+ }
+ read_unlock_bh(&xfrm_policy_lock);
+
+ while (gc_list) {
+ dst = gc_list;
+ gc_list = dst->next;
+ dst_free(dst);
+ }
+}
+
+static int bundle_depends_on(struct dst_entry *dst, struct xfrm_state *x)
+{
+ do {
+ if (dst->xfrm == x)
+ return 1;
+ } while ((dst = dst->child) != NULL);
+ return 0;
+}
+
+int xfrm_flush_bundles(struct xfrm_state *x)
+{
+ int i;
+ struct xfrm_policy *pol;
+ struct dst_entry *dst, **dstp, *gc_list = NULL;
+
+ read_lock_bh(&xfrm_policy_lock);
+ for (i=0; i<2*XFRM_POLICY_MAX; i++) {
+ for (pol = xfrm_policy_list[i]; pol; pol = pol->next) {
+ write_lock(&pol->lock);
+ dstp = &pol->bundles;
+ while ((dst=*dstp) != NULL) {
+ if (bundle_depends_on(dst, x)) {
+ *dstp = dst->next;
+ dst->next = gc_list;
+ gc_list = dst;
+ } else {
+ dstp = &dst->next;
+ }
+ }
+ write_unlock(&pol->lock);
+ }
+ }
+ read_unlock_bh(&xfrm_policy_lock);
+
+ while (gc_list) {
+ dst = gc_list;
+ gc_list = dst->next;
+ dst_free(dst);
+ }
+
+ return 0;
+}
+
+/* Well... that's _TASK_. We need to scan through transformation
+ * list and figure out what mss tcp should generate in order to
+ * final datagram fit to mtu. Mama mia... :-)
+ *
+ * Apparently, some easy way exists, but we used to choose the most
+ * bizarre ones. :-) So, raising Kalashnikov... tra-ta-ta.
+ *
+ * Consider this function as something like dark humour. :-)
+ */
+static int xfrm_get_mss(struct dst_entry *dst, u32 mtu)
+{
+ int res = mtu - dst->header_len;
+
+ for (;;) {
+ struct dst_entry *d = dst;
+ int m = res;
+
+ do {
+ struct xfrm_state *x = d->xfrm;
+ if (x) {
+ spin_lock_bh(&x->lock);
+ if (x->km.state == XFRM_STATE_VALID &&
+ x->type && x->type->get_max_size)
+ m = x->type->get_max_size(d->xfrm, m);
+ else
+ m += x->props.header_len;
+ spin_unlock_bh(&x->lock);
+ }
+ } while ((d = d->child) != NULL);
+
+ if (m <= mtu)
+ break;
+ res -= (m - mtu);
+ if (res < 88)
+ return mtu;
+ }
+
+ return res + dst->header_len;
+}
+
+int xfrm_policy_register_afinfo(struct xfrm_policy_afinfo *afinfo)
+{
+ int err = 0;
+ if (unlikely(afinfo == NULL))
+ return -EINVAL;
+ if (unlikely(afinfo->family >= NPROTO))
+ return -EAFNOSUPPORT;
+ write_lock(&xfrm_policy_afinfo_lock);
+ if (unlikely(xfrm_policy_afinfo[afinfo->family] != NULL))
+ err = -ENOBUFS;
+ else {
+ struct dst_ops *dst_ops = afinfo->dst_ops;
+ if (likely(dst_ops->kmem_cachep == NULL))
+ dst_ops->kmem_cachep = xfrm_dst_cache;
+ if (likely(dst_ops->check == NULL))
+ dst_ops->check = xfrm_dst_check;
+ if (likely(dst_ops->destroy == NULL))
+ dst_ops->destroy = xfrm_dst_destroy;
+ if (likely(dst_ops->negative_advice == NULL))
+ dst_ops->negative_advice = xfrm_negative_advice;
+ if (likely(dst_ops->link_failure == NULL))
+ dst_ops->link_failure = xfrm_link_failure;
+ if (likely(dst_ops->get_mss == NULL))
+ dst_ops->get_mss = xfrm_get_mss;
+ if (likely(afinfo->garbage_collect == NULL))
+ afinfo->garbage_collect = __xfrm_garbage_collect;
+ xfrm_policy_afinfo[afinfo->family] = afinfo;
+ }
+ write_unlock(&xfrm_policy_afinfo_lock);
+ return err;
+}
+
+int xfrm_policy_unregister_afinfo(struct xfrm_policy_afinfo *afinfo)
+{
+ int err = 0;
+ if (unlikely(afinfo == NULL))
+ return -EINVAL;
+ if (unlikely(afinfo->family >= NPROTO))
+ return -EAFNOSUPPORT;
+ write_lock(&xfrm_policy_afinfo_lock);
+ if (likely(xfrm_policy_afinfo[afinfo->family] != NULL)) {
+ if (unlikely(xfrm_policy_afinfo[afinfo->family] != afinfo))
+ err = -EINVAL;
+ else {
+ struct dst_ops *dst_ops = afinfo->dst_ops;
+ xfrm_policy_afinfo[afinfo->family] = NULL;
+ dst_ops->kmem_cachep = NULL;
+ dst_ops->check = NULL;
+ dst_ops->destroy = NULL;
+ dst_ops->negative_advice = NULL;
+ dst_ops->link_failure = NULL;
+ dst_ops->get_mss = NULL;
+ afinfo->garbage_collect = NULL;
+ }
+ }
+ write_unlock(&xfrm_policy_afinfo_lock);
+ return err;
+}
+
+struct xfrm_policy_afinfo *xfrm_policy_get_afinfo(unsigned short family)
+{
+ struct xfrm_policy_afinfo *afinfo;
+ if (unlikely(family >= NPROTO))
+ return NULL;
+ read_lock(&xfrm_policy_afinfo_lock);
+ afinfo = xfrm_policy_afinfo[family];
+ if (likely(afinfo != NULL))
+ read_lock(&afinfo->lock);
+ read_unlock(&xfrm_policy_afinfo_lock);
+ return afinfo;
+}
+
+void xfrm_policy_put_afinfo(struct xfrm_policy_afinfo *afinfo)
+{
+ if (unlikely(afinfo == NULL))
+ return;
+ read_unlock(&afinfo->lock);
+}
+
+void __init xfrm_policy_init(void)
+{
+ xfrm_dst_cache = kmem_cache_create("xfrm_dst_cache",
+ sizeof(struct xfrm_dst),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+ if (!xfrm_dst_cache)
+ panic("XFRM: failed to allocate xfrm_dst_cache\n");
+}
+
+void __init xfrm_init(void)
+{
+ xfrm_state_init();
+ flow_cache_init();
+ xfrm_policy_init();
+}
+
--- /dev/null
+/*
+ * xfrm_state.c
+ *
+ * Changes:
+ * Mitsuru KANDA @USAGI
+ * Kazunori MIYAZAWA @USAGI
+ * Kunihiro Ishiguro
+ * IPv6 support
+ * YOSHIFUJI Hideaki @USAGI
+ * Split up af-specific functions
+ *
+ */
+
+#include <net/xfrm.h>
+#include <linux/pfkeyv2.h>
+#include <linux/ipsec.h>
+
+/* Each xfrm_state may be linked to two tables:
+
+ 1. Hash table by (spi,daddr,ah/esp) to find SA by SPI. (input,ctl)
+ 2. Hash table by daddr to find what SAs exist for given
+ destination/tunnel endpoint. (output)
+ */
+
+static spinlock_t xfrm_state_lock = SPIN_LOCK_UNLOCKED;
+
+/* Hash table to find appropriate SA towards given target (endpoint
+ * of tunnel or destination of transport mode) allowed by selector.
+ *
+ * Main use is finding SA after policy selected tunnel or transport mode.
+ * Also, it can be used by ah/esp icmp error handler to find offending SA.
+ */
+static struct list_head xfrm_state_bydst[XFRM_DST_HSIZE];
+static struct list_head xfrm_state_byspi[XFRM_DST_HSIZE];
+
+DECLARE_WAIT_QUEUE_HEAD(km_waitq);
+
+static rwlock_t xfrm_state_afinfo_lock = RW_LOCK_UNLOCKED;
+static struct xfrm_state_afinfo *xfrm_state_afinfo[NPROTO];
+
+static void __xfrm_state_delete(struct xfrm_state *x);
+
+static inline unsigned long make_jiffies(long secs)
+{
+ if (secs >= (MAX_SCHEDULE_TIMEOUT-1)/HZ)
+ return MAX_SCHEDULE_TIMEOUT-1;
+ else
+ return secs*HZ;
+}
+
+static void xfrm_timer_handler(unsigned long data)
+{
+ struct xfrm_state *x = (struct xfrm_state*)data;
+ unsigned long now = (unsigned long)xtime.tv_sec;
+ long next = LONG_MAX;
+ int warn = 0;
+
+ spin_lock(&x->lock);
+ if (x->km.state == XFRM_STATE_DEAD)
+ goto out;
+ if (x->km.state == XFRM_STATE_EXPIRED)
+ goto expired;
+ if (x->lft.hard_add_expires_seconds) {
+ long tmo = x->lft.hard_add_expires_seconds +
+ x->curlft.add_time - now;
+ if (tmo <= 0)
+ goto expired;
+ if (tmo < next)
+ next = tmo;
+ }
+ if (x->lft.hard_use_expires_seconds && x->curlft.use_time) {
+ long tmo = x->lft.hard_use_expires_seconds +
+ x->curlft.use_time - now;
+ if (tmo <= 0)
+ goto expired;
+ if (tmo < next)
+ next = tmo;
+ }
+ if (x->km.dying)
+ goto resched;
+ if (x->lft.soft_add_expires_seconds) {
+ long tmo = x->lft.soft_add_expires_seconds +
+ x->curlft.add_time - now;
+ if (tmo <= 0)
+ warn = 1;
+ else if (tmo < next)
+ next = tmo;
+ }
+ if (x->lft.soft_use_expires_seconds && x->curlft.use_time) {
+ long tmo = x->lft.soft_use_expires_seconds +
+ x->curlft.use_time - now;
+ if (tmo <= 0)
+ warn = 1;
+ else if (tmo < next)
+ next = tmo;
+ }
+
+ if (warn)
+ km_warn_expired(x);
+resched:
+ if (next != LONG_MAX &&
+ !mod_timer(&x->timer, jiffies + make_jiffies(next)))
+ atomic_inc(&x->refcnt);
+ goto out;
+
+expired:
+ if (x->km.state == XFRM_STATE_ACQ && x->id.spi == 0) {
+ x->km.state = XFRM_STATE_EXPIRED;
+ wake_up(&km_waitq);
+ next = 2;
+ goto resched;
+ }
+ if (x->id.spi != 0)
+ km_expired(x);
+ __xfrm_state_delete(x);
+
+out:
+ spin_unlock(&x->lock);
+ xfrm_state_put(x);
+}
+
+struct xfrm_state *xfrm_state_alloc(void)
+{
+ struct xfrm_state *x;
+
+ x = kmalloc(sizeof(struct xfrm_state), GFP_ATOMIC);
+
+ if (x) {
+ memset(x, 0, sizeof(struct xfrm_state));
+ atomic_set(&x->refcnt, 1);
+ INIT_LIST_HEAD(&x->bydst);
+ INIT_LIST_HEAD(&x->byspi);
+ init_timer(&x->timer);
+ x->timer.function = xfrm_timer_handler;
+ x->timer.data = (unsigned long)x;
+ x->curlft.add_time = (unsigned long)xtime.tv_sec;
+ x->lft.soft_byte_limit = XFRM_INF;
+ x->lft.soft_packet_limit = XFRM_INF;
+ x->lft.hard_byte_limit = XFRM_INF;
+ x->lft.hard_packet_limit = XFRM_INF;
+ x->lock = SPIN_LOCK_UNLOCKED;
+ }
+ return x;
+}
+
+void __xfrm_state_destroy(struct xfrm_state *x)
+{
+ BUG_TRAP(x->km.state == XFRM_STATE_DEAD);
+ if (del_timer(&x->timer))
+ BUG();
+ if (x->aalg)
+ kfree(x->aalg);
+ if (x->ealg)
+ kfree(x->ealg);
+ if (x->calg)
+ kfree(x->calg);
+ if (x->type)
+ xfrm_put_type(x->type);
+ kfree(x);
+}
+
+static void __xfrm_state_delete(struct xfrm_state *x)
+{
+ int kill = 0;
+
+ if (x->km.state != XFRM_STATE_DEAD) {
+ x->km.state = XFRM_STATE_DEAD;
+ kill = 1;
+ spin_lock(&xfrm_state_lock);
+ list_del(&x->bydst);
+ atomic_dec(&x->refcnt);
+ if (x->id.spi) {
+ list_del(&x->byspi);
+ atomic_dec(&x->refcnt);
+ }
+ spin_unlock(&xfrm_state_lock);
+ if (del_timer(&x->timer))
+ atomic_dec(&x->refcnt);
+ if (atomic_read(&x->refcnt) != 1)
+ xfrm_flush_bundles(x);
+ }
+
+ if (kill && x->type)
+ x->type->destructor(x);
+ wake_up(&km_waitq);
+}
+
+void xfrm_state_delete(struct xfrm_state *x)
+{
+ spin_lock_bh(&x->lock);
+ __xfrm_state_delete(x);
+ spin_unlock_bh(&x->lock);
+}
+
+void xfrm_state_flush(u8 proto)
+{
+ int i;
+ struct xfrm_state *x;
+
+ spin_lock_bh(&xfrm_state_lock);
+ for (i = 0; i < XFRM_DST_HSIZE; i++) {
+restart:
+ list_for_each_entry(x, xfrm_state_bydst+i, bydst) {
+ if (proto == IPSEC_PROTO_ANY || x->id.proto == proto) {
+ atomic_inc(&x->refcnt);
+ spin_unlock_bh(&xfrm_state_lock);
+
+ xfrm_state_delete(x);
+ xfrm_state_put(x);
+
+ spin_lock_bh(&xfrm_state_lock);
+ goto restart;
+ }
+ }
+ }
+ spin_unlock_bh(&xfrm_state_lock);
+ wake_up(&km_waitq);
+}
+
+static int
+xfrm_init_tempsel(struct xfrm_state *x, struct flowi *fl,
+ struct xfrm_tmpl *tmpl,
+ xfrm_address_t *daddr, xfrm_address_t *saddr,
+ unsigned short family)
+{
+ struct xfrm_state_afinfo *afinfo = xfrm_state_get_afinfo(family);
+ if (!afinfo)
+ return -1;
+ afinfo->init_tempsel(x, fl, tmpl, daddr, saddr);
+ xfrm_state_put_afinfo(afinfo);
+ return 0;
+}
+
+struct xfrm_state *
+xfrm_state_find(xfrm_address_t *daddr, xfrm_address_t *saddr,
+ struct flowi *fl, struct xfrm_tmpl *tmpl,
+ struct xfrm_policy *pol, int *err,
+ unsigned short family)
+{
+ unsigned h = xfrm_dst_hash(daddr, family);
+ struct xfrm_state *x;
+ int acquire_in_progress = 0;
+ int error = 0;
+ struct xfrm_state *best = NULL;
+
+ spin_lock_bh(&xfrm_state_lock);
+ list_for_each_entry(x, xfrm_state_bydst+h, bydst) {
+ if (x->props.family == family &&
+ x->props.reqid == tmpl->reqid &&
+ xfrm_state_addr_check(x, daddr, saddr, family) &&
+ tmpl->mode == x->props.mode &&
+ tmpl->id.proto == x->id.proto) {
+ /* Resolution logic:
+ 1. There is a valid state with matching selector.
+ Done.
+ 2. Valid state with inappropriate selector. Skip.
+
+ Entering area of "sysdeps".
+
+ 3. If state is not valid, selector is temporary,
+ it selects only session which triggered
+ previous resolution. Key manager will do
+ something to install a state with proper
+ selector.
+ */
+ if (x->km.state == XFRM_STATE_VALID) {
+ if (!xfrm_selector_match(&x->sel, fl, family))
+ continue;
+ if (!best ||
+ best->km.dying > x->km.dying ||
+ (best->km.dying == x->km.dying &&
+ best->curlft.add_time < x->curlft.add_time))
+ best = x;
+ } else if (x->km.state == XFRM_STATE_ACQ) {
+ acquire_in_progress = 1;
+ } else if (x->km.state == XFRM_STATE_ERROR ||
+ x->km.state == XFRM_STATE_EXPIRED) {
+ if (xfrm_selector_match(&x->sel, fl, family))
+ error = 1;
+ }
+ }
+ }
+
+ if (best) {
+ atomic_inc(&best->refcnt);
+ spin_unlock_bh(&xfrm_state_lock);
+ return best;
+ }
+
+ x = NULL;
+ if (!error && !acquire_in_progress &&
+ ((x = xfrm_state_alloc()) != NULL)) {
+ /* Initialize temporary selector matching only
+ * to current session. */
+ xfrm_init_tempsel(x, fl, tmpl, daddr, saddr, family);
+
+ if (km_query(x, tmpl, pol) == 0) {
+ x->km.state = XFRM_STATE_ACQ;
+ list_add_tail(&x->bydst, xfrm_state_bydst+h);
+ atomic_inc(&x->refcnt);
+ if (x->id.spi) {
+ h = xfrm_spi_hash(&x->id.daddr, x->id.spi, x->id.proto, family);
+ list_add(&x->byspi, xfrm_state_byspi+h);
+ atomic_inc(&x->refcnt);
+ }
+ x->lft.hard_add_expires_seconds = XFRM_ACQ_EXPIRES;
+ atomic_inc(&x->refcnt);
+ mod_timer(&x->timer, XFRM_ACQ_EXPIRES*HZ);
+ } else {
+ x->km.state = XFRM_STATE_DEAD;
+ xfrm_state_put(x);
+ x = NULL;
+ error = 1;
+ }
+ }
+ spin_unlock_bh(&xfrm_state_lock);
+ if (!x)
+ *err = acquire_in_progress ? -EAGAIN :
+ (error ? -ESRCH : -ENOMEM);
+ return x;
+}
+
+void xfrm_state_insert(struct xfrm_state *x)
+{
+ unsigned h = xfrm_dst_hash(&x->id.daddr, x->props.family);
+
+ spin_lock_bh(&xfrm_state_lock);
+ list_add(&x->bydst, xfrm_state_bydst+h);
+ atomic_inc(&x->refcnt);
+
+ h = xfrm_spi_hash(&x->id.daddr, x->id.spi, x->id.proto, x->props.family);
+
+ list_add(&x->byspi, xfrm_state_byspi+h);
+ atomic_inc(&x->refcnt);
+
+ if (!mod_timer(&x->timer, jiffies + HZ))
+ atomic_inc(&x->refcnt);
+
+ spin_unlock_bh(&xfrm_state_lock);
+ wake_up(&km_waitq);
+}
+
+int xfrm_state_check_expire(struct xfrm_state *x)
+{
+ if (!x->curlft.use_time)
+ x->curlft.use_time = (unsigned long)xtime.tv_sec;
+
+ if (x->km.state != XFRM_STATE_VALID)
+ return -EINVAL;
+
+ if (x->curlft.bytes >= x->lft.hard_byte_limit ||
+ x->curlft.packets >= x->lft.hard_packet_limit) {
+ km_expired(x);
+ if (!mod_timer(&x->timer, jiffies + XFRM_ACQ_EXPIRES*HZ))
+ atomic_inc(&x->refcnt);
+ return -EINVAL;
+ }
+
+ if (!x->km.dying &&
+ (x->curlft.bytes >= x->lft.soft_byte_limit ||
+ x->curlft.packets >= x->lft.soft_packet_limit))
+ km_warn_expired(x);
+ return 0;
+}
+
+int xfrm_state_check_space(struct xfrm_state *x, struct sk_buff *skb)
+{
+ int nhead = x->props.header_len + LL_RESERVED_SPACE(skb->dst->dev)
+ - skb_headroom(skb);
+
+ if (nhead > 0)
+ return pskb_expand_head(skb, nhead, 0, GFP_ATOMIC);
+
+ /* Check tail too... */
+ return 0;
+}
+
+struct xfrm_state *
+xfrm_state_lookup(xfrm_address_t *daddr, u32 spi, u8 proto,
+ unsigned short family)
+{
+ struct xfrm_state *x;
+ struct xfrm_state_afinfo *afinfo = xfrm_state_get_afinfo(family);
+ if (!afinfo)
+ return NULL;
+
+ spin_lock_bh(&xfrm_state_lock);
+ x = afinfo->state_lookup(daddr, spi, proto);
+ spin_unlock_bh(&xfrm_state_lock);
+ xfrm_state_put_afinfo(afinfo);
+ return x;
+}
+
+struct xfrm_state *
+xfrm_find_acq(u8 mode, u16 reqid, u8 proto,
+ xfrm_address_t *daddr, xfrm_address_t *saddr,
+ int create, unsigned short family)
+{
+ struct xfrm_state *x;
+ struct xfrm_state_afinfo *afinfo = xfrm_state_get_afinfo(family);
+ if (!afinfo)
+ return NULL;
+
+ spin_lock_bh(&xfrm_state_lock);
+ x = afinfo->find_acq(mode, reqid, proto, daddr, saddr, create);
+ spin_unlock_bh(&xfrm_state_lock);
+ xfrm_state_put_afinfo(afinfo);
+ return x;
+}
+
+/* Silly enough, but I'm lazy to build resolution list */
+
+struct xfrm_state * xfrm_find_acq_byseq(u32 seq)
+{
+ int i;
+ struct xfrm_state *x;
+
+ spin_lock_bh(&xfrm_state_lock);
+ for (i = 0; i < XFRM_DST_HSIZE; i++) {
+ list_for_each_entry(x, xfrm_state_bydst+i, bydst) {
+ if (x->km.seq == seq) {
+ atomic_inc(&x->refcnt);
+ spin_unlock_bh(&xfrm_state_lock);
+ return x;
+ }
+ }
+ }
+ spin_unlock_bh(&xfrm_state_lock);
+ return NULL;
+}
+
+u32 xfrm_get_acqseq(void)
+{
+ u32 res;
+ static u32 acqseq;
+ static spinlock_t acqseq_lock = SPIN_LOCK_UNLOCKED;
+
+ spin_lock_bh(&acqseq_lock);
+ res = (++acqseq ? : ++acqseq);
+ spin_unlock_bh(&acqseq_lock);
+ return res;
+}
+
+void
+xfrm_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi)
+{
+ u32 h;
+ struct xfrm_state *x0;
+
+ if (x->id.spi)
+ return;
+
+ if (minspi == maxspi) {
+ x0 = xfrm_state_lookup(&x->id.daddr, minspi, x->id.proto, x->props.family);
+ if (x0) {
+ xfrm_state_put(x0);
+ return;
+ }
+ x->id.spi = minspi;
+ } else {
+ u32 spi = 0;
+ minspi = ntohl(minspi);
+ maxspi = ntohl(maxspi);
+ for (h=0; h<maxspi-minspi+1; h++) {
+ spi = minspi + net_random()%(maxspi-minspi+1);
+ x0 = xfrm_state_lookup(&x->id.daddr, minspi, x->id.proto, x->props.family);
+ if (x0 == NULL)
+ break;
+ xfrm_state_put(x0);
+ }
+ x->id.spi = htonl(spi);
+ }
+ if (x->id.spi) {
+ spin_lock_bh(&xfrm_state_lock);
+ h = xfrm_spi_hash(&x->id.daddr, x->id.spi, x->id.proto, x->props.family);
+ list_add(&x->byspi, xfrm_state_byspi+h);
+ atomic_inc(&x->refcnt);
+ spin_unlock_bh(&xfrm_state_lock);
+ wake_up(&km_waitq);
+ }
+}
+
+int xfrm_state_walk(u8 proto, int (*func)(struct xfrm_state *, int, void*),
+ void *data)
+{
+ int i;
+ struct xfrm_state *x;
+ int count = 0;
+ int err = 0;
+
+ spin_lock_bh(&xfrm_state_lock);
+ for (i = 0; i < XFRM_DST_HSIZE; i++) {
+ list_for_each_entry(x, xfrm_state_bydst+i, bydst) {
+ if (proto == IPSEC_PROTO_ANY || x->id.proto == proto)
+ count++;
+ }
+ }
+ if (count == 0) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ for (i = 0; i < XFRM_DST_HSIZE; i++) {
+ list_for_each_entry(x, xfrm_state_bydst+i, bydst) {
+ if (proto != IPSEC_PROTO_ANY && x->id.proto != proto)
+ continue;
+ err = func(x, --count, data);
+ if (err)
+ goto out;
+ }
+ }
+out:
+ spin_unlock_bh(&xfrm_state_lock);
+ return err;
+}
+
+
+int xfrm_replay_check(struct xfrm_state *x, u32 seq)
+{
+ u32 diff;
+
+ seq = ntohl(seq);
+
+ if (unlikely(seq == 0))
+ return -EINVAL;
+
+ if (likely(seq > x->replay.seq))
+ return 0;
+
+ diff = x->replay.seq - seq;
+ if (diff >= x->props.replay_window) {
+ x->stats.replay_window++;
+ return -EINVAL;
+ }
+
+ if (x->replay.bitmap & (1U << diff)) {
+ x->stats.replay++;
+ return -EINVAL;
+ }
+ return 0;
+}
+
+void xfrm_replay_advance(struct xfrm_state *x, u32 seq)
+{
+ u32 diff;
+
+ seq = ntohl(seq);
+
+ if (seq > x->replay.seq) {
+ diff = seq - x->replay.seq;
+ if (diff < x->props.replay_window)
+ x->replay.bitmap = ((x->replay.bitmap) << diff) | 1;
+ else
+ x->replay.bitmap = 1;
+ x->replay.seq = seq;
+ } else {
+ diff = x->replay.seq - seq;
+ x->replay.bitmap |= (1U << diff);
+ }
+}
+
+int xfrm_check_selectors(struct xfrm_state **x, int n, struct flowi *fl)
+{
+ int i;
+
+ for (i=0; i<n; i++) {
+ int match;
+ match = xfrm_selector_match(&x[i]->sel, fl, x[i]->props.family);
+ if (!match)
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static struct list_head xfrm_km_list = LIST_HEAD_INIT(xfrm_km_list);
+static rwlock_t xfrm_km_lock = RW_LOCK_UNLOCKED;
+
+void km_warn_expired(struct xfrm_state *x)
+{
+ struct xfrm_mgr *km;
+
+ x->km.dying = 1;
+ read_lock(&xfrm_km_lock);
+ list_for_each_entry(km, &xfrm_km_list, list)
+ km->notify(x, 0);
+ read_unlock(&xfrm_km_lock);
+}
+
+void km_expired(struct xfrm_state *x)
+{
+ struct xfrm_mgr *km;
+
+ x->km.state = XFRM_STATE_EXPIRED;
+
+ read_lock(&xfrm_km_lock);
+ list_for_each_entry(km, &xfrm_km_list, list)
+ km->notify(x, 1);
+ read_unlock(&xfrm_km_lock);
+ wake_up(&km_waitq);
+}
+
+int km_query(struct xfrm_state *x, struct xfrm_tmpl *t, struct xfrm_policy *pol)
+{
+ int err = -EINVAL;
+ struct xfrm_mgr *km;
+
+ read_lock(&xfrm_km_lock);
+ list_for_each_entry(km, &xfrm_km_list, list) {
+ err = km->acquire(x, t, pol, XFRM_POLICY_OUT);
+ if (!err)
+ break;
+ }
+ read_unlock(&xfrm_km_lock);
+ return err;
+}
+
+int xfrm_user_policy(struct sock *sk, int optname, u8 *optval, int optlen)
+{
+ int err;
+ u8 *data;
+ struct xfrm_mgr *km;
+ struct xfrm_policy *pol = NULL;
+
+ if (optlen <= 0 || optlen > PAGE_SIZE)
+ return -EMSGSIZE;
+
+ data = kmalloc(optlen, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ err = -EFAULT;
+ if (copy_from_user(data, optval, optlen))
+ goto out;
+
+ err = -EINVAL;
+ read_lock(&xfrm_km_lock);
+ list_for_each_entry(km, &xfrm_km_list, list) {
+ pol = km->compile_policy(sk->family, optname, data, optlen, &err);
+ if (err >= 0)
+ break;
+ }
+ read_unlock(&xfrm_km_lock);
+
+ if (err >= 0) {
+ xfrm_sk_policy_insert(sk, err, pol);
+ err = 0;
+ }
+
+out:
+ kfree(data);
+ return err;
+}
+
+int xfrm_register_km(struct xfrm_mgr *km)
+{
+ write_lock_bh(&xfrm_km_lock);
+ list_add_tail(&km->list, &xfrm_km_list);
+ write_unlock_bh(&xfrm_km_lock);
+ return 0;
+}
+
+int xfrm_unregister_km(struct xfrm_mgr *km)
+{
+ write_lock_bh(&xfrm_km_lock);
+ list_del(&km->list);
+ write_unlock_bh(&xfrm_km_lock);
+ return 0;
+}
+
+int xfrm_state_register_afinfo(struct xfrm_state_afinfo *afinfo)
+{
+ int err = 0;
+ if (unlikely(afinfo == NULL))
+ return -EINVAL;
+ if (unlikely(afinfo->family >= NPROTO))
+ return -EAFNOSUPPORT;
+ write_lock(&xfrm_state_afinfo_lock);
+ if (unlikely(xfrm_state_afinfo[afinfo->family] != NULL))
+ err = -ENOBUFS;
+ else {
+ afinfo->state_bydst = xfrm_state_bydst;
+ afinfo->state_byspi = xfrm_state_byspi;
+ xfrm_state_afinfo[afinfo->family] = afinfo;
+ }
+ write_unlock(&xfrm_state_afinfo_lock);
+ return err;
+}
+
+int xfrm_state_unregister_afinfo(struct xfrm_state_afinfo *afinfo)
+{
+ int err = 0;
+ if (unlikely(afinfo == NULL))
+ return -EINVAL;
+ if (unlikely(afinfo->family >= NPROTO))
+ return -EAFNOSUPPORT;
+ write_lock(&xfrm_state_afinfo_lock);
+ if (likely(xfrm_state_afinfo[afinfo->family] != NULL)) {
+ if (unlikely(xfrm_state_afinfo[afinfo->family] != afinfo))
+ err = -EINVAL;
+ else {
+ xfrm_state_afinfo[afinfo->family] = NULL;
+ afinfo->state_byspi = NULL;
+ afinfo->state_bydst = NULL;
+ }
+ }
+ write_unlock(&xfrm_state_afinfo_lock);
+ return err;
+}
+
+struct xfrm_state_afinfo *xfrm_state_get_afinfo(unsigned short family)
+{
+ struct xfrm_state_afinfo *afinfo;
+ if (unlikely(family >= NPROTO))
+ return NULL;
+ read_lock(&xfrm_state_afinfo_lock);
+ afinfo = xfrm_state_afinfo[family];
+ if (likely(afinfo != NULL))
+ read_lock(&afinfo->lock);
+ read_unlock(&xfrm_state_afinfo_lock);
+ return afinfo;
+}
+
+void xfrm_state_put_afinfo(struct xfrm_state_afinfo *afinfo)
+{
+ if (unlikely(afinfo == NULL))
+ return;
+ read_unlock(&afinfo->lock);
+}
+
+void __init xfrm_state_init(void)
+{
+ int i;
+
+ for (i=0; i<XFRM_DST_HSIZE; i++) {
+ INIT_LIST_HEAD(&xfrm_state_bydst[i]);
+ INIT_LIST_HEAD(&xfrm_state_byspi[i]);
+ }
+}
+
--- /dev/null
+/* xfrm_user.c: User interface to configure xfrm engine.
+ *
+ * Copyright (C) 2002 David S. Miller (davem@redhat.com)
+ *
+ * Changes:
+ * Mitsuru KANDA @USAGI
+ * Kazunori MIYAZAWA @USAGI
+ * Kunihiro Ishiguro
+ * IPv6 support
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/socket.h>
+#include <linux/string.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/pfkeyv2.h>
+#include <linux/ipsec.h>
+#include <linux/init.h>
+#include <linux/security.h>
+#include <net/sock.h>
+#include <net/xfrm.h>
+
+static struct sock *xfrm_nl;
+
+static int verify_one_alg(struct rtattr **xfrma, enum xfrm_attr_type_t type)
+{
+ struct rtattr *rt = xfrma[type - 1];
+ struct xfrm_algo *algp;
+
+ if (!rt)
+ return 0;
+
+ if ((rt->rta_len - sizeof(*rt)) < sizeof(*algp))
+ return -EINVAL;
+
+ algp = RTA_DATA(rt);
+ switch (type) {
+ case XFRMA_ALG_AUTH:
+ if (!algp->alg_key_len &&
+ strcmp(algp->alg_name, "digest_null") != 0)
+ return -EINVAL;
+ break;
+
+ case XFRMA_ALG_CRYPT:
+ if (!algp->alg_key_len &&
+ strcmp(algp->alg_name, "cipher_null") != 0)
+ return -EINVAL;
+ break;
+
+ case XFRMA_ALG_COMP:
+ /* Zero length keys are legal. */
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
+ algp->alg_name[CRYPTO_MAX_ALG_NAME - 1] = '\0';
+ return 0;
+}
+
+static int verify_newsa_info(struct xfrm_usersa_info *p,
+ struct rtattr **xfrma)
+{
+ int err;
+
+ err = -EINVAL;
+ switch (p->family) {
+ case AF_INET:
+ break;
+
+ case AF_INET6:
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ break;
+#else
+ err = -EAFNOSUPPORT;
+ goto out;
+#endif
+
+ default:
+ goto out;
+ };
+
+ err = -EINVAL;
+ switch (p->id.proto) {
+ case IPPROTO_AH:
+ if (!xfrma[XFRMA_ALG_AUTH-1] ||
+ xfrma[XFRMA_ALG_CRYPT-1] ||
+ xfrma[XFRMA_ALG_COMP-1])
+ goto out;
+ break;
+
+ case IPPROTO_ESP:
+ if ((!xfrma[XFRMA_ALG_AUTH-1] &&
+ !xfrma[XFRMA_ALG_CRYPT-1]) ||
+ xfrma[XFRMA_ALG_COMP-1])
+ goto out;
+ break;
+
+ case IPPROTO_COMP:
+ if (!xfrma[XFRMA_ALG_COMP-1] ||
+ xfrma[XFRMA_ALG_AUTH-1] ||
+ xfrma[XFRMA_ALG_CRYPT-1])
+ goto out;
+ break;
+
+ default:
+ goto out;
+ };
+
+ if ((err = verify_one_alg(xfrma, XFRMA_ALG_AUTH)))
+ goto out;
+ if ((err = verify_one_alg(xfrma, XFRMA_ALG_CRYPT)))
+ goto out;
+ if ((err = verify_one_alg(xfrma, XFRMA_ALG_COMP)))
+ goto out;
+
+ err = -EINVAL;
+ switch (p->mode) {
+ case 0:
+ case 1:
+ break;
+
+ default:
+ goto out;
+ };
+
+ err = 0;
+
+out:
+ return err;
+}
+
+static int attach_one_algo(struct xfrm_algo **algpp, struct rtattr *u_arg)
+{
+ struct rtattr *rta = u_arg;
+ struct xfrm_algo *p, *ualg;
+
+ if (!rta)
+ return 0;
+
+ ualg = RTA_DATA(rta);
+ p = kmalloc(sizeof(*ualg) + ualg->alg_key_len, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ memcpy(p, ualg, sizeof(*ualg) + ualg->alg_key_len);
+ *algpp = p;
+ return 0;
+}
+
+static void copy_from_user_state(struct xfrm_state *x, struct xfrm_usersa_info *p)
+{
+ memcpy(&x->id, &p->id, sizeof(x->id));
+ memcpy(&x->sel, &p->sel, sizeof(x->sel));
+ memcpy(&x->lft, &p->lft, sizeof(x->lft));
+ x->props.mode = p->mode;
+ x->props.replay_window = p->replay_window;
+ x->props.reqid = p->reqid;
+ x->props.family = p->family;
+ x->props.saddr = x->sel.saddr;
+}
+
+static struct xfrm_state *xfrm_state_construct(struct xfrm_usersa_info *p,
+ struct rtattr **xfrma,
+ int *errp)
+{
+ struct xfrm_state *x = xfrm_state_alloc();
+ int err = -ENOMEM;
+
+ if (!x)
+ goto error_no_put;
+
+ copy_from_user_state(x, p);
+
+ if ((err = attach_one_algo(&x->aalg, xfrma[XFRMA_ALG_AUTH-1])))
+ goto error;
+ if ((err = attach_one_algo(&x->ealg, xfrma[XFRMA_ALG_CRYPT-1])))
+ goto error;
+ if ((err = attach_one_algo(&x->calg, xfrma[XFRMA_ALG_COMP-1])))
+ goto error;
+
+ err = -ENOENT;
+ x->type = xfrm_get_type(x->id.proto, x->props.family);
+ if (x->type == NULL)
+ goto error;
+
+ err = x->type->init_state(x, NULL);
+ if (err)
+ goto error;
+
+ x->curlft.add_time = (unsigned long) xtime.tv_sec;
+ x->km.state = XFRM_STATE_VALID;
+ x->km.seq = p->seq;
+
+ return x;
+
+error:
+ xfrm_state_put(x);
+error_no_put:
+ *errp = err;
+ return NULL;
+}
+
+static int xfrm_add_sa(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
+{
+ struct xfrm_usersa_info *p = NLMSG_DATA(nlh);
+ struct xfrm_state *x, *x1;
+ int err;
+
+ err = verify_newsa_info(p, (struct rtattr **) xfrma);
+ if (err)
+ return err;
+
+ x = xfrm_state_construct(p, (struct rtattr **) xfrma, &err);
+ if (!x)
+ return err;
+
+ x1 = xfrm_state_lookup(&x->props.saddr, x->id.spi, x->id.proto, x->props.family);
+ if (x1) {
+ xfrm_state_put(x);
+ xfrm_state_put(x1);
+ return -EEXIST;
+ }
+
+ xfrm_state_insert(x);
+
+ return 0;
+}
+
+static int xfrm_del_sa(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
+{
+ struct xfrm_state *x;
+ struct xfrm_usersa_id *p = NLMSG_DATA(nlh);
+
+ x = xfrm_state_lookup(&p->saddr, p->spi, p->proto, p->family);
+ if (x == NULL)
+ return -ESRCH;
+
+ xfrm_state_delete(x);
+ xfrm_state_put(x);
+
+ return 0;
+}
+
+static void copy_to_user_state(struct xfrm_state *x, struct xfrm_usersa_info *p)
+{
+ memcpy(&p->id, &x->id, sizeof(p->id));
+ memcpy(&p->sel, &x->sel, sizeof(p->sel));
+ memcpy(&p->lft, &x->lft, sizeof(p->lft));
+ memcpy(&p->curlft, &x->curlft, sizeof(p->curlft));
+ memcpy(&p->stats, &x->stats, sizeof(p->stats));
+ p->mode = x->props.mode;
+ p->replay_window = x->props.replay_window;
+ p->reqid = x->props.reqid;
+ p->family = x->props.family;
+ p->seq = x->km.seq;
+}
+
+struct xfrm_dump_info {
+ struct sk_buff *in_skb;
+ struct sk_buff *out_skb;
+ u32 nlmsg_seq;
+ int start_idx;
+ int this_idx;
+};
+
+static int dump_one_state(struct xfrm_state *x, int count, void *ptr)
+{
+ struct xfrm_dump_info *sp = ptr;
+ struct sk_buff *in_skb = sp->in_skb;
+ struct sk_buff *skb = sp->out_skb;
+ struct xfrm_usersa_info *p;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ if (sp->this_idx < sp->start_idx)
+ goto out;
+
+ nlh = NLMSG_PUT(skb, NETLINK_CB(in_skb).pid,
+ sp->nlmsg_seq,
+ XFRM_MSG_NEWSA, sizeof(*p));
+ nlh->nlmsg_flags = 0;
+
+ p = NLMSG_DATA(nlh);
+ copy_to_user_state(x, p);
+
+ if (x->aalg)
+ RTA_PUT(skb, XFRMA_ALG_AUTH,
+ sizeof(*(x->aalg))+(x->aalg->alg_key_len+7)/8, x->aalg);
+ if (x->ealg)
+ RTA_PUT(skb, XFRMA_ALG_CRYPT,
+ sizeof(*(x->ealg))+(x->ealg->alg_key_len+7)/8, x->ealg);
+ if (x->calg)
+ RTA_PUT(skb, XFRMA_ALG_COMP, sizeof(*(x->calg)), x->calg);
+
+ nlh->nlmsg_len = skb->tail - b;
+out:
+ sp->this_idx++;
+ return 0;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int xfrm_dump_sa(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ struct xfrm_dump_info info;
+
+ info.in_skb = cb->skb;
+ info.out_skb = skb;
+ info.nlmsg_seq = cb->nlh->nlmsg_seq;
+ info.this_idx = 0;
+ info.start_idx = cb->args[0];
+ (void) xfrm_state_walk(IPSEC_PROTO_ANY, dump_one_state, &info);
+ cb->args[0] = info.this_idx;
+
+ return skb->len;
+}
+
+static struct sk_buff *xfrm_state_netlink(struct sk_buff *in_skb,
+ struct xfrm_state *x, u32 seq)
+{
+ struct xfrm_dump_info info;
+ struct sk_buff *skb;
+
+ skb = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC);
+ if (!skb)
+ return ERR_PTR(-ENOMEM);
+
+ NETLINK_CB(skb).dst_pid = NETLINK_CB(in_skb).pid;
+ info.in_skb = in_skb;
+ info.out_skb = skb;
+ info.nlmsg_seq = seq;
+ info.this_idx = info.start_idx = 0;
+
+ if (dump_one_state(x, 0, &info)) {
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ return skb;
+}
+
+static int xfrm_get_sa(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
+{
+ struct xfrm_usersa_id *p = NLMSG_DATA(nlh);
+ struct xfrm_state *x;
+ struct sk_buff *resp_skb;
+ int err;
+
+ x = xfrm_state_lookup(&p->saddr, p->spi, p->proto, p->family);
+ err = -ESRCH;
+ if (x == NULL)
+ goto out_noput;
+
+ resp_skb = xfrm_state_netlink(skb, x, nlh->nlmsg_seq);
+ if (IS_ERR(resp_skb)) {
+ err = PTR_ERR(resp_skb);
+ } else {
+ err = netlink_unicast(xfrm_nl, resp_skb,
+ NETLINK_CB(skb).pid, MSG_DONTWAIT);
+ }
+ xfrm_state_put(x);
+out_noput:
+ return err;
+}
+
+static int verify_userspi_info(struct xfrm_userspi_info *p)
+{
+ switch (p->info.id.proto) {
+ case IPPROTO_AH:
+ case IPPROTO_ESP:
+ break;
+
+ case IPPROTO_COMP:
+ /* IPCOMP spi is 16-bits. */
+ if (p->min >= 0x10000 ||
+ p->max >= 0x10000)
+ return -EINVAL;
+
+ default:
+ return -EINVAL;
+ };
+
+ if (p->min > p->max)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
+{
+ struct xfrm_state *x;
+ struct xfrm_userspi_info *p;
+ struct sk_buff *resp_skb;
+ int err;
+
+ p = NLMSG_DATA(nlh);
+ err = verify_userspi_info(p);
+ if (err)
+ goto out_noput;
+ x = xfrm_find_acq(p->info.mode, p->info.reqid, p->info.id.proto,
+ &p->info.sel.daddr,
+ &p->info.sel.saddr, 1,
+ p->info.family);
+ err = -ENOENT;
+ if (x == NULL)
+ goto out_noput;
+
+ resp_skb = ERR_PTR(-ENOENT);
+
+ spin_lock_bh(&x->lock);
+ if (x->km.state != XFRM_STATE_DEAD) {
+ xfrm_alloc_spi(x, p->min, p->max);
+ if (x->id.spi)
+ resp_skb = xfrm_state_netlink(skb, x, nlh->nlmsg_seq);
+ }
+ spin_unlock_bh(&x->lock);
+
+ if (IS_ERR(resp_skb)) {
+ err = PTR_ERR(resp_skb);
+ goto out;
+ }
+
+ err = netlink_unicast(xfrm_nl, resp_skb,
+ NETLINK_CB(skb).pid, MSG_DONTWAIT);
+
+out:
+ xfrm_state_put(x);
+out_noput:
+ return err;
+}
+
+static int verify_policy_dir(__u8 dir)
+{
+ switch (dir) {
+ case XFRM_POLICY_IN:
+ case XFRM_POLICY_OUT:
+ case XFRM_POLICY_FWD:
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
+ return 0;
+}
+
+static int verify_newpolicy_info(struct xfrm_userpolicy_info *p)
+{
+ switch (p->share) {
+ case XFRM_SHARE_ANY:
+ case XFRM_SHARE_SESSION:
+ case XFRM_SHARE_USER:
+ case XFRM_SHARE_UNIQUE:
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
+ switch (p->action) {
+ case XFRM_POLICY_ALLOW:
+ case XFRM_POLICY_BLOCK:
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
+ switch (p->family) {
+ case AF_INET:
+ break;
+
+ case AF_INET6:
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ break;
+#else
+ return -EAFNOSUPPORT;
+#endif
+
+ default:
+ return -EINVAL;
+ };
+
+ return verify_policy_dir(p->dir);
+}
+
+static void copy_templates(struct xfrm_policy *xp, struct xfrm_user_tmpl *ut,
+ int nr)
+{
+ int i;
+
+ xp->xfrm_nr = nr;
+ for (i = 0; i < nr; i++, ut++) {
+ struct xfrm_tmpl *t = &xp->xfrm_vec[i];
+
+ memcpy(&t->id, &ut->id, sizeof(struct xfrm_id));
+ memcpy(&t->saddr, &ut->saddr,
+ sizeof(xfrm_address_t));
+ t->reqid = ut->reqid;
+ t->mode = ut->mode;
+ t->share = ut->share;
+ t->optional = ut->optional;
+ t->aalgos = ut->aalgos;
+ t->ealgos = ut->ealgos;
+ t->calgos = ut->calgos;
+ }
+}
+
+static int copy_user_tmpl(struct xfrm_policy *pol, struct rtattr **xfrma)
+{
+ struct rtattr *rt = xfrma[XFRMA_TMPL-1];
+ struct xfrm_user_tmpl *utmpl;
+ int nr;
+
+ if (!rt) {
+ pol->xfrm_nr = 0;
+ } else {
+ nr = (rt->rta_len - sizeof(*rt)) / sizeof(*utmpl);
+
+ if (nr > XFRM_MAX_DEPTH)
+ return -EINVAL;
+
+ copy_templates(pol, RTA_DATA(rt), nr);
+ }
+ return 0;
+}
+
+static void copy_from_user_policy(struct xfrm_policy *xp, struct xfrm_userpolicy_info *p)
+{
+ xp->priority = p->priority;
+ xp->index = p->index;
+ memcpy(&xp->selector, &p->sel, sizeof(xp->selector));
+ memcpy(&xp->lft, &p->lft, sizeof(xp->lft));
+ xp->action = p->action;
+ xp->flags = p->flags;
+ xp->family = p->family;
+ /* XXX xp->share = p->share; */
+}
+
+static void copy_to_user_policy(struct xfrm_policy *xp, struct xfrm_userpolicy_info *p, int dir)
+{
+ memcpy(&p->sel, &xp->selector, sizeof(p->sel));
+ memcpy(&p->lft, &xp->lft, sizeof(p->lft));
+ memcpy(&p->curlft, &xp->curlft, sizeof(p->curlft));
+ p->priority = xp->priority;
+ p->index = xp->index;
+ p->family = xp->family;
+ p->dir = dir;
+ p->action = xp->action;
+ p->flags = xp->flags;
+ p->share = XFRM_SHARE_ANY; /* XXX xp->share */
+}
+
+static struct xfrm_policy *xfrm_policy_construct(struct xfrm_userpolicy_info *p, struct rtattr **xfrma, int *errp)
+{
+ struct xfrm_policy *xp = xfrm_policy_alloc(GFP_KERNEL);
+ int err;
+
+ if (!xp) {
+ *errp = -ENOMEM;
+ return NULL;
+ }
+
+ copy_from_user_policy(xp, p);
+ err = copy_user_tmpl(xp, xfrma);
+ if (err) {
+ *errp = err;
+ kfree(xp);
+ xp = NULL;
+ }
+
+ return xp;
+}
+
+static int xfrm_add_policy(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
+{
+ struct xfrm_userpolicy_info *p = NLMSG_DATA(nlh);
+ struct xfrm_policy *xp;
+ int err;
+
+ err = verify_newpolicy_info(p);
+ if (err)
+ return err;
+
+ xp = xfrm_policy_construct(p, (struct rtattr **) xfrma, &err);
+ if (!xp)
+ return err;
+
+ err = xfrm_policy_insert(p->dir, xp, 1);
+ if (err) {
+ kfree(xp);
+ return err;
+ }
+
+ xfrm_pol_put(xp);
+
+ return 0;
+}
+
+static int xfrm_del_policy(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
+{
+ struct xfrm_policy *xp;
+ struct xfrm_userpolicy_id *p;
+ int err;
+
+ p = NLMSG_DATA(nlh);
+
+ err = verify_policy_dir(p->dir);
+ if (err)
+ return err;
+
+ xp = xfrm_policy_delete(p->dir, &p->sel);
+ if (xp == NULL)
+ return -ENOENT;
+ xfrm_policy_kill(xp);
+ xfrm_pol_put(xp);
+ return 0;
+}
+
+static int dump_one_policy(struct xfrm_policy *xp, int dir, int count, void *ptr)
+{
+ struct xfrm_dump_info *sp = ptr;
+ struct xfrm_userpolicy_info *p;
+ struct sk_buff *in_skb = sp->in_skb;
+ struct sk_buff *skb = sp->out_skb;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ if (sp->this_idx < sp->start_idx)
+ goto out;
+
+ nlh = NLMSG_PUT(skb, NETLINK_CB(in_skb).pid,
+ sp->nlmsg_seq,
+ XFRM_MSG_NEWPOLICY, sizeof(*p));
+ p = NLMSG_DATA(nlh);
+ nlh->nlmsg_flags = 0;
+
+ copy_to_user_policy(xp, p, dir);
+
+ if (xp->xfrm_nr) {
+ struct xfrm_user_tmpl vec[XFRM_MAX_DEPTH];
+ int i;
+
+ for (i = 0; i < xp->xfrm_nr; i++) {
+ struct xfrm_user_tmpl *up = &vec[i];
+ struct xfrm_tmpl *kp = &xp->xfrm_vec[i];
+
+ memcpy(&up->id, &kp->id, sizeof(up->id));
+ memcpy(&up->saddr, &kp->saddr, sizeof(up->saddr));
+ up->reqid = kp->reqid;
+ up->mode = kp->mode;
+ up->share = kp->share;
+ up->optional = kp->optional;
+ up->aalgos = kp->aalgos;
+ up->ealgos = kp->ealgos;
+ up->calgos = kp->calgos;
+ }
+ RTA_PUT(skb, XFRMA_TMPL,
+ (sizeof(struct xfrm_user_tmpl) * xp->xfrm_nr),
+ vec);
+ }
+
+ nlh->nlmsg_len = skb->tail - b;
+out:
+ sp->this_idx++;
+ return 0;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int xfrm_dump_policy(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ struct xfrm_dump_info info;
+
+ info.in_skb = cb->skb;
+ info.out_skb = skb;
+ info.nlmsg_seq = cb->nlh->nlmsg_seq;
+ info.this_idx = 0;
+ info.start_idx = cb->args[0];
+ (void) xfrm_policy_walk(dump_one_policy, &info);
+ cb->args[0] = info.this_idx;
+
+ return skb->len;
+}
+
+static struct sk_buff *xfrm_policy_netlink(struct sk_buff *in_skb,
+ struct xfrm_policy *xp,
+ int dir, u32 seq)
+{
+ struct xfrm_dump_info info;
+ struct sk_buff *skb;
+
+ skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!skb)
+ return ERR_PTR(-ENOMEM);
+
+ NETLINK_CB(skb).dst_pid = NETLINK_CB(in_skb).pid;
+ info.in_skb = in_skb;
+ info.out_skb = skb;
+ info.nlmsg_seq = seq;
+ info.this_idx = info.start_idx = 0;
+
+ if (dump_one_policy(xp, dir, 0, &info) < 0) {
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ return skb;
+}
+
+static int xfrm_get_policy(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
+{
+ struct xfrm_policy *xp;
+ struct xfrm_userpolicy_id *p;
+ struct sk_buff *resp_skb;
+ int err;
+
+ p = NLMSG_DATA(nlh);
+ xp = xfrm_policy_byid(p->dir, p->index, 0);
+ if (xp == NULL)
+ return -ENOENT;
+
+ resp_skb = xfrm_policy_netlink(skb, xp, p->dir, nlh->nlmsg_seq);
+ if (IS_ERR(resp_skb)) {
+ err = PTR_ERR(resp_skb);
+ } else {
+ err = netlink_unicast(xfrm_nl, resp_skb,
+ NETLINK_CB(skb).pid, MSG_DONTWAIT);
+ }
+
+ xfrm_pol_put(xp);
+
+ return err;
+}
+
+static const int xfrm_msg_min[(XFRM_MSG_MAX + 1 - XFRM_MSG_BASE)] = {
+ NLMSG_LENGTH(sizeof(struct xfrm_usersa_info)), /* NEW SA */
+ NLMSG_LENGTH(sizeof(struct xfrm_usersa_id)), /* DEL SA */
+ NLMSG_LENGTH(sizeof(struct xfrm_usersa_id)), /* GET SA */
+ NLMSG_LENGTH(sizeof(struct xfrm_userpolicy_info)),/* NEW POLICY */
+ NLMSG_LENGTH(sizeof(struct xfrm_userpolicy_id)), /* DEL POLICY */
+ NLMSG_LENGTH(sizeof(struct xfrm_userpolicy_id)), /* GET POLICY */
+ NLMSG_LENGTH(sizeof(struct xfrm_userspi_info)), /* ALLOC SPI */
+ NLMSG_LENGTH(sizeof(struct xfrm_user_acquire)), /* ACQUIRE */
+ NLMSG_LENGTH(sizeof(struct xfrm_user_expire)), /* EXPIRE */
+};
+
+static struct xfrm_link {
+ int (*doit)(struct sk_buff *, struct nlmsghdr *, void **);
+ int (*dump)(struct sk_buff *, struct netlink_callback *);
+} xfrm_dispatch[] = {
+ { .doit = xfrm_add_sa, },
+ { .doit = xfrm_del_sa, },
+ {
+ .doit = xfrm_get_sa,
+ .dump = xfrm_dump_sa,
+ },
+ { .doit = xfrm_add_policy },
+ { .doit = xfrm_del_policy },
+ {
+ .doit = xfrm_get_policy,
+ .dump = xfrm_dump_policy,
+ },
+ { .doit = xfrm_alloc_userspi },
+};
+
+static int xfrm_done(struct netlink_callback *cb)
+{
+ return 0;
+}
+
+static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, int *errp)
+{
+ struct rtattr *xfrma[XFRMA_MAX];
+ struct xfrm_link *link;
+ int type, min_len;
+
+ if (!(nlh->nlmsg_flags & NLM_F_REQUEST))
+ return 0;
+
+ type = nlh->nlmsg_type;
+
+ /* A control message: ignore them */
+ if (type < XFRM_MSG_BASE)
+ return 0;
+
+ /* Unknown message: reply with EINVAL */
+ if (type > XFRM_MSG_MAX)
+ goto err_einval;
+
+ type -= XFRM_MSG_BASE;
+ link = &xfrm_dispatch[type];
+
+ /* All operations require privileges, even GET */
+ if (security_netlink_recv(skb)) {
+ *errp = -EPERM;
+ return -1;
+ }
+
+ if ((type == 2 || type == 5) && (nlh->nlmsg_flags & NLM_F_DUMP)) {
+ u32 rlen;
+
+ if (link->dump == NULL)
+ goto err_einval;
+
+ if ((*errp = netlink_dump_start(xfrm_nl, skb, nlh,
+ link->dump,
+ xfrm_done)) != 0) {
+ return -1;
+ }
+ rlen = NLMSG_ALIGN(nlh->nlmsg_len);
+ if (rlen > skb->len)
+ rlen = skb->len;
+ skb_pull(skb, rlen);
+ return -1;
+ }
+
+ memset(xfrma, 0, sizeof(xfrma));
+
+ if (nlh->nlmsg_len < (min_len = xfrm_msg_min[type]))
+ goto err_einval;
+
+ if (nlh->nlmsg_len > min_len) {
+ int attrlen = nlh->nlmsg_len - NLMSG_ALIGN(min_len);
+ struct rtattr *attr = (void *) nlh + NLMSG_ALIGN(min_len);
+
+ while (RTA_OK(attr, attrlen)) {
+ unsigned short flavor = attr->rta_type;
+ if (flavor) {
+ if (flavor > XFRMA_MAX)
+ goto err_einval;
+ xfrma[flavor - 1] = attr;
+ }
+ attr = RTA_NEXT(attr, attrlen);
+ }
+ }
+
+ if (link->doit == NULL)
+ goto err_einval;
+ *errp = link->doit(skb, nlh, (void **) &xfrma);
+
+ return *errp;
+
+err_einval:
+ *errp = -EINVAL;
+ return -1;
+}
+
+static int xfrm_user_rcv_skb(struct sk_buff *skb)
+{
+ int err;
+ struct nlmsghdr *nlh;
+
+ while (skb->len >= NLMSG_SPACE(0)) {
+ u32 rlen;
+
+ nlh = (struct nlmsghdr *) skb->data;
+ if (nlh->nlmsg_len < sizeof(*nlh) ||
+ skb->len < nlh->nlmsg_len)
+ return 0;
+ rlen = NLMSG_ALIGN(nlh->nlmsg_len);
+ if (rlen > skb->len)
+ rlen = skb->len;
+ if (xfrm_user_rcv_msg(skb, nlh, &err)) {
+ if (err == 0)
+ return -1;
+ netlink_ack(skb, nlh, err);
+ } else if (nlh->nlmsg_flags & NLM_F_ACK)
+ netlink_ack(skb, nlh, 0);
+ skb_pull(skb, rlen);
+ }
+
+ return 0;
+}
+
+static void xfrm_netlink_rcv(struct sock *sk, int len)
+{
+ do {
+ struct sk_buff *skb;
+
+ down(&xfrm_cfg_sem);
+
+ while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
+ if (xfrm_user_rcv_skb(skb)) {
+ if (skb->len)
+ skb_queue_head(&sk->receive_queue, skb);
+ else
+ kfree_skb(skb);
+ break;
+ }
+ kfree_skb(skb);
+ }
+
+ up(&xfrm_cfg_sem);
+
+ } while (xfrm_nl && xfrm_nl->receive_queue.qlen);
+}
+
+static int build_expire(struct sk_buff *skb, struct xfrm_state *x, int hard)
+{
+ struct xfrm_user_expire *ue;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, 0, 0, XFRM_MSG_EXPIRE,
+ sizeof(*ue));
+ ue = NLMSG_DATA(nlh);
+ nlh->nlmsg_flags = 0;
+
+ copy_to_user_state(x, &ue->state);
+ ue->hard = (hard != 0) ? 1 : 0;
+
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int xfrm_send_notify(struct xfrm_state *x, int hard)
+{
+ struct sk_buff *skb;
+
+ skb = alloc_skb(sizeof(struct xfrm_user_expire) + 16, GFP_ATOMIC);
+ if (skb == NULL)
+ return -ENOMEM;
+
+ if (build_expire(skb, x, hard) < 0)
+ BUG();
+
+ NETLINK_CB(skb).dst_groups = XFRMGRP_EXPIRE;
+
+ return netlink_broadcast(xfrm_nl, skb, 0, XFRMGRP_EXPIRE, GFP_ATOMIC);
+}
+
+static int build_acquire(struct sk_buff *skb, struct xfrm_state *x,
+ struct xfrm_tmpl *xt, struct xfrm_policy *xp,
+ int dir)
+{
+ struct xfrm_user_acquire *ua;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+ __u32 seq = xfrm_get_acqseq();
+
+ nlh = NLMSG_PUT(skb, 0, 0, XFRM_MSG_ACQUIRE,
+ sizeof(*ua));
+ ua = NLMSG_DATA(nlh);
+ nlh->nlmsg_flags = 0;
+
+ memcpy(&ua->id, &x->id, sizeof(ua->id));
+ memcpy(&ua->saddr, &x->props.saddr, sizeof(ua->saddr));
+ copy_to_user_policy(xp, &ua->policy, dir);
+ ua->aalgos = xt->aalgos;
+ ua->ealgos = xt->ealgos;
+ ua->calgos = xt->calgos;
+ ua->seq = x->km.seq = seq;
+
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int xfrm_send_acquire(struct xfrm_state *x, struct xfrm_tmpl *xt,
+ struct xfrm_policy *xp, int dir)
+{
+ struct sk_buff *skb;
+
+ skb = alloc_skb(sizeof(struct xfrm_user_acquire) + 16, GFP_ATOMIC);
+ if (skb == NULL)
+ return -ENOMEM;
+
+ if (build_acquire(skb, x, xt, xp, dir) < 0)
+ BUG();
+
+ NETLINK_CB(skb).dst_groups = XFRMGRP_ACQUIRE;
+
+ return netlink_broadcast(xfrm_nl, skb, 0, XFRMGRP_ACQUIRE, GFP_ATOMIC);
+}
+
+/* User gives us xfrm_user_policy_info followed by an array of 0
+ * or more templates.
+ */
+struct xfrm_policy *xfrm_compile_policy(u16 family, int opt,
+ u8 *data, int len, int *dir)
+{
+ struct xfrm_userpolicy_info *p = (struct xfrm_userpolicy_info *)data;
+ struct xfrm_user_tmpl *ut = (struct xfrm_user_tmpl *) (p + 1);
+ struct xfrm_policy *xp;
+ int nr;
+
+ switch (family) {
+ case AF_INET:
+ if (opt != IP_XFRM_POLICY) {
+ *dir = -EOPNOTSUPP;
+ return NULL;
+ }
+ break;
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ case AF_INET6:
+ if (opt != IPV6_XFRM_POLICY) {
+ *dir = -EOPNOTSUPP;
+ return NULL;
+ }
+ break;
+#endif
+ default:
+ *dir = -EINVAL;
+ return NULL;
+ }
+
+ *dir = -EINVAL;
+
+ if (len < sizeof(*p) ||
+ verify_newpolicy_info(p))
+ return NULL;
+
+ nr = ((len - sizeof(*p)) / sizeof(*ut));
+ if (nr > XFRM_MAX_DEPTH)
+ return NULL;
+
+ xp = xfrm_policy_alloc(GFP_KERNEL);
+ if (xp == NULL) {
+ *dir = -ENOBUFS;
+ return NULL;
+ }
+
+ copy_from_user_policy(xp, p);
+ copy_templates(xp, ut, nr);
+
+ *dir = p->dir;
+
+ return xp;
+}
+
+static struct xfrm_mgr netlink_mgr = {
+ .id = "netlink",
+ .notify = xfrm_send_notify,
+ .acquire = xfrm_send_acquire,
+ .compile_policy = xfrm_compile_policy,
+};
+
+static int __init xfrm_user_init(void)
+{
+ printk(KERN_INFO "Initializing IPsec netlink socket\n");
+
+ xfrm_nl = netlink_kernel_create(NETLINK_XFRM, xfrm_netlink_rcv);
+ if (xfrm_nl == NULL)
+ panic("xfrm_user_init: cannot initialize xfrm_nl\n");
+
+
+ xfrm_register_km(&netlink_mgr);
+
+ return 0;
+}
+
+static void __exit xfrm_user_exit(void)
+{
+ xfrm_unregister_km(&netlink_mgr);
+ sock_release(xfrm_nl->socket);
+}
+
+module_init(xfrm_user_init);
+module_exit(xfrm_user_exit);