Commit 4a618fbe authored by Alexey Kuznetsov's avatar Alexey Kuznetsov Committed by David S. Miller

[NET]: IPSEC updates.

- Add ESP tranformer.
- Add AF_KEY socket layer.
- Rework xfrm structures for user interfaces
- Add CONFIG_IP_{AH,ESP}.
parent 464ff460
/*
* Definitions for the SECurity layer
*
* Author:
* Robert Muchsel <muchsel@acm.org>
*
* 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.
*/
#ifndef _LINUX_IPSEC_H #ifndef _LINUX_IPSEC_H
#define _LINUX_IPSEC_H #define _LINUX_IPSEC_H
#include <linux/config.h> /* The definitions, required to talk to KAME racoon IKE. */
#include <linux/socket.h>
#include <net/sock.h>
#include <linux/skbuff.h>
/* Values for the set/getsockopt calls */ #include <linux/pfkeyv2.h>
/* These defines are compatible with NRL IPv6, however their semantics #define IPSEC_PORT_ANY 0
is different */ #define IPSEC_ULPROTO_ANY 255
#define IPSEC_PROTO_ANY 255
#define IPSEC_LEVEL_NONE -1 /* send plaintext, accept any */ enum {
#define IPSEC_LEVEL_DEFAULT 0 /* encrypt/authenticate if possible */ IPSEC_MODE_ANY = 0, /* We do not support this for SA */
/* the default MUST be 0, because a */ IPSEC_MODE_TRANSPORT = 1,
/* socket is initialized with 0's */ IPSEC_MODE_TUNNEL = 2
#define IPSEC_LEVEL_USE 1 /* use outbound, don't require inbound */ };
#define IPSEC_LEVEL_REQUIRE 2 /* require both directions */
#define IPSEC_LEVEL_UNIQUE 2 /* for compatibility only */
#ifdef __KERNEL__ enum {
IPSEC_DIR_ANY = 0,
IPSEC_DIR_INBOUND = 1,
IPSEC_DIR_OUTBOUND = 2,
IPSEC_DIR_FWD = 3, /* It is our own */
IPSEC_DIR_MAX = 4,
IPSEC_DIR_INVALID = 5
};
/* skb bit flags set on packet input processing */ enum {
IPSEC_POLICY_DISCARD = 0,
IPSEC_POLICY_NONE = 1,
IPSEC_POLICY_IPSEC = 2,
IPSEC_POLICY_ENTRUST = 3,
IPSEC_POLICY_BYPASS = 4
};
#define RCV_SEC 0x0f /* options on receive */ enum {
#define RCV_AUTH 0x01 /* was authenticated */ IPSEC_LEVEL_DEFAULT = 0,
#define RCV_CRYPT 0x02 /* was encrypted */ IPSEC_LEVEL_USE = 1,
#define RCV_TUNNEL 0x04 /* was tunneled */ IPSEC_LEVEL_REQUIRE = 2,
#define SND_SEC 0xf0 /* options on send, these are */ IPSEC_LEVEL_UNIQUE = 3
#define SND_AUTH 0x10 /* currently unused */ };
#define SND_CRYPT 0x20
#define SND_TUNNEL 0x40
/* #define IPSEC_MANUAL_REQID_MAX 0x3fff
* FIXME: ignores network encryption for now..
*/
#ifdef CONFIG_NET_SECURITY #define IPSEC_REPLAYWSIZE 32
static __inline__ int ipsec_sk_policy(struct sock *sk, struct sk_buff *skb)
{
return ((sk->authentication < IPSEC_LEVEL_REQUIRE) ||
(skb->security & RCV_AUTH)) &&
((sk->encryption < IPSEC_LEVEL_REQUIRE) ||
(skb->security & RCV_CRYPT));
}
#else #ifdef __KERNEL__
struct sock;
struct sk_buff;
static __inline__ int ipsec_sk_policy(struct sock *sk, struct sk_buff *skb) static __inline__ int ipsec_sk_policy(struct sock *sk, struct sk_buff *skb)
{ {
return 1; return 1;
} }
#endif /* CONFIG */ #endif
#endif /* __KERNEL__ */
#endif /* _LINUX_IPSEC_H */ #endif /* _LINUX_IPSEC_H */
...@@ -187,7 +187,7 @@ struct sadb_x_policy { ...@@ -187,7 +187,7 @@ struct sadb_x_policy {
struct sadb_x_ipsecrequest { struct sadb_x_ipsecrequest {
uint16_t sadb_x_ipsecrequest_len; uint16_t sadb_x_ipsecrequest_len;
uint16_t sadb_x_ipsecrequest_exttype; uint16_t sadb_x_ipsecrequest_proto;
uint8_t sadb_x_ipsecrequest_mode; uint8_t sadb_x_ipsecrequest_mode;
uint8_t sadb_x_ipsecrequest_level; uint8_t sadb_x_ipsecrequest_level;
uint16_t sadb_x_ipsecrequest_reqid; uint16_t sadb_x_ipsecrequest_reqid;
...@@ -249,8 +249,8 @@ struct sadb_x_ipsecrequest { ...@@ -249,8 +249,8 @@ struct sadb_x_ipsecrequest {
/* Encryption algorithms */ /* Encryption algorithms */
#define SADB_EALG_NONE 0 #define SADB_EALG_NONE 0
#define SADB_EALG_DESCBC 2 #define SADB_EALG_DESCBC 1
#define SADB_EALG_3DESCBC 3 #define SADB_EALG_3DESCBC 2
#define SADB_EALG_NULL 11 #define SADB_EALG_NULL 11
#define SADB_EALG_MAX 11 #define SADB_EALG_MAX 11
......
...@@ -237,7 +237,7 @@ extern void dst_init(void); ...@@ -237,7 +237,7 @@ extern void dst_init(void);
struct flowi; struct flowi;
extern int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl, extern int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
struct sock *sk, int flags); struct sock *sk, int flags);
extern void xfrm_init(void);
#endif #endif
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
#include <linux/list.h> #include <linux/list.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/crypto.h>
#include <net/dst.h> #include <net/dst.h>
#include <net/route.h> #include <net/route.h>
...@@ -113,6 +114,38 @@ struct xfrm_selector ...@@ -113,6 +114,38 @@ struct xfrm_selector
void *owner; void *owner;
}; };
struct xfrm_lifetime_cfg
{
u64 soft_byte_limit;
u64 hard_byte_limit;
u64 soft_packet_limit;
u64 hard_packet_limit;
u64 soft_add_expires_seconds;
u64 hard_add_expires_seconds;
u64 soft_use_expires_seconds;
u64 hard_use_expires_seconds;
};
struct xfrm_lifetime_cur
{
u64 bytes;
u64 packets;
u64 add_time;
u64 use_time;
};
struct xfrm_replay_state
{
u32 oseq;
u32 seq;
u32 bitmap;
};
struct xfrm_algo {
char alg_name[CRYPTO_MAX_ALG_NAME];
int alg_key_len; /* in bits */
char alg_key[0];
};
/* Full description of state of transformer. */ /* Full description of state of transformer. */
struct xfrm_state struct xfrm_state
...@@ -130,40 +163,39 @@ struct xfrm_state ...@@ -130,40 +163,39 @@ struct xfrm_state
struct { struct {
int state; int state;
u32 seq; u32 seq;
u32 warn_bytes; u64 warn_bytes;
} km; } km;
/* Parameters of this state. */ /* Parameters of this state. */
struct { struct {
u8 mode; u8 mode;
u8 algo; u8 replay_window;
u8 aalgo, ealgo, calgo;
u16 reqid;
xfrm_address_t saddr; xfrm_address_t saddr;
int header_len; int header_len;
int trailer_len; int trailer_len;
u32 hard_byte_limit;
u32 soft_byte_limit;
u32 replay_window;
/* More... */
} props; } props;
struct xfrm_lifetime_cfg lft;
/* Data for transformer */
struct xfrm_algo *aalg;
struct xfrm_algo *ealg;
struct xfrm_algo *calg;
/* State for replay detection */ /* State for replay detection */
struct { struct xfrm_replay_state replay;
u32 oseq;
u32 seq;
u32 bitmap;
} replay;
/* Statistics */ /* Statistics */
struct { struct {
unsigned long lastuse;
unsigned long expires;
u32 bytes;
u32 replay_window; u32 replay_window;
u32 replay; u32 replay;
u32 integrity_failed; u32 integrity_failed;
/* More... */
} stats; } stats;
struct xfrm_lifetime_cur curlft;
/* Reference to data common to all the instances of this /* Reference to data common to all the instances of this
* transformer. */ * transformer. */
struct xfrm_type *type; struct xfrm_type *type;
...@@ -182,14 +214,12 @@ enum { ...@@ -182,14 +214,12 @@ enum {
XFRM_STATE_DEAD XFRM_STATE_DEAD
}; };
#define XFRM_DST_HSIZE 1024
struct xfrm_type struct xfrm_type
{ {
char *description; char *description;
atomic_t refcnt; struct module *owner;
__u8 proto; __u8 proto;
__u8 algo;
int (*init_state)(struct xfrm_state *x, void *args); int (*init_state)(struct xfrm_state *x, void *args);
void (*destructor)(struct xfrm_state *); void (*destructor)(struct xfrm_state *);
...@@ -199,6 +229,11 @@ struct xfrm_type ...@@ -199,6 +229,11 @@ struct xfrm_type
u32 (*get_max_size)(struct xfrm_state *, int size); u32 (*get_max_size)(struct xfrm_state *, int size);
}; };
extern int xfrm_register_type(struct xfrm_type *type);
extern int xfrm_unregister_type(struct xfrm_type *type);
extern struct xfrm_type *xfrm_get_type(u8 proto);
extern void xfrm_put_type(struct xfrm_type *type);
struct xfrm_tmpl struct xfrm_tmpl
{ {
/* id in template is interpreted as: /* id in template is interpreted as:
...@@ -212,6 +247,8 @@ struct xfrm_tmpl ...@@ -212,6 +247,8 @@ struct xfrm_tmpl
/* Source address of tunnel. Ignored, if it is not a tunnel. */ /* Source address of tunnel. Ignored, if it is not a tunnel. */
xfrm_address_t saddr; xfrm_address_t saddr;
__u16 reqid;
/* Mode: transport/tunnel */ /* Mode: transport/tunnel */
__u8 mode; __u8 mode;
...@@ -219,7 +256,9 @@ struct xfrm_tmpl ...@@ -219,7 +256,9 @@ struct xfrm_tmpl
__u8 share; __u8 share;
/* Bit mask of algos allowed for acquisition */ /* Bit mask of algos allowed for acquisition */
__u32 algos; __u32 aalgos;
__u32 ealgos;
__u32 calgos;
/* If template statically resolved, hold ref here */ /* If template statically resolved, hold ref here */
struct xfrm_state *resolved; struct xfrm_state *resolved;
...@@ -238,8 +277,8 @@ enum ...@@ -238,8 +277,8 @@ enum
enum enum
{ {
XFRM_POLICY_IN = 0, XFRM_POLICY_IN = 0,
XFRM_POLICY_FWD = 1, XFRM_POLICY_OUT = 1,
XFRM_POLICY_OUT = 2, XFRM_POLICY_FWD = 2,
XFRM_POLICY_MAX = 3 XFRM_POLICY_MAX = 3
}; };
...@@ -254,8 +293,8 @@ struct xfrm_policy ...@@ -254,8 +293,8 @@ struct xfrm_policy
u32 priority; u32 priority;
u32 index; u32 index;
struct xfrm_selector selector; struct xfrm_selector selector;
unsigned long expires; struct xfrm_lifetime_cfg lft;
unsigned long lastuse; struct xfrm_lifetime_cur curlft;
struct dst_entry *bundles; struct dst_entry *bundles;
__u8 action; __u8 action;
#define XFRM_POLICY_ALLOW 0 #define XFRM_POLICY_ALLOW 0
...@@ -267,6 +306,18 @@ struct xfrm_policy ...@@ -267,6 +306,18 @@ struct xfrm_policy
struct xfrm_tmpl xfrm_vec[XFRM_MAX_DEPTH]; struct xfrm_tmpl xfrm_vec[XFRM_MAX_DEPTH];
}; };
struct xfrm_mgr
{
struct list_head list;
char *id;
int (*notify)(struct xfrm_state *x, int event);
int (*acquire)(struct xfrm_state *x, struct xfrm_tmpl *, struct xfrm_policy *xp, int dir);
};
extern int xfrm_register_km(struct xfrm_mgr *km);
extern int xfrm_unregister_km(struct xfrm_mgr *km);
extern struct xfrm_policy *xfrm_policy_list[XFRM_POLICY_MAX]; extern struct xfrm_policy *xfrm_policy_list[XFRM_POLICY_MAX];
static inline void xfrm_pol_hold(struct xfrm_policy *policy) static inline void xfrm_pol_hold(struct xfrm_policy *policy)
...@@ -366,21 +417,36 @@ static inline int xfrm_route_forward(struct sk_buff *skb) ...@@ -366,21 +417,36 @@ static inline int xfrm_route_forward(struct sk_buff *skb)
extern void xfrm_state_init(void); extern void xfrm_state_init(void);
extern void xfrm_input_init(void); extern void xfrm_input_init(void);
extern int xfrm_state_walk(u8 proto, int (*func)(struct xfrm_state *, int, void*), void *);
extern struct xfrm_state *xfrm_state_alloc(void); extern struct xfrm_state *xfrm_state_alloc(void);
extern struct xfrm_state *xfrm_state_find(u32 daddr, struct flowi *fl, struct xfrm_tmpl *tmpl); extern struct xfrm_state *xfrm_state_find(u32 daddr, struct flowi *fl, struct xfrm_tmpl *tmpl, struct xfrm_policy *pol);
extern int xfrm_state_check_expire(struct xfrm_state *x); extern int xfrm_state_check_expire(struct xfrm_state *x);
extern void xfrm_state_insert(struct xfrm_state *x); extern void xfrm_state_insert(struct xfrm_state *x);
extern int xfrm_state_check_space(struct xfrm_state *x, struct sk_buff *skb); extern int xfrm_state_check_space(struct xfrm_state *x, struct sk_buff *skb);
extern struct xfrm_state * xfrm_state_lookup(u32 daddr, u32 spi, u8 proto); extern struct xfrm_state *xfrm_state_lookup(u32 daddr, u32 spi, u8 proto);
extern struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl); extern struct xfrm_state *xfrm_find_acq_byseq(u32 seq);
extern void xfrm_state_delete(struct xfrm_state *x);
extern void xfrm_state_flush(u8 proto);
extern int xfrm_replay_check(struct xfrm_state *x, u32 seq); extern int xfrm_replay_check(struct xfrm_state *x, u32 seq);
extern void xfrm_replay_advance(struct xfrm_state *x, u32 seq); extern void xfrm_replay_advance(struct xfrm_state *x, u32 seq);
extern int xfrm_check_selectors(struct xfrm_state **x, int n, struct flowi *fl); extern int xfrm_check_selectors(struct xfrm_state **x, int n, struct flowi *fl);
extern int xfrm4_rcv(struct sk_buff *skb); extern int xfrm4_rcv(struct sk_buff *skb);
struct xfrm_policy *xfrm_policy_alloc(void);
extern int xfrm_policy_walk(int (*func)(struct xfrm_policy *, int, int, void*), void *);
struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl);
int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl);
struct xfrm_policy *xfrm_policy_delete(int dir, struct xfrm_selector *sel);
struct xfrm_policy *xfrm_policy_byid(int dir, u32 id, int delete);
void xfrm_policy_flush(void);
int xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm,
struct flowi *fl, struct dst_entry **dst_p);
void xfrm_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi);
struct xfrm_state * xfrm_find_acq(u8 mode, u16 reqid, u8 proto, u32 daddr, u32 saddr);
extern void xfrm_policy_flush(void);
extern void xfrm_policy_kill(struct xfrm_policy *);
extern wait_queue_head_t *km_waitq; extern wait_queue_head_t *km_waitq;
extern void km_notify(struct xfrm_state *x, int event); extern void km_warn_expired(struct xfrm_state *x);
extern int km_query(struct xfrm_state *x); extern void km_expired(struct xfrm_state *x);
extern int km_query(struct xfrm_state *x, struct xfrm_tmpl *, struct xfrm_policy *pol);
extern int ah4_init(void);
...@@ -139,6 +139,15 @@ config UNIX ...@@ -139,6 +139,15 @@ config UNIX
Say Y unless you know what you are doing. Say Y unless you know what you are doing.
config NET_KEY
tristate "PF_KEY sockets"
---help---
PF_KEYv2 socket family, compatible to KAME ones.
They are required if you are going to use IPsec tools ported
from KAME.
Say Y unless you know what you are doing.
config INET config INET
bool "TCP/IP networking" bool "TCP/IP networking"
---help--- ---help---
......
...@@ -16,6 +16,7 @@ obj-$(CONFIG_INET) += ipv4/ ...@@ -16,6 +16,7 @@ obj-$(CONFIG_INET) += ipv4/
obj-$(CONFIG_UNIX) += unix/ obj-$(CONFIG_UNIX) += unix/
obj-$(CONFIG_IPV6) += ipv6/ obj-$(CONFIG_IPV6) += ipv6/
obj-$(CONFIG_PACKET) += packet/ obj-$(CONFIG_PACKET) += packet/
obj-$(CONFIG_NET_KEY) += key/
obj-$(CONFIG_NET_SCHED) += sched/ obj-$(CONFIG_NET_SCHED) += sched/
obj-$(CONFIG_BRIDGE) += bridge/ obj-$(CONFIG_BRIDGE) += bridge/
obj-$(CONFIG_IPX) += ipx/ obj-$(CONFIG_IPX) += ipx/
......
...@@ -348,5 +348,19 @@ config SYN_COOKIES ...@@ -348,5 +348,19 @@ config SYN_COOKIES
If unsure, say N. If unsure, say N.
config INET_AH
bool "IP: AH transformation"
---help---
Support for IPsec AH.
If unsure, say Y.
config INET_ESP
bool "IP: ESP transformation"
---help---
Support for IPsec ESP.
If unsure, say Y.
source "net/ipv4/netfilter/Kconfig" source "net/ipv4/netfilter/Kconfig"
...@@ -16,9 +16,11 @@ obj-$(CONFIG_IP_MROUTE) += ipmr.o ...@@ -16,9 +16,11 @@ obj-$(CONFIG_IP_MROUTE) += ipmr.o
obj-$(CONFIG_NET_IPIP) += ipip.o obj-$(CONFIG_NET_IPIP) += ipip.o
obj-$(CONFIG_NET_IPGRE) += ip_gre.o obj-$(CONFIG_NET_IPGRE) += ip_gre.o
obj-$(CONFIG_SYN_COOKIES) += syncookies.o obj-$(CONFIG_SYN_COOKIES) += syncookies.o
obj-$(CONFIG_INET_AH) += ah.o
obj-$(CONFIG_INET_ESP) += esp.o
obj-$(CONFIG_IP_PNP) += ipconfig.o obj-$(CONFIG_IP_PNP) += ipconfig.o
obj-$(CONFIG_NETFILTER) += netfilter/ obj-$(CONFIG_NETFILTER) += netfilter/
obj-y += xfrm_policy.o xfrm_state.o xfrm_input.o ah.o obj-y += xfrm_policy.o xfrm_state.o xfrm_input.o
include $(TOPDIR)/Rules.make include $(TOPDIR)/Rules.make
#include <linux/config.h>
#include <linux/module.h>
#include <net/ip.h> #include <net/ip.h>
#include <net/xfrm.h> #include <net/xfrm.h>
#include <linux/crypto.h> #include <linux/crypto.h>
#include <linux/pfkeyv2.h>
#include <net/icmp.h> #include <net/icmp.h>
#include <asm/scatterlist.h>
struct ah_data struct ah_data
{ {
...@@ -9,7 +13,7 @@ struct ah_data ...@@ -9,7 +13,7 @@ struct ah_data
int key_len; int key_len;
int digest_len; int digest_len;
void (*digest)(struct xfrm_state*, void (*digest)(struct ah_data*,
struct sk_buff *skb, struct sk_buff *skb,
u8 *digest); u8 *digest);
...@@ -67,12 +71,19 @@ void skb_ah_walk(const struct sk_buff *skb, struct crypto_tfm *tfm) ...@@ -67,12 +71,19 @@ void skb_ah_walk(const struct sk_buff *skb, struct crypto_tfm *tfm)
int len = skb->len; int len = skb->len;
int start = skb->len - skb->data_len; int start = skb->len - skb->data_len;
int i, copy = start - offset; int i, copy = start - offset;
struct scatterlist sg;
/* Checksum header. */ /* Checksum header. */
if (copy > 0) { if (copy > 0) {
if (copy > len) if (copy > len)
copy = len; copy = len;
tfm->__crt_alg->cra_digest.dia_update(tfm->crt_ctx, skb->data+offset, copy);
sg.page = virt_to_page(skb->data + offset);
sg.offset = (unsigned long)(skb->data + offset) % PAGE_SIZE;
sg.length = copy;
crypto_hmac_update(tfm, &sg, 1);
if ((len -= copy) == 0) if ((len -= copy) == 0)
return; return;
offset += copy; offset += copy;
...@@ -85,14 +96,17 @@ void skb_ah_walk(const struct sk_buff *skb, struct crypto_tfm *tfm) ...@@ -85,14 +96,17 @@ void skb_ah_walk(const struct sk_buff *skb, struct crypto_tfm *tfm)
end = start + skb_shinfo(skb)->frags[i].size; end = start + skb_shinfo(skb)->frags[i].size;
if ((copy = end - offset) > 0) { if ((copy = end - offset) > 0) {
u8 *vaddr;
skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
if (copy > len) if (copy > len)
copy = len; copy = len;
vaddr = kmap_skb_frag(frag);
tfm->__crt_alg->cra_digest.dia_update(tfm->crt_ctx, vaddr+frag->page_offset+offset-start, copy); sg.page = frag->page;
kunmap_skb_frag(vaddr); sg.offset = frag->page_offset + offset-start;
sg.length = copy;
crypto_hmac_update(tfm, &sg, 1);
if (!(len -= copy)) if (!(len -= copy))
return; return;
offset += copy; offset += copy;
...@@ -124,6 +138,19 @@ void skb_ah_walk(const struct sk_buff *skb, struct crypto_tfm *tfm) ...@@ -124,6 +138,19 @@ void skb_ah_walk(const struct sk_buff *skb, struct crypto_tfm *tfm)
BUG(); BUG();
} }
static void
ah_hmac_digest(struct ah_data *ahp, struct sk_buff *skb, u8 *auth_data)
{
struct crypto_tfm *tfm = ahp->tfm;
char digest[crypto_tfm_alg_digestsize(tfm)];
memset(auth_data, 0, ahp->digest_len);
crypto_hmac_init(tfm, ahp->key, &ahp->key_len);
skb_ah_walk(skb, tfm);
crypto_hmac_final(tfm, ahp->key, &ahp->key_len, digest);
memcpy(auth_data, digest, ahp->digest_len);
}
int ah_output(struct sk_buff *skb) int ah_output(struct sk_buff *skb)
{ {
int err; int err;
...@@ -186,7 +213,7 @@ int ah_output(struct sk_buff *skb) ...@@ -186,7 +213,7 @@ int ah_output(struct sk_buff *skb)
ah->reserved = 0; ah->reserved = 0;
ah->spi = x->id.spi; ah->spi = x->id.spi;
ah->seq_no = htonl(++x->replay.oseq); ah->seq_no = htonl(++x->replay.oseq);
ahp->digest(x, skb, ah->auth_data); ahp->digest(ahp, skb, ah->auth_data);
top_iph->tos = iph->tos; top_iph->tos = iph->tos;
top_iph->ttl = iph->ttl; top_iph->ttl = iph->ttl;
if (x->props.mode) { if (x->props.mode) {
...@@ -202,7 +229,7 @@ int ah_output(struct sk_buff *skb) ...@@ -202,7 +229,7 @@ int ah_output(struct sk_buff *skb)
skb->nh.raw = skb->data; skb->nh.raw = skb->data;
x->stats.bytes += skb->len; x->curlft.bytes += skb->len;
spin_unlock_bh(&x->lock); spin_unlock_bh(&x->lock);
if ((skb->dst = dst_pop(dst)) == NULL) if ((skb->dst = dst_pop(dst)) == NULL)
goto error; goto error;
...@@ -258,7 +285,7 @@ int ah_input(struct xfrm_state *x, struct sk_buff *skb) ...@@ -258,7 +285,7 @@ int ah_input(struct xfrm_state *x, struct sk_buff *skb)
u8 auth_data[ahp->digest_len]; u8 auth_data[ahp->digest_len];
memcpy(auth_data, ah->auth_data, ahp->digest_len); memcpy(auth_data, ah->auth_data, ahp->digest_len);
skb_push(skb, skb->data - skb->nh.raw); skb_push(skb, skb->data - skb->nh.raw);
ahp->digest(x, skb, ah->auth_data); ahp->digest(ahp, skb, ah->auth_data);
if (memcmp(ah->auth_data, auth_data, ahp->digest_len)) { if (memcmp(ah->auth_data, auth_data, ahp->digest_len)) {
x->stats.integrity_failed++; x->stats.integrity_failed++;
goto out; goto out;
...@@ -295,16 +322,75 @@ void ah4_err(struct sk_buff *skb, u32 info) ...@@ -295,16 +322,75 @@ void ah4_err(struct sk_buff *skb, u32 info)
xfrm_state_put(x); xfrm_state_put(x);
} }
int ah_init_state(struct xfrm_state *x, void *args)
{
struct ah_data *ahp = NULL;
if (x->aalg == NULL || x->aalg->alg_key_len == 0 ||
x->aalg->alg_key_len > 512)
goto error;
ahp = kmalloc(sizeof(*ahp), GFP_KERNEL);
if (ahp == NULL)
return -ENOMEM;
ahp->key = x->aalg->alg_key;
ahp->key_len = (x->aalg->alg_key_len+7)/8;
ahp->tfm = crypto_alloc_tfm(x->aalg->alg_name, 0);
ahp->digest = ah_hmac_digest;
ahp->digest_len = 12;
x->props.header_len = (12 + ahp->digest_len + 7)&~7;
if (x->props.mode)
x->props.header_len += 20;
x->data = ahp;
return 0;
error:
if (ahp) {
if (ahp->tfm)
crypto_free_tfm(ahp->tfm);
kfree(ahp);
}
return -EINVAL;
}
void ah_destroy(struct xfrm_state *x)
{
struct ah_data *ahp = x->data;
if (ahp->tfm) {
crypto_free_tfm(ahp->tfm);
ahp->tfm = NULL;
}
}
static struct xfrm_type ah_type =
{
.description = "AH4",
.proto = IPPROTO_AH,
.init_state = ah_init_state,
.destructor = ah_destroy,
.input = ah_input,
.output = ah_output
};
static struct inet_protocol ah4_protocol = { static struct inet_protocol ah4_protocol = {
.handler = xfrm4_rcv, .handler = xfrm4_rcv,
.err_handler = ah4_err, .err_handler = ah4_err,
}; };
int __init ah4_init(void) int __init ah4_init(void)
{ {
SET_MODULE_OWNER(&ah_type);
if (xfrm_register_type(&ah_type) < 0) {
printk(KERN_INFO "ip ah init: can't add xfrm type\n");
return -EAGAIN;
}
if (inet_add_protocol(&ah4_protocol, IPPROTO_AH) < 0) { if (inet_add_protocol(&ah4_protocol, IPPROTO_AH) < 0) {
printk(KERN_INFO "ip ah init: can't add protocol\n"); printk(KERN_INFO "ip ah init: can't add protocol\n");
xfrm_unregister_type(&ah_type);
return -EAGAIN; return -EAGAIN;
} }
return 0; return 0;
...@@ -314,4 +400,10 @@ static void __exit ah4_fini(void) ...@@ -314,4 +400,10 @@ static void __exit ah4_fini(void)
{ {
if (inet_del_protocol(&ah4_protocol, IPPROTO_AH) < 0) if (inet_del_protocol(&ah4_protocol, IPPROTO_AH) < 0)
printk(KERN_INFO "ip ah close: can't remove protocol\n"); printk(KERN_INFO "ip ah close: can't remove protocol\n");
if (xfrm_unregister_type(&ah_type) < 0)
printk(KERN_INFO "ip ah close: can't remove xfrm type\n");
} }
module_init(ah4_init);
module_exit(ah4_fini);
MODULE_LICENSE("GPL");
#include <linux/config.h>
#include <linux/module.h>
#include <net/ip.h>
#include <net/xfrm.h>
#include <asm/scatterlist.h>
#include <linux/crypto.h>
#include <linux/pfkeyv2.h>
#include <linux/random.h>
#include <net/icmp.h>
#define MAX_SG_ONSTACK 4
/* BUGS:
* - we assume replay seqno is always present.
*/
struct esp_data
{
/* Confidentiality */
struct {
u8 *key; /* Key */
int key_len; /* Key length */
u8 *ivec; /* ivec buffer */
/* ivlen is offset from enc_data, where encrypted data start.
* It is logically different of crypto_tfm_alg_ivsize(tfm).
* We assume that it is either zero (no ivec), or
* >= crypto_tfm_alg_ivsize(tfm). */
int ivlen;
int padlen; /* 0..255 */
struct crypto_tfm *tfm; /* crypto handle */
} conf;
/* Integrity. It is active when authlen != 0 */
struct {
u8 *key; /* Key */
int key_len; /* Length of the key */
/* authlen is length of trailer containing auth token.
* If it is not zero it is assumed to be
* >= crypto_tfm_alg_digestsize(atfm) */
int authlen;
void (*digest)(struct esp_data*,
struct sk_buff *skb,
int offset,
int len,
u8 *digest);
struct crypto_tfm *tfm;
} auth;
};
/* Move to common area: it is shared with AH. */
void skb_digest_walk(const struct sk_buff *skb, struct crypto_tfm *tfm,
int offset, int len)
{
int start = skb->len - skb->data_len;
int i, copy = start - offset;
/* Checksum header. */
if (copy > 0) {
if (copy > len)
copy = len;
tfm->__crt_alg->cra_digest.dia_update(tfm->crt_ctx, skb->data+offset, copy);
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) {
u8 *vaddr;
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
if (copy > len)
copy = len;
vaddr = kmap_skb_frag(frag);
tfm->__crt_alg->cra_digest.dia_update(tfm->crt_ctx, vaddr+frag->page_offset+offset-start, copy);
kunmap_skb_frag(vaddr);
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_digest_walk(list, tfm, offset-start, copy);
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;
}
/* Common with AH after some work on arguments. */
static void
esp_hmac_digest(struct esp_data *esp, struct sk_buff *skb, int offset,
int len, u8 *auth_data)
{
struct crypto_tfm *tfm = esp->auth.tfm;
int i;
char tmp_digest[crypto_tfm_alg_digestsize(tfm)];
char pad[crypto_tfm_alg_blocksize(tfm)];
memset(auth_data, 0, esp->auth.authlen);
memset(pad, 0, sizeof(pad));
memcpy(pad, esp->auth.key, esp->auth.key_len);
for (i = 0; i < crypto_tfm_alg_blocksize(tfm); i++)
pad[i] ^= 0x36;
crypto_digest_init(tfm);
tfm->__crt_alg->cra_digest.dia_update(tfm->crt_ctx, pad, sizeof(pad));
skb_digest_walk(skb, tfm, offset, len);
tfm->__crt_alg->cra_digest.dia_update(tfm->crt_ctx, skb->data+offset, len);
crypto_digest_final(tfm, tmp_digest);
memset(pad, 0, sizeof(pad));
memcpy(pad, esp->auth.key, esp->auth.key_len);
for (i = 0; i < crypto_tfm_alg_blocksize(tfm); i++)
pad[i] ^= 0x5c;
crypto_digest_init(tfm);
tfm->__crt_alg->cra_digest.dia_update(tfm->crt_ctx, pad, sizeof(pad));
tfm->__crt_alg->cra_digest.dia_update(tfm->crt_ctx, tmp_digest, crypto_tfm_alg_digestsize(tfm));
crypto_digest_final(tfm, auth_data);
}
/* 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 beoynd 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);
}
int esp_output(struct sk_buff *skb)
{
int err;
struct dst_entry *dst = skb->dst;
struct xfrm_state *x = dst->xfrm;
struct iphdr *iph, *top_iph;
struct ip_esp_hdr *esph;
struct crypto_tfm *tfm;
struct esp_data *esp;
struct sk_buff *trailer;
int blksize;
int clen;
int nfrags;
union {
struct iphdr iph;
char buf[60];
} tmp_iph;
/* First, if the skb is not checksummed, complete checksum. */
if (skb->ip_summed == CHECKSUM_HW && skb_checksum_help(skb) == NULL)
return -EINVAL;
spin_lock_bh(&x->lock);
if ((err = xfrm_state_check_expire(x)) != 0)
goto error;
if ((err = xfrm_state_check_space(x, skb)) != 0)
goto error;
err = -ENOMEM;
/* Strip IP header in transport mode. Save it. */
if (!x->props.mode) {
iph = skb->nh.iph;
memcpy(&tmp_iph, iph, iph->ihl*4);
__skb_pull(skb, iph->ihl*4);
}
/* Now skb is pure payload to encrypt */
/* Round to block size */
clen = skb->len;
esp = x->data;
tfm = esp->conf.tfm;
blksize = crypto_tfm_alg_blocksize(tfm);
clen = (clen + 2 + blksize-1)&~(blksize-1);
if (esp->conf.padlen)
clen = (clen + esp->conf.padlen-1)&~(esp->conf.padlen-1);
if ((nfrags = skb_cow_data(skb, clen-skb->len+esp->auth.authlen, &trailer)) < 0)
goto error;
/* Fill padding... */
do {
int i;
for (i=0; i<clen-skb->len - 2; i++)
*(u8*)(trailer->tail + i) = i+1;
} while (0);
*(u8*)(trailer->tail + clen-skb->len - 2) = (clen - skb->len)-2;
pskb_put(skb, trailer, clen - skb->len);
iph = skb->nh.iph;
if (x->props.mode) {
top_iph = (struct iphdr*)skb_push(skb, x->props.header_len);
esph = (struct ip_esp_hdr*)(top_iph+1);
*(u8*)(trailer->tail - 1) = IPPROTO_IP;
top_iph->ihl = 4;
top_iph->version = 5;
top_iph->tos = iph->tos; /* DS disclosed */
top_iph->tot_len = htons(skb->len + esp->auth.authlen);
top_iph->id = inet_getid(((struct rtable*)dst)->peer, 0);
top_iph->frag_off = iph->frag_off&htons(IP_DF);
top_iph->ttl = iph->ttl; /* TTL disclosed */
top_iph->protocol = IPPROTO_ESP;
top_iph->check = 0;
top_iph->saddr = x->props.saddr.xfrm4_addr;
top_iph->daddr = x->id.daddr.xfrm4_addr;
memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
} else {
esph = (struct ip_esp_hdr*)skb_push(skb, x->props.header_len);
top_iph = (struct iphdr*)skb_push(skb, iph->ihl*4);
memcpy(top_iph, &tmp_iph, iph->ihl*4);
iph = &tmp_iph.iph;
top_iph->tot_len = htons(skb->len + esp->auth.authlen);
top_iph->protocol = IPPROTO_ESP;
top_iph->check = 0;
top_iph->frag_off = iph->frag_off;
*(u8*)(trailer->tail - 1) = iph->protocol;
}
esph->spi = x->id.spi;
esph->seq_no = htonl(++x->replay.oseq);
if (esp->conf.ivlen)
crypto_cipher_set_iv(tfm, esp->conf.ivec, crypto_tfm_alg_ivsize(tfm));
do {
struct scatterlist sgbuf[nfrags>MAX_SG_ONSTACK ? 0 : nfrags];
struct scatterlist *sg = sgbuf;
if (unlikely(nfrags > MAX_SG_ONSTACK)) {
sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC);
if (!sg)
goto error;
}
skb_to_sgvec(skb, sg, esph->enc_data+esp->conf.ivlen-skb->data, clen);
crypto_cipher_encrypt(tfm, sg, nfrags);
if (unlikely(sg != sgbuf))
kfree(sg);
} while (0);
if (esp->conf.ivlen) {
memcpy(esph->enc_data, esp->conf.ivec, crypto_tfm_alg_ivsize(tfm));
crypto_cipher_get_iv(tfm, esp->conf.ivec, crypto_tfm_alg_ivsize(tfm));
}
if (esp->auth.authlen) {
esp->auth.digest(esp, skb, (u8*)esph-skb->data,
8+esp->conf.ivlen+clen, trailer->tail);
pskb_put(skb, trailer, esp->auth.authlen);
}
ip_send_check(top_iph);
skb->nh.raw = skb->data;
x->curlft.bytes += skb->len;
spin_unlock_bh(&x->lock);
if ((skb->dst = dst_pop(dst)) == NULL)
goto error;
return NET_XMIT_BYPASS;
error:
spin_unlock_bh(&x->lock);
kfree_skb(skb);
return err;
}
int esp_input(struct xfrm_state *x, struct sk_buff *skb)
{
struct iphdr *iph;
struct ip_esp_hdr *esph;
struct esp_data *esp = x->data;
struct sk_buff *trailer;
int blksize = crypto_tfm_alg_blocksize(esp->conf.tfm);
int elen = skb->len - 8 - esp->conf.ivlen - esp->auth.authlen;
int nfrags;
if (!pskb_may_pull(skb, sizeof(struct ip_esp_hdr)))
goto out;
if (elen <= 0 || (elen & (blksize-1)))
goto out;
/* If integrity check is required, do this. */
if (esp->auth.authlen) {
int icvsize = crypto_tfm_alg_digestsize(esp->auth.tfm);
u8 sum[icvsize];
u8 sum1[icvsize];
esp->auth.digest(esp, skb, 0, skb->len-esp->auth.authlen, sum);
if (skb_copy_bits(skb, skb->len-esp->auth.authlen, sum1, icvsize))
BUG();
if (unlikely(memcmp(sum, sum1, icvsize))) {
x->stats.integrity_failed++;
goto out;
}
}
if ((nfrags = skb_cow_data(skb, 0, &trailer)) < 0)
goto out;
esph = (struct ip_esp_hdr*)skb->data;
iph = skb->nh.iph;
/* Get ivec. This can be wrong, check against another impls. */
if (esp->conf.ivlen)
crypto_cipher_set_iv(esp->conf.tfm, esph->enc_data, crypto_tfm_alg_ivsize(esp->conf.tfm));
{
u8 nexthdr[2];
struct scatterlist sgbuf[nfrags>MAX_SG_ONSTACK ? 0 : nfrags];
struct scatterlist *sg = sgbuf;
u8 workbuf[60];
int padlen;
if (unlikely(nfrags > MAX_SG_ONSTACK)) {
sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC);
if (!sg)
goto out;
}
skb_to_sgvec(skb, sg, 8+esp->conf.ivlen, elen);
crypto_cipher_decrypt(esp->conf.tfm, sg, nfrags);
if (unlikely(sg != sgbuf))
kfree(sg);
if (skb_copy_bits(skb, skb->len-esp->auth.authlen-2,
nexthdr, 2))
BUG();
padlen = nexthdr[0];
if (padlen+2 >= elen)
goto out;
/* ... check padding bits here. Silly. :-) */
iph->protocol = nexthdr[1];
pskb_trim(skb, skb->len - esp->auth.authlen - padlen - 2);
memcpy(workbuf, skb->nh.raw, iph->ihl*4);
skb->h.raw = skb_pull(skb, 8 + esp->conf.ivlen);
skb->nh.raw += 8 + esp->conf.ivlen;
memcpy(skb->nh.raw, workbuf, iph->ihl*4);
skb->nh.iph->tot_len = htons(skb->len);
}
return 0;
out:
return -EINVAL;
}
static u32 esp4_get_max_size(struct xfrm_state *x, int mtu)
{
struct esp_data *esp = x->data;
u32 blksize = crypto_tfm_alg_blocksize(esp->conf.tfm);
if (x->props.mode) {
mtu = (mtu + 2 + blksize-1)&~(blksize-1);
} else {
/* The worst case. */
mtu += 2 + blksize;
}
if (esp->conf.padlen)
mtu = (mtu + esp->conf.padlen-1)&~(esp->conf.padlen-1);
return mtu + x->props.header_len + esp->auth.authlen;
}
void esp4_err(struct sk_buff *skb, u32 info)
{
struct iphdr *iph = (struct iphdr*)skb->data;
struct ip_esp_hdr *esph = (struct ip_esp_hdr*)(skb->data+(iph->ihl<<2));
struct xfrm_state *x;
if (skb->h.icmph->type != ICMP_DEST_UNREACH ||
skb->h.icmph->code != ICMP_FRAG_NEEDED)
return;
x = xfrm_state_lookup(iph->daddr, esph->spi, IPPROTO_ESP);
if (!x)
return;
printk(KERN_DEBUG "pmtu discvovery on SA ESP/%08x/%08x\n",
ntohl(esph->spi), ntohl(iph->daddr));
xfrm_state_put(x);
}
void esp_destroy(struct xfrm_state *x)
{
struct esp_data *esp = x->data;
if (esp->conf.tfm) {
crypto_free_tfm(esp->conf.tfm);
esp->conf.tfm = NULL;
}
if (esp->conf.ivec) {
kfree(esp->conf.ivec);
esp->conf.ivec = NULL;
}
if (esp->auth.tfm) {
crypto_free_tfm(esp->auth.tfm);
esp->auth.tfm = NULL;
}
}
int esp_init_state(struct xfrm_state *x, void *args)
{
struct esp_data *esp = NULL;
if (x->aalg) {
if (x->aalg->alg_key_len == 0 || x->aalg->alg_key_len > 512)
goto error;
}
if (x->ealg == NULL || x->ealg->alg_key_len == 0)
goto error;
esp = kmalloc(sizeof(*esp), GFP_KERNEL);
if (esp == NULL)
return -ENOMEM;
if (x->aalg) {
esp->auth.key = x->aalg->alg_key;
esp->auth.key_len = (x->aalg->alg_key_len+7)/8;
esp->auth.tfm = crypto_alloc_tfm(x->aalg->alg_name, 0);
if (esp->auth.tfm == NULL)
goto error;
esp->auth.digest = esp_hmac_digest;
esp->auth.authlen = crypto_tfm_alg_digestsize(esp->auth.tfm);
}
esp->conf.key = x->ealg->alg_key;
esp->conf.key_len = (x->ealg->alg_key_len+7)/8;
esp->conf.tfm = crypto_alloc_tfm(x->ealg->alg_name, CRYPTO_TFM_MODE_CBC);
if (esp->conf.tfm == NULL)
goto error;
esp->conf.ivlen = crypto_tfm_alg_ivsize(esp->conf.tfm);
esp->conf.padlen = 0;
if (esp->conf.ivlen) {
esp->conf.ivec = kmalloc(esp->conf.ivlen, GFP_KERNEL);
get_random_bytes(esp->conf.ivec, esp->conf.ivlen);
}
crypto_cipher_setkey(esp->conf.tfm, esp->conf.key, esp->conf.key_len);
x->props.header_len = 8 + esp->conf.ivlen;
if (x->props.mode)
x->props.header_len += 20;
x->props.trailer_len = esp->auth.authlen + crypto_tfm_alg_blocksize(esp->conf.tfm);
x->data = esp;
return 0;
error:
if (esp) {
if (esp->auth.tfm)
crypto_free_tfm(esp->auth.tfm);
if (esp->conf.tfm)
crypto_free_tfm(esp->conf.tfm);
kfree(esp);
}
return -EINVAL;
}
static struct xfrm_type esp_type =
{
.description = "ESP4",
.proto = IPPROTO_ESP,
.init_state = esp_init_state,
.destructor = esp_destroy,
.get_max_size = esp4_get_max_size,
.input = esp_input,
.output = esp_output
};
static struct inet_protocol esp4_protocol = {
.handler = xfrm4_rcv,
.err_handler = esp4_err,
};
int __init esp4_init(void)
{
SET_MODULE_OWNER(&esp_type);
if (xfrm_register_type(&esp_type) < 0) {
printk(KERN_INFO "ip esp init: can't add xfrm type\n");
return -EAGAIN;
}
if (inet_add_protocol(&esp4_protocol, IPPROTO_ESP) < 0) {
printk(KERN_INFO "ip esp init: can't add protocol\n");
xfrm_unregister_type(&esp_type);
return -EAGAIN;
}
return 0;
}
static void __exit esp4_fini(void)
{
if (inet_del_protocol(&esp4_protocol, IPPROTO_ESP) < 0)
printk(KERN_INFO "ip esp close: can't remove protocol\n");
if (xfrm_unregister_type(&esp_type) < 0)
printk(KERN_INFO "ip esp close: can't remove xfrm type\n");
}
module_init(esp4_init);
module_exit(esp4_fini);
MODULE_LICENSE("GPL");
...@@ -155,6 +155,53 @@ void __init flow_cache_init(void) ...@@ -155,6 +155,53 @@ void __init flow_cache_init(void)
memset(flow_table, 0, PAGE_SIZE<<order); memset(flow_table, 0, PAGE_SIZE<<order);
} }
static struct xfrm_type *xfrm_type_map[256];
static rwlock_t xfrm_type_lock = RW_LOCK_UNLOCKED;
int xfrm_register_type(struct xfrm_type *type)
{
int err = 0;
write_lock(&xfrm_type_lock);
if (xfrm_type_map[type->proto] == NULL)
xfrm_type_map[type->proto] = type;
else
err = -EEXIST;
write_unlock(&xfrm_type_lock);
return err;
}
int xfrm_unregister_type(struct xfrm_type *type)
{
int err = 0;
write_lock(&xfrm_type_lock);
if (xfrm_type_map[type->proto] != type)
err = -ENOENT;
else
xfrm_type_map[type->proto] = NULL;
write_unlock(&xfrm_type_lock);
return err;
}
struct xfrm_type *xfrm_get_type(u8 proto)
{
struct xfrm_type *type;
read_lock(&xfrm_type_lock);
type = xfrm_type_map[proto];
if (type && !try_inc_mod_count(type->owner))
type = NULL;
write_unlock(&xfrm_type_lock);
return type;
}
void xfrm_put_type(struct xfrm_type *type)
{
if (type->owner)
__MOD_DEC_USE_COUNT(type->owner);
}
/* Allocate xfrm_policy. Not used here, it is supposed to be used by pfkeyv2 /* Allocate xfrm_policy. Not used here, it is supposed to be used by pfkeyv2
* SPD calls. * SPD calls.
...@@ -203,6 +250,10 @@ void xfrm_policy_kill(struct xfrm_policy *policy) ...@@ -203,6 +250,10 @@ void xfrm_policy_kill(struct xfrm_policy *policy)
struct dst_entry *dst; struct dst_entry *dst;
int i; int i;
write_lock_bh(&policy->lock);
if (policy->dead)
goto out;
policy->dead = 1; policy->dead = 1;
for (i=0; i<policy->xfrm_nr; i++) { for (i=0; i<policy->xfrm_nr; i++) {
...@@ -216,6 +267,128 @@ void xfrm_policy_kill(struct xfrm_policy *policy) ...@@ -216,6 +267,128 @@ void xfrm_policy_kill(struct xfrm_policy *policy)
policy->bundles = dst->next; policy->bundles = dst->next;
dst_free(dst); dst_free(dst);
} }
out:
write_unlock_bh(&policy->lock);
}
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++;
write_unlock_bh(&xfrm_policy_lock);
if (pol) {
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[dir]; (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(&xfrm_policy_lock);
for (dir = 0; dir < 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 < XFRM_POLICY_MAX; dir++) {
for (xp = xfrm_policy_list[dir]; xp; xp = xp->next) {
error = func(xp, dir, --count, data);
if (error)
goto out;
}
}
out:
read_unlock(&xfrm_policy_lock);
return error;
} }
...@@ -224,14 +397,13 @@ void xfrm_policy_kill(struct xfrm_policy *policy) ...@@ -224,14 +397,13 @@ void xfrm_policy_kill(struct xfrm_policy *policy)
struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl) struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl)
{ {
struct xfrm_policy *pol; struct xfrm_policy *pol;
unsigned long now = xtime.tv_sec; // Not now :-) u64 now = (unsigned long)xtime.tv_sec;
read_lock(&xfrm_policy_lock); read_lock(&xfrm_policy_lock);
for (pol = xfrm_policy_list[dir]; pol; pol = pol->next) { for (pol = xfrm_policy_list[dir]; pol; pol = pol->next) {
struct xfrm_selector *sel = &pol->selector; struct xfrm_selector *sel = &pol->selector;
if (xfrm4_selector_match(sel, fl) && now < pol->expires) { if (xfrm4_selector_match(sel, fl) /* XXX && XXX now < pol->validtime */) {
pol->lastuse = now;
atomic_inc(&pol->refcnt); atomic_inc(&pol->refcnt);
break; break;
} }
...@@ -261,7 +433,7 @@ xfrm_tmpl_resolve(struct xfrm_policy *policy, struct flowi *fl, ...@@ -261,7 +433,7 @@ xfrm_tmpl_resolve(struct xfrm_policy *policy, struct flowi *fl,
xfrm[i] = tmpl->resolved; xfrm[i] = tmpl->resolved;
atomic_inc(&tmpl->resolved->refcnt); atomic_inc(&tmpl->resolved->refcnt);
} else { } else {
xfrm[i] = xfrm_state_find(daddr, fl, tmpl); xfrm[i] = xfrm_state_find(daddr, fl, tmpl, policy);
if (xfrm[i] == NULL) { if (xfrm[i] == NULL) {
error = -ENOMEM; error = -ENOMEM;
goto fail; goto fail;
...@@ -269,11 +441,11 @@ xfrm_tmpl_resolve(struct xfrm_policy *policy, struct flowi *fl, ...@@ -269,11 +441,11 @@ xfrm_tmpl_resolve(struct xfrm_policy *policy, struct flowi *fl,
if (xfrm[i]->km.state == XFRM_STATE_VALID) if (xfrm[i]->km.state == XFRM_STATE_VALID)
continue; continue;
i++;
if (xfrm[i]->km.state == XFRM_STATE_ERROR) if (xfrm[i]->km.state == XFRM_STATE_ERROR)
error = -EINVAL; error = -EINVAL;
else else
error = -EAGAIN; error = -EAGAIN;
i++;
goto fail; goto fail;
} }
} }
...@@ -539,7 +711,7 @@ xfrm_state_ok(struct xfrm_tmpl *tmpl, struct xfrm_state *x) ...@@ -539,7 +711,7 @@ xfrm_state_ok(struct xfrm_tmpl *tmpl, struct xfrm_state *x)
return x->id.proto == tmpl->id.proto && return x->id.proto == tmpl->id.proto &&
(x->id.spi == tmpl->id.spi || !tmpl->id.spi) && (x->id.spi == tmpl->id.spi || !tmpl->id.spi) &&
x->props.mode == tmpl->mode && x->props.mode == tmpl->mode &&
(tmpl->algos & (1<<x->props.algo)) && (tmpl->aalgos & (1<<x->props.aalgo)) &&
(!x->props.mode || !tmpl->saddr.xfrm4_addr || (!x->props.mode || !tmpl->saddr.xfrm4_addr ||
tmpl->saddr.xfrm4_addr == x->props.saddr.xfrm4_addr); tmpl->saddr.xfrm4_addr == x->props.saddr.xfrm4_addr);
} }
...@@ -798,5 +970,4 @@ void __init xfrm_init(void) ...@@ -798,5 +970,4 @@ void __init xfrm_init(void)
xfrm_state_init(); xfrm_state_init();
xfrm_input_init(); xfrm_input_init();
ah4_init();
} }
...@@ -13,7 +13,9 @@ ...@@ -13,7 +13,9 @@
by policy. (output) by policy. (output)
*/ */
spinlock_t xfrm_state_lock = SPIN_LOCK_UNLOCKED; static spinlock_t xfrm_state_lock = SPIN_LOCK_UNLOCKED;
#define XFRM_DST_HSIZE 1024
/* Hash table to find appropriate SA towards given target (endpoint /* Hash table to find appropriate SA towards given target (endpoint
* of tunnel or destination of transport mode) allowed by selector. * of tunnel or destination of transport mode) allowed by selector.
...@@ -21,8 +23,8 @@ spinlock_t xfrm_state_lock = SPIN_LOCK_UNLOCKED; ...@@ -21,8 +23,8 @@ spinlock_t xfrm_state_lock = SPIN_LOCK_UNLOCKED;
* Main use is finding SA after policy selected tunnel or transport mode. * 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. * Also, it can be used by ah/esp icmp error handler to find offending SA.
*/ */
struct list_head xfrm_state_bydst[XFRM_DST_HSIZE]; static struct list_head xfrm_state_bydst[XFRM_DST_HSIZE];
struct list_head xfrm_state_byspi[XFRM_DST_HSIZE]; static struct list_head xfrm_state_byspi[XFRM_DST_HSIZE];
wait_queue_head_t *km_waitq; wait_queue_head_t *km_waitq;
...@@ -45,13 +47,68 @@ struct xfrm_state *xfrm_state_alloc(void) ...@@ -45,13 +47,68 @@ struct xfrm_state *xfrm_state_alloc(void)
void __xfrm_state_destroy(struct xfrm_state *x) void __xfrm_state_destroy(struct xfrm_state *x)
{ {
BUG_TRAP(x->km.state == XFRM_STATE_DEAD); BUG_TRAP(x->km.state == XFRM_STATE_DEAD);
if (x->aalg)
kfree(x->aalg);
if (x->ealg)
kfree(x->ealg);
if (x->calg)
kfree(x->calg);
if (x->type) if (x->type)
x->type->destructor(x); xfrm_put_type(x->type);
kfree(x); kfree(x);
} }
void xfrm_state_delete(struct xfrm_state *x)
{
int kill = 0;
spin_lock_bh(&x->lock);
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);
}
spin_unlock_bh(&x->lock);
if (kill && x->type)
x->type->destructor(x);
wake_up(km_waitq);
}
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 || 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);
}
struct xfrm_state * struct xfrm_state *
xfrm_state_find(u32 daddr, struct flowi *fl, struct xfrm_tmpl *tmpl) xfrm_state_find(u32 daddr, struct flowi *fl, struct xfrm_tmpl *tmpl, struct xfrm_policy *pol)
{ {
unsigned h = ntohl(daddr); unsigned h = ntohl(daddr);
struct xfrm_state *x; struct xfrm_state *x;
...@@ -86,7 +143,8 @@ xfrm_state_find(u32 daddr, struct flowi *fl, struct xfrm_tmpl *tmpl) ...@@ -86,7 +143,8 @@ xfrm_state_find(u32 daddr, struct flowi *fl, struct xfrm_tmpl *tmpl)
return x; return x;
} else if (x->km.state == XFRM_STATE_ACQ) { } else if (x->km.state == XFRM_STATE_ACQ) {
acquire_in_progress = 1; acquire_in_progress = 1;
} else if (x->km.state == XFRM_STATE_ERROR) { } else if (x->km.state == XFRM_STATE_ERROR ||
x->km.state == XFRM_STATE_EXPIRED) {
if (xfrm4_selector_match(&x->sel, fl)) if (xfrm4_selector_match(&x->sel, fl))
error = 1; error = 1;
} }
...@@ -112,10 +170,23 @@ xfrm_state_find(u32 daddr, struct flowi *fl, struct xfrm_tmpl *tmpl) ...@@ -112,10 +170,23 @@ xfrm_state_find(u32 daddr, struct flowi *fl, struct xfrm_tmpl *tmpl)
x->sel.proto = fl->proto; x->sel.proto = fl->proto;
x->sel.ifindex = fl->oif; x->sel.ifindex = fl->oif;
x->id = tmpl->id; x->id = tmpl->id;
if (x->id.daddr.xfrm4_addr == 0)
if (km_query(x) == 0) { x->id.daddr = x->sel.daddr;
x->props.saddr = tmpl->saddr;
if (x->props.saddr.xfrm4_addr == 0)
x->props.saddr = x->sel.saddr;
x->props.mode = tmpl->mode;
if (km_query(x, tmpl, pol) == 0) {
x->km.state = XFRM_STATE_ACQ;
list_add_tail(&x->bydst, xfrm_state_bydst+h); list_add_tail(&x->bydst, xfrm_state_bydst+h);
atomic_inc(&x->refcnt); atomic_inc(&x->refcnt);
if (x->id.spi) {
h = ntohl(x->id.daddr.xfrm4_addr^x->id.spi^x->id.proto);
h = (h ^ (h>>10) ^ (h>>20)) % XFRM_DST_HSIZE;
list_add(&x->byspi, xfrm_state_byspi+h);
atomic_inc(&x->refcnt);
}
} else { } else {
x->km.state = XFRM_STATE_DEAD; x->km.state = XFRM_STATE_DEAD;
xfrm_state_put(x); xfrm_state_put(x);
...@@ -142,6 +213,7 @@ void xfrm_state_insert(struct xfrm_state *x) ...@@ -142,6 +213,7 @@ void xfrm_state_insert(struct xfrm_state *x)
atomic_inc(&x->refcnt); atomic_inc(&x->refcnt);
spin_unlock_bh(&xfrm_state_lock); spin_unlock_bh(&xfrm_state_lock);
wake_up(km_waitq);
} }
int xfrm_state_check_expire(struct xfrm_state *x) int xfrm_state_check_expire(struct xfrm_state *x)
...@@ -149,16 +221,16 @@ int xfrm_state_check_expire(struct xfrm_state *x) ...@@ -149,16 +221,16 @@ int xfrm_state_check_expire(struct xfrm_state *x)
if (x->km.state != XFRM_STATE_VALID) if (x->km.state != XFRM_STATE_VALID)
return -EINVAL; return -EINVAL;
if (x->props.hard_byte_limit && if (x->lft.hard_byte_limit &&
x->stats.bytes >= x->props.hard_byte_limit) { x->curlft.bytes >= x->lft.hard_byte_limit) {
km_notify(x, SADB_EXT_LIFETIME_HARD); km_expired(x);
return -EINVAL; return -EINVAL;
} }
if (x->km.warn_bytes && if (x->km.warn_bytes &&
x->stats.bytes >= x->km.warn_bytes) { x->curlft.bytes >= x->km.warn_bytes) {
x->km.warn_bytes = 0; x->km.warn_bytes = 0;
km_notify(x, SADB_EXT_LIFETIME_SOFT); km_warn_expired(x);
} }
return 0; return 0;
} }
...@@ -189,7 +261,6 @@ xfrm_state_lookup(u32 daddr, u32 spi, u8 proto) ...@@ -189,7 +261,6 @@ xfrm_state_lookup(u32 daddr, u32 spi, u8 proto)
daddr == x->id.daddr.xfrm4_addr && daddr == x->id.daddr.xfrm4_addr &&
proto == x->id.proto) { proto == x->id.proto) {
atomic_inc(&x->refcnt); atomic_inc(&x->refcnt);
x->stats.lastuse = xtime.tv_sec;
spin_unlock_bh(&xfrm_state_lock); spin_unlock_bh(&xfrm_state_lock);
return x; return x;
} }
...@@ -198,6 +269,150 @@ xfrm_state_lookup(u32 daddr, u32 spi, u8 proto) ...@@ -198,6 +269,150 @@ xfrm_state_lookup(u32 daddr, u32 spi, u8 proto)
return NULL; return NULL;
} }
struct xfrm_state *
xfrm_find_acq(u8 mode, u16 reqid, u8 proto, u32 daddr, u32 saddr)
{
struct xfrm_state *x, *x0;
unsigned h = ntohl(daddr);
h = (h ^ (h>>16)) % XFRM_DST_HSIZE;
x0 = NULL;
spin_lock_bh(&xfrm_state_lock);
list_for_each_entry(x, xfrm_state_bydst+h, bydst) {
if (daddr == x->id.daddr.xfrm4_addr &&
mode == x->props.mode &&
proto == x->id.proto &&
saddr == x->props.saddr.xfrm4_addr &&
(!reqid || reqid == x->props.reqid)) {
if (!x0)
x0 = x;
if (x->km.state != XFRM_STATE_ACQ)
continue;
x0 = x;
break;
}
}
if (x0) {
atomic_inc(&x0->refcnt);
} else if ((x0 = xfrm_state_alloc()) != NULL) {
x0->sel.daddr.xfrm4_addr = daddr;
x0->sel.daddr.xfrm4_mask = ~0;
x0->sel.saddr.xfrm4_addr = saddr;
x0->sel.saddr.xfrm4_mask = ~0;
x0->sel.prefixlen_d = 32;
x0->sel.prefixlen_s = 32;
x0->props.saddr.xfrm4_addr = saddr;
x0->km.state = XFRM_STATE_ACQ;
x0->id.daddr.xfrm4_addr = daddr;
x0->id.proto = proto;
x0->props.mode = mode;
x0->props.reqid = reqid;
atomic_inc(&x0->refcnt);
list_add_tail(&x0->bydst, xfrm_state_bydst+h);
wake_up(km_waitq);
}
spin_unlock_bh(&xfrm_state_lock);
return x0;
}
/* 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;
}
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.xfrm4_addr, minspi, x->id.proto);
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.xfrm4_addr, htonl(spi), x->id.proto);
if (x0 == NULL)
break;
xfrm_state_put(x0);
}
x->id.spi = htonl(spi);
}
if (x->id.spi) {
spin_lock_bh(&xfrm_state_lock);
h = ntohl(x->id.daddr.xfrm4_addr^x->id.spi^x->id.proto);
h = (h ^ (h>>10) ^ (h>>20)) % XFRM_DST_HSIZE;
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 == 255 || 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 != 255 && 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) int xfrm_replay_check(struct xfrm_state *x, u32 seq)
{ {
u32 diff; u32 diff;
...@@ -253,13 +468,60 @@ int xfrm_check_selectors(struct xfrm_state **x, int n, struct flowi *fl) ...@@ -253,13 +468,60 @@ int xfrm_check_selectors(struct xfrm_state **x, int n, struct flowi *fl)
return 0; return 0;
} }
void km_notify(struct xfrm_state *x, int event) 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;
read_lock(&xfrm_km_lock);
list_for_each_entry(km, &xfrm_km_list, list)
km->notify(x, 0);
read_unlock(&xfrm_km_lock);
} }
int km_query(struct xfrm_state *x) void km_expired(struct xfrm_state *x)
{ {
return -EINVAL; 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);
}
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_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;
} }
void __init xfrm_state_init(void) void __init xfrm_state_init(void)
......
#
# Makefile for the key AF.
#
obj-$(CONFIG_NET_KEY) += af_key.o
include $(TOPDIR)/Rules.make
/*
* net/key/pfkeyv2.c An implemenation of PF_KEYv2 sockets.
*
* 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.
*
* Authors: Maxim Giryaev <gem@asplinux.ru>
* David S. Miller <davem@redhat.com>
* Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/socket.h>
#include <linux/pfkeyv2.h>
#include <linux/ipsec.h>
#include <linux/skbuff.h>
#include <linux/rtnetlink.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <net/xfrm.h>
#include <net/sock.h>
/* List of all pfkey sockets. */
static struct sock * pfkey_table;
static DECLARE_WAIT_QUEUE_HEAD(pfkey_table_wait);
static rwlock_t pfkey_table_lock = RW_LOCK_UNLOCKED;
static atomic_t pfkey_table_users = ATOMIC_INIT(0);
static atomic_t pfkey_socks_nr = ATOMIC_INIT(0);
struct pfkey_opt {
int registered;
int promisc;
};
#define pfkey_sk(__sk) ((struct pfkey_opt *)(__sk)->protinfo)
static void pfkey_sock_destruct(struct sock *sk)
{
skb_queue_purge(&sk->receive_queue);
if (!sk->dead) {
printk("Attempt to release alive pfkey socket: %p\n", sk);
return;
}
BUG_TRAP(atomic_read(&sk->rmem_alloc)==0);
BUG_TRAP(atomic_read(&sk->wmem_alloc)==0);
kfree(pfkey_sk(sk));
atomic_dec(&pfkey_socks_nr);
MOD_DEC_USE_COUNT;
}
static void pfkey_table_grab(void)
{
write_lock_bh(&pfkey_table_lock);
if (atomic_read(&pfkey_table_users)) {
DECLARE_WAITQUEUE(wait, current);
add_wait_queue_exclusive(&pfkey_table_wait, &wait);
for(;;) {
set_current_state(TASK_UNINTERRUPTIBLE);
if (atomic_read(&pfkey_table_users) == 0)
break;
write_unlock_bh(&pfkey_table_lock);
schedule();
write_lock_bh(&pfkey_table_lock);
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(&pfkey_table_wait, &wait);
}
}
static __inline__ void pfkey_table_ungrab(void)
{
write_unlock_bh(&pfkey_table_lock);
wake_up(&pfkey_table_wait);
}
static __inline__ void pfkey_lock_table(void)
{
/* read_lock() synchronizes us to pfkey_table_grab */
read_lock(&pfkey_table_lock);
atomic_inc(&pfkey_table_users);
read_unlock(&pfkey_table_lock);
}
static __inline__ void pfkey_unlock_table(void)
{
if (atomic_dec_and_test(&pfkey_table_users))
wake_up(&pfkey_table_wait);
}
static struct proto_ops pfkey_ops;
static void pfkey_insert(struct sock *sk)
{
pfkey_table_grab();
sk->next = pfkey_table;
pfkey_table = sk;
sock_hold(sk);
pfkey_table_ungrab();
}
static void pfkey_remove(struct sock *sk)
{
struct sock **skp;
pfkey_table_grab();
for (skp = &pfkey_table; *skp; skp = &((*skp)->next)) {
if (*skp == sk) {
*skp = sk->next;
__sock_put(sk);
break;
}
}
pfkey_table_ungrab();
}
static int pfkey_create(struct socket *sock, int protocol)
{
struct sock *sk;
struct pfkey_opt *pfk;
int err;
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (sock->type != SOCK_RAW)
return -ESOCKTNOSUPPORT;
if (protocol != PF_KEY_V2)
return -EPROTONOSUPPORT;
MOD_INC_USE_COUNT;
err = -ENOMEM;
sk = sk_alloc(PF_KEY, GFP_KERNEL, 1, NULL);
if (sk == NULL)
goto out;
sock->ops = &pfkey_ops;
sock_init_data(sock, sk);
err = -ENOMEM;
pfk = pfkey_sk(sk) = kmalloc(sizeof(*pfk), GFP_KERNEL);
if (!pfk) {
sk_free(sk);
goto out;
}
memset(pfk, 0, sizeof(*pfk));
sk->family = PF_KEY;
sk->destruct = pfkey_sock_destruct;
atomic_inc(&pfkey_socks_nr);
pfkey_insert(sk);
return 0;
out:
MOD_DEC_USE_COUNT;
return err;
}
static int pfkey_release(struct socket *sock)
{
struct sock *sk = sock->sk;
if (!sk)
return 0;
pfkey_remove(sk);
sock_orphan(sk);
sock->sk = NULL;
skb_queue_purge(&sk->write_queue);
sock_put(sk);
return 0;
}
static void pfkey_broadcast_one(struct sk_buff *skb, struct sk_buff **skb2,
int allocation, struct sock *sk)
{
sock_hold(sk);
if (*skb2 == NULL) {
if (atomic_read(&skb->users) != 1) {
*skb2 = skb_clone(skb, allocation);
} else {
*skb2 = skb;
atomic_inc(&skb->users);
}
}
if (*skb2 != NULL) {
if (atomic_read(&sk->rmem_alloc) <= sk->rcvbuf) {
skb_orphan(*skb2);
skb_set_owner_r(*skb2, sk);
skb_queue_tail(&sk->receive_queue, *skb2);
sk->data_ready(sk, (*skb2)->len);
*skb2 = NULL;
}
}
sock_put(sk);
}
/* Send SKB to all pfkey sockets matching selected criteria. */
#define BROADCAST_ALL 0
#define BROADCAST_ONE 1
#define BROADCAST_REGISTERED 2
#define BROADCAST_PROMISC_ONLY 4
static void pfkey_broadcast(struct sk_buff *skb, int allocation,
int broadcast_flags, struct sock *one_sk)
{
struct sock *sk;
struct sk_buff *skb2 = NULL;
/* XXX Do we need something like netlink_overrun? I think
* XXX PF_KEY socket apps will not mind current behavior.
*/
if (!skb)
return;
pfkey_lock_table();
for (sk = pfkey_table; sk; sk = sk->next) {
struct pfkey_opt *pfk = pfkey_sk(sk);
/* Yes, it means that if you are meant to receive this
* pfkey message you receive it twice as promiscuous
* socket.
*/
if (pfk->promisc)
pfkey_broadcast_one(skb, &skb2, allocation, sk);
/* the exact target will be processed later */
if (sk == one_sk)
continue;
if (broadcast_flags != BROADCAST_ALL) {
if (broadcast_flags & BROADCAST_PROMISC_ONLY)
continue;
if ((broadcast_flags & BROADCAST_REGISTERED) &&
!pfk->registered)
continue;
if (broadcast_flags & BROADCAST_ONE)
continue;
}
pfkey_broadcast_one(skb, &skb2, allocation, sk);
}
pfkey_unlock_table();
if (one_sk != NULL)
pfkey_broadcast_one(skb, &skb2, allocation, one_sk);
if (skb2)
kfree_skb(skb2);
kfree_skb(skb);
}
static inline void pfkey_hdr_dup(struct sadb_msg *new, struct sadb_msg *orig)
{
*new = *orig;
}
static void pfkey_error(struct sadb_msg *orig, int err)
{
struct sk_buff *skb = alloc_skb(sizeof(struct sadb_msg) + 16, GFP_KERNEL);
struct sadb_msg *hdr;
if (!skb)
return;
/* Woe be to the platform trying to support PFKEY yet
* having normal errnos outside the 1-255 range, inclusive.
*/
err = -err;
if (err == ERESTARTSYS ||
err == ERESTARTNOHAND ||
err == ERESTARTNOINTR)
err = EINTR;
if (err >= 512)
err = EINVAL;
if (err <= 0 || err >= 256)
BUG();
hdr = (struct sadb_msg *) skb_put(skb, sizeof(struct sadb_msg));
pfkey_hdr_dup(hdr, orig);
hdr->sadb_msg_errno = (uint8_t) err;
hdr->sadb_msg_len = (sizeof(struct sadb_msg) /
sizeof(uint64_t));
pfkey_broadcast(skb, GFP_KERNEL, BROADCAST_ALL, NULL);
}
static u8 sadb_ext_min_len[] = {
[SADB_EXT_RESERVED] = (u8) 0,
[SADB_EXT_SA] = (u8) sizeof(struct sadb_sa),
[SADB_EXT_LIFETIME_CURRENT] = (u8) sizeof(struct sadb_lifetime),
[SADB_EXT_LIFETIME_HARD] = (u8) sizeof(struct sadb_lifetime),
[SADB_EXT_LIFETIME_SOFT] = (u8) sizeof(struct sadb_lifetime),
[SADB_EXT_ADDRESS_SRC] = (u8) sizeof(struct sadb_address),
[SADB_EXT_ADDRESS_DST] = (u8) sizeof(struct sadb_address),
[SADB_EXT_ADDRESS_PROXY] = (u8) sizeof(struct sadb_address),
[SADB_EXT_KEY_AUTH] = (u8) sizeof(struct sadb_key),
[SADB_EXT_KEY_ENCRYPT] = (u8) sizeof(struct sadb_key),
[SADB_EXT_IDENTITY_SRC] = (u8) sizeof(struct sadb_ident),
[SADB_EXT_IDENTITY_DST] = (u8) sizeof(struct sadb_ident),
[SADB_EXT_SENSITIVITY] = (u8) sizeof(struct sadb_sens),
[SADB_EXT_PROPOSAL] = (u8) sizeof(struct sadb_prop),
[SADB_EXT_SUPPORTED_AUTH] = (u8) sizeof(struct sadb_supported),
[SADB_EXT_SUPPORTED_ENCRYPT] = (u8) sizeof(struct sadb_supported),
[SADB_EXT_SPIRANGE] = (u8) sizeof(struct sadb_spirange),
[SADB_X_EXT_KMPRIVATE] = (u8) sizeof(struct sadb_x_kmprivate),
[SADB_X_EXT_POLICY] = (u8) sizeof(struct sadb_x_policy),
[SADB_X_EXT_SA2] = (u8) sizeof(struct sadb_x_sa2),
};
/* Verify sadb_address_{len,prefixlen} against sa_family. */
static int verify_address_len(void *p)
{
struct sadb_address *sp = p;
struct sockaddr *addr = (struct sockaddr *)(sp + 1);
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
int len;
switch (addr->sa_family) {
case AF_INET:
len = sizeof(*sp) + sizeof(*sin) + (sizeof(uint64_t) - 1);
len /= sizeof(uint64_t);
if (sp->sadb_address_len != len ||
sp->sadb_address_prefixlen > 32)
return -EINVAL;
break;
case AF_INET6:
len = sizeof(*sp) + sizeof(*sin6) + (sizeof(uint64_t) - 1);
len /= sizeof(uint64_t);
if (sp->sadb_address_len != len ||
sp->sadb_address_prefixlen > 128)
return -EINVAL;
break;
default:
/* It is user using kernel to keep track of security
* associations for another protocol, such as
* OSPF/RSVP/RIPV2/MIP. It is user's job to verify
* lengths.
*
* XXX Actually, association/policy database is not yet
* XXX able to cope with arbitrary sockaddr families.
* XXX When it can, remove this -EINVAL. -DaveM
*/
return -EINVAL;
break;
};
return 0;
}
static int present_and_same_family(struct sadb_address *src,
struct sadb_address *dst)
{
struct sockaddr *s_addr, *d_addr;
if (!src || !dst)
return 0;
s_addr = (struct sockaddr *)(src + 1);
d_addr = (struct sockaddr *)(dst + 1);
if (s_addr->sa_family != d_addr->sa_family)
return 0;
if (s_addr->sa_family != AF_INET)
return 0;
return 1;
}
static int parse_exthdrs(struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
char *p = (char *) hdr;
int len = skb->len;
len -= sizeof(*hdr);
p += sizeof(*hdr);
while (len > 0) {
struct sadb_ext *ehdr = (struct sadb_ext *) p;
uint16_t ext_type;
int ext_len;
ext_len = ehdr->sadb_ext_len;
ext_len *= sizeof(uint64_t);
ext_type = ehdr->sadb_ext_type;
if (ext_len < sizeof(uint64_t) ||
ext_len > len ||
ext_type == SADB_EXT_RESERVED)
return -EINVAL;
if (ext_type <= SADB_EXT_MAX) {
int min = (int) sadb_ext_min_len[ext_type];
if (ext_len < min)
return -EINVAL;
if (ext_hdrs[ext_type-1] != NULL)
return -EINVAL;
if (ext_type == SADB_EXT_ADDRESS_SRC ||
ext_type == SADB_EXT_ADDRESS_DST ||
ext_type == SADB_EXT_ADDRESS_PROXY) {
if (verify_address_len(p))
return -EINVAL;
}
ext_hdrs[ext_type-1] = p;
}
p += ext_len;
len -= ext_len;
}
return 0;
}
static uint16_t
pfkey_satype2proto(uint8_t satype)
{
switch (satype) {
case SADB_SATYPE_UNSPEC:
return IPSEC_PROTO_ANY;
case SADB_SATYPE_AH:
return IPPROTO_AH;
case SADB_SATYPE_ESP:
return IPPROTO_ESP;
case SADB_X_SATYPE_IPCOMP:
return IPPROTO_COMP;
break;
default:
return 0;
}
/* NOTREACHED */
}
static uint8_t
pfkey_proto2satype(uint16_t proto)
{
switch (proto) {
case IPPROTO_AH:
return SADB_SATYPE_AH;
case IPPROTO_ESP:
return SADB_SATYPE_ESP;
case IPPROTO_COMP:
return SADB_X_SATYPE_IPCOMP;
break;
default:
return 0;
}
/* NOTREACHED */
}
static xfrm_address_t *pfkey_sadb_addr2xfrm_addr(struct sadb_address *addr,
xfrm_address_t *xaddr)
{
switch (((struct sockaddr*)(addr + 1))->sa_family) {
case AF_INET:
xaddr->xfrm4_addr =
((struct sockaddr_in*)(addr + 1))->sin_addr.s_addr;
xaddr->xfrm4_mask = ~0 << (32 - addr->sadb_address_prefixlen);
break;
case AF_INET6:
memcpy(xaddr->a6,
&((struct sockaddr_in6*)(addr + 1))->sin6_addr,
sizeof(xaddr->a6));
default:
return NULL;
}
return xaddr;
}
static struct xfrm_state *pfkey_xfrm_state_lookup(struct sadb_msg *hdr, void **ext_hdrs)
{
struct xfrm_state *x;
struct sadb_sa *sa;
struct sadb_address *addr;
uint16_t proto;
sa = (struct sadb_sa *) ext_hdrs[SADB_EXT_SA-1];
if (sa == NULL)
return NULL;
proto = pfkey_satype2proto(hdr->sadb_msg_satype);
if (proto == 0)
return NULL;
/* sadb_address_len should be checked by caller */
addr = (struct sadb_address *) ext_hdrs[SADB_EXT_ADDRESS_DST-1];
if (addr == NULL)
return NULL;
switch (((struct sockaddr *)(addr + 1))->sa_family) {
case AF_INET:
x = xfrm_state_lookup(
((struct sockaddr_in*)(addr + 1))->sin_addr.s_addr,
sa->sadb_sa_spi, proto);
break;
case AF_INET6:
/* XXX handle IPv6 */
default:
x = NULL;
break;
}
return x;
}
/* Table of algos supported by pfkeyv2 interface. */
struct algo_desc {
char *id;
struct sadb_alg desc;
};
struct algo_desc aalg_list[] = {
{ .id = NULL,
.desc = {
.sadb_alg_id = SADB_AALG_NONE,
.sadb_alg_ivlen = 0,
.sadb_alg_minbits = 0,
.sadb_alg_maxbits = 0
}
},
{ .id = "md5",
.desc = {
.sadb_alg_id = SADB_AALG_MD5HMAC,
.sadb_alg_ivlen = 0,
.sadb_alg_minbits = 128,
.sadb_alg_maxbits = 128
}
},
{ .id = "sha1",
.desc = {
.sadb_alg_id = SADB_AALG_SHA1HMAC,
.sadb_alg_ivlen = 0,
.sadb_alg_minbits = 160,
.sadb_alg_maxbits = 160
}
}
};
struct algo_desc ealg_list[] = {
{ .id = NULL,
.desc = {
.sadb_alg_id = SADB_EALG_NONE,
.sadb_alg_ivlen = 0,
.sadb_alg_minbits = 0,
.sadb_alg_maxbits = 2048
}
},
{ .id = "des",
.desc = {
.sadb_alg_id = SADB_EALG_DESCBC,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 64,
.sadb_alg_maxbits = 64
}
},
{ .id = "des3_ede",
.desc = {
.sadb_alg_id = SADB_EALG_3DESCBC,
.sadb_alg_ivlen = 8,
.sadb_alg_minbits = 192,
.sadb_alg_maxbits = 192
}
}
};
static struct algo_desc *aalg_get_byid(int alg_id)
{
int i;
for (i=0; i<sizeof(aalg_list)/sizeof(aalg_list[0]); i++) {
if (aalg_list[i].desc.sadb_alg_id == alg_id)
return &aalg_list[i];
}
return NULL;
}
static struct algo_desc *ealg_get_byid(int alg_id)
{
int i;
for (i=0; i<sizeof(ealg_list)/sizeof(ealg_list[0]); i++) {
if (ealg_list[i].desc.sadb_alg_id == alg_id)
return &ealg_list[i];
}
return NULL;
}
static struct algo_desc *aalg_get_byname(char *name)
{
int i;
if (!name)
return NULL;
for (i=1; i<sizeof(aalg_list)/sizeof(aalg_list[0]); i++) {
if (strcmp(name, aalg_list[i].id) == 0)
return &aalg_list[i];
}
return NULL;
}
static struct algo_desc *ealg_get_byname(char *name)
{
int i;
if (!name)
return NULL;
for (i=1; i<sizeof(ealg_list)/sizeof(ealg_list[0]); i++) {
if (strcmp(name, ealg_list[i].id) == 0)
return &ealg_list[i];
}
return NULL;
}
#define PFKEY_ALIGN8(a) (1 + (((a) - 1) | (8 - 1)))
static struct sk_buff * pfkey_xfrm_state2msg(struct xfrm_state *x, int add_keys, int hsc)
{
struct sk_buff *skb;
struct sadb_msg *hdr;
struct sadb_sa *sa;
struct sadb_lifetime *lifetime;
struct sadb_address *addr;
struct sadb_key *key;
struct sadb_x_sa2 *sa2;
int size;
int auth_key_size = 0;
int encrypt_key_size = 0;
/* base, SA, (lifetime (HSC),) address(SD), (address(P),)
key(AE), (identity(SD),) (sensitivity)> */
size = sizeof(struct sadb_msg) +sizeof(struct sadb_sa) +
sizeof(struct sadb_lifetime) +
((hsc & 1) ? sizeof(struct sadb_lifetime) : 0) +
((hsc & 2) ? sizeof(struct sadb_lifetime) : 0) +
sizeof(struct sadb_address)*2 +
sizeof(struct sockaddr_in)*2 + /* XXX */
sizeof(struct sadb_x_sa2);
/* XXX identity & sensitivity */
if (x->sel.saddr.xfrm4_addr != x->props.saddr.xfrm4_addr)
size += sizeof(struct sadb_address) +
sizeof(struct sockaddr_in); /* XXX */
if (add_keys) {
if (x->aalg && x->aalg->alg_key_len) {
auth_key_size =
PFKEY_ALIGN8((x->aalg->alg_key_len + 7) / 8);
size += sizeof(struct sadb_key) + auth_key_size;
}
if (x->ealg && x->ealg->alg_key_len) {
encrypt_key_size =
PFKEY_ALIGN8((x->ealg->alg_key_len+7) / 8);
size += sizeof(struct sadb_key) + encrypt_key_size;
}
}
skb = alloc_skb(size + 16, GFP_ATOMIC);
if (skb == NULL)
return ERR_PTR(-ENOBUFS);
/* call should fill header later */
hdr = (struct sadb_msg *) skb_put(skb, sizeof(struct sadb_msg));
memset(hdr, 0, size); /* XXX do we need this ? */
hdr->sadb_msg_len = size / sizeof(uint64_t);
/* sa */
sa = (struct sadb_sa *) skb_put(skb, sizeof(struct sadb_sa));
sa->sadb_sa_len = sizeof(struct sadb_sa)/sizeof(uint64_t);
sa->sadb_sa_exttype = SADB_EXT_SA;
sa->sadb_sa_spi = x->id.spi;
sa->sadb_sa_replay = x->props.replay_window;
sa->sadb_sa_state = (x->km.state == XFRM_STATE_VALID ?
SADB_SASTATE_MATURE :
SADB_SASTATE_LARVAL);
sa->sadb_sa_auth = 0;
if (x->aalg) {
struct algo_desc *a = aalg_get_byname(x->aalg->alg_name);
sa->sadb_sa_auth = a ? a->desc.sadb_alg_id : 0;
}
sa->sadb_sa_encrypt = 0;
if (x->ealg) {
struct algo_desc *a = ealg_get_byname(x->ealg->alg_name);
sa->sadb_sa_encrypt = a ? a->desc.sadb_alg_id : 0;
}
sa->sadb_sa_flags = SADB_SAFLAGS_PFS;
/* hard time */
if (hsc & 2) {
lifetime = (struct sadb_lifetime *) skb_put(skb,
sizeof(struct sadb_lifetime));
lifetime->sadb_lifetime_len =
sizeof(struct sadb_lifetime)/sizeof(uint64_t);
lifetime->sadb_lifetime_exttype = SADB_EXT_LIFETIME_HARD;
lifetime->sadb_lifetime_allocations = x->lft.hard_packet_limit;
lifetime->sadb_lifetime_bytes = x->lft.hard_byte_limit;
lifetime->sadb_lifetime_addtime = x->lft.hard_add_expires_seconds;
lifetime->sadb_lifetime_usetime = x->lft.hard_use_expires_seconds;
}
/* soft time */
if (hsc & 1) {
lifetime = (struct sadb_lifetime *) skb_put(skb,
sizeof(struct sadb_lifetime));
lifetime->sadb_lifetime_len =
sizeof(struct sadb_lifetime)/sizeof(uint64_t);
lifetime->sadb_lifetime_exttype = SADB_EXT_LIFETIME_SOFT;
lifetime->sadb_lifetime_allocations = x->lft.soft_packet_limit;
lifetime->sadb_lifetime_bytes = x->lft.soft_byte_limit;
lifetime->sadb_lifetime_addtime = x->lft.soft_add_expires_seconds;
lifetime->sadb_lifetime_usetime = x->lft.soft_use_expires_seconds;
}
/* current time */
lifetime = (struct sadb_lifetime *) skb_put(skb,
sizeof(struct sadb_lifetime));
lifetime->sadb_lifetime_len =
sizeof(struct sadb_lifetime)/sizeof(uint64_t);
lifetime->sadb_lifetime_exttype = SADB_EXT_LIFETIME_CURRENT;
lifetime->sadb_lifetime_allocations = x->curlft.packets;
lifetime->sadb_lifetime_bytes = x->curlft.bytes;
lifetime->sadb_lifetime_addtime = x->curlft.add_time;
lifetime->sadb_lifetime_usetime = x->curlft.use_time;
/* src address */
addr = (struct sadb_address*) skb_put(skb,
sizeof(struct sadb_address)+sizeof(struct sockaddr_in));
addr->sadb_address_len =
(sizeof(struct sadb_address)+sizeof(struct sockaddr_in))/
sizeof(uint64_t);
addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
addr->sadb_address_proto = 0;
addr->sadb_address_prefixlen = 32; /* XXX */
addr->sadb_address_reserved = 0;
((struct sockaddr_in*)(addr + 1))->sin_family = AF_INET;
((struct sockaddr_in*)(addr + 1))->sin_addr.s_addr =
x->props.saddr.xfrm4_addr;
/* dst address */
addr = (struct sadb_address*) skb_put(skb,
sizeof(struct sadb_address)+sizeof(struct sockaddr_in));
addr->sadb_address_len =
(sizeof(struct sadb_address)+sizeof(struct sockaddr_in))/
sizeof(uint64_t);
addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
addr->sadb_address_proto = 0;
addr->sadb_address_prefixlen = 32; /* XXX */
addr->sadb_address_reserved = 0;
((struct sockaddr_in*)(addr + 1))->sin_family = AF_INET;
((struct sockaddr_in*)(addr + 1))->sin_addr.s_addr =
x->id.daddr.xfrm4_addr;
if (x->sel.saddr.xfrm4_addr != x->props.saddr.xfrm4_addr) {
addr = (struct sadb_address*) skb_put(skb,
sizeof(struct sadb_address)+sizeof(struct sockaddr_in));
addr->sadb_address_len =
(sizeof(struct sadb_address)+sizeof(struct sockaddr_in))/
sizeof(uint64_t);
addr->sadb_address_exttype = SADB_EXT_ADDRESS_PROXY;
addr->sadb_address_proto = x->sel.proto;
addr->sadb_address_prefixlen = x->sel.prefixlen_s;
addr->sadb_address_reserved = 0;
((struct sockaddr_in*)(addr + 1))->sin_family = AF_INET;
((struct sockaddr_in*)(addr + 1))->sin_addr.s_addr =
x->sel.saddr.xfrm4_addr;
((struct sockaddr_in*)(addr + 1))->sin_port =
x->sel.sport;
}
/* auth key */
if (add_keys && auth_key_size) {
key = (struct sadb_key *) skb_put(skb,
sizeof(struct sadb_key)+auth_key_size);
key->sadb_key_len = (sizeof(struct sadb_key) + auth_key_size) /
sizeof(uint64_t);
key->sadb_key_exttype = SADB_EXT_KEY_AUTH;
key->sadb_key_bits = x->aalg->alg_key_len;
key->sadb_key_reserved = 0;
memcpy(key + 1, x->aalg->alg_key, (x->aalg->alg_key_len+7)/8);
}
/* encrypt key */
if (add_keys && encrypt_key_size) {
key = (struct sadb_key *) skb_put(skb,
sizeof(struct sadb_key)+encrypt_key_size);
key->sadb_key_len = (sizeof(struct sadb_key) +
encrypt_key_size) / sizeof(uint64_t);
key->sadb_key_exttype = SADB_EXT_KEY_ENCRYPT;
key->sadb_key_bits = x->ealg->alg_key_len;
key->sadb_key_reserved = 0;
memcpy(key + 1, x->ealg->alg_key,
(x->ealg->alg_key_len+7)/8);
}
/* sa */
sa2 = (struct sadb_x_sa2 *) skb_put(skb, sizeof(struct sadb_x_sa2));
sa2->sadb_x_sa2_len = sizeof(struct sadb_x_sa2)/sizeof(uint64_t);
sa2->sadb_x_sa2_exttype = SADB_X_EXT_SA2;
sa2->sadb_x_sa2_mode = x->props.mode + 1;
sa2->sadb_x_sa2_reserved1 = 0;
sa2->sadb_x_sa2_reserved2 = 0;
sa2->sadb_x_sa2_sequence = 0;
sa2->sadb_x_sa2_reqid = x->props.reqid;
return skb;
}
static struct xfrm_state * pfkey_msg2xfrm_state(struct sadb_msg *hdr,
void **ext_hdrs)
{
struct xfrm_state *x;
struct sadb_lifetime *lifetime;
struct sadb_sa *sa;
struct sadb_key *key;
uint16_t proto;
sa = (struct sadb_sa *) ext_hdrs[SADB_EXT_SA-1];
if (!sa ||
!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
ext_hdrs[SADB_EXT_ADDRESS_DST-1]))
return ERR_PTR(-EINVAL);
if (hdr->sadb_msg_satype == SADB_SATYPE_ESP &&
!ext_hdrs[SADB_EXT_KEY_ENCRYPT-1])
return ERR_PTR(-EINVAL);
if (hdr->sadb_msg_satype == SADB_SATYPE_AH &&
!ext_hdrs[SADB_EXT_KEY_AUTH-1])
return ERR_PTR(-EINVAL);
if (!!ext_hdrs[SADB_EXT_LIFETIME_HARD-1] !=
!!ext_hdrs[SADB_EXT_LIFETIME_SOFT-1])
return ERR_PTR(-EINVAL);
/* XXX Do we need this check ? */
if (((struct sadb_address *) ext_hdrs[SADB_EXT_ADDRESS_SRC-1])->sadb_address_prefixlen != 32 ||
((struct sadb_address *) ext_hdrs[SADB_EXT_ADDRESS_DST-1])->sadb_address_prefixlen != 32)
return ERR_PTR(-EINVAL);
proto = pfkey_satype2proto(hdr->sadb_msg_satype);
if (proto == 0)
return ERR_PTR(-EINVAL);
/* XXX setkey set SADB_SASTATE_LARVAL here
if (hdr->sadb_msg_type == SADB_ADD &&
sa->sadb_sa_state != SADB_SASTATE_MATURE)
return ERR_PTR(-EINVAL);
*/
if (sa->sadb_sa_auth > SADB_AALG_MAX ||
sa->sadb_sa_encrypt > SADB_EALG_MAX)
return ERR_PTR(-EINVAL);
key = (struct sadb_key*) ext_hdrs[SADB_EXT_KEY_AUTH-1];
if (key != NULL &&
((key->sadb_key_bits+7) / 8 == 0 ||
(key->sadb_key_bits+7) / 8 > key->sadb_key_len * sizeof(uint64_t)))
return ERR_PTR(-EINVAL);
key = ext_hdrs[SADB_EXT_KEY_ENCRYPT-1];
if (key != NULL &&
((key->sadb_key_bits+7) / 8 == 0 ||
(key->sadb_key_bits+7) / 8 > key->sadb_key_len * sizeof(uint64_t)))
return ERR_PTR(-EINVAL);
x = xfrm_state_alloc();
if (x == NULL)
return ERR_PTR(-ENOBUFS);
x->id.proto = proto;
x->id.spi = sa->sadb_sa_spi;
x->props.replay_window = sa->sadb_sa_replay;
lifetime = (struct sadb_lifetime*) ext_hdrs[SADB_EXT_LIFETIME_HARD-1];
if (lifetime != NULL) {
x->lft.hard_packet_limit = lifetime->sadb_lifetime_allocations;
x->lft.hard_byte_limit = lifetime->sadb_lifetime_bytes;
x->lft.hard_add_expires_seconds = lifetime->sadb_lifetime_addtime;
x->lft.hard_use_expires_seconds = lifetime->sadb_lifetime_usetime;
}
lifetime = (struct sadb_lifetime*) ext_hdrs[SADB_EXT_LIFETIME_SOFT-1];
if (lifetime != NULL) {
x->lft.soft_packet_limit = lifetime->sadb_lifetime_allocations;
x->lft.soft_byte_limit = lifetime->sadb_lifetime_bytes;
x->lft.soft_add_expires_seconds = lifetime->sadb_lifetime_addtime;
x->lft.soft_use_expires_seconds = lifetime->sadb_lifetime_usetime;
}
key = (struct sadb_key*) ext_hdrs[SADB_EXT_KEY_AUTH-1];
if (sa->sadb_sa_auth) {
int keysize = 0;
struct algo_desc *a = aalg_get_byid(sa->sadb_sa_auth);
if (!a)
goto out;
if (key)
keysize = (key->sadb_key_bits + 7) / 8;
x->aalg = kmalloc(sizeof(*x->aalg) + keysize, GFP_KERNEL);
if (!x->aalg)
goto out;
strcpy(x->aalg->alg_name, a->id);
x->aalg->alg_key_len = 0;
if (key) {
x->aalg->alg_key_len = key->sadb_key_bits;
memcpy(x->aalg->alg_key, key+1, keysize);
}
x->props.aalgo = sa->sadb_sa_auth;
/* x->algo.flags = sa->sadb_sa_flags; */
}
key = (struct sadb_key*) ext_hdrs[SADB_EXT_KEY_ENCRYPT-1];
if (sa->sadb_sa_encrypt) {
int keysize = 0;
struct algo_desc *a = ealg_get_byid(sa->sadb_sa_encrypt);
if (!a)
goto out;
if (key)
keysize = (key->sadb_key_bits + 7) / 8;
x->ealg = kmalloc(sizeof(*x->ealg) + keysize, GFP_KERNEL);
if (!x->ealg)
goto out;
strcpy(x->ealg->alg_name, a->id);
x->ealg->alg_key_len = 0;
if (key) {
x->ealg->alg_key_len = key->sadb_key_bits;
memcpy(x->ealg->alg_key, key+1, keysize);
}
x->props.ealgo = sa->sadb_sa_encrypt;
}
/* x->algo.flags = sa->sadb_sa_flags; */
pfkey_sadb_addr2xfrm_addr(
(struct sadb_address *) ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
&x->props.saddr);
pfkey_sadb_addr2xfrm_addr(
(struct sadb_address *) ext_hdrs[SADB_EXT_ADDRESS_DST-1],
&x->id.daddr);
if (ext_hdrs[SADB_X_EXT_SA2-1]) {
struct sadb_x_sa2 *sa2 = (void*)ext_hdrs[SADB_X_EXT_SA2-1];
x->props.mode = sa2->sadb_x_sa2_mode;
if (x->props.mode)
x->props.mode--;
x->props.reqid = sa2->sadb_x_sa2_reqid;
}
x->sel.saddr = x->props.saddr;
x->sel.daddr = x->id.daddr;
x->type = xfrm_get_type(proto);
if (x->type == NULL)
goto out;
if (x->type->init_state(x, NULL))
goto out;
x->curlft.add_time = (unsigned long)xtime.tv_sec;
x->km.warn_bytes = x->lft.soft_byte_limit;
x->km.state = XFRM_STATE_VALID;
return x;
out:
if (x->aalg)
kfree(x->aalg);
if (x->ealg)
kfree(x->ealg);
kfree(x);
return ERR_PTR(-ENOBUFS);
}
static int pfkey_reserved(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
return -EOPNOTSUPP;
}
static int pfkey_getspi(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
struct sk_buff *resp_skb;
struct sadb_x_sa2 *sa2;
struct sadb_address *addr;
struct sockaddr_in *saddr, *daddr;
struct sadb_msg *out_hdr;
struct xfrm_state *x;
u8 mode;
u16 reqid;
u8 proto;
if (!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
ext_hdrs[SADB_EXT_ADDRESS_DST-1]) ||
!ext_hdrs[SADB_EXT_SPIRANGE-1])
return -EINVAL;
proto = pfkey_satype2proto(hdr->sadb_msg_satype);
if (proto == 0)
return -EINVAL;
if ((sa2 = ext_hdrs[SADB_X_EXT_SA2]) != NULL) {
mode = sa2->sadb_x_sa2_mode - 1;
reqid = sa2->sadb_x_sa2_reqid;
} else {
mode = 0;
reqid = 0;
}
addr = ext_hdrs[SADB_EXT_ADDRESS_SRC];
saddr = (struct sockaddr_in*)(addr + 1);
addr = ext_hdrs[SADB_EXT_ADDRESS_DST];
daddr = (struct sockaddr_in*)(addr + 1);
x = xfrm_find_acq(mode, reqid, proto, daddr->sin_addr.s_addr,
saddr->sin_addr.s_addr);
if (x == NULL)
return -ENOENT;
resp_skb = NULL;
spin_lock_bh(&x->lock);
if (x->km.state != XFRM_STATE_DEAD) {
struct sadb_spirange *range = ext_hdrs[SADB_EXT_SPIRANGE];
xfrm_alloc_spi(x, range->sadb_spirange_min, range->sadb_spirange_max);
if (x->id.spi)
resp_skb = pfkey_xfrm_state2msg(x, 0, 3);
}
spin_unlock_bh(&x->lock);
if (IS_ERR(resp_skb)) {
xfrm_state_put(x);
return PTR_ERR(resp_skb);
}
out_hdr = (struct sadb_msg *) resp_skb->data;
out_hdr->sadb_msg_version = hdr->sadb_msg_version;
out_hdr->sadb_msg_type = SADB_GETSPI;
out_hdr->sadb_msg_satype = pfkey_proto2satype(proto);
out_hdr->sadb_msg_errno = 0;
out_hdr->sadb_msg_reserved = 0;
out_hdr->sadb_msg_seq = hdr->sadb_msg_seq;
out_hdr->sadb_msg_pid = hdr->sadb_msg_pid;
xfrm_state_put(x);
pfkey_broadcast(resp_skb, GFP_KERNEL, BROADCAST_ONE, sk);
return 0;
}
static int pfkey_acquire(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
struct xfrm_state *x;
if (hdr->sadb_msg_len != sizeof(struct sadb_msg)/8)
return -EOPNOTSUPP;
if (hdr->sadb_msg_seq == 0 || hdr->sadb_msg_errno == 0)
return 0;
x = xfrm_find_acq_byseq(hdr->sadb_msg_seq);
if (x == NULL)
return 0;
if (x->km.state == XFRM_STATE_ACQ)
xfrm_state_delete(x);
xfrm_state_put(x);
return 0;
}
static int pfkey_update(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
struct sk_buff *resp_skb;
int err;
if (!ext_hdrs[SADB_EXT_SA-1] ||
!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
ext_hdrs[SADB_EXT_ADDRESS_DST-1]))
return -EINVAL;
if (hdr->sadb_msg_satype == SADB_SATYPE_ESP &&
!ext_hdrs[SADB_EXT_KEY_ENCRYPT-1])
return -EINVAL;
if (hdr->sadb_msg_satype == SADB_SATYPE_AH &&
!ext_hdrs[SADB_EXT_KEY_AUTH-1])
return -EINVAL;
if (!!ext_hdrs[SADB_EXT_LIFETIME_HARD-1] !=
!!ext_hdrs[SADB_EXT_LIFETIME_SOFT-1])
return -EINVAL;
/* XXX Implement XXX */
resp_skb = NULL;
err = -EOPNOTSUPP;
if (!err)
pfkey_broadcast(resp_skb, GFP_KERNEL, BROADCAST_ONE, sk);
return err;
}
static int pfkey_add(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
struct sk_buff *out_skb;
struct sadb_msg *out_hdr;
struct xfrm_state *x;
struct xfrm_state *x1;
x = pfkey_msg2xfrm_state(hdr, ext_hdrs);
if (IS_ERR(x))
return PTR_ERR(x);
/* XXX there is race condition */
x1 = pfkey_xfrm_state_lookup(hdr, ext_hdrs);
if (x1 != NULL) {
x->km.state = XFRM_STATE_DEAD;
xfrm_state_put(x);
xfrm_state_put(x1);
return -EEXIST;
}
xfrm_state_insert(x);
out_skb = pfkey_xfrm_state2msg(x, 0, 3);
if (IS_ERR(out_skb))
return PTR_ERR(out_skb); /* XXX Should we return 0 here ? */
out_hdr = (struct sadb_msg *) out_skb->data;
out_hdr->sadb_msg_version = hdr->sadb_msg_version;
out_hdr->sadb_msg_type = SADB_ADD;
out_hdr->sadb_msg_satype = pfkey_proto2satype(x->id.proto);
out_hdr->sadb_msg_errno = 0;
out_hdr->sadb_msg_reserved = 0;
out_hdr->sadb_msg_seq = hdr->sadb_msg_seq;
out_hdr->sadb_msg_pid = hdr->sadb_msg_pid;
pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ALL, sk);
return 0;
}
static int pfkey_delete(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
struct xfrm_state *x;
if (!ext_hdrs[SADB_EXT_SA-1] ||
!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
ext_hdrs[SADB_EXT_ADDRESS_DST-1]))
return -EINVAL;
x = pfkey_xfrm_state_lookup(hdr, ext_hdrs);
if (x == NULL)
return -ESRCH;
xfrm_state_delete(x);
xfrm_state_put(x);
pfkey_broadcast(skb_clone(skb, GFP_KERNEL), GFP_KERNEL,
BROADCAST_ALL, sk);
return 0;
}
static int pfkey_get(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
struct sk_buff *out_skb;
struct sadb_msg *out_hdr;
struct xfrm_state *x;
if (!ext_hdrs[SADB_EXT_SA-1] ||
!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
ext_hdrs[SADB_EXT_ADDRESS_DST-1]))
return -EINVAL;
x = pfkey_xfrm_state_lookup(hdr, ext_hdrs);
if (x == NULL)
return -ESRCH;
out_skb = pfkey_xfrm_state2msg(x, 1, 3);
xfrm_state_put(x);
if (IS_ERR(out_skb))
return PTR_ERR(out_skb);
out_hdr = (struct sadb_msg *) out_skb->data;
out_hdr->sadb_msg_version = hdr->sadb_msg_version;
out_hdr->sadb_msg_type = SADB_DUMP;
out_hdr->sadb_msg_satype = pfkey_proto2satype(x->id.proto);
out_hdr->sadb_msg_errno = 0;
out_hdr->sadb_msg_reserved = 0;
out_hdr->sadb_msg_seq = hdr->sadb_msg_seq;
out_hdr->sadb_msg_pid = hdr->sadb_msg_pid;
pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ONE, sk);
return 0;
}
static struct sk_buff *compose_sadb_supported(struct sadb_msg *orig, int allocation)
{
struct sk_buff *skb;
struct sadb_msg *hdr;
int len, ah_len, esp_len, i;
ah_len = sizeof(aalg_list)/sizeof(aalg_list[0]) - 1;
ah_len *= sizeof(struct sadb_alg);
esp_len = sizeof(ealg_list)/sizeof(ealg_list[0]) - 1;
esp_len *= sizeof(struct sadb_alg);
if (ah_len)
ah_len += sizeof(struct sadb_supported);
if (esp_len)
esp_len += sizeof(struct sadb_supported);
len = esp_len + ah_len + sizeof(struct sadb_msg);
skb = alloc_skb(allocation, len + 16);
if (!skb)
goto out_put_algs;
hdr = (struct sadb_msg *) skb_put(skb, sizeof(*hdr));
pfkey_hdr_dup(hdr, orig);
hdr->sadb_msg_errno = 0;
hdr->sadb_msg_len = len / sizeof(uint64_t);
if (ah_len) {
struct sadb_supported *sp;
struct sadb_alg *ap;
sp = (struct sadb_supported *) skb_put(skb, ah_len);
ap = (struct sadb_alg *) (sp + 1);
sp->sadb_supported_len = ah_len / sizeof(uint64_t);
sp->sadb_supported_exttype = SADB_EXT_SUPPORTED_AUTH;
for (i=1; i<sizeof(aalg_list)/sizeof(aalg_list[0]); i++)
*ap++ = aalg_list[i].desc;
}
if (esp_len) {
struct sadb_supported *sp;
struct sadb_alg *ap;
sp = (struct sadb_supported *) skb_put(skb, esp_len);
ap = (struct sadb_alg *) (sp + 1);
sp->sadb_supported_len = esp_len / sizeof(uint64_t);
sp->sadb_supported_exttype = SADB_EXT_SUPPORTED_ENCRYPT;
for (i=1; i<sizeof(ealg_list)/sizeof(ealg_list[0]); i++)
*ap++ = ealg_list[i].desc;
}
out_put_algs:
return skb;
}
static int pfkey_register(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
struct pfkey_opt *pfk = pfkey_sk(sk);
struct sk_buff *supp_skb;
if (hdr->sadb_msg_satype > SADB_SATYPE_MAX)
return -EINVAL;
if (hdr->sadb_msg_satype != SADB_SATYPE_UNSPEC) {
if (pfk->registered&(1<<hdr->sadb_msg_satype))
return -EEXIST;
pfk->registered |= (1<<hdr->sadb_msg_satype);
}
supp_skb = compose_sadb_supported(hdr, GFP_KERNEL);
if (!supp_skb) {
if (hdr->sadb_msg_satype != SADB_SATYPE_UNSPEC)
pfk->registered &= ~(1<<hdr->sadb_msg_satype);
return -ENOBUFS;
}
pfkey_broadcast(supp_skb, GFP_KERNEL, BROADCAST_REGISTERED, sk);
return 0;
}
static int pfkey_flush(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
unsigned proto;
struct sk_buff *skb_out;
struct sadb_msg *hdr_out;
proto = pfkey_satype2proto(hdr->sadb_msg_satype);
if (proto == 0)
return -EINVAL;
skb_out = alloc_skb(sizeof(struct sadb_msg) + 16, GFP_KERNEL);
if (!skb_out)
return -ENOBUFS;
xfrm_state_flush(proto);
hdr_out = (struct sadb_msg *) skb_put(skb_out, sizeof(struct sadb_msg));
pfkey_hdr_dup(hdr_out, hdr);
hdr_out->sadb_msg_errno = (uint8_t) 0;
hdr_out->sadb_msg_len = (sizeof(struct sadb_msg) / sizeof(uint64_t));
pfkey_broadcast(skb_out, GFP_KERNEL, BROADCAST_ALL, NULL);
return 0;
}
struct pfkey_dump_data
{
struct sk_buff *skb;
struct sadb_msg *hdr;
struct sock *sk;
};
static int dump_sa(struct xfrm_state *x, int count, void *ptr)
{
struct pfkey_dump_data *data = ptr;
struct sk_buff *out_skb;
struct sadb_msg *out_hdr;
out_skb = pfkey_xfrm_state2msg(x, 1, 3);
if (IS_ERR(out_skb))
return PTR_ERR(out_skb);
out_hdr = (struct sadb_msg *) out_skb->data;
out_hdr->sadb_msg_version = data->hdr->sadb_msg_version;
out_hdr->sadb_msg_type = SADB_DUMP;
out_hdr->sadb_msg_satype = pfkey_proto2satype(x->id.proto);
out_hdr->sadb_msg_errno = 0;
out_hdr->sadb_msg_reserved = 0;
out_hdr->sadb_msg_seq = count;
out_hdr->sadb_msg_pid = data->hdr->sadb_msg_pid;
pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ONE, data->sk);
return 0;
}
static int pfkey_dump(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
u8 proto;
struct pfkey_dump_data data = { .skb = skb, .hdr = hdr, .sk = sk };
proto = pfkey_satype2proto(hdr->sadb_msg_satype);
if (proto == 0)
return -EINVAL;
return xfrm_state_walk(proto, dump_sa, &data);
}
static int pfkey_promisc(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
struct pfkey_opt *pfk = pfkey_sk(sk);
int satype = hdr->sadb_msg_satype;
if (hdr->sadb_msg_len == (sizeof(*hdr) / sizeof(uint64_t))) {
/* XXX we mangle packet... */
hdr->sadb_msg_errno = 0;
if (satype != 0 && satype != 1)
return -EINVAL;
pfk->promisc = satype;
}
pfkey_broadcast(skb_clone(skb, GFP_KERNEL), GFP_KERNEL, BROADCAST_ALL, NULL);
return 0;
}
static int
parse_ipsecrequest(struct xfrm_policy *xp, struct sadb_x_ipsecrequest *rq)
{
struct xfrm_tmpl *t = xp->xfrm_vec + xp->xfrm_nr;
struct sockaddr_in *addr;
if (xp->xfrm_nr >= XFRM_MAX_DEPTH)
return -EINVAL;
if (rq->sadb_x_ipsecrequest_mode == 0)
return -EINVAL;
t->id.proto = rq->sadb_x_ipsecrequest_proto;
t->mode = rq->sadb_x_ipsecrequest_mode-1;
t->share = rq->sadb_x_ipsecrequest_level;
t->reqid = rq->sadb_x_ipsecrequest_reqid;
addr = (void*)(rq+1);
t->saddr.xfrm4_addr = addr->sin_addr.s_addr;
addr++;
t->id.daddr.xfrm4_addr = addr->sin_addr.s_addr;
/* No way to set this via kame pfkey */
t->aalgos = t->ealgos = t->calgos = ~0;
xp->xfrm_nr++;
return 0;
}
static int
parse_ipsecrequests(struct xfrm_policy *xp, struct sadb_x_policy *pol)
{
int err;
int len = pol->sadb_x_policy_len*8 - sizeof(struct sadb_x_policy);
struct sadb_x_ipsecrequest *rq = (void*)(pol+1);
while (len > sizeof(struct sadb_x_ipsecrequest)) {
if ((err = parse_ipsecrequest(xp, rq)) < 0)
return err;
len -= rq->sadb_x_ipsecrequest_len;
rq = (void*)((u8*)rq + rq->sadb_x_ipsecrequest_len);
}
return 0;
}
static struct sk_buff * pfkey_xfrm_policy2msg(struct xfrm_policy *xp, int dir)
{
struct sk_buff *skb;
struct sadb_msg *hdr;
struct sadb_address *addr;
struct sadb_lifetime *lifetime;
struct sadb_x_policy *pol;
int i;
int size;
size = sizeof(struct sadb_msg) +
sizeof(struct sadb_lifetime) * 3 +
sizeof(struct sadb_address)*2 +
sizeof(struct sockaddr_in)*2 + /* XXX */
sizeof(struct sadb_x_policy) +
xp->xfrm_nr*(sizeof(struct sadb_x_ipsecrequest) +
sizeof(struct sockaddr_in)*2);
skb = alloc_skb(size + 16, GFP_ATOMIC);
if (skb == NULL)
return ERR_PTR(-ENOBUFS);
/* call should fill header later */
hdr = (struct sadb_msg *) skb_put(skb, sizeof(struct sadb_msg));
memset(hdr, 0, size); /* XXX do we need this ? */
hdr->sadb_msg_len = size / sizeof(uint64_t);
/* src address */
addr = (struct sadb_address*) skb_put(skb,
sizeof(struct sadb_address)+sizeof(struct sockaddr_in));
addr->sadb_address_len =
(sizeof(struct sadb_address)+sizeof(struct sockaddr_in))/
sizeof(uint64_t);
addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
addr->sadb_address_proto = xp->selector.proto;
addr->sadb_address_prefixlen = xp->selector.prefixlen_s;
addr->sadb_address_reserved = 0;
((struct sockaddr_in*)(addr + 1))->sin_family = AF_INET;
((struct sockaddr_in*)(addr + 1))->sin_addr.s_addr =
xp->selector.saddr.xfrm4_addr;
((struct sockaddr_in*)(addr + 1))->sin_port =
xp->selector.sport;
/* dst address */
addr = (struct sadb_address*) skb_put(skb,
sizeof(struct sadb_address)+sizeof(struct sockaddr_in));
addr->sadb_address_len =
(sizeof(struct sadb_address)+sizeof(struct sockaddr_in))/
sizeof(uint64_t);
addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
addr->sadb_address_proto = xp->selector.proto;
addr->sadb_address_prefixlen = xp->selector.prefixlen_d;
addr->sadb_address_reserved = 0;
((struct sockaddr_in*)(addr + 1))->sin_family = AF_INET;
((struct sockaddr_in*)(addr + 1))->sin_addr.s_addr =
xp->selector.daddr.xfrm4_addr;
((struct sockaddr_in*)(addr + 1))->sin_port =
xp->selector.dport;
/* hard time */
lifetime = (struct sadb_lifetime *) skb_put(skb,
sizeof(struct sadb_lifetime));
lifetime->sadb_lifetime_len =
sizeof(struct sadb_lifetime)/sizeof(uint64_t);
lifetime->sadb_lifetime_exttype = SADB_EXT_LIFETIME_HARD;
lifetime->sadb_lifetime_allocations = xp->lft.hard_packet_limit;
lifetime->sadb_lifetime_bytes = xp->lft.hard_byte_limit;
lifetime->sadb_lifetime_addtime = xp->lft.hard_add_expires_seconds;
lifetime->sadb_lifetime_usetime = xp->lft.hard_use_expires_seconds;
/* soft time */
lifetime = (struct sadb_lifetime *) skb_put(skb,
sizeof(struct sadb_lifetime));
lifetime->sadb_lifetime_len =
sizeof(struct sadb_lifetime)/sizeof(uint64_t);
lifetime->sadb_lifetime_exttype = SADB_EXT_LIFETIME_SOFT;
lifetime->sadb_lifetime_allocations = xp->lft.soft_packet_limit;
lifetime->sadb_lifetime_bytes = xp->lft.soft_byte_limit;
lifetime->sadb_lifetime_addtime = xp->lft.soft_add_expires_seconds;
lifetime->sadb_lifetime_usetime = xp->lft.soft_use_expires_seconds;
/* current time */
lifetime = (struct sadb_lifetime *) skb_put(skb,
sizeof(struct sadb_lifetime));
lifetime->sadb_lifetime_len =
sizeof(struct sadb_lifetime)/sizeof(uint64_t);
lifetime->sadb_lifetime_exttype = SADB_EXT_LIFETIME_CURRENT;
lifetime->sadb_lifetime_allocations = xp->curlft.packets;
lifetime->sadb_lifetime_bytes = xp->curlft.bytes;
lifetime->sadb_lifetime_addtime = xp->curlft.add_time;
lifetime->sadb_lifetime_usetime = xp->curlft.use_time;
pol = (struct sadb_x_policy *) skb_put(skb, sizeof(struct sadb_x_policy));
pol->sadb_x_policy_len = sizeof(struct sadb_x_policy)/sizeof(uint64_t);
pol->sadb_x_policy_exttype = SADB_X_EXT_POLICY;
pol->sadb_x_policy_type = IPSEC_POLICY_DISCARD;
if (xp->action == XFRM_POLICY_ALLOW) {
if (xp->xfrm_nr)
pol->sadb_x_policy_type = IPSEC_POLICY_IPSEC;
else
pol->sadb_x_policy_type = IPSEC_POLICY_NONE;
}
pol->sadb_x_policy_dir = dir+1;
pol->sadb_x_policy_id = xp->index;
for (i=0; i<xp->xfrm_nr; i++) {
struct sadb_x_ipsecrequest *rq;
struct xfrm_tmpl *t = xp->xfrm_vec + i;
struct sockaddr_in *addr;
size = sizeof(struct sadb_x_ipsecrequest)+2*sizeof(struct sockaddr_in);
rq = (void*)skb_put(skb, size);
pol->sadb_x_policy_len += size/8;
rq->sadb_x_ipsecrequest_len = size;
rq->sadb_x_ipsecrequest_proto = t->id.proto;
rq->sadb_x_ipsecrequest_mode = t->mode+1;
rq->sadb_x_ipsecrequest_level = t->share;
rq->sadb_x_ipsecrequest_reqid = t->reqid;
addr = (void*)(rq+1);
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = t->saddr.xfrm4_addr;
addr->sin_port = 0;
addr++;
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = t->id.daddr.xfrm4_addr;
addr->sin_port = 0;
}
return skb;
}
static int pfkey_spdadd(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
int err;
struct sadb_lifetime *lifetime;
struct sadb_address *sa;
struct sadb_x_policy *pol;
struct xfrm_policy *xp;
struct sk_buff *out_skb;
struct sadb_msg *out_hdr;
if (!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
ext_hdrs[SADB_EXT_ADDRESS_DST-1]) ||
!ext_hdrs[SADB_X_EXT_POLICY-1])
return -EINVAL;
pol = ext_hdrs[SADB_X_EXT_POLICY-1];
if (pol->sadb_x_policy_type > IPSEC_POLICY_IPSEC)
return -EINVAL;
if (!pol->sadb_x_policy_dir || pol->sadb_x_policy_dir >= IPSEC_DIR_MAX)
return -EINVAL;
xp = xfrm_policy_alloc();
if (xp == NULL)
return -ENOBUFS;
xp->index = pol->sadb_x_policy_id;
xp->action = (pol->sadb_x_policy_type == IPSEC_POLICY_DISCARD ?
XFRM_POLICY_BLOCK : XFRM_POLICY_ALLOW);
sa = ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
pfkey_sadb_addr2xfrm_addr(sa, &xp->selector.saddr);
xp->selector.prefixlen_s = sa->sadb_address_prefixlen;
xp->selector.proto = sa->sadb_address_proto;
xp->selector.sport = ((struct sockaddr_in*)(sa+1))->sin_port;
if (xp->selector.sport)
xp->selector.sport_mask = ~0;
sa = ext_hdrs[SADB_EXT_ADDRESS_DST-1],
pfkey_sadb_addr2xfrm_addr(sa, &xp->selector.daddr);
xp->selector.prefixlen_d = sa->sadb_address_prefixlen;
xp->selector.proto = sa->sadb_address_proto;
xp->selector.dport = ((struct sockaddr_in*)(sa+1))->sin_port;
if (xp->selector.dport)
xp->selector.dport_mask = ~0;
xp->curlft.add_time = (unsigned long)xtime.tv_sec;
xp->curlft.use_time = (unsigned long)xtime.tv_sec;
xp->lft.soft_byte_limit = -1;
xp->lft.hard_byte_limit = -1;
xp->lft.soft_packet_limit = -1;
xp->lft.hard_packet_limit = -1;
xp->lft.soft_add_expires_seconds = -1;
xp->lft.hard_add_expires_seconds = -1;
xp->lft.soft_use_expires_seconds = -1;
xp->lft.hard_use_expires_seconds = -1;
if ((lifetime = ext_hdrs[SADB_EXT_LIFETIME_HARD-1]) != NULL) {
xp->lft.hard_packet_limit = lifetime->sadb_lifetime_allocations;
xp->lft.hard_byte_limit = lifetime->sadb_lifetime_bytes;
xp->lft.hard_add_expires_seconds = lifetime->sadb_lifetime_addtime;
xp->lft.hard_use_expires_seconds = lifetime->sadb_lifetime_usetime;
}
if ((lifetime = ext_hdrs[SADB_EXT_LIFETIME_SOFT-1]) != NULL) {
xp->lft.soft_packet_limit = lifetime->sadb_lifetime_allocations;
xp->lft.soft_byte_limit = lifetime->sadb_lifetime_bytes;
xp->lft.soft_add_expires_seconds = lifetime->sadb_lifetime_addtime;
xp->lft.soft_use_expires_seconds = lifetime->sadb_lifetime_usetime;
}
xp->xfrm_nr = 0;
if (pol->sadb_x_policy_type == IPSEC_POLICY_IPSEC &&
(err = parse_ipsecrequests(xp, pol)) < 0)
goto out;
out_skb = pfkey_xfrm_policy2msg(xp, pol->sadb_x_policy_dir-1);
if (IS_ERR(out_skb)) {
err = PTR_ERR(out_skb);
goto out;
}
err = xfrm_policy_insert(pol->sadb_x_policy_dir-1, xp,
hdr->sadb_msg_type != SADB_X_SPDUPDATE);
if (err) {
kfree_skb(out_skb);
goto out;
}
xfrm_pol_put(xp);
out_hdr = (struct sadb_msg *) out_skb->data;
out_hdr->sadb_msg_version = hdr->sadb_msg_version;
out_hdr->sadb_msg_type = hdr->sadb_msg_type;
out_hdr->sadb_msg_satype = 0;
out_hdr->sadb_msg_errno = 0;
out_hdr->sadb_msg_reserved = 0;
out_hdr->sadb_msg_seq = hdr->sadb_msg_seq;
out_hdr->sadb_msg_pid = hdr->sadb_msg_pid;
pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ALL, sk);
return 0;
out:
kfree(xp);
return err;
}
static int pfkey_spddelete(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
int err;
struct sadb_address *sa;
struct sadb_x_policy *pol;
struct xfrm_policy *xp;
struct sk_buff *out_skb;
struct sadb_msg *out_hdr;
struct xfrm_selector sel;
if (!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
ext_hdrs[SADB_EXT_ADDRESS_DST-1]) ||
!ext_hdrs[SADB_X_EXT_POLICY-1])
return -EINVAL;
pol = ext_hdrs[SADB_X_EXT_POLICY-1];
if (!pol->sadb_x_policy_dir || pol->sadb_x_policy_dir >= IPSEC_DIR_MAX)
return -EINVAL;
memset(&sel, 0, sizeof(sel));
sa = ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
pfkey_sadb_addr2xfrm_addr(sa, &sel.saddr);
sel.prefixlen_s = sa->sadb_address_prefixlen;
sel.proto = sa->sadb_address_proto;
sel.sport = ((struct sockaddr_in*)(sa+1))->sin_port;
if (sel.sport)
sel.sport_mask = ~0;
sa = ext_hdrs[SADB_EXT_ADDRESS_DST-1],
pfkey_sadb_addr2xfrm_addr(sa, &sel.daddr);
sel.prefixlen_d = sa->sadb_address_prefixlen;
sel.proto = sa->sadb_address_proto;
sel.dport = ((struct sockaddr_in*)(sa+1))->sin_port;
if (sel.dport)
sel.dport_mask = ~0;
xp = xfrm_policy_delete(pol->sadb_x_policy_dir-1, &sel);
if (xp == NULL)
return -ENOENT;
err = 0;
out_skb = pfkey_xfrm_policy2msg(xp, pol->sadb_x_policy_dir-1);
if (IS_ERR(out_skb)) {
err = PTR_ERR(out_skb);
goto out;
}
out_hdr = (struct sadb_msg *) out_skb->data;
out_hdr->sadb_msg_version = hdr->sadb_msg_version;
out_hdr->sadb_msg_type = SADB_X_SPDDELETE;
out_hdr->sadb_msg_satype = 0;
out_hdr->sadb_msg_errno = 0;
out_hdr->sadb_msg_reserved = 0;
out_hdr->sadb_msg_seq = hdr->sadb_msg_seq;
out_hdr->sadb_msg_pid = hdr->sadb_msg_pid;
pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ALL, sk);
err = 0;
out:
if (xp) {
xfrm_policy_kill(xp);
xfrm_pol_put(xp);
}
return err;
}
static int pfkey_spdget(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
int err;
struct sadb_x_policy *pol;
struct xfrm_policy *xp;
struct sk_buff *out_skb;
struct sadb_msg *out_hdr;
if (!ext_hdrs[SADB_X_EXT_POLICY-1])
return -EINVAL;
pol = ext_hdrs[SADB_X_EXT_POLICY-1];
if (!pol->sadb_x_policy_dir || pol->sadb_x_policy_dir >= IPSEC_DIR_MAX)
return -EINVAL;
xp = xfrm_policy_byid(pol->sadb_x_policy_dir-1, pol->sadb_x_policy_id,
hdr->sadb_msg_type == SADB_X_SPDDELETE2);
if (xp == NULL)
return -ENOENT;
err = 0;
out_skb = pfkey_xfrm_policy2msg(xp, pol->sadb_x_policy_dir-1);
if (IS_ERR(out_skb)) {
err = PTR_ERR(out_skb);
goto out;
}
out_hdr = (struct sadb_msg *) out_skb->data;
out_hdr->sadb_msg_version = hdr->sadb_msg_version;
out_hdr->sadb_msg_type = hdr->sadb_msg_type;
out_hdr->sadb_msg_satype = 0;
out_hdr->sadb_msg_errno = 0;
out_hdr->sadb_msg_reserved = 0;
out_hdr->sadb_msg_seq = hdr->sadb_msg_seq;
out_hdr->sadb_msg_pid = hdr->sadb_msg_pid;
pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ALL, sk);
err = 0;
out:
if (xp) {
if (hdr->sadb_msg_type == SADB_X_SPDDELETE2)
xfrm_policy_kill(xp);
xfrm_pol_put(xp);
}
return err;
}
static int dump_sp(struct xfrm_policy *xp, int dir, int count, void *ptr)
{
struct pfkey_dump_data *data = ptr;
struct sk_buff *out_skb;
struct sadb_msg *out_hdr;
out_skb = pfkey_xfrm_policy2msg(xp, dir);
if (IS_ERR(out_skb))
return PTR_ERR(out_skb);
out_hdr = (struct sadb_msg *) out_skb->data;
out_hdr->sadb_msg_version = data->hdr->sadb_msg_version;
out_hdr->sadb_msg_type = SADB_X_SPDDUMP;
out_hdr->sadb_msg_satype = SADB_SATYPE_UNSPEC;
out_hdr->sadb_msg_errno = 0;
out_hdr->sadb_msg_reserved = 0;
out_hdr->sadb_msg_seq = count;
out_hdr->sadb_msg_pid = data->hdr->sadb_msg_pid;
pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ONE, data->sk);
return 0;
}
static int pfkey_spddump(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
struct pfkey_dump_data data = { .skb = skb, .hdr = hdr, .sk = sk };
return xfrm_policy_walk(dump_sp, &data);
}
static int pfkey_spdflush(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
struct sk_buff *skb_out;
struct sadb_msg *hdr_out;
skb_out = alloc_skb(sizeof(struct sadb_msg) + 16, GFP_KERNEL);
if (!skb_out)
return -ENOBUFS;
xfrm_policy_flush();
hdr_out = (struct sadb_msg *) skb_put(skb_out, sizeof(struct sadb_msg));
pfkey_hdr_dup(hdr_out, hdr);
hdr_out->sadb_msg_errno = (uint8_t) 0;
hdr_out->sadb_msg_len = (sizeof(struct sadb_msg) / sizeof(uint64_t));
pfkey_broadcast(skb_out, GFP_KERNEL, BROADCAST_ALL, NULL);
return 0;
}
typedef int (*pfkey_handler)(struct sock *sk, struct sk_buff *skb,
struct sadb_msg *hdr, void **ext_hdrs);
static pfkey_handler pfkey_funcs[SADB_MAX + 1] = {
[SADB_RESERVED] = pfkey_reserved,
[SADB_GETSPI] = pfkey_getspi,
[SADB_UPDATE] = pfkey_update,
[SADB_ADD] = pfkey_add,
[SADB_DELETE] = pfkey_delete,
[SADB_GET] = pfkey_get,
[SADB_ACQUIRE] = pfkey_acquire,
[SADB_REGISTER] = pfkey_register,
[SADB_EXPIRE] = NULL,
[SADB_FLUSH] = pfkey_flush,
[SADB_DUMP] = pfkey_dump,
[SADB_X_PROMISC] = pfkey_promisc,
[SADB_X_PCHANGE] = NULL,
[SADB_X_SPDUPDATE] = pfkey_spdadd,
[SADB_X_SPDADD] = pfkey_spdadd,
[SADB_X_SPDDELETE] = pfkey_spddelete,
[SADB_X_SPDGET] = pfkey_spdget,
[SADB_X_SPDACQUIRE] = NULL,
[SADB_X_SPDDUMP] = pfkey_spddump,
[SADB_X_SPDFLUSH] = pfkey_spdflush,
[SADB_X_SPDSETIDX] = pfkey_spdadd,
[SADB_X_SPDDELETE2] = pfkey_spdget,
};
static int pfkey_process(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr)
{
void *ext_hdrs[SADB_EXT_MAX];
int err;
pfkey_broadcast(skb_clone(skb, GFP_KERNEL), GFP_KERNEL,
BROADCAST_PROMISC_ONLY, NULL);
memset(ext_hdrs, 0, sizeof(ext_hdrs));
err = parse_exthdrs(skb, hdr, ext_hdrs);
if (!err) {
err = -EOPNOTSUPP;
if (pfkey_funcs[hdr->sadb_msg_type])
err = pfkey_funcs[hdr->sadb_msg_type](sk, skb, hdr, ext_hdrs);
}
return err;
}
static struct sadb_msg *pfkey_get_base_msg(struct sk_buff *skb, int *errp)
{
struct sadb_msg *hdr = NULL;
if (skb->len < sizeof(*hdr)) {
*errp = -EMSGSIZE;
} else {
hdr = (struct sadb_msg *) skb->data;
if (hdr->sadb_msg_version != PF_KEY_V2 ||
hdr->sadb_msg_reserved != 0 ||
(hdr->sadb_msg_type <= SADB_RESERVED ||
hdr->sadb_msg_type > SADB_MAX)) {
hdr = NULL;
*errp = -EINVAL;
} else if (hdr->sadb_msg_len != (skb->len /
sizeof(uint64_t)) ||
hdr->sadb_msg_len < (sizeof(struct sadb_msg) /
sizeof(uint64_t))) {
hdr = NULL;
*errp = -EMSGSIZE;
} else {
*errp = 0;
}
}
return hdr;
}
int count_ah_combs(struct xfrm_tmpl *t)
{
int sz = 0;
int i;
for (i=1; i<sizeof(aalg_list)/sizeof(aalg_list[0]); i++) {
if (t->aalgos&(1<<aalg_list[i].desc.sadb_alg_id))
sz += sizeof(struct sadb_comb);
}
return sz + sizeof(struct sadb_prop);
}
int count_esp_combs(struct xfrm_tmpl *t)
{
int sz = 0;
int i, k;
for (i=1; i<sizeof(ealg_list)/sizeof(ealg_list[0]); i++) {
if (!(t->ealgos&(1<<ealg_list[i].desc.sadb_alg_id)))
continue;
for (k=1; k<sizeof(aalg_list)/sizeof(aalg_list[0]); k++) {
if (t->aalgos&(1<<aalg_list[i].desc.sadb_alg_id))
sz += sizeof(struct sadb_comb);
}
}
return sz + sizeof(struct sadb_prop);
}
void dump_ah_combs(struct sk_buff *skb, struct xfrm_tmpl *t)
{
struct sadb_prop *p;
int i;
p = (struct sadb_prop*)skb_put(skb, sizeof(struct sadb_prop));
p->sadb_prop_len = sizeof(struct sadb_prop)/8;
p->sadb_prop_exttype = SADB_EXT_PROPOSAL;
p->sadb_prop_replay = 32;
for (i=1; i<sizeof(aalg_list)/sizeof(aalg_list[0]); i++) {
if (t->aalgos&(1<<aalg_list[i].desc.sadb_alg_id)) {
struct sadb_comb *c;
c = (struct sadb_comb*)skb_put(skb, sizeof(struct sadb_comb));
memset(c, 0, sizeof(*c));
p->sadb_prop_len += sizeof(struct sadb_comb)/8;
c->sadb_comb_auth = aalg_list[i].desc.sadb_alg_id;
c->sadb_comb_auth_minbits = aalg_list[i].desc.sadb_alg_minbits;
c->sadb_comb_auth_maxbits = aalg_list[i].desc.sadb_alg_maxbits;
c->sadb_comb_hard_addtime = 24*60*60;
c->sadb_comb_soft_addtime = 20*60*60;
c->sadb_comb_hard_usetime = 8*60*60;
c->sadb_comb_soft_usetime = 7*60*60;
}
}
}
void dump_esp_combs(struct sk_buff *skb, struct xfrm_tmpl *t)
{
struct sadb_prop *p;
int i, k;
p = (struct sadb_prop*)skb_put(skb, sizeof(struct sadb_prop));
p->sadb_prop_len = sizeof(struct sadb_prop)/8;
p->sadb_prop_exttype = SADB_EXT_PROPOSAL;
p->sadb_prop_replay = 32;
for (i=1; i<sizeof(ealg_list)/sizeof(ealg_list[0]); i++) {
if (!(t->ealgos&(1<<ealg_list[i].desc.sadb_alg_id)))
continue;
for (k=1; k<sizeof(aalg_list)/sizeof(aalg_list[0]); k++) {
struct sadb_comb *c;
if (!(t->aalgos&(1<<aalg_list[i].desc.sadb_alg_id)))
continue;
c = (struct sadb_comb*)skb_put(skb, sizeof(struct sadb_comb));
memset(c, 0, sizeof(*c));
p->sadb_prop_len += sizeof(struct sadb_comb)/8;
c->sadb_comb_auth = aalg_list[k].desc.sadb_alg_id;
c->sadb_comb_auth_minbits = aalg_list[k].desc.sadb_alg_minbits;
c->sadb_comb_auth_maxbits = aalg_list[k].desc.sadb_alg_maxbits;
c->sadb_comb_encrypt = ealg_list[i].desc.sadb_alg_id;
c->sadb_comb_encrypt_minbits = ealg_list[i].desc.sadb_alg_minbits;
c->sadb_comb_encrypt_maxbits = ealg_list[i].desc.sadb_alg_maxbits;
c->sadb_comb_hard_addtime = 24*60*60;
c->sadb_comb_soft_addtime = 20*60*60;
c->sadb_comb_hard_usetime = 8*60*60;
c->sadb_comb_soft_usetime = 7*60*60;
}
}
}
static int pfkey_send_notify(struct xfrm_state *x, int hard)
{
struct sk_buff *out_skb;
struct sadb_msg *out_hdr;
int hsc = (hard ? 2 : 1);
out_skb = pfkey_xfrm_state2msg(x, 0, hsc);
if (IS_ERR(out_skb))
return PTR_ERR(out_skb);
out_hdr = (struct sadb_msg *) out_skb->data;
out_hdr->sadb_msg_version = PF_KEY_V2;
out_hdr->sadb_msg_type = SADB_EXPIRE;
out_hdr->sadb_msg_satype = pfkey_proto2satype(x->id.proto);
out_hdr->sadb_msg_errno = 0;
out_hdr->sadb_msg_reserved = 0;
out_hdr->sadb_msg_seq = 0;
out_hdr->sadb_msg_pid = 0;
pfkey_broadcast(out_skb, GFP_KERNEL, BROADCAST_REGISTERED, NULL);
return 0;
}
static int pfkey_send_acquire(struct xfrm_state *x, struct xfrm_tmpl *t, struct xfrm_policy *xp, int dir)
{
struct sk_buff *skb;
struct sadb_msg *hdr;
struct sadb_address *addr;
struct sadb_x_policy *pol;
int size;
static u32 acqseq;
size = sizeof(struct sadb_msg) +
sizeof(struct sadb_address)*2 +
sizeof(struct sockaddr_in)*2 + /* XXX */
sizeof(struct sadb_x_policy);
if (x->id.proto == IPPROTO_AH)
size += count_ah_combs(t);
else if (x->id.proto == IPPROTO_ESP)
size += count_esp_combs(t);
skb = alloc_skb(size + 16, GFP_ATOMIC);
if (skb == NULL)
return -ENOMEM;
hdr = (struct sadb_msg *) skb_put(skb, sizeof(struct sadb_msg));
hdr->sadb_msg_version = PF_KEY_V2;
hdr->sadb_msg_type = SADB_ACQUIRE;
hdr->sadb_msg_satype = pfkey_proto2satype(x->id.proto);
hdr->sadb_msg_len = size / sizeof(uint64_t);
hdr->sadb_msg_errno = 0;
hdr->sadb_msg_reserved = 0;
hdr->sadb_msg_seq = (++acqseq ? : ++acqseq);
x->km.seq = acqseq;
hdr->sadb_msg_pid = 0;
/* src address */
addr = (struct sadb_address*) skb_put(skb,
sizeof(struct sadb_address)+sizeof(struct sockaddr_in));
addr->sadb_address_len =
(sizeof(struct sadb_address)+sizeof(struct sockaddr_in))/
sizeof(uint64_t);
addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
addr->sadb_address_proto = 0;
addr->sadb_address_prefixlen = 32;
addr->sadb_address_reserved = 0;
((struct sockaddr_in*)(addr + 1))->sin_family = AF_INET;
((struct sockaddr_in*)(addr + 1))->sin_addr.s_addr =
x->props.saddr.xfrm4_addr;
((struct sockaddr_in*)(addr + 1))->sin_port = 0;
/* dst address */
addr = (struct sadb_address*) skb_put(skb,
sizeof(struct sadb_address)+sizeof(struct sockaddr_in));
addr->sadb_address_len =
(sizeof(struct sadb_address)+sizeof(struct sockaddr_in))/
sizeof(uint64_t);
addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
addr->sadb_address_proto = 0;
addr->sadb_address_prefixlen = 32;
addr->sadb_address_reserved = 0;
((struct sockaddr_in*)(addr + 1))->sin_family = AF_INET;
((struct sockaddr_in*)(addr + 1))->sin_addr.s_addr =
x->id.daddr.xfrm4_addr;
((struct sockaddr_in*)(addr + 1))->sin_port = 0;
pol = (struct sadb_x_policy *) skb_put(skb, sizeof(struct sadb_x_policy));
pol->sadb_x_policy_len = sizeof(struct sadb_x_policy)/sizeof(uint64_t);
pol->sadb_x_policy_exttype = SADB_X_EXT_POLICY;
pol->sadb_x_policy_type = IPSEC_POLICY_IPSEC;
pol->sadb_x_policy_dir = dir+1;
pol->sadb_x_policy_id = xp->index;
/* Set sadb_comb's. */
if (x->id.proto == IPPROTO_AH)
dump_ah_combs(skb, t);
else if (x->id.proto == IPPROTO_ESP)
dump_esp_combs(skb, t);
pfkey_broadcast(skb, GFP_ATOMIC, BROADCAST_REGISTERED, NULL);
return 0;
}
static int pfkey_sendmsg(struct kiocb *kiocb,
struct socket *sock, struct msghdr *msg, int len,
struct scm_cookie *scm)
{
struct sock *sk = sock->sk;
struct sk_buff *skb = NULL;
struct sadb_msg *hdr = NULL;
int err;
err = -EOPNOTSUPP;
if (msg->msg_flags & MSG_OOB)
goto out;
err = -EMSGSIZE;
if ((unsigned)len > sk->sndbuf-32)
goto out;
err = -ENOBUFS;
skb = alloc_skb(len, GFP_KERNEL);
if (skb == NULL)
goto out;
err = -EFAULT;
if (memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len))
goto out;
hdr = pfkey_get_base_msg(skb, &err);
if (!hdr)
goto out;
err = pfkey_process(sk, skb, hdr);
out:
if (err && hdr)
pfkey_error(hdr, err);
if (skb)
kfree_skb(skb);
return err;
}
static int pfkey_recvmsg(struct kiocb *kiocb,
struct socket *sock, struct msghdr *msg, int len,
int flags, struct scm_cookie *scm)
{
struct sock *sk = sock->sk;
struct sk_buff *skb;
int copied, err;
err = -EINVAL;
if (flags & ~(MSG_PEEK|MSG_DONTWAIT|MSG_TRUNC))
goto out;
msg->msg_namelen = 0;
skb = skb_recv_datagram(sk, flags, flags & MSG_DONTWAIT, &err);
if (skb == NULL)
goto out;
copied = skb->len;
if (copied > len) {
msg->msg_flags |= MSG_TRUNC;
copied = len;
}
skb->h.raw = skb->data;
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
if (err)
goto out_free;
sock_recv_timestamp(msg, sk, skb);
err = (flags & MSG_TRUNC) ? skb->len : copied;
out_free:
skb_free_datagram(sk, skb);
out:
return err;
}
static struct proto_ops pfkey_ops = {
.family = PF_KEY,
/* Operations that make no sense on pfkey sockets. */
.bind = sock_no_bind,
.connect = sock_no_connect,
.socketpair = sock_no_socketpair,
.accept = sock_no_accept,
.getname = sock_no_getname,
.ioctl = sock_no_ioctl,
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = sock_no_setsockopt,
.getsockopt = sock_no_getsockopt,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
/* Now the operations that really occur. */
.release = pfkey_release,
.poll = datagram_poll,
.sendmsg = pfkey_sendmsg,
.recvmsg = pfkey_recvmsg,
};
static struct net_proto_family pfkey_family_ops = {
.family = PF_KEY,
.create = pfkey_create,
};
#ifdef CONFIG_PROC_FS
static int pfkey_read_proc(char *buffer, char **start, off_t offset,
int length, int *eof, void *data)
{
off_t pos = 0;
off_t begin = 0;
int len = 0;
struct sock *s;
len += sprintf(buffer,"sk RefCnt Rmem Wmem User Inode\n");
read_lock(&pfkey_table_lock);
for (s = pfkey_table; s; s = s->next) {
len += sprintf(buffer+len,"%p %-6d %-6u %-6u %-6u %-6lu",
s,
atomic_read(&s->refcnt),
atomic_read(&s->rmem_alloc),
atomic_read(&s->wmem_alloc),
sock_i_uid(s),
sock_i_ino(s)
);
buffer[len++] = '\n';
pos = begin + len;
if (pos < offset) {
len = 0;
begin = pos;
}
if(pos > offset + length)
goto done;
}
*eof = 1;
done:
read_unlock(&pfkey_table_lock);
*start = buffer + (offset - begin);
len -= (offset - begin);
if (len > length)
len = length;
if (len < 0)
len = 0;
return len;
}
#endif
static struct xfrm_mgr pfkeyv2_mgr =
{
.id = "pfkeyv2",
.notify = pfkey_send_notify,
.acquire = pfkey_send_acquire
};
static void __exit ipsec_pfkey_exit(void)
{
xfrm_unregister_km(&pfkeyv2_mgr);
remove_proc_entry("net/pfkey", 0);
sock_unregister(PF_KEY);
}
static int __init ipsec_pfkey_init(void)
{
sock_register(&pfkey_family_ops);
#ifdef CONFIG_PROC_FS
create_proc_read_entry("net/pfkey", 0, 0, pfkey_read_proc, NULL);
#endif
xfrm_register_km(&pfkeyv2_mgr);
return 0;
}
module_init(ipsec_pfkey_init);
module_exit(ipsec_pfkey_exit);
MODULE_LICENSE("GPL");
...@@ -53,6 +53,7 @@ extern __u32 sysctl_rmem_max; ...@@ -53,6 +53,7 @@ extern __u32 sysctl_rmem_max;
#include <linux/inet.h> #include <linux/inet.h>
#include <linux/mroute.h> #include <linux/mroute.h>
#include <linux/igmp.h> #include <linux/igmp.h>
#include <net/xfrm.h>
extern struct net_proto_family inet_family_ops; extern struct net_proto_family inet_family_ops;
...@@ -282,6 +283,43 @@ extern int (*dlci_ioctl_hook)(unsigned int, void *); ...@@ -282,6 +283,43 @@ extern int (*dlci_ioctl_hook)(unsigned int, void *);
EXPORT_SYMBOL(dlci_ioctl_hook); EXPORT_SYMBOL(dlci_ioctl_hook);
#endif #endif
EXPORT_SYMBOL(xfrm_policy_alloc);
EXPORT_SYMBOL(__xfrm_policy_destroy);
EXPORT_SYMBOL(xfrm_policy_lookup);
EXPORT_SYMBOL(xfrm_bundle_create);
EXPORT_SYMBOL(xfrm_lookup);
EXPORT_SYMBOL(__xfrm_policy_check);
EXPORT_SYMBOL(__xfrm_route_forward);
EXPORT_SYMBOL(xfrm_state_alloc);
EXPORT_SYMBOL(__xfrm_state_destroy);
EXPORT_SYMBOL(xfrm_state_find);
EXPORT_SYMBOL(xfrm_state_insert);
EXPORT_SYMBOL(xfrm_state_check_expire);
EXPORT_SYMBOL(xfrm_state_check_space);
EXPORT_SYMBOL(xfrm_state_lookup);
EXPORT_SYMBOL(xfrm_replay_check);
EXPORT_SYMBOL(xfrm_replay_advance);
EXPORT_SYMBOL(xfrm_check_selectors);
EXPORT_SYMBOL(xfrm4_rcv);
EXPORT_SYMBOL(xfrm_register_type);
EXPORT_SYMBOL(xfrm_unregister_type);
EXPORT_SYMBOL(xfrm_get_type);
EXPORT_SYMBOL(inet_peer_idlock);
EXPORT_SYMBOL(xfrm_register_km);
EXPORT_SYMBOL(xfrm_unregister_km);
EXPORT_SYMBOL(xfrm_state_delete);
EXPORT_SYMBOL(xfrm_state_walk);
EXPORT_SYMBOL(xfrm_find_acq_byseq);
EXPORT_SYMBOL(xfrm_find_acq);
EXPORT_SYMBOL(xfrm_alloc_spi);
EXPORT_SYMBOL(xfrm_state_flush);
EXPORT_SYMBOL(xfrm_policy_kill);
EXPORT_SYMBOL(xfrm_policy_delete);
EXPORT_SYMBOL(xfrm_policy_insert);
EXPORT_SYMBOL(xfrm_policy_walk);
EXPORT_SYMBOL(xfrm_policy_flush);
EXPORT_SYMBOL(xfrm_policy_byid);
#if defined (CONFIG_IPV6_MODULE) || defined (CONFIG_IP_SCTP_MODULE) #if defined (CONFIG_IPV6_MODULE) || defined (CONFIG_IP_SCTP_MODULE)
/* inet functions common to v4 and v6 */ /* inet functions common to v4 and v6 */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment