Commit 5792771d authored by Kazunori Miyazawa's avatar Kazunori Miyazawa Committed by David S. Miller

[IPSEC]: Add full ipv6 support.

Credits also to Mitsuru Kanda <kanda@karaba.org>,
YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>,
and Kunihiro Ishiguro.
parent 86e7191b
...@@ -74,6 +74,21 @@ struct rt0_hdr { ...@@ -74,6 +74,21 @@ struct rt0_hdr {
#define rt0_type rt_hdr.type; #define rt0_type rt_hdr.type;
}; };
struct ipv6_auth_hdr {
__u8 nexthdr;
__u8 hdrlen; /* This one is measured in 32 bit units! */
__u16 reserved;
__u32 spi;
__u32 seq_no; /* Sequence number */
__u8 auth_data[4]; /* Length variable but >=4. Mind the 64 bit alignment! */
};
struct ipv6_esp_hdr {
__u32 spi;
__u32 seq_no; /* Sequence number */
__u8 enc_data[8]; /* Length variable but >=8. Mind the 64 bit alignment! */
};
/* /*
* IPv6 fixed header * IPv6 fixed header
* *
......
...@@ -247,7 +247,10 @@ extern void dst_init(void); ...@@ -247,7 +247,10 @@ 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 int xfrm6_lookup(struct dst_entry **dst_p, struct flowi *fl,
struct sock *sk, int flags);
extern void xfrm_init(void); extern void xfrm_init(void);
extern void xfrm6_init(void);
#endif #endif
......
...@@ -57,6 +57,8 @@ extern struct rt6_info *rt6_lookup(struct in6_addr *daddr, ...@@ -57,6 +57,8 @@ extern struct rt6_info *rt6_lookup(struct in6_addr *daddr,
struct in6_addr *saddr, struct in6_addr *saddr,
int oif, int flags); int oif, int flags);
extern struct rt6_info *ndisc_get_dummy_rt(void);
/* /*
* support functions for ND * support functions for ND
* *
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include <net/dst.h> #include <net/dst.h>
#include <net/route.h> #include <net/route.h>
#include <net/ip6_fib.h>
#define XFRM_ALIGN8(len) (((len) + 7) & ~7) #define XFRM_ALIGN8(len) (((len) + 7) & ~7)
...@@ -282,6 +283,7 @@ struct xfrm_dst ...@@ -282,6 +283,7 @@ struct xfrm_dst
struct xfrm_dst *next; struct xfrm_dst *next;
struct dst_entry dst; struct dst_entry dst;
struct rtable rt; struct rtable rt;
struct rt6_info rt6;
} u; } u;
}; };
...@@ -308,26 +310,42 @@ secpath_put(struct sec_path *sp) ...@@ -308,26 +310,42 @@ secpath_put(struct sec_path *sp)
if (sp && atomic_dec_and_test(&sp->refcnt)) if (sp && atomic_dec_and_test(&sp->refcnt))
__secpath_destroy(sp); __secpath_destroy(sp);
} }
extern int __xfrm_policy_check(struct sock *, int dir, struct sk_buff *skb, unsigned short family);
extern int __xfrm_policy_check(struct sock *, int dir, struct sk_buff *skb);
static inline int xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb) static inline int xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb)
{ {
if (sk && sk->policy[XFRM_POLICY_IN]) if (sk && sk->policy[XFRM_POLICY_IN])
return __xfrm_policy_check(sk, dir, skb); return __xfrm_policy_check(sk, dir, skb, AF_INET);
return !xfrm_policy_list[dir] || return !xfrm_policy_list[dir] ||
(skb->dst->flags & DST_NOPOLICY) || (skb->dst->flags & DST_NOPOLICY) ||
__xfrm_policy_check(sk, dir, skb); __xfrm_policy_check(sk, dir, skb, AF_INET);
} }
extern int __xfrm_route_forward(struct sk_buff *skb); static inline int xfrm6_policy_check(struct sock *sk, int dir, struct sk_buff *skb)
{
if (sk && sk->policy[XFRM_POLICY_IN])
return __xfrm_policy_check(sk, dir, skb, AF_INET6);
return !xfrm_policy_list[dir] ||
(skb->dst->flags & DST_NOPOLICY) ||
__xfrm_policy_check(sk, dir, skb, AF_INET6);
}
extern int __xfrm_route_forward(struct sk_buff *skb, unsigned short family);
static inline int xfrm_route_forward(struct sk_buff *skb) static inline int xfrm_route_forward(struct sk_buff *skb)
{ {
return !xfrm_policy_list[XFRM_POLICY_OUT] || return !xfrm_policy_list[XFRM_POLICY_OUT] ||
(skb->dst->flags & DST_NOXFRM) || (skb->dst->flags & DST_NOXFRM) ||
__xfrm_route_forward(skb); __xfrm_route_forward(skb, AF_INET);
}
static inline int xfrm6_route_forward(struct sk_buff *skb)
{
return !xfrm_policy_list[XFRM_POLICY_OUT] ||
(skb->dst->flags & DST_NOXFRM) ||
__xfrm_route_forward(skb, AF_INET6);
} }
extern int __xfrm_sk_clone_policy(struct sock *sk); extern int __xfrm_sk_clone_policy(struct sock *sk);
...@@ -380,12 +398,16 @@ extern void xfrm_state_init(void); ...@@ -380,12 +398,16 @@ 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 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, u32 saddr, struct flowi *fl, struct xfrm_tmpl *tmpl, extern struct xfrm_state *xfrm4_state_find(u32 daddr, u32 saddr, struct flowi *fl, struct xfrm_tmpl *tmpl,
struct xfrm_policy *pol, int *err);
extern struct xfrm_state *xfrm6_state_find(struct in6_addr *daddr, struct in6_addr *saddr,
struct flowi *fl, struct xfrm_tmpl *tmpl,
struct xfrm_policy *pol, int *err); struct xfrm_policy *pol, int *err);
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 *xfrm4_state_lookup(u32 daddr, u32 spi, u8 proto);
extern struct xfrm_state *xfrm6_state_lookup(struct in6_addr *daddr, u32 spi, u8 proto);
extern struct xfrm_state *xfrm_find_acq_byseq(u32 seq); extern struct xfrm_state *xfrm_find_acq_byseq(u32 seq);
extern void xfrm_state_delete(struct xfrm_state *x); extern void xfrm_state_delete(struct xfrm_state *x);
extern void xfrm_state_flush(u8 proto); extern void xfrm_state_flush(u8 proto);
...@@ -393,17 +415,21 @@ extern int xfrm_replay_check(struct xfrm_state *x, u32 seq); ...@@ -393,17 +415,21 @@ 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);
extern int xfrm6_rcv(struct sk_buff *skb);
extern int xfrm6_clear_mutable_options(struct sk_buff *skb, u16 *nh_offset, int dir);
extern int xfrm_user_policy(struct sock *sk, int optname, u8 *optval, int optlen); extern int xfrm_user_policy(struct sock *sk, int optname, u8 *optval, int optlen);
struct xfrm_policy *xfrm_policy_alloc(int gfp); struct xfrm_policy *xfrm_policy_alloc(int gfp);
extern int xfrm_policy_walk(int (*func)(struct xfrm_policy *, int, int, void*), 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); struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl, unsigned short family);
int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl); 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_delete(int dir, struct xfrm_selector *sel);
struct xfrm_policy *xfrm_policy_byid(int dir, u32 id, int delete); struct xfrm_policy *xfrm_policy_byid(int dir, u32 id, int delete);
void xfrm_policy_flush(void); void xfrm_policy_flush(void);
void xfrm_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi); 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, int create); struct xfrm_state * xfrm_find_acq(u8 mode, u16 reqid, u8 proto, u32 daddr, u32 saddr, int create);
struct xfrm_state * xfrm6_find_acq(u8 mode, u16 reqid, u8 proto, struct in6_addr *daddr,
struct in6_addr *saddr, int create);
extern void xfrm_policy_flush(void); extern void xfrm_policy_flush(void);
extern void xfrm_policy_kill(struct xfrm_policy *); extern void xfrm_policy_kill(struct xfrm_policy *);
extern int xfrm_sk_policy_insert(struct sock *sk, int dir, struct xfrm_policy *pol); extern int xfrm_sk_policy_insert(struct sock *sk, int dir, struct xfrm_policy *pol);
...@@ -425,23 +451,129 @@ extern struct xfrm_algo_desc *xfrm_ealg_get_byid(int alg_id); ...@@ -425,23 +451,129 @@ extern struct xfrm_algo_desc *xfrm_ealg_get_byid(int alg_id);
extern struct xfrm_algo_desc *xfrm_aalg_get_byname(char *name); extern struct xfrm_algo_desc *xfrm_aalg_get_byname(char *name);
extern struct xfrm_algo_desc *xfrm_ealg_get_byname(char *name); extern struct xfrm_algo_desc *xfrm_ealg_get_byname(char *name);
static __inline__ int addr_match(void *token1, void *token2, int prefixlen)
{
__u32 *a1 = token1;
__u32 *a2 = token2;
int pdw;
int pbi;
pdw = prefixlen >> 5; /* num of whole __u32 in prefix */
pbi = prefixlen & 0x1f; /* num of bits in incomplete u32 in prefix */
if (pdw)
if (memcmp(a1, a2, pdw << 2))
return 0;
if (pbi) {
__u32 mask;
mask = htonl((0xffffffff) << (32 - pbi));
if ((a1[pdw] ^ a2[pdw]) & mask)
return 0;
}
return 1;
}
static inline int static inline int
xfrm6_selector_match(struct xfrm_selector *sel, struct flowi *fl) xfrm6_selector_match(struct xfrm_selector *sel, struct flowi *fl)
{ {
return !memcmp(fl->fl6_dst, sel->daddr.a6, sizeof(struct in6_addr)) && return addr_match(fl->fl6_dst, &sel->daddr, sel->prefixlen_d) &&
addr_match(fl->fl6_src, &sel->saddr, sel->prefixlen_s) &&
!((fl->uli_u.ports.dport^sel->dport)&sel->dport_mask) && !((fl->uli_u.ports.dport^sel->dport)&sel->dport_mask) &&
!((fl->uli_u.ports.sport^sel->sport)&sel->sport_mask) && !((fl->uli_u.ports.sport^sel->sport)&sel->sport_mask) &&
(fl->proto == sel->proto || !sel->proto) && (fl->proto == sel->proto || !sel->proto) &&
(fl->oif == sel->ifindex || !sel->ifindex) && (fl->oif == sel->ifindex || !sel->ifindex);
!memcmp(fl->fl6_src, sel->saddr.a6, sizeof(struct in6_addr));
} }
extern int xfrm6_register_type(struct xfrm_type *type); extern int xfrm6_register_type(struct xfrm_type *type);
extern int xfrm6_unregister_type(struct xfrm_type *type); extern int xfrm6_unregister_type(struct xfrm_type *type);
extern struct xfrm_type *xfrm6_get_type(u8 proto); extern struct xfrm_type *xfrm6_get_type(u8 proto);
extern struct xfrm_state *xfrm6_state_lookup(struct in6_addr *daddr, u32 spi, u8 proto); struct ah_data
struct xfrm_state * xfrm6_find_acq(u8 mode, u16 reqid, u8 proto, struct in6_addr *daddr, struct in6_addr *saddr, int create); {
void xfrm6_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi); u8 *key;
int key_len;
u8 *work_icv;
int icv_full_len;
int icv_trunc_len;
void (*icv)(struct ah_data*,
struct sk_buff *skb, u8 *icv);
struct crypto_tfm *tfm;
};
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 icv_full_len != 0 */
struct {
u8 *key; /* Key */
int key_len; /* Length of the key */
u8 *work_icv;
int icv_full_len;
int icv_trunc_len;
void (*icv)(struct esp_data*,
struct sk_buff *skb,
int offset, int len, u8 *icv);
struct crypto_tfm *tfm;
} auth;
};
typedef void (icv_update_fn_t)(struct crypto_tfm *, struct scatterlist *, unsigned int);
extern void skb_ah_walk(const struct sk_buff *skb,
struct crypto_tfm *tfm, icv_update_fn_t icv_update);
extern void skb_icv_walk(const struct sk_buff *skb, struct crypto_tfm *tfm,
int offset, int len, icv_update_fn_t icv_update);
extern int skb_to_sgvec(struct sk_buff *skb, struct scatterlist *sg, int offset, int len);
extern int skb_cow_data(struct sk_buff *skb, int tailbits, struct sk_buff **trailer);
extern void *pskb_put(struct sk_buff *skb, struct sk_buff *tail, int len);
static inline void
ah_hmac_digest(struct ah_data *ahp, struct sk_buff *skb, u8 *auth_data)
{
struct crypto_tfm *tfm = ahp->tfm;
memset(auth_data, 0, ahp->icv_trunc_len);
crypto_hmac_init(tfm, ahp->key, &ahp->key_len);
skb_ah_walk(skb, tfm, crypto_hmac_update);
crypto_hmac_final(tfm, ahp->key, &ahp->key_len, ahp->work_icv);
memcpy(auth_data, ahp->work_icv, ahp->icv_trunc_len);
}
static inline 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;
char *icv = esp->auth.work_icv;
memset(auth_data, 0, esp->auth.icv_trunc_len);
crypto_hmac_init(tfm, esp->auth.key, &esp->auth.key_len);
skb_icv_walk(skb, tfm, offset, len, crypto_hmac_update);
crypto_hmac_final(tfm, esp->auth.key, &esp->auth.key_len, icv);
memcpy(auth_data, icv, esp->auth.icv_trunc_len);
}
typedef int (xfrm_dst_lookup_t)(struct xfrm_dst **dst, struct flowi *fl);
int xfrm_dst_lookup_register(xfrm_dst_lookup_t *dst_lookup, unsigned short family);
void xfrm_dst_lookup_unregister(unsigned short family);
#endif /* _NET_XFRM_H */ #endif /* _NET_XFRM_H */
...@@ -7,25 +7,8 @@ ...@@ -7,25 +7,8 @@
#include <net/icmp.h> #include <net/icmp.h>
#include <asm/scatterlist.h> #include <asm/scatterlist.h>
#define AH_HLEN_NOICV 12
typedef void (icv_update_fn_t)(struct crypto_tfm *,
struct scatterlist *, unsigned int);
struct ah_data
{
u8 *key;
int key_len;
u8 *work_icv;
int icv_full_len;
int icv_trunc_len;
void (*icv)(struct ah_data*,
struct sk_buff *skb, u8 *icv);
struct crypto_tfm *tfm;
};
#define AH_HLEN_NOICV 12
/* Clear mutable options and find final destination to substitute /* Clear mutable options and find final destination to substitute
* into IP header for icv calculation. Options are already checked * into IP header for icv calculation. Options are already checked
...@@ -71,92 +54,6 @@ static int ip_clear_mutable_options(struct iphdr *iph, u32 *daddr) ...@@ -71,92 +54,6 @@ static int ip_clear_mutable_options(struct iphdr *iph, u32 *daddr)
return 0; return 0;
} }
static void skb_ah_walk(const struct sk_buff *skb,
struct crypto_tfm *tfm, icv_update_fn_t icv_update)
{
int offset = 0;
int len = skb->len;
int start = skb->len - skb->data_len;
int i, copy = start - offset;
struct scatterlist sg;
/* Checksum header. */
if (copy > 0) {
if (copy > len)
copy = len;
sg.page = virt_to_page(skb->data + offset);
sg.offset = (unsigned long)(skb->data + offset) % PAGE_SIZE;
sg.length = copy;
icv_update(tfm, &sg, 1);
if ((len -= copy) == 0)
return;
offset += copy;
}
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
BUG_TRAP(start <= offset + len);
end = start + skb_shinfo(skb)->frags[i].size;
if ((copy = end - offset) > 0) {
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
if (copy > len)
copy = len;
sg.page = frag->page;
sg.offset = frag->page_offset + offset-start;
sg.length = copy;
icv_update(tfm, &sg, 1);
if (!(len -= copy))
return;
offset += copy;
}
start = end;
}
if (skb_shinfo(skb)->frag_list) {
struct sk_buff *list = skb_shinfo(skb)->frag_list;
for (; list; list = list->next) {
int end;
BUG_TRAP(start <= offset + len);
end = start + list->len;
if ((copy = end - offset) > 0) {
if (copy > len)
copy = len;
skb_ah_walk(list, tfm, icv_update);
if ((len -= copy) == 0)
return;
offset += copy;
}
start = end;
}
}
if (len)
BUG();
}
static void
ah_hmac_digest(struct ah_data *ahp, struct sk_buff *skb, u8 *auth_data)
{
struct crypto_tfm *tfm = ahp->tfm;
memset(auth_data, 0, ahp->icv_trunc_len);
crypto_hmac_init(tfm, ahp->key, &ahp->key_len);
skb_ah_walk(skb, tfm, crypto_hmac_update);
crypto_hmac_final(tfm, ahp->key, &ahp->key_len, ahp->work_icv);
memcpy(auth_data, ahp->work_icv, ahp->icv_trunc_len);
}
static int ah_output(struct sk_buff *skb) static int ah_output(struct sk_buff *skb)
{ {
int err; int err;
...@@ -330,7 +227,7 @@ void ah4_err(struct sk_buff *skb, u32 info) ...@@ -330,7 +227,7 @@ void ah4_err(struct sk_buff *skb, u32 info)
skb->h.icmph->code != ICMP_FRAG_NEEDED) skb->h.icmph->code != ICMP_FRAG_NEEDED)
return; return;
x = xfrm_state_lookup(iph->daddr, ah->spi, IPPROTO_AH); x = xfrm4_state_lookup(iph->daddr, ah->spi, IPPROTO_AH);
if (!x) if (!x)
return; return;
printk(KERN_DEBUG "pmtu discvovery on SA AH/%08x/%08x\n", printk(KERN_DEBUG "pmtu discvovery on SA AH/%08x/%08x\n",
......
...@@ -8,312 +8,8 @@ ...@@ -8,312 +8,8 @@
#include <linux/random.h> #include <linux/random.h>
#include <net/icmp.h> #include <net/icmp.h>
#define MAX_SG_ONSTACK 4
typedef void (icv_update_fn_t)(struct crypto_tfm *,
struct scatterlist *, unsigned int);
/* 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 icv_full_len != 0 */
struct {
u8 *key; /* Key */
int key_len; /* Length of the key */
u8 *work_icv;
int icv_full_len;
int icv_trunc_len;
void (*icv)(struct esp_data*,
struct sk_buff *skb,
int offset, int len, u8 *icv);
struct crypto_tfm *tfm;
} auth;
};
/* Move to common area: it is shared with AH. */
void skb_icv_walk(const struct sk_buff *skb, struct crypto_tfm *tfm,
int offset, int len, icv_update_fn_t icv_update)
{
int start = skb->len - skb->data_len;
int i, copy = start - offset;
struct scatterlist sg;
/* Checksum header. */
if (copy > 0) {
if (copy > len)
copy = len;
sg.page = virt_to_page(skb->data + offset);
sg.offset = (unsigned long)(skb->data + offset) % PAGE_SIZE;
sg.length = copy;
icv_update(tfm, &sg, 1);
if ((len -= copy) == 0)
return;
offset += copy;
}
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
BUG_TRAP(start <= offset + len);
end = start + skb_shinfo(skb)->frags[i].size;
if ((copy = end - offset) > 0) {
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
if (copy > len)
copy = len;
sg.page = frag->page;
sg.offset = frag->page_offset + offset-start;
sg.length = copy;
icv_update(tfm, &sg, 1);
if (!(len -= copy))
return;
offset += copy;
}
start = end;
}
if (skb_shinfo(skb)->frag_list) {
struct sk_buff *list = skb_shinfo(skb)->frag_list;
for (; list; list = list->next) {
int end;
BUG_TRAP(start <= offset + len);
end = start + list->len;
if ((copy = end - offset) > 0) {
if (copy > len)
copy = len;
skb_icv_walk(list, tfm, offset-start, copy, icv_update);
if ((len -= copy) == 0)
return;
offset += copy;
}
start = end;
}
}
if (len)
BUG();
}
/* Looking generic it is not used in another places. */
int
skb_to_sgvec(struct sk_buff *skb, struct scatterlist *sg, int offset, int len)
{
int start = skb->len - skb->data_len;
int i, copy = start - offset;
int elt = 0;
if (copy > 0) {
if (copy > len)
copy = len;
sg[elt].page = virt_to_page(skb->data + offset);
sg[elt].offset = (unsigned long)(skb->data + offset) % PAGE_SIZE;
sg[elt].length = copy;
elt++;
if ((len -= copy) == 0)
return elt;
offset += copy;
}
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
BUG_TRAP(start <= offset + len);
end = start + skb_shinfo(skb)->frags[i].size;
if ((copy = end - offset) > 0) {
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
if (copy > len)
copy = len;
sg[elt].page = frag->page;
sg[elt].offset = frag->page_offset+offset-start;
sg[elt].length = copy;
elt++;
if (!(len -= copy))
return elt;
offset += copy;
}
start = end;
}
if (skb_shinfo(skb)->frag_list) {
struct sk_buff *list = skb_shinfo(skb)->frag_list;
for (; list; list = list->next) {
int end;
BUG_TRAP(start <= offset + len);
end = start + list->len;
if ((copy = end - offset) > 0) {
if (copy > len)
copy = len;
elt += skb_to_sgvec(list, sg+elt, offset - start, copy);
if ((len -= copy) == 0)
return elt;
offset += copy;
}
start = end;
}
}
if (len)
BUG();
return elt;
}
/* 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;
char *icv = esp->auth.work_icv;
memset(auth_data, 0, esp->auth.icv_trunc_len);
crypto_hmac_init(tfm, esp->auth.key, &esp->auth.key_len);
skb_icv_walk(skb, tfm, offset, len, crypto_hmac_update);
crypto_hmac_final(tfm, esp->auth.key, &esp->auth.key_len, icv);
memcpy(auth_data, icv, esp->auth.icv_trunc_len);
}
/* Check that skb data bits are writable. If they are not, copy data
* to newly created private area. If "tailbits" is given, make sure that
* tailbits bytes beyond current end of skb are writable.
*
* Returns amount of elements of scatterlist to load for subsequent
* transformations and pointer to writable trailer skb.
*/
int skb_cow_data(struct sk_buff *skb, int tailbits, struct sk_buff **trailer)
{
int copyflag;
int elt;
struct sk_buff *skb1, **skb_p;
/* If skb is cloned or its head is paged, reallocate
* head pulling out all the pages (pages are considered not writable
* at the moment even if they are anonymous).
*/
if ((skb_cloned(skb) || skb_shinfo(skb)->nr_frags) &&
__pskb_pull_tail(skb, skb_pagelen(skb)-skb_headlen(skb)) == NULL)
return -ENOMEM;
/* Easy case. Most of packets will go this way. */
if (!skb_shinfo(skb)->frag_list) {
/* A little of trouble, not enough of space for trailer.
* This should not happen, when stack is tuned to generate
* good frames. OK, on miss we reallocate and reserve even more
* space, 128 bytes is fair. */
if (skb_tailroom(skb) < tailbits &&
pskb_expand_head(skb, 0, tailbits-skb_tailroom(skb)+128, GFP_ATOMIC))
return -ENOMEM;
/* Voila! */ #define MAX_SG_ONSTACK 4
*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 esp_output(struct sk_buff *skb)
{ {
...@@ -575,7 +271,7 @@ void esp4_err(struct sk_buff *skb, u32 info) ...@@ -575,7 +271,7 @@ void esp4_err(struct sk_buff *skb, u32 info)
skb->h.icmph->code != ICMP_FRAG_NEEDED) skb->h.icmph->code != ICMP_FRAG_NEEDED)
return; return;
x = xfrm_state_lookup(iph->daddr, esph->spi, IPPROTO_ESP); x = xfrm4_state_lookup(iph->daddr, esph->spi, IPPROTO_ESP);
if (!x) if (!x)
return; return;
printk(KERN_DEBUG "pmtu discvovery on SA ESP/%08x/%08x\n", printk(KERN_DEBUG "pmtu discvovery on SA ESP/%08x/%08x\n",
......
...@@ -96,6 +96,7 @@ ...@@ -96,6 +96,7 @@
#include <net/arp.h> #include <net/arp.h>
#include <net/tcp.h> #include <net/tcp.h>
#include <net/icmp.h> #include <net/icmp.h>
#include <net/xfrm.h>
#ifdef CONFIG_SYSCTL #ifdef CONFIG_SYSCTL
#include <linux/sysctl.h> #include <linux/sysctl.h>
#endif #endif
...@@ -2599,6 +2600,13 @@ static int ip_rt_acct_read(char *buffer, char **start, off_t offset, ...@@ -2599,6 +2600,13 @@ static int ip_rt_acct_read(char *buffer, char **start, off_t offset,
#endif /* CONFIG_PROC_FS */ #endif /* CONFIG_PROC_FS */
#endif /* CONFIG_NET_CLS_ROUTE */ #endif /* CONFIG_NET_CLS_ROUTE */
int xfrm_dst_lookup(struct xfrm_dst **dst, struct flowi *fl)
{
int err = 0;
err = __ip_route_output_key((struct rtable**)dst, fl);
return err;
}
int __init ip_rt_init(void) int __init ip_rt_init(void)
{ {
int i, order, goal, rc = 0; int i, order, goal, rc = 0;
...@@ -2680,6 +2688,7 @@ int __init ip_rt_init(void) ...@@ -2680,6 +2688,7 @@ int __init ip_rt_init(void)
ip_rt_gc_interval; ip_rt_gc_interval;
add_timer(&rt_periodic_timer); add_timer(&rt_periodic_timer);
xfrm_dst_lookup_register(xfrm_dst_lookup, AF_INET);
#ifdef CONFIG_PROC_FS #ifdef CONFIG_PROC_FS
if (rt_cache_proc_init()) if (rt_cache_proc_init())
goto out_enomem; goto out_enomem;
......
...@@ -8,9 +8,11 @@ ...@@ -8,9 +8,11 @@
* Software Foundation; either version 2 of the License, or (at your option) * Software Foundation; either version 2 of the License, or (at your option)
* any later version. * any later version.
*/ */
#include <linux/config.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/pfkeyv2.h> #include <linux/pfkeyv2.h>
#include <net/xfrm.h> #include <net/xfrm.h>
#include <asm/scatterlist.h>
/* /*
* Algorithms supported by IPsec. These entries contain properties which * Algorithms supported by IPsec. These entries contain properties which
...@@ -348,3 +350,333 @@ int xfrm_count_enc_supported(void) ...@@ -348,3 +350,333 @@ int xfrm_count_enc_supported(void)
n++; n++;
return n; return n;
} }
#if defined(CONFIG_INET_AH) || defined(CONFIG_INET_AH_MODULE) || defined(CONFIG_INET6_AH) || defined(CONFIG_INET6_AH_MODULE)
void skb_ah_walk(const struct sk_buff *skb,
struct crypto_tfm *tfm, icv_update_fn_t icv_update)
{
int offset = 0;
int len = skb->len;
int start = skb->len - skb->data_len;
int i, copy = start - offset;
struct scatterlist sg;
/* Checksum header. */
if (copy > 0) {
if (copy > len)
copy = len;
sg.page = virt_to_page(skb->data + offset);
sg.offset = (unsigned long)(skb->data + offset) % PAGE_SIZE;
sg.length = copy;
icv_update(tfm, &sg, 1);
if ((len -= copy) == 0)
return;
offset += copy;
}
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
BUG_TRAP(start <= offset + len);
end = start + skb_shinfo(skb)->frags[i].size;
if ((copy = end - offset) > 0) {
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
if (copy > len)
copy = len;
sg.page = frag->page;
sg.offset = frag->page_offset + offset-start;
sg.length = copy;
icv_update(tfm, &sg, 1);
if (!(len -= copy))
return;
offset += copy;
}
start = end;
}
if (skb_shinfo(skb)->frag_list) {
struct sk_buff *list = skb_shinfo(skb)->frag_list;
for (; list; list = list->next) {
int end;
BUG_TRAP(start <= offset + len);
end = start + list->len;
if ((copy = end - offset) > 0) {
if (copy > len)
copy = len;
skb_ah_walk(list, tfm, icv_update);
if ((len -= copy) == 0)
return;
offset += copy;
}
start = end;
}
}
if (len)
BUG();
}
#endif
#if defined(CONFIG_INET_ESP) || defined(CONFIG_INET_ESP_MODULE) || defined(CONFIG_INET6_ESP) || defined(CONFIG_INET6_ESP_MODULE)
/* Move to common area: it is shared with AH. */
void skb_icv_walk(const struct sk_buff *skb, struct crypto_tfm *tfm,
int offset, int len, icv_update_fn_t icv_update)
{
int start = skb->len - skb->data_len;
int i, copy = start - offset;
struct scatterlist sg;
/* Checksum header. */
if (copy > 0) {
if (copy > len)
copy = len;
sg.page = virt_to_page(skb->data + offset);
sg.offset = (unsigned long)(skb->data + offset) % PAGE_SIZE;
sg.length = copy;
icv_update(tfm, &sg, 1);
if ((len -= copy) == 0)
return;
offset += copy;
}
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
BUG_TRAP(start <= offset + len);
end = start + skb_shinfo(skb)->frags[i].size;
if ((copy = end - offset) > 0) {
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
if (copy > len)
copy = len;
sg.page = frag->page;
sg.offset = frag->page_offset + offset-start;
sg.length = copy;
icv_update(tfm, &sg, 1);
if (!(len -= copy))
return;
offset += copy;
}
start = end;
}
if (skb_shinfo(skb)->frag_list) {
struct sk_buff *list = skb_shinfo(skb)->frag_list;
for (; list; list = list->next) {
int end;
BUG_TRAP(start <= offset + len);
end = start + list->len;
if ((copy = end - offset) > 0) {
if (copy > len)
copy = len;
skb_icv_walk(list, tfm, offset-start, copy, icv_update);
if ((len -= copy) == 0)
return;
offset += copy;
}
start = end;
}
}
if (len)
BUG();
}
/* Looking generic it is not used in another places. */
int
skb_to_sgvec(struct sk_buff *skb, struct scatterlist *sg, int offset, int len)
{
int start = skb->len - skb->data_len;
int i, copy = start - offset;
int elt = 0;
if (copy > 0) {
if (copy > len)
copy = len;
sg[elt].page = virt_to_page(skb->data + offset);
sg[elt].offset = (unsigned long)(skb->data + offset) % PAGE_SIZE;
sg[elt].length = copy;
elt++;
if ((len -= copy) == 0)
return elt;
offset += copy;
}
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
BUG_TRAP(start <= offset + len);
end = start + skb_shinfo(skb)->frags[i].size;
if ((copy = end - offset) > 0) {
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
if (copy > len)
copy = len;
sg[elt].page = frag->page;
sg[elt].offset = frag->page_offset+offset-start;
sg[elt].length = copy;
elt++;
if (!(len -= copy))
return elt;
offset += copy;
}
start = end;
}
if (skb_shinfo(skb)->frag_list) {
struct sk_buff *list = skb_shinfo(skb)->frag_list;
for (; list; list = list->next) {
int end;
BUG_TRAP(start <= offset + len);
end = start + list->len;
if ((copy = end - offset) > 0) {
if (copy > len)
copy = len;
elt += skb_to_sgvec(list, sg+elt, offset - start, copy);
if ((len -= copy) == 0)
return elt;
offset += copy;
}
start = end;
}
}
if (len)
BUG();
return elt;
}
/* Check that skb data bits are writable. If they are not, copy data
* to newly created private area. If "tailbits" is given, make sure that
* tailbits bytes beyond current end of skb are writable.
*
* Returns amount of elements of scatterlist to load for subsequent
* transformations and pointer to writable trailer skb.
*/
int skb_cow_data(struct sk_buff *skb, int tailbits, struct sk_buff **trailer)
{
int copyflag;
int elt;
struct sk_buff *skb1, **skb_p;
/* If skb is cloned or its head is paged, reallocate
* head pulling out all the pages (pages are considered not writable
* at the moment even if they are anonymous).
*/
if ((skb_cloned(skb) || skb_shinfo(skb)->nr_frags) &&
__pskb_pull_tail(skb, skb_pagelen(skb)-skb_headlen(skb)) == NULL)
return -ENOMEM;
/* Easy case. Most of packets will go this way. */
if (!skb_shinfo(skb)->frag_list) {
/* A little of trouble, not enough of space for trailer.
* This should not happen, when stack is tuned to generate
* good frames. OK, on miss we reallocate and reserve even more
* space, 128 bytes is fair. */
if (skb_tailroom(skb) < tailbits &&
pskb_expand_head(skb, 0, tailbits-skb_tailroom(skb)+128, GFP_ATOMIC))
return -ENOMEM;
/* Voila! */
*trailer = skb;
return 1;
}
/* Misery. We are in troubles, going to mincer fragments... */
elt = 1;
skb_p = &skb_shinfo(skb)->frag_list;
copyflag = 0;
while ((skb1 = *skb_p) != NULL) {
int ntail = 0;
/* The fragment is partially pulled by someone,
* this can happen on input. Copy it and everything
* after it. */
if (skb_shared(skb1))
copyflag = 1;
/* If the skb is the last, worry about trailer. */
if (skb1->next == NULL && tailbits) {
if (skb_shinfo(skb1)->nr_frags ||
skb_shinfo(skb1)->frag_list ||
skb_tailroom(skb1) < tailbits)
ntail = tailbits + 128;
}
if (copyflag ||
skb_cloned(skb1) ||
ntail ||
skb_shinfo(skb1)->nr_frags ||
skb_shinfo(skb1)->frag_list) {
struct sk_buff *skb2;
/* Fuck, we are miserable poor guys... */
if (ntail == 0)
skb2 = skb_copy(skb1, GFP_ATOMIC);
else
skb2 = skb_copy_expand(skb1,
skb_headroom(skb1),
ntail,
GFP_ATOMIC);
if (unlikely(skb2 == NULL))
return -ENOMEM;
if (skb1->sk)
skb_set_owner_w(skb, skb1->sk);
/* Looking around. Are we still alive?
* OK, link new skb, drop old one */
skb2->next = skb1->next;
*skb_p = skb2;
kfree_skb(skb1);
skb1 = skb2;
}
elt++;
*trailer = skb1;
skb_p = &skb1->next;
}
return elt;
}
void *pskb_put(struct sk_buff *skb, struct sk_buff *tail, int len)
{
if (tail != skb) {
skb->data_len += len;
skb->len += len;
}
return skb_put(tail, len);
}
#endif
/* Changes
*
* Mitsuru KANDA @USAGI : IPv6 Support
* Kazunori MIYAZAWA @USAGI :
* YOSHIFUJI Hideaki @USAGI :
* Kunihiro Ishiguro :
*
*/
#include <net/ip.h> #include <net/ip.h>
#include <net/ipv6.h>
#include <net/xfrm.h> #include <net/xfrm.h>
static kmem_cache_t *secpath_cachep; static kmem_cache_t *secpath_cachep;
...@@ -64,7 +74,7 @@ int xfrm4_rcv(struct sk_buff *skb) ...@@ -64,7 +74,7 @@ int xfrm4_rcv(struct sk_buff *skb)
if (xfrm_nr == XFRM_MAX_DEPTH) if (xfrm_nr == XFRM_MAX_DEPTH)
goto drop; goto drop;
x = xfrm_state_lookup(iph->daddr, spi, iph->protocol); x = xfrm4_state_lookup(iph->daddr, spi, iph->protocol);
if (x == NULL) if (x == NULL)
goto drop; goto drop;
...@@ -157,3 +167,288 @@ void __init xfrm_input_init(void) ...@@ -157,3 +167,288 @@ void __init xfrm_input_init(void)
if (!secpath_cachep) if (!secpath_cachep)
panic("IP: failed to allocate secpath_cache\n"); panic("IP: failed to allocate secpath_cache\n");
} }
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
/* Fetch spi and seq frpm ipsec header */
static int xfrm6_parse_spi(struct sk_buff *skb, u8 nexthdr, u32 *spi, u32 *seq)
{
int offset, offset_seq;
switch (nexthdr) {
case IPPROTO_AH:
offset = offsetof(struct ip_auth_hdr, spi);
offset_seq = offsetof(struct ip_auth_hdr, seq_no);
break;
case IPPROTO_ESP:
offset = offsetof(struct ip_esp_hdr, spi);
offset_seq = offsetof(struct ip_esp_hdr, seq_no);
break;
case IPPROTO_COMP:
if (!pskb_may_pull(skb, 4))
return -EINVAL;
*spi = *(u16*)(skb->h.raw + 2);
*seq = 0;
return 0;
default:
return 1;
}
if (!pskb_may_pull(skb, 16))
return -EINVAL;
*spi = *(u32*)(skb->h.raw + offset);
*seq = *(u32*)(skb->h.raw + offset_seq);
return 0;
}
static int zero_out_mutable_opts(struct ipv6_opt_hdr *opthdr)
{
u8 *opt = (u8 *)opthdr;
int len = ipv6_optlen(opthdr);
int off = 0;
int optlen = 0;
off += 2;
len -= 2;
while (len > 0) {
switch (opt[off]) {
case IPV6_TLV_PAD0:
optlen = 1;
break;
default:
if (len < 2)
goto bad;
optlen = opt[off+1]+2;
if (len < optlen)
goto bad;
if (opt[off] & 0x20)
memset(&opt[off+2], 0, opt[off+1]);
break;
}
off += optlen;
len -= optlen;
}
if (len == 0)
return 1;
bad:
return 0;
}
int xfrm6_clear_mutable_options(struct sk_buff *skb, u16 *nh_offset, int dir)
{
u16 offset = sizeof(struct ipv6hdr);
struct ipv6_opt_hdr *exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
unsigned int packet_len = skb->tail - skb->nh.raw;
u8 nexthdr = skb->nh.ipv6h->nexthdr;
u8 nextnexthdr = 0;
*nh_offset = ((unsigned char *)&skb->nh.ipv6h->nexthdr) - skb->nh.raw;
while (offset + 1 <= packet_len) {
switch (nexthdr) {
case NEXTHDR_HOP:
*nh_offset = offset;
offset += ipv6_optlen(exthdr);
if (!zero_out_mutable_opts(exthdr)) {
if (net_ratelimit())
printk(KERN_WARNING "overrun hopopts\n");
return 0;
}
nexthdr = exthdr->nexthdr;
exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
break;
case NEXTHDR_ROUTING:
*nh_offset = offset;
offset += ipv6_optlen(exthdr);
((struct ipv6_rt_hdr*)exthdr)->segments_left = 0;
nexthdr = exthdr->nexthdr;
exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
break;
case NEXTHDR_DEST:
*nh_offset = offset;
offset += ipv6_optlen(exthdr);
if (!zero_out_mutable_opts(exthdr)) {
if (net_ratelimit())
printk(KERN_WARNING "overrun destopt\n");
return 0;
}
nexthdr = exthdr->nexthdr;
exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
break;
case NEXTHDR_AUTH:
if (dir == XFRM_POLICY_OUT) {
memset(((struct ipv6_auth_hdr*)exthdr)->auth_data, 0,
(((struct ipv6_auth_hdr*)exthdr)->hdrlen - 1) << 2);
}
if (exthdr->nexthdr == NEXTHDR_DEST) {
offset += (((struct ipv6_auth_hdr*)exthdr)->hdrlen + 2) << 2;
exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
nextnexthdr = exthdr->nexthdr;
if (!zero_out_mutable_opts(exthdr)) {
if (net_ratelimit())
printk(KERN_WARNING "overrun destopt\n");
return 0;
}
}
return nexthdr;
default :
return nexthdr;
}
}
return nexthdr;
}
int xfrm6_rcv(struct sk_buff *skb)
{
int err;
u32 spi, seq;
struct xfrm_state *xfrm_vec[XFRM_MAX_DEPTH];
struct xfrm_state *x;
int xfrm_nr = 0;
int decaps = 0;
struct ipv6hdr *hdr = skb->nh.ipv6h;
unsigned char *tmp_hdr = NULL;
int hdr_len = 0;
u16 nh_offset = 0;
u8 nexthdr = 0;
if (hdr->nexthdr == IPPROTO_AH || hdr->nexthdr == IPPROTO_ESP) {
nh_offset = ((unsigned char*)&skb->nh.ipv6h->nexthdr) - skb->nh.raw;
hdr_len = sizeof(struct ipv6hdr);
} else {
hdr_len = skb->h.raw - skb->nh.raw;
}
tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC);
if (!tmp_hdr)
goto drop;
memcpy(tmp_hdr, skb->nh.raw, hdr_len);
nexthdr = xfrm6_clear_mutable_options(skb, &nh_offset, XFRM_POLICY_IN);
hdr->priority = 0;
hdr->flow_lbl[0] = 0;
hdr->flow_lbl[1] = 0;
hdr->flow_lbl[2] = 0;
hdr->hop_limit = 0;
if ((err = xfrm6_parse_spi(skb, nexthdr, &spi, &seq)) != 0)
goto drop;
do {
struct ipv6hdr *iph = skb->nh.ipv6h;
if (xfrm_nr == XFRM_MAX_DEPTH)
goto drop;
x = xfrm6_state_lookup(&iph->daddr, spi, nexthdr);
if (x == NULL)
goto drop;
spin_lock(&x->lock);
if (unlikely(x->km.state != XFRM_STATE_VALID))
goto drop_unlock;
if (x->props.replay_window && xfrm_replay_check(x, seq))
goto drop_unlock;
nexthdr = x->type->input(x, skb);
if (nexthdr <= 0)
goto drop_unlock;
if (x->props.replay_window)
xfrm_replay_advance(x, seq);
x->curlft.bytes += skb->len;
x->curlft.packets++;
spin_unlock(&x->lock);
xfrm_vec[xfrm_nr++] = x;
iph = skb->nh.ipv6h; /* ??? */
if (nexthdr == NEXTHDR_DEST) {
if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8) ||
!pskb_may_pull(skb, (skb->h.raw-skb->data)+((skb->h.raw[1]+1)<<3))) {
err = -EINVAL;
goto drop;
}
nexthdr = skb->h.raw[0];
nh_offset = skb->h.raw - skb->nh.raw;
skb_pull(skb, (skb->h.raw[1]+1)<<3);
skb->h.raw = skb->data;
}
if (x->props.mode) { /* XXX */
if (iph->nexthdr != IPPROTO_IPV6)
goto drop;
skb->nh.raw = skb->data;
iph = skb->nh.ipv6h;
decaps = 1;
break;
}
if ((err = xfrm6_parse_spi(skb, nexthdr, &spi, &seq)) < 0)
goto drop;
} while (!err);
memcpy(skb->nh.raw, tmp_hdr, hdr_len);
skb->nh.raw[nh_offset] = nexthdr;
skb->nh.ipv6h->payload_len = htons(hdr_len + skb->len - sizeof(struct ipv6hdr));
/* Allocate new secpath or COW existing one. */
if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) {
struct sec_path *sp;
sp = kmem_cache_alloc(secpath_cachep, SLAB_ATOMIC);
if (!sp)
goto drop;
if (skb->sp) {
memcpy(sp, skb->sp, sizeof(struct sec_path));
secpath_put(skb->sp);
} else
sp->len = 0;
atomic_set(&sp->refcnt, 1);
skb->sp = sp;
}
if (xfrm_nr + skb->sp->len > XFRM_MAX_DEPTH)
goto drop;
memcpy(skb->sp->xvec+skb->sp->len, xfrm_vec, xfrm_nr*sizeof(void*));
skb->sp->len += xfrm_nr;
if (decaps) {
if (!(skb->dev->flags&IFF_LOOPBACK)) {
dst_release(skb->dst);
skb->dst = NULL;
}
netif_rx(skb);
return 0;
} else {
return -nexthdr;
}
drop_unlock:
spin_unlock(&x->lock);
xfrm_state_put(x);
drop:
if (tmp_hdr) kfree(tmp_hdr);
while (--xfrm_nr >= 0)
xfrm_state_put(xfrm_vec[xfrm_nr]);
kfree_skb(skb);
return 0;
}
#endif /* CONFIG_IPV6 || CONFIG_IPV6_MODULE */
/* Changes
*
* Mitsuru KANDA @USAGI : IPv6 Support
* Kazunori MIYAZAWA @USAGI :
* Kunihiro Ishiguro :
*
*/
#include <linux/config.h> #include <linux/config.h>
#include <net/xfrm.h> #include <net/xfrm.h>
#include <net/ip.h> #include <net/ip.h>
#include <net/ipv6.h>
#include <net/ip6_route.h>
DECLARE_MUTEX(xfrm_cfg_sem); DECLARE_MUTEX(xfrm_cfg_sem);
...@@ -10,6 +20,11 @@ static rwlock_t xfrm_policy_lock = RW_LOCK_UNLOCKED; ...@@ -10,6 +20,11 @@ static rwlock_t xfrm_policy_lock = RW_LOCK_UNLOCKED;
struct xfrm_policy *xfrm_policy_list[XFRM_POLICY_MAX*2]; struct xfrm_policy *xfrm_policy_list[XFRM_POLICY_MAX*2];
extern struct dst_ops xfrm4_dst_ops; extern struct dst_ops xfrm4_dst_ops;
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
extern struct dst_ops xfrm6_dst_ops;
#endif
static inline int xfrm_dst_lookup(struct xfrm_dst **dst, struct flowi *fl, unsigned short family);
/* Limited flow cache. Its function now is to accelerate search for /* Limited flow cache. Its function now is to accelerate search for
* policy rules. * policy rules.
...@@ -48,6 +63,24 @@ static inline u32 flow_hash(struct flowi *fl) ...@@ -48,6 +63,24 @@ static inline u32 flow_hash(struct flowi *fl)
return hash & (FLOWCACHE_HASH_SIZE-1); return hash & (FLOWCACHE_HASH_SIZE-1);
} }
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
static inline u32 flow_hash6(struct flowi *fl)
{
u32 hash = fl->fl6_src->s6_addr32[2] ^
fl->fl6_src->s6_addr32[3] ^
fl->uli_u.ports.sport;
hash = ((hash & 0xF0F0F0F0) >> 4) | ((hash & 0x0F0F0F0F) << 4);
hash ^= fl->fl6_dst->s6_addr32[2] ^
fl->fl6_dst->s6_addr32[3] ^
fl->uli_u.ports.dport;
hash ^= (hash >> 10);
hash ^= (hash >> 20);
return hash & (FLOWCACHE_HASH_SIZE-1);
}
#endif
static int flow_lwm = 2*FLOWCACHE_HASH_SIZE; static int flow_lwm = 2*FLOWCACHE_HASH_SIZE;
static int flow_hwm = 4*FLOWCACHE_HASH_SIZE; static int flow_hwm = 4*FLOWCACHE_HASH_SIZE;
...@@ -77,13 +110,27 @@ static void flow_cache_shrink(int cpu) ...@@ -77,13 +110,27 @@ static void flow_cache_shrink(int cpu)
} }
} }
struct xfrm_policy *flow_lookup(int dir, struct flowi *fl) struct xfrm_policy *flow_lookup(int dir, struct flowi *fl,
unsigned short family)
{ {
struct xfrm_policy *pol; struct xfrm_policy *pol = NULL;
struct flow_entry *fle; struct flow_entry *fle;
u32 hash = flow_hash(fl); u32 hash;
int cpu; int cpu;
switch (family) {
case AF_INET:
hash = flow_hash(fl);
break;
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
case AF_INET6:
hash = flow_hash6(fl);
break;
#endif
default:
return NULL;
}
local_bh_disable(); local_bh_disable();
cpu = smp_processor_id(); cpu = smp_processor_id();
...@@ -101,7 +148,7 @@ struct xfrm_policy *flow_lookup(int dir, struct flowi *fl) ...@@ -101,7 +148,7 @@ struct xfrm_policy *flow_lookup(int dir, struct flowi *fl)
} }
} }
pol = xfrm_policy_lookup(dir, fl); pol = xfrm_policy_lookup(dir, fl, family);
if (fle) { if (fle) {
/* Stale flow entry found. Update it. */ /* Stale flow entry found. Update it. */
...@@ -199,6 +246,46 @@ struct xfrm_type *xfrm_get_type(u8 proto) ...@@ -199,6 +246,46 @@ struct xfrm_type *xfrm_get_type(u8 proto)
return type; return type;
} }
static xfrm_dst_lookup_t *__xfrm_dst_lookup[AF_MAX];
rwlock_t xdl_lock = RW_LOCK_UNLOCKED;
int xfrm_dst_lookup_register(xfrm_dst_lookup_t *dst_lookup,
unsigned short family)
{
int err = 0;
write_lock(&xdl_lock);
if (__xfrm_dst_lookup[family])
err = -ENOBUFS;
else {
__xfrm_dst_lookup[family] = dst_lookup;
}
write_unlock(&xdl_lock);
return err;
}
void xfrm_dst_lookup_unregister(unsigned short family)
{
write_lock(&xdl_lock);
if (__xfrm_dst_lookup[family])
__xfrm_dst_lookup[family] = 0;
write_unlock(&xdl_lock);
}
static inline int xfrm_dst_lookup(struct xfrm_dst **dst, struct flowi *fl,
unsigned short family)
{
int err = 0;
read_lock(&xdl_lock);
if (__xfrm_dst_lookup[family])
err = __xfrm_dst_lookup[family](dst, fl);
else
err = -EINVAL;
read_unlock(&xdl_lock);
return err;
}
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
static struct xfrm_type *xfrm6_type_map[256]; static struct xfrm_type *xfrm6_type_map[256];
static rwlock_t xfrm6_type_lock = RW_LOCK_UNLOCKED; static rwlock_t xfrm6_type_lock = RW_LOCK_UNLOCKED;
...@@ -506,15 +593,32 @@ int xfrm_policy_walk(int (*func)(struct xfrm_policy *, int, int, void*), ...@@ -506,15 +593,32 @@ int xfrm_policy_walk(int (*func)(struct xfrm_policy *, int, int, void*),
/* Find policy to apply to this flow. */ /* Find policy to apply to this flow. */
struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl) struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl,
unsigned short family)
{ {
struct xfrm_policy *pol; struct xfrm_policy *pol;
read_lock_bh(&xfrm_policy_lock); read_lock_bh(&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;
int match;
if (xfrm4_selector_match(sel, fl)) { if (pol->family != family)
continue;
switch (family) {
case AF_INET:
match = xfrm4_selector_match(sel, fl);
break;
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
case AF_INET6:
match = xfrm6_selector_match(sel, fl);
break;
#endif
default:
match = 0;
}
if (match) {
atomic_inc(&pol->refcnt); atomic_inc(&pol->refcnt);
break; break;
} }
...@@ -529,7 +633,21 @@ struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struct flowi ...@@ -529,7 +633,21 @@ struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struct flowi
read_lock_bh(&xfrm_policy_lock); read_lock_bh(&xfrm_policy_lock);
if ((pol = sk->policy[dir]) != NULL) { if ((pol = sk->policy[dir]) != NULL) {
if (xfrm4_selector_match(&pol->selector, fl)) int match;
switch (sk->family) {
case AF_INET:
match = xfrm4_selector_match(&pol->selector, fl);
break;
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
case AF_INET6:
match = xfrm6_selector_match(&pol->selector, fl);
break;
#endif
default:
match = 0;
}
if (match)
atomic_inc(&pol->refcnt); atomic_inc(&pol->refcnt);
else else
pol = NULL; pol = NULL;
...@@ -630,7 +748,7 @@ void __xfrm_sk_free_policy(struct xfrm_policy *pol, int dir) ...@@ -630,7 +748,7 @@ void __xfrm_sk_free_policy(struct xfrm_policy *pol, int dir)
/* Resolve list of templates for the flow, given policy. */ /* Resolve list of templates for the flow, given policy. */
static int static int
xfrm_tmpl_resolve(struct xfrm_policy *policy, struct flowi *fl, xfrm4_tmpl_resolve(struct xfrm_policy *policy, struct flowi *fl,
struct xfrm_state **xfrm) struct xfrm_state **xfrm)
{ {
int nx; int nx;
...@@ -649,7 +767,7 @@ xfrm_tmpl_resolve(struct xfrm_policy *policy, struct flowi *fl, ...@@ -649,7 +767,7 @@ xfrm_tmpl_resolve(struct xfrm_policy *policy, struct flowi *fl,
local = tmpl->saddr.xfrm4_addr; local = tmpl->saddr.xfrm4_addr;
} }
x = xfrm_state_find(remote, local, fl, tmpl, policy, &error); x = xfrm4_state_find(remote, local, fl, tmpl, policy, &error);
if (x && x->km.state == XFRM_STATE_VALID) { if (x && x->km.state == XFRM_STATE_VALID) {
xfrm[nx++] = x; xfrm[nx++] = x;
...@@ -674,6 +792,53 @@ xfrm_tmpl_resolve(struct xfrm_policy *policy, struct flowi *fl, ...@@ -674,6 +792,53 @@ xfrm_tmpl_resolve(struct xfrm_policy *policy, struct flowi *fl,
return error; return error;
} }
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
static int
xfrm6_tmpl_resolve(struct xfrm_policy *policy, struct flowi *fl,
struct xfrm_state **xfrm)
{
int nx;
int i, error;
struct in6_addr *daddr = fl->fl6_dst;
struct in6_addr *saddr = fl->fl6_src;
for (nx=0, i = 0; i < policy->xfrm_nr; i++) {
struct xfrm_state *x=NULL;
struct in6_addr *remote = daddr;
struct in6_addr *local = saddr;
struct xfrm_tmpl *tmpl = &policy->xfrm_vec[i];
if (tmpl->mode) {
remote = (struct in6_addr*)&tmpl->id.daddr;
local = (struct in6_addr*)&tmpl->saddr;
}
x = xfrm6_state_find(remote, local, fl, tmpl, policy, &error);
if (x && x->km.state == XFRM_STATE_VALID) {
xfrm[nx++] = x;
daddr = remote;
saddr = local;
continue;
}
if (x) {
error = (x->km.state == XFRM_STATE_ERROR ?
-EINVAL : -EAGAIN);
xfrm_state_put(x);
}
if (!tmpl->optional)
goto fail;
}
return nx;
fail:
for (nx--; nx>=0; nx--)
xfrm_state_put(xfrm[nx]);
return error;
}
#endif
/* Check that the bundle accepts the flow and its components are /* Check that the bundle accepts the flow and its components are
* still valid. * still valid.
*/ */
...@@ -694,6 +859,24 @@ static int xfrm_bundle_ok(struct xfrm_dst *xdst, struct flowi *fl) ...@@ -694,6 +859,24 @@ static int xfrm_bundle_ok(struct xfrm_dst *xdst, struct flowi *fl)
return 0; return 0;
} }
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
static int xfrm6_bundle_ok(struct xfrm_dst *xdst, struct flowi *fl)
{
do {
if (xdst->u.dst.ops != &xfrm6_dst_ops)
return 1;
if (!xfrm6_selector_match(&xdst->u.dst.xfrm->sel, fl))
return 0;
if (xdst->u.dst.xfrm->km.state != XFRM_STATE_VALID ||
xdst->u.dst.path->obsolete > 0)
return 0;
xdst = (struct xfrm_dst*)xdst->u.dst.child;
} while (xdst);
return 0;
}
#endif
/* Allocate chain of dst_entry's, attach known xfrm's, calculate /* Allocate chain of dst_entry's, attach known xfrm's, calculate
* all the metrics... Shortly, bundle a bundle. * all the metrics... Shortly, bundle a bundle.
...@@ -744,7 +927,7 @@ xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx, ...@@ -744,7 +927,7 @@ xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx,
.saddr = local } .saddr = local }
} }
}; };
err = __ip_route_output_key(&rt, &fl_tunnel); err = xfrm_dst_lookup((struct xfrm_dst**)&rt, &fl_tunnel, AF_INET);
if (err) if (err)
goto error; goto error;
} else { } else {
...@@ -791,6 +974,97 @@ xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx, ...@@ -791,6 +974,97 @@ xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx,
return err; return err;
} }
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
static int
xfrm6_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx,
struct flowi *fl, struct dst_entry **dst_p)
{
struct dst_entry *dst, *dst_prev;
struct rt6_info *rt0 = (struct rt6_info*)(*dst_p);
struct rt6_info *rt = rt0;
struct in6_addr *remote = fl->fl6_dst;
struct in6_addr *local = fl->fl6_src;
int i;
int err = 0;
int header_len = 0;
dst = dst_prev = NULL;
for (i = 0; i < nx; i++) {
struct dst_entry *dst1 = dst_alloc(&xfrm6_dst_ops);
if (unlikely(dst1 == NULL)) {
err = -ENOBUFS;
goto error;
}
dst1->xfrm = xfrm[i];
if (!dst)
dst = dst1;
else {
dst_prev->child = dst1;
dst1->flags |= DST_NOHASH;
dst_clone(dst1);
}
dst_prev = dst1;
if (xfrm[i]->props.mode) {
remote = (struct in6_addr*)&xfrm[i]->id.daddr;
local = (struct in6_addr*)&xfrm[i]->props.saddr;
}
header_len += xfrm[i]->props.header_len;
}
if (ipv6_addr_cmp(remote, fl->fl6_dst)) {
struct flowi fl_tunnel = { .nl_u = { .ip6_u =
{ .daddr = remote,
.saddr = local }
}
};
err = xfrm_dst_lookup((struct xfrm_dst**)&dst, &fl_tunnel, AF_INET6);
if (err)
goto error;
} else {
dst_clone(&rt->u.dst);
}
dst_prev->child = &rt->u.dst;
for (dst_prev = dst; dst_prev != &rt->u.dst; dst_prev = dst_prev->child) {
struct xfrm_dst *x = (struct xfrm_dst*)dst_prev;
x->u.rt.fl = *fl;
dst_prev->dev = rt->u.dst.dev;
if (rt->u.dst.dev)
dev_hold(rt->u.dst.dev);
dst_prev->obsolete = -1;
dst_prev->flags |= DST_HOST;
dst_prev->lastuse = jiffies;
dst_prev->header_len = header_len;
memcpy(&dst_prev->metrics, &rt->u.dst.metrics, sizeof(dst_prev->metrics));
dst_prev->path = &rt->u.dst;
/* Copy neighbout for reachability confirmation */
dst_prev->neighbour = neigh_clone(rt->u.dst.neighbour);
dst_prev->input = rt->u.dst.input;
dst_prev->output = dst_prev->xfrm->type->output;
/* Sheit... I remember I did this right. Apparently,
* it was magically lost, so this code needs audit */
x->u.rt6.rt6i_flags = rt0->rt6i_flags&(RTCF_BROADCAST|RTCF_MULTICAST|RTCF_LOCAL);
x->u.rt6.rt6i_metric = rt0->rt6i_metric;
x->u.rt6.rt6i_node = rt0->rt6i_node;
x->u.rt6.rt6i_hoplimit = rt0->rt6i_hoplimit;
x->u.rt6.rt6i_gateway = rt0->rt6i_gateway;
memcpy(&x->u.rt6.rt6i_gateway, &rt0->rt6i_gateway, sizeof(x->u.rt6.rt6i_gateway));
header_len -= x->u.dst.xfrm->props.header_len;
}
*dst_p = dst;
return 0;
error:
if (dst)
dst_free(dst);
return err;
}
#endif
/* Main function: finds/creates a bundle for given flow. /* Main function: finds/creates a bundle for given flow.
* *
* At the moment we eat a raw IP route. Mostly to speed up lookups * At the moment we eat a raw IP route. Mostly to speed up lookups
...@@ -806,9 +1080,7 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl, ...@@ -806,9 +1080,7 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
int nx = 0; int nx = 0;
int err; int err;
u32 genid; u32 genid;
u16 family = (*dst_p)->ops->family;
fl->oif = rt->u.dst.dev->ifindex;
fl->fl4_src = rt->rt_src;
restart: restart:
genid = xfrm_policy_genid; genid = xfrm_policy_genid;
...@@ -821,10 +1093,11 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl, ...@@ -821,10 +1093,11 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
if ((rt->u.dst.flags & DST_NOXFRM) || !xfrm_policy_list[XFRM_POLICY_OUT]) if ((rt->u.dst.flags & DST_NOXFRM) || !xfrm_policy_list[XFRM_POLICY_OUT])
return 0; return 0;
policy = flow_lookup(XFRM_POLICY_OUT, fl); policy = flow_lookup(XFRM_POLICY_OUT, fl, family);
}
if (!policy) if (!policy)
return 0; return 0;
}
policy->curlft.use_time = (unsigned long)xtime.tv_sec; policy->curlft.use_time = (unsigned long)xtime.tv_sec;
...@@ -846,6 +1119,9 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl, ...@@ -846,6 +1119,9 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
* LATER: help from flow cache. It is optional, this * LATER: help from flow cache. It is optional, this
* is required only for output policy. * is required only for output policy.
*/ */
if (family == AF_INET) {
fl->oif = rt->u.dst.dev->ifindex;
fl->fl4_src = rt->rt_src;
read_lock_bh(&policy->lock); read_lock_bh(&policy->lock);
for (dst = policy->bundles; dst; dst = dst->next) { for (dst = policy->bundles; dst; dst = dst->next) {
struct xfrm_dst *xdst = (struct xfrm_dst*)dst; struct xfrm_dst *xdst = (struct xfrm_dst*)dst;
...@@ -853,16 +1129,38 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl, ...@@ -853,16 +1129,38 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
xdst->u.rt.fl.fl4_src == fl->fl4_src && xdst->u.rt.fl.fl4_src == fl->fl4_src &&
xdst->u.rt.fl.oif == fl->oif && xdst->u.rt.fl.oif == fl->oif &&
xfrm_bundle_ok(xdst, fl)) { xfrm_bundle_ok(xdst, fl)) {
dst_hold(dst); dst_clone(dst);
break;
}
}
read_unlock_bh(&policy->lock);
if (dst)
break;
nx = xfrm4_tmpl_resolve(policy, fl, xfrm);
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
} else if (family == AF_INET6) {
read_lock_bh(&policy->lock);
for (dst = policy->bundles; dst; dst = dst->next) {
struct xfrm_dst *xdst = (struct xfrm_dst*)dst;
if (!ipv6_addr_cmp(&xdst->u.rt6.rt6i_dst.addr, fl->fl6_dst) &&
!ipv6_addr_cmp(&xdst->u.rt6.rt6i_src.addr, fl->fl6_src) &&
xfrm6_bundle_ok(xdst, fl)) {
dst_clone(dst);
break; break;
} }
} }
read_unlock_bh(&policy->lock); read_unlock_bh(&policy->lock);
if (dst)
break;
nx = xfrm6_tmpl_resolve(policy, fl, xfrm);
#endif
} else {
return -EINVAL;
}
if (dst) if (dst)
break; break;
nx = xfrm_tmpl_resolve(policy, fl, xfrm);
if (unlikely(nx<0)) { if (unlikely(nx<0)) {
err = nx; err = nx;
if (err == -EAGAIN) { if (err == -EAGAIN) {
...@@ -873,7 +1171,18 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl, ...@@ -873,7 +1171,18 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
__set_task_state(tsk, TASK_INTERRUPTIBLE); __set_task_state(tsk, TASK_INTERRUPTIBLE);
add_wait_queue(&km_waitq, &wait); add_wait_queue(&km_waitq, &wait);
err = xfrm_tmpl_resolve(policy, fl, xfrm); switch (family) {
case AF_INET:
err = xfrm4_tmpl_resolve(policy, fl, xfrm);
break;
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
case AF_INET6:
err = xfrm6_tmpl_resolve(policy, fl, xfrm);
break;
#endif
default:
err = -EINVAL;
}
if (err == -EAGAIN) if (err == -EAGAIN)
schedule(); schedule();
__set_task_state(tsk, TASK_RUNNING); __set_task_state(tsk, TASK_RUNNING);
...@@ -896,7 +1205,19 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl, ...@@ -896,7 +1205,19 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
} }
dst = &rt->u.dst; dst = &rt->u.dst;
switch (family) {
case AF_INET:
err = xfrm_bundle_create(policy, xfrm, nx, fl, &dst); err = xfrm_bundle_create(policy, xfrm, nx, fl, &dst);
break;
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
case AF_INET6:
err = xfrm6_bundle_create(policy, xfrm, nx, fl, &dst);
break;
#endif
default:
err = -EINVAL;
}
if (unlikely(err)) { if (unlikely(err)) {
int i; int i;
for (i=0; i<nx; i++) for (i=0; i<nx; i++)
...@@ -962,7 +1283,7 @@ xfrm_policy_ok(struct xfrm_tmpl *tmpl, struct sec_path *sp, int idx) ...@@ -962,7 +1283,7 @@ xfrm_policy_ok(struct xfrm_tmpl *tmpl, struct sec_path *sp, int idx)
} }
static void static void
_decode_session(struct sk_buff *skb, struct flowi *fl) _decode_session4(struct sk_buff *skb, struct flowi *fl)
{ {
struct iphdr *iph = skb->nh.iph; struct iphdr *iph = skb->nh.iph;
u8 *xprth = skb->nh.raw + iph->ihl*4; u8 *xprth = skb->nh.raw + iph->ihl*4;
...@@ -1008,18 +1329,109 @@ _decode_session(struct sk_buff *skb, struct flowi *fl) ...@@ -1008,18 +1329,109 @@ _decode_session(struct sk_buff *skb, struct flowi *fl)
fl->fl4_src = iph->saddr; fl->fl4_src = iph->saddr;
} }
int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb) #if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
static inline int
xfrm6_state_ok(struct xfrm_tmpl *tmpl, struct xfrm_state *x)
{
return x->id.proto == tmpl->id.proto &&
(x->id.spi == tmpl->id.spi || !tmpl->id.spi) &&
x->props.mode == tmpl->mode &&
(tmpl->aalgos & (1<<x->props.aalgo)) &&
(!x->props.mode || !ipv6_addr_any((struct in6_addr*)&x->props.saddr) ||
!ipv6_addr_cmp((struct in6_addr *)&tmpl->saddr, (struct in6_addr*)&x->props.saddr));
}
static inline int
xfrm6_policy_ok(struct xfrm_tmpl *tmpl, struct sec_path *sp, int idx)
{
for (; idx < sp->len; idx++) {
if (xfrm6_state_ok(tmpl, sp->xvec[idx]))
return ++idx;
}
return -1;
}
static inline void
_decode_session6(struct sk_buff *skb, struct flowi *fl)
{
u16 offset = sizeof(struct ipv6hdr);
struct ipv6hdr *hdr = skb->nh.ipv6h;
struct ipv6_opt_hdr *exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
u8 nexthdr = skb->nh.ipv6h->nexthdr;
fl->fl6_dst = &hdr->daddr;
fl->fl6_src = &hdr->saddr;
while (pskb_may_pull(skb, skb->nh.raw + offset + 1 - skb->data)) {
switch (nexthdr) {
case NEXTHDR_ROUTING:
case NEXTHDR_HOP:
case NEXTHDR_DEST:
offset += ipv6_optlen(exthdr);
nexthdr = exthdr->nexthdr;
exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
break;
case IPPROTO_UDP:
case IPPROTO_TCP:
case IPPROTO_SCTP:
if (pskb_may_pull(skb, skb->nh.raw + offset + 4 - skb->data)) {
u16 *ports = (u16 *)exthdr;
fl->uli_u.ports.sport = ports[0];
fl->uli_u.ports.dport = ports[1];
}
return;
/* XXX Why are there these headers? */
case IPPROTO_AH:
case IPPROTO_ESP:
default:
fl->uli_u.spi = 0;
return;
};
}
}
#endif
int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
unsigned short family)
{ {
struct xfrm_policy *pol; struct xfrm_policy *pol;
struct flowi fl; struct flowi fl;
_decode_session(skb, &fl); switch (family) {
case AF_INET:
_decode_session4(skb, &fl);
break;
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
case AF_INET6:
_decode_session6(skb, &fl);
break;
#endif
default :
return 0;
}
/* First, check used SA against their selectors. */ /* First, check used SA against their selectors. */
if (skb->sp) { if (skb->sp) {
int i; int i;
for (i=skb->sp->len-1; i>=0; i--) { for (i=skb->sp->len-1; i>=0; i--) {
if (!xfrm4_selector_match(&skb->sp->xvec[i]->sel, &fl)) int match;
switch (family) {
case AF_INET:
match = xfrm4_selector_match(&skb->sp->xvec[i]->sel, &fl);
break;
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
case AF_INET6:
match = xfrm6_selector_match(&skb->sp->xvec[i]->sel, &fl);
break;
#endif
default:
match = 0;
}
if (!match)
return 0; return 0;
} }
} }
...@@ -1029,7 +1441,7 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb) ...@@ -1029,7 +1441,7 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb)
pol = xfrm_sk_policy_lookup(sk, dir, &fl); pol = xfrm_sk_policy_lookup(sk, dir, &fl);
if (!pol) if (!pol)
pol = flow_lookup(dir, &fl); pol = flow_lookup(dir, &fl, family);
if (!pol) if (!pol)
return 1; return 1;
...@@ -1050,7 +1462,18 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb) ...@@ -1050,7 +1462,18 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb)
* are implied between each two transformations. * are implied between each two transformations.
*/ */
for (i = pol->xfrm_nr-1, k = 0; i >= 0; i--) { for (i = pol->xfrm_nr-1, k = 0; i >= 0; i--) {
switch (family) {
case AF_INET:
k = xfrm_policy_ok(pol->xfrm_vec+i, sp, k); k = xfrm_policy_ok(pol->xfrm_vec+i, sp, k);
break;
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
case AF_INET6:
k = xfrm6_policy_ok(pol->xfrm_vec+i, sp, k);
break;
#endif
default:
k = -1;
}
if (k < 0) if (k < 0)
goto reject; goto reject;
} }
...@@ -1064,18 +1487,29 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb) ...@@ -1064,18 +1487,29 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb)
return 0; return 0;
} }
int __xfrm_route_forward(struct sk_buff *skb) int __xfrm_route_forward(struct sk_buff *skb, unsigned short family)
{ {
struct flowi fl; struct flowi fl;
_decode_session(skb, &fl); switch (family) {
case AF_INET:
_decode_session4(skb, &fl);
break;
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
case AF_INET6:
_decode_session6(skb, &fl);
break;
#endif
default:
return 0;
}
return xfrm_lookup(&skb->dst, &fl, NULL, 0) == 0; return xfrm_lookup(&skb->dst, &fl, NULL, 0) == 0;
} }
/* Optimize later using cookies and generation ids. */ /* Optimize later using cookies and generation ids. */
static struct dst_entry *xfrm4_dst_check(struct dst_entry *dst, u32 cookie) static struct dst_entry *xfrm_dst_check(struct dst_entry *dst, u32 cookie)
{ {
struct dst_entry *child = dst; struct dst_entry *child = dst;
...@@ -1091,19 +1525,19 @@ static struct dst_entry *xfrm4_dst_check(struct dst_entry *dst, u32 cookie) ...@@ -1091,19 +1525,19 @@ static struct dst_entry *xfrm4_dst_check(struct dst_entry *dst, u32 cookie)
return dst; return dst;
} }
static void xfrm4_dst_destroy(struct dst_entry *dst) static void xfrm_dst_destroy(struct dst_entry *dst)
{ {
xfrm_state_put(dst->xfrm); xfrm_state_put(dst->xfrm);
dst->xfrm = NULL; dst->xfrm = NULL;
} }
static void xfrm4_link_failure(struct sk_buff *skb) static void xfrm_link_failure(struct sk_buff *skb)
{ {
/* Impossible. Such dst must be popped before reaches point of failure. */ /* Impossible. Such dst must be popped before reaches point of failure. */
return; return;
} }
static struct dst_entry *xfrm4_negative_advice(struct dst_entry *dst) static struct dst_entry *xfrm_negative_advice(struct dst_entry *dst)
{ {
if (dst) { if (dst) {
if (dst->obsolete) { if (dst->obsolete) {
...@@ -1114,8 +1548,7 @@ static struct dst_entry *xfrm4_negative_advice(struct dst_entry *dst) ...@@ -1114,8 +1548,7 @@ static struct dst_entry *xfrm4_negative_advice(struct dst_entry *dst)
return dst; return dst;
} }
static void __xfrm_garbage_collect(void)
static int xfrm4_garbage_collect(void)
{ {
int i; int i;
struct xfrm_policy *pol; struct xfrm_policy *pol;
...@@ -1145,10 +1578,22 @@ static int xfrm4_garbage_collect(void) ...@@ -1145,10 +1578,22 @@ static int xfrm4_garbage_collect(void)
gc_list = dst->next; gc_list = dst->next;
dst_free(dst); dst_free(dst);
} }
}
static inline int xfrm4_garbage_collect(void)
{
__xfrm_garbage_collect();
return (atomic_read(&xfrm4_dst_ops.entries) > xfrm4_dst_ops.gc_thresh*2); return (atomic_read(&xfrm4_dst_ops.entries) > xfrm4_dst_ops.gc_thresh*2);
} }
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
static inline int xfrm6_garbage_collect(void)
{
__xfrm_garbage_collect();
return (atomic_read(&xfrm6_dst_ops.entries) > xfrm6_dst_ops.gc_thresh*2);
}
#endif
static int bundle_depends_on(struct dst_entry *dst, struct xfrm_state *x) static int bundle_depends_on(struct dst_entry *dst, struct xfrm_state *x)
{ {
do { do {
...@@ -1203,6 +1648,18 @@ static void xfrm4_update_pmtu(struct dst_entry *dst, u32 mtu) ...@@ -1203,6 +1648,18 @@ static void xfrm4_update_pmtu(struct dst_entry *dst, u32 mtu)
path->ops->update_pmtu(path, mtu); path->ops->update_pmtu(path, mtu);
} }
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
static void xfrm6_update_pmtu(struct dst_entry *dst, u32 mtu)
{
struct dst_entry *path = dst->path;
if (mtu >= 1280 && mtu < dst_pmtu(dst))
return;
path->ops->update_pmtu(path, mtu);
}
#endif
/* Well... that's _TASK_. We need to scan through transformation /* Well... that's _TASK_. We need to scan through transformation
* list and figure out what mss tcp should generate in order to * list and figure out what mss tcp should generate in order to
* final datagram fit to mtu. Mama mia... :-) * final datagram fit to mtu. Mama mia... :-)
...@@ -1212,7 +1669,7 @@ static void xfrm4_update_pmtu(struct dst_entry *dst, u32 mtu) ...@@ -1212,7 +1669,7 @@ static void xfrm4_update_pmtu(struct dst_entry *dst, u32 mtu)
* *
* Consider this function as something like dark humour. :-) * Consider this function as something like dark humour. :-)
*/ */
static int xfrm4_get_mss(struct dst_entry *dst, u32 mtu) static int xfrm_get_mss(struct dst_entry *dst, u32 mtu)
{ {
int res = mtu - dst->header_len; int res = mtu - dst->header_len;
...@@ -1247,16 +1704,32 @@ struct dst_ops xfrm4_dst_ops = { ...@@ -1247,16 +1704,32 @@ struct dst_ops xfrm4_dst_ops = {
.family = AF_INET, .family = AF_INET,
.protocol = __constant_htons(ETH_P_IP), .protocol = __constant_htons(ETH_P_IP),
.gc = xfrm4_garbage_collect, .gc = xfrm4_garbage_collect,
.check = xfrm4_dst_check, .check = xfrm_dst_check,
.destroy = xfrm4_dst_destroy, .destroy = xfrm_dst_destroy,
.negative_advice = xfrm4_negative_advice, .negative_advice = xfrm_negative_advice,
.link_failure = xfrm4_link_failure, .link_failure = xfrm_link_failure,
.update_pmtu = xfrm4_update_pmtu, .update_pmtu = xfrm4_update_pmtu,
.get_mss = xfrm4_get_mss, .get_mss = xfrm_get_mss,
.gc_thresh = 1024, .gc_thresh = 1024,
.entry_size = sizeof(struct xfrm_dst), .entry_size = sizeof(struct xfrm_dst),
}; };
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
struct dst_ops xfrm6_dst_ops = {
.family = AF_INET6,
.protocol = __constant_htons(ETH_P_IPV6),
.gc = xfrm6_garbage_collect,
.check = xfrm_dst_check,
.destroy = xfrm_dst_destroy,
.negative_advice = xfrm_negative_advice,
.link_failure = xfrm_link_failure,
.update_pmtu = xfrm6_update_pmtu,
.get_mss = xfrm_get_mss,
.gc_thresh = 1024,
.entry_size = sizeof(struct xfrm_dst),
};
#endif /* CONFIG_IPV6 || CONFIG_IPV6_MODULE */
void __init xfrm_init(void) void __init xfrm_init(void)
{ {
xfrm4_dst_ops.kmem_cachep = kmem_cache_create("xfrm4_dst_cache", xfrm4_dst_ops.kmem_cachep = kmem_cache_create("xfrm4_dst_cache",
...@@ -1267,8 +1740,12 @@ void __init xfrm_init(void) ...@@ -1267,8 +1740,12 @@ void __init xfrm_init(void)
if (!xfrm4_dst_ops.kmem_cachep) if (!xfrm4_dst_ops.kmem_cachep)
panic("IP: failed to allocate xfrm4_dst_cache\n"); panic("IP: failed to allocate xfrm4_dst_cache\n");
flow_cache_init(); #if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
xfrm6_dst_ops.kmem_cachep = xfrm4_dst_ops.kmem_cachep;
#endif
flow_cache_init();
xfrm_state_init(); xfrm_state_init();
xfrm_input_init(); xfrm_input_init();
} }
/* Changes
*
* Mitsuru KANDA @USAGI : IPv6 Support
* Kazunori MIYAZAWA @USAGI :
* Kunihiro Ishiguro :
*
*/
#include <net/xfrm.h> #include <net/xfrm.h>
#include <linux/pfkeyv2.h> #include <linux/pfkeyv2.h>
#include <linux/ipsec.h> #include <linux/ipsec.h>
...@@ -207,7 +215,7 @@ void xfrm_state_flush(u8 proto) ...@@ -207,7 +215,7 @@ void xfrm_state_flush(u8 proto)
} }
struct xfrm_state * struct xfrm_state *
xfrm_state_find(u32 daddr, u32 saddr, struct flowi *fl, struct xfrm_tmpl *tmpl, xfrm4_state_find(u32 daddr, u32 saddr, struct flowi *fl, struct xfrm_tmpl *tmpl,
struct xfrm_policy *pol, int *err) struct xfrm_policy *pol, int *err)
{ {
unsigned h = ntohl(daddr); unsigned h = ntohl(daddr);
...@@ -290,6 +298,7 @@ xfrm_state_find(u32 daddr, u32 saddr, struct flowi *fl, struct xfrm_tmpl *tmpl, ...@@ -290,6 +298,7 @@ xfrm_state_find(u32 daddr, u32 saddr, struct flowi *fl, struct xfrm_tmpl *tmpl,
x->props.saddr.xfrm4_addr = saddr; x->props.saddr.xfrm4_addr = saddr;
x->props.mode = tmpl->mode; x->props.mode = tmpl->mode;
x->props.reqid = tmpl->reqid; x->props.reqid = tmpl->reqid;
x->props.family = AF_INET;
if (km_query(x, tmpl, pol) == 0) { if (km_query(x, tmpl, pol) == 0) {
x->km.state = XFRM_STATE_ACQ; x->km.state = XFRM_STATE_ACQ;
...@@ -318,14 +327,133 @@ xfrm_state_find(u32 daddr, u32 saddr, struct flowi *fl, struct xfrm_tmpl *tmpl, ...@@ -318,14 +327,133 @@ xfrm_state_find(u32 daddr, u32 saddr, struct flowi *fl, struct xfrm_tmpl *tmpl,
return x; return x;
} }
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
struct xfrm_state *
xfrm6_state_find(struct in6_addr *daddr, struct in6_addr *saddr, struct flowi *fl, struct xfrm_tmpl *tmpl,
struct xfrm_policy *pol, int *err)
{
unsigned h = ntohl(daddr->s6_addr32[2]^daddr->s6_addr32[3]);
struct xfrm_state *x;
int acquire_in_progress = 0;
int error = 0;
struct xfrm_state *best = NULL;
h = (h ^ (h>>16)) % XFRM_DST_HSIZE;
spin_lock_bh(&xfrm_state_lock);
list_for_each_entry(x, xfrm_state_bydst+h, bydst) {
if (x->props.family == AF_INET6&&
!ipv6_addr_cmp(daddr, (struct in6_addr *)&x->id.daddr) &&
x->props.reqid == tmpl->reqid &&
(!ipv6_addr_cmp(saddr, (struct in6_addr *)&x->props.saddr)|| ipv6_addr_any(saddr)) &&
tmpl->mode == x->props.mode &&
tmpl->id.proto == x->id.proto) {
/* Resolution logic:
1. There is a valid state with matching selector.
Done.
2. Valid state with inappropriate selector. Skip.
Entering area of "sysdeps".
3. If state is not valid, selector is temporary,
it selects only session which triggered
previous resolution. Key manager will do
something to install a state with proper
selector.
*/
if (x->km.state == XFRM_STATE_VALID) {
if (!xfrm6_selector_match(&x->sel, fl))
continue;
if (!best ||
best->km.dying > x->km.dying ||
(best->km.dying == x->km.dying &&
best->curlft.add_time < x->curlft.add_time))
best = x;
} else if (x->km.state == XFRM_STATE_ACQ) {
acquire_in_progress = 1;
} else if (x->km.state == XFRM_STATE_ERROR ||
x->km.state == XFRM_STATE_EXPIRED) {
if (xfrm6_selector_match(&x->sel, fl))
error = 1;
}
}
}
if (best) {
atomic_inc(&best->refcnt);
spin_unlock_bh(&xfrm_state_lock);
return best;
}
x = NULL;
if (!error && !acquire_in_progress &&
((x = xfrm_state_alloc()) != NULL)) {
/* Initialize temporary selector matching only
* to current session. */
memcpy(&x->sel.daddr, fl->fl6_dst, sizeof(struct in6_addr));
memcpy(&x->sel.saddr, fl->fl6_src, sizeof(struct in6_addr));
x->sel.dport = fl->uli_u.ports.dport;
x->sel.dport_mask = ~0;
x->sel.sport = fl->uli_u.ports.sport;
x->sel.sport_mask = ~0;
x->sel.prefixlen_d = 128;
x->sel.prefixlen_s = 128;
x->sel.proto = fl->proto;
x->sel.ifindex = fl->oif;
x->id = tmpl->id;
if (ipv6_addr_any((struct in6_addr*)&x->id.daddr))
memcpy(&x->id.daddr, daddr, sizeof(x->sel.daddr));
memcpy(&x->props.saddr, &tmpl->saddr, sizeof(x->props.saddr));
if (ipv6_addr_any((struct in6_addr*)&x->props.saddr))
memcpy(&x->props.saddr, &saddr, sizeof(x->sel.saddr));
x->props.mode = tmpl->mode;
x->props.reqid = tmpl->reqid;
x->props.family = AF_INET6;
if (km_query(x, tmpl, pol) == 0) {
x->km.state = XFRM_STATE_ACQ;
list_add_tail(&x->bydst, xfrm_state_bydst+h);
atomic_inc(&x->refcnt);
if (x->id.spi) {
struct in6_addr *addr = (struct in6_addr*)&x->id.daddr;
h = ntohl((addr->s6_addr32[2]^addr->s6_addr32[3])^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);
}
x->lft.hard_add_expires_seconds = ACQ_EXPIRES;
atomic_inc(&x->refcnt);
mod_timer(&x->timer, ACQ_EXPIRES*HZ);
} else {
x->km.state = XFRM_STATE_DEAD;
xfrm_state_put(x);
x = NULL;
error = 1;
}
}
spin_unlock_bh(&xfrm_state_lock);
if (!x)
*err = acquire_in_progress ? -EAGAIN :
(error ? -ESRCH : -ENOMEM);
return x;
}
#endif /* CONFIG_IPV6 || CONFIG_IPV6_MODULE */
void xfrm_state_insert(struct xfrm_state *x) void xfrm_state_insert(struct xfrm_state *x)
{ {
unsigned h = 0; unsigned h = 0;
if (x->props.family == AF_INET) switch (x->props.family) {
case AF_INET:
h = ntohl(x->id.daddr.xfrm4_addr); h = ntohl(x->id.daddr.xfrm4_addr);
else if (x->props.family == AF_INET6) break;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
case AF_INET6:
h = ntohl(x->id.daddr.a6[2]^x->id.daddr.a6[3]); h = ntohl(x->id.daddr.a6[2]^x->id.daddr.a6[3]);
break;
#endif
default:
return;
}
h = (h ^ (h>>16)) % XFRM_DST_HSIZE; h = (h ^ (h>>16)) % XFRM_DST_HSIZE;
...@@ -384,7 +512,7 @@ int xfrm_state_check_space(struct xfrm_state *x, struct sk_buff *skb) ...@@ -384,7 +512,7 @@ int xfrm_state_check_space(struct xfrm_state *x, struct sk_buff *skb)
} }
struct xfrm_state * struct xfrm_state *
xfrm_state_lookup(u32 daddr, u32 spi, u8 proto) xfrm4_state_lookup(u32 daddr, u32 spi, u8 proto)
{ {
unsigned h = ntohl(daddr^spi^proto); unsigned h = ntohl(daddr^spi^proto);
struct xfrm_state *x; struct xfrm_state *x;
...@@ -406,6 +534,31 @@ xfrm_state_lookup(u32 daddr, u32 spi, u8 proto) ...@@ -406,6 +534,31 @@ xfrm_state_lookup(u32 daddr, u32 spi, u8 proto)
return NULL; return NULL;
} }
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
struct xfrm_state *
xfrm6_state_lookup(struct in6_addr *daddr, u32 spi, u8 proto)
{
unsigned h = ntohl(daddr->s6_addr32[2]^daddr->s6_addr32[3]^spi^proto);
struct xfrm_state *x;
h = (h ^ (h>>10) ^ (h>>20)) % XFRM_DST_HSIZE;
spin_lock_bh(&xfrm_state_lock);
list_for_each_entry(x, xfrm_state_byspi+h, byspi) {
if (x->props.family == AF_INET6 &&
spi == x->id.spi &&
!ipv6_addr_cmp(daddr, (struct in6_addr *)x->id.daddr.a6) &&
proto == x->id.proto) {
atomic_inc(&x->refcnt);
spin_unlock_bh(&xfrm_state_lock);
return x;
}
}
spin_unlock_bh(&xfrm_state_lock);
return NULL;
}
#endif
struct xfrm_state * struct xfrm_state *
xfrm_find_acq(u8 mode, u16 reqid, u8 proto, u32 daddr, u32 saddr, int create) xfrm_find_acq(u8 mode, u16 reqid, u8 proto, u32 daddr, u32 saddr, int create)
{ {
...@@ -445,7 +598,59 @@ xfrm_find_acq(u8 mode, u16 reqid, u8 proto, u32 daddr, u32 saddr, int create) ...@@ -445,7 +598,59 @@ xfrm_find_acq(u8 mode, u16 reqid, u8 proto, u32 daddr, u32 saddr, int create)
x0->km.state = XFRM_STATE_ACQ; x0->km.state = XFRM_STATE_ACQ;
x0->id.daddr.xfrm4_addr = daddr; x0->id.daddr.xfrm4_addr = daddr;
x0->id.proto = proto; x0->id.proto = proto;
x0->props.mode = mode;
x0->props.reqid = reqid;
x0->props.family = AF_INET; x0->props.family = AF_INET;
x0->lft.hard_add_expires_seconds = ACQ_EXPIRES;
atomic_inc(&x0->refcnt);
mod_timer(&x0->timer, jiffies + ACQ_EXPIRES*HZ);
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;
}
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
struct xfrm_state *
xfrm6_find_acq(u8 mode, u16 reqid, u8 proto, struct in6_addr *daddr, struct in6_addr *saddr, int create)
{
struct xfrm_state *x, *x0;
unsigned h = ntohl(daddr->s6_addr32[2]^daddr->s6_addr32[3]);
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 (x->props.family == AF_INET6 &&
!ipv6_addr_cmp(daddr, (struct in6_addr *)x->id.daddr.a6) &&
mode == x->props.mode &&
proto == x->id.proto &&
!ipv6_addr_cmp(saddr, (struct in6_addr *)x->props.saddr.a6) &&
reqid == x->props.reqid &&
x->km.state == XFRM_STATE_ACQ) {
if (!x0)
x0 = x;
if (x->id.spi)
continue;
x0 = x;
break;
}
}
if (x0) {
atomic_inc(&x0->refcnt);
} else if (create && (x0 = xfrm_state_alloc()) != NULL) {
memcpy(x0->sel.daddr.a6, daddr, sizeof(struct in6_addr));
memcpy(x0->sel.saddr.a6, saddr, sizeof(struct in6_addr));
x0->sel.prefixlen_d = 128;
x0->sel.prefixlen_s = 128;
memcpy(x0->props.saddr.a6, saddr, sizeof(struct in6_addr));
x0->km.state = XFRM_STATE_ACQ;
memcpy(x0->id.daddr.a6, daddr, sizeof(struct in6_addr));
x0->id.proto = proto;
x0->props.family = AF_INET6;
x0->props.mode = mode; x0->props.mode = mode;
x0->props.reqid = reqid; x0->props.reqid = reqid;
x0->lft.hard_add_expires_seconds = ACQ_EXPIRES; x0->lft.hard_add_expires_seconds = ACQ_EXPIRES;
...@@ -458,6 +663,7 @@ xfrm_find_acq(u8 mode, u16 reqid, u8 proto, u32 daddr, u32 saddr, int create) ...@@ -458,6 +663,7 @@ xfrm_find_acq(u8 mode, u16 reqid, u8 proto, u32 daddr, u32 saddr, int create)
spin_unlock_bh(&xfrm_state_lock); spin_unlock_bh(&xfrm_state_lock);
return x0; return x0;
} }
#endif
/* Silly enough, but I'm lazy to build resolution list */ /* Silly enough, but I'm lazy to build resolution list */
...@@ -491,7 +697,18 @@ xfrm_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi) ...@@ -491,7 +697,18 @@ xfrm_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi)
return; return;
if (minspi == maxspi) { if (minspi == maxspi) {
x0 = xfrm_state_lookup(x->id.daddr.xfrm4_addr, minspi, x->id.proto); switch(x->props.family) {
case AF_INET:
x0 = xfrm4_state_lookup(x->id.daddr.xfrm4_addr, minspi, x->id.proto);
break;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
case AF_INET6:
x0 = xfrm6_state_lookup((struct in6_addr*)x->id.daddr.a6, minspi, x->id.proto);
break;
#endif
default:
x0 = NULL;
}
if (x0) { if (x0) {
xfrm_state_put(x0); xfrm_state_put(x0);
return; return;
...@@ -503,7 +720,18 @@ xfrm_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi) ...@@ -503,7 +720,18 @@ xfrm_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi)
maxspi = ntohl(maxspi); maxspi = ntohl(maxspi);
for (h=0; h<maxspi-minspi+1; h++) { for (h=0; h<maxspi-minspi+1; h++) {
spi = minspi + net_random()%(maxspi-minspi+1); spi = minspi + net_random()%(maxspi-minspi+1);
x0 = xfrm_state_lookup(x->id.daddr.xfrm4_addr, htonl(spi), x->id.proto); switch(x->props.family) {
case AF_INET:
x0 = xfrm4_state_lookup(x->id.daddr.xfrm4_addr, minspi, x->id.proto);
break;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
case AF_INET6:
x0 = xfrm6_state_lookup((struct in6_addr*)x->id.daddr.a6, minspi, x->id.proto);
break;
#endif
default:
x0 = NULL;
}
if (x0 == NULL) if (x0 == NULL)
break; break;
xfrm_state_put(x0); xfrm_state_put(x0);
...@@ -512,7 +740,18 @@ xfrm_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi) ...@@ -512,7 +740,18 @@ xfrm_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi)
} }
if (x->id.spi) { if (x->id.spi) {
spin_lock_bh(&xfrm_state_lock); spin_lock_bh(&xfrm_state_lock);
switch(x->props.family) {
case AF_INET:
h = ntohl(x->id.daddr.xfrm4_addr^x->id.spi^x->id.proto); h = ntohl(x->id.daddr.xfrm4_addr^x->id.spi^x->id.proto);
break;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
case AF_INET6:
h = ntohl(x->id.daddr.a6[2]^x->id.daddr.a6[3]^x->id.spi^x->id.proto);
break;
#endif
default:
h = 0; /* XXX */
}
h = (h ^ (h>>10) ^ (h>>20)) % XFRM_DST_HSIZE; h = (h ^ (h>>10) ^ (h>>20)) % XFRM_DST_HSIZE;
list_add(&x->byspi, xfrm_state_byspi+h); list_add(&x->byspi, xfrm_state_byspi+h);
atomic_inc(&x->refcnt); atomic_inc(&x->refcnt);
...@@ -605,14 +844,21 @@ int xfrm_check_selectors(struct xfrm_state **x, int n, struct flowi *fl) ...@@ -605,14 +844,21 @@ int xfrm_check_selectors(struct xfrm_state **x, int n, struct flowi *fl)
int i; int i;
for (i=0; i<n; i++) { for (i=0; i<n; i++) {
if (x[i]->props.family == AF_INET && int match;
!xfrm4_selector_match(&x[i]->sel, fl)) switch(x[i]->props.family) {
return -EINVAL; case AF_INET:
match = xfrm4_selector_match(&x[i]->sel, fl);
break;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
if (x[i]->props.family == AF_INET6 && case AF_INET6:
!xfrm6_selector_match(&x[i]->sel, fl)) match = xfrm6_selector_match(&x[i]->sel, fl);
return -EINVAL; break;
#endif #endif
default:
match = 0;
}
if (!match)
return -EINVAL;
} }
return 0; return 0;
} }
...@@ -722,118 +968,3 @@ void __init xfrm_state_init(void) ...@@ -722,118 +968,3 @@ void __init xfrm_state_init(void)
} }
} }
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
struct xfrm_state *
xfrm6_state_lookup(struct in6_addr *daddr, u32 spi, u8 proto)
{
unsigned h = ntohl(daddr->s6_addr32[2]^daddr->s6_addr32[3]^spi^proto);
struct xfrm_state *x;
h = (h ^ (h>>10) ^ (h>>20)) % XFRM_DST_HSIZE;
spin_lock_bh(&xfrm_state_lock);
list_for_each_entry(x, xfrm_state_byspi+h, byspi) {
if (x->props.family == AF_INET6 &&
spi == x->id.spi &&
!ipv6_addr_cmp(daddr, (struct in6_addr *)x->id.daddr.a6) &&
proto == x->id.proto) {
atomic_inc(&x->refcnt);
spin_unlock_bh(&xfrm_state_lock);
return x;
}
}
spin_unlock_bh(&xfrm_state_lock);
return NULL;
}
struct xfrm_state *
xfrm6_find_acq(u8 mode, u16 reqid, u8 proto, struct in6_addr *daddr, struct in6_addr *saddr, int create)
{
struct xfrm_state *x, *x0;
unsigned h = ntohl(daddr->s6_addr32[2]^daddr->s6_addr32[3]);
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 (x->props.family == AF_INET6 &&
!memcmp(daddr, x->id.daddr.a6, sizeof(struct in6_addr)) &&
mode == x->props.mode &&
proto == x->id.proto &&
!memcmp(saddr, x->props.saddr.a6, sizeof(struct in6_addr)) &&
reqid == x->props.reqid &&
x->km.state == XFRM_STATE_ACQ) {
if (!x0)
x0 = x;
if (x->id.spi)
continue;
x0 = x;
break;
}
}
if (x0) {
atomic_inc(&x0->refcnt);
} else if (create && (x0 = xfrm_state_alloc()) != NULL) {
memcpy(x0->sel.daddr.a6, daddr, sizeof(struct in6_addr));
memcpy(x0->sel.saddr.a6, saddr, sizeof(struct in6_addr));
x0->sel.prefixlen_d = 128;
x0->sel.prefixlen_s = 128;
memcpy(x0->props.saddr.a6, saddr, sizeof(struct in6_addr));
x0->km.state = XFRM_STATE_ACQ;
memcpy(x0->id.daddr.a6, daddr, sizeof(struct in6_addr));
x0->id.proto = proto;
x0->props.family = AF_INET6;
x0->props.mode = mode;
x0->props.reqid = reqid;
x0->lft.hard_add_expires_seconds = ACQ_EXPIRES;
atomic_inc(&x0->refcnt);
mod_timer(&x0->timer, jiffies + ACQ_EXPIRES*HZ);
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;
}
void
xfrm6_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 = xfrm6_state_lookup((struct in6_addr*)x->id.daddr.a6, 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 = xfrm6_state_lookup((struct in6_addr*)x->id.daddr.a6, 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.a6[2]^x->id.daddr.a6[3]^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);
}
}
#endif /* CONFIG_IPV6 || CONFIG_IPV6_MODULE */
...@@ -234,7 +234,7 @@ static int xfrm_add_sa(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma) ...@@ -234,7 +234,7 @@ static int xfrm_add_sa(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
switch (x->props.family) { switch (x->props.family) {
case AF_INET: case AF_INET:
x1 = xfrm_state_lookup(x->props.saddr.xfrm4_addr, x1 = xfrm4_state_lookup(x->props.saddr.xfrm4_addr,
x->id.spi, x->id.proto); x->id.spi, x->id.proto);
break; break;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
...@@ -265,7 +265,7 @@ static int xfrm_del_sa(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma) ...@@ -265,7 +265,7 @@ static int xfrm_del_sa(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
switch (p->family) { switch (p->family) {
case AF_INET: case AF_INET:
x = xfrm_state_lookup(p->saddr.xfrm4_addr, p->spi, p->proto); x = xfrm4_state_lookup(p->saddr.xfrm4_addr, p->spi, p->proto);
break; break;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
case AF_INET6: case AF_INET6:
...@@ -395,7 +395,7 @@ static int xfrm_get_sa(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma) ...@@ -395,7 +395,7 @@ static int xfrm_get_sa(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfrma)
switch (p->family) { switch (p->family) {
case AF_INET: case AF_INET:
x = xfrm_state_lookup(p->saddr.xfrm4_addr, p->spi, p->proto); x = xfrm4_state_lookup(p->saddr.xfrm4_addr, p->spi, p->proto);
break; break;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
case AF_INET6: case AF_INET6:
......
...@@ -17,5 +17,20 @@ config IPV6_PRIVACY ...@@ -17,5 +17,20 @@ config IPV6_PRIVACY
See <file:Documentation/networking/ip-sysctl.txt> for details. See <file:Documentation/networking/ip-sysctl.txt> for details.
source "net/ipv6/netfilter/Kconfig" config INET6_AH
tristate "IPv6: AH transformation"
depends on IPV6
---help---
Support for IPsec AH.
If unsure, say Y.
config INET6_ESP
tristate "IPv6: ESP transformation"
depends on IPV6
---help---
Support for IPsec ESP.
If unsure, say Y.
source "net/ipv6/netfilter/Kconfig"
...@@ -10,4 +10,6 @@ ipv6-objs := af_inet6.o ip6_output.o ip6_input.o addrconf.o sit.o \ ...@@ -10,4 +10,6 @@ ipv6-objs := af_inet6.o ip6_output.o ip6_input.o addrconf.o sit.o \
exthdrs.o sysctl_net_ipv6.o datagram.o proc.o \ exthdrs.o sysctl_net_ipv6.o datagram.o proc.o \
ip6_flowlabel.o ipv6_syms.o ip6_flowlabel.o ipv6_syms.o
obj-$(CONFIG_INET6_AH) += ah6.o
obj-$(CONFIG_INET6_ESP) += esp6.o
obj-$(CONFIG_NETFILTER) += netfilter/ obj-$(CONFIG_NETFILTER) += netfilter/
/*
* Copyright (C)2002 USAGI/WIDE Project
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Authors
*
* Mitsuru KANDA @USAGI : IPv6 Support
* Kazunori MIYAZAWA @USAGI :
* Kunihiro Ishiguro :
*
* This file is derived from net/ipv4/ah.c.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <net/ip.h>
#include <net/xfrm.h>
#include <linux/crypto.h>
#include <linux/pfkeyv2.h>
#include <net/icmp.h>
#include <net/ipv6.h>
#include <net/xfrm.h>
#include <asm/scatterlist.h>
#define AH_HLEN_NOICV 12
/* XXX no ipv6 ah specific */
#define NIP6(addr) \
ntohs((addr).s6_addr16[0]),\
ntohs((addr).s6_addr16[1]),\
ntohs((addr).s6_addr16[2]),\
ntohs((addr).s6_addr16[3]),\
ntohs((addr).s6_addr16[4]),\
ntohs((addr).s6_addr16[5]),\
ntohs((addr).s6_addr16[6]),\
ntohs((addr).s6_addr16[7])
int ah6_output(struct sk_buff *skb)
{
int err;
int hdr_len = sizeof(struct ipv6hdr);
struct dst_entry *dst = skb->dst;
struct xfrm_state *x = dst->xfrm;
struct ipv6hdr *iph = NULL;
struct ip_auth_hdr *ah;
struct ah_data *ahp;
u16 nh_offset = 0;
u8 nexthdr;
printk(KERN_DEBUG "%s\n", __FUNCTION__);
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;
if (x->props.mode) {
iph = skb->nh.ipv6h;
skb->nh.ipv6h = (struct ipv6hdr*)skb_push(skb, x->props.header_len);
skb->nh.ipv6h->version = 6;
skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
skb->nh.ipv6h->nexthdr = IPPROTO_AH;
memcpy(&skb->nh.ipv6h->saddr, &x->props.saddr, sizeof(struct in6_addr));
memcpy(&skb->nh.ipv6h->daddr, &x->id.daddr, sizeof(struct in6_addr));
ah = (struct ip_auth_hdr*)(skb->nh.ipv6h+1);
ah->nexthdr = IPPROTO_IPV6;
} else {
hdr_len = skb->h.raw - skb->nh.raw;
iph = kmalloc(hdr_len, GFP_ATOMIC);
if (!iph) {
err = -ENOMEM;
goto error;
}
memcpy(iph, skb->data, hdr_len);
skb->nh.ipv6h = (struct ipv6hdr*)skb_push(skb, x->props.header_len);
memcpy(skb->nh.ipv6h, iph, hdr_len);
nexthdr = xfrm6_clear_mutable_options(skb, &nh_offset, XFRM_POLICY_OUT);
if (nexthdr == 0)
goto error;
skb->nh.raw[nh_offset] = IPPROTO_AH;
skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
ah = (struct ip_auth_hdr*)(skb->nh.raw+hdr_len);
skb->h.raw = (unsigned char*) ah;
ah->nexthdr = nexthdr;
}
skb->nh.ipv6h->priority = 0;
skb->nh.ipv6h->flow_lbl[0] = 0;
skb->nh.ipv6h->flow_lbl[1] = 0;
skb->nh.ipv6h->flow_lbl[2] = 0;
skb->nh.ipv6h->hop_limit = 0;
ahp = x->data;
ah->hdrlen = (XFRM_ALIGN8(ahp->icv_trunc_len +
AH_HLEN_NOICV) >> 2) - 2;
ah->reserved = 0;
ah->spi = x->id.spi;
ah->seq_no = htonl(++x->replay.oseq);
ahp->icv(ahp, skb, ah->auth_data);
if (x->props.mode) {
skb->nh.ipv6h->hop_limit = iph->hop_limit;
skb->nh.ipv6h->priority = iph->priority;
skb->nh.ipv6h->flow_lbl[0] = iph->flow_lbl[0];
skb->nh.ipv6h->flow_lbl[1] = iph->flow_lbl[1];
skb->nh.ipv6h->flow_lbl[2] = iph->flow_lbl[2];
} else {
memcpy(skb->nh.ipv6h, iph, hdr_len);
skb->nh.raw[nh_offset] = IPPROTO_AH;
skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
kfree (iph);
}
skb->nh.raw = skb->data;
x->curlft.bytes += skb->len;
x->curlft.packets++;
spin_unlock_bh(&x->lock);
if ((skb->dst = dst_pop(dst)) == NULL)
goto error_nolock;
return NET_XMIT_BYPASS;
error:
spin_unlock_bh(&x->lock);
error_nolock:
kfree_skb(skb);
return err;
}
int ah6_input(struct xfrm_state *x, struct sk_buff *skb)
{
int ah_hlen;
struct ipv6hdr *iph;
struct ipv6_auth_hdr *ah;
struct ah_data *ahp;
unsigned char *tmp_hdr = NULL;
int hdr_len = skb->h.raw - skb->nh.raw;
u8 nexthdr = 0;
if (!pskb_may_pull(skb, sizeof(struct ip_auth_hdr)))
goto out;
ah = (struct ipv6_auth_hdr*)skb->data;
ahp = x->data;
ah_hlen = (ah->hdrlen + 2) << 2;
if (ah_hlen != XFRM_ALIGN8(ahp->icv_full_len + AH_HLEN_NOICV) &&
ah_hlen != XFRM_ALIGN8(ahp->icv_trunc_len + AH_HLEN_NOICV))
goto out;
if (!pskb_may_pull(skb, ah_hlen))
goto out;
/* We are going to _remove_ AH header to keep sockets happy,
* so... Later this can change. */
if (skb_cloned(skb) &&
pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
goto out;
tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC);
if (!tmp_hdr)
goto out;
memcpy(tmp_hdr, skb->nh.raw, hdr_len);
ah = (struct ipv6_auth_hdr*)skb->data;
iph = skb->nh.ipv6h;
{
u8 auth_data[ahp->icv_trunc_len];
memcpy(auth_data, ah->auth_data, ahp->icv_trunc_len);
skb_push(skb, skb->data - skb->nh.raw);
ahp->icv(ahp, skb, ah->auth_data);
if (memcmp(ah->auth_data, auth_data, ahp->icv_trunc_len)) {
if (net_ratelimit())
printk(KERN_WARNING "ipsec ah authentication error\n");
x->stats.integrity_failed++;
goto free_out;
}
}
nexthdr = ah->nexthdr;
skb->nh.raw = skb_pull(skb, (ah->hdrlen+2)<<2);
memcpy(skb->nh.raw, tmp_hdr, hdr_len);
skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
skb_pull(skb, hdr_len);
skb->h.raw = skb->data;
kfree(tmp_hdr);
return nexthdr;
free_out:
kfree(tmp_hdr);
out:
return -EINVAL;
}
void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
int type, int code, int offset, __u32 info)
{
struct ipv6hdr *iph = (struct ipv6hdr*)skb->data;
struct ip_auth_hdr *ah = (struct ip_auth_hdr*)(skb->data+offset);
struct xfrm_state *x;
if (type != ICMPV6_DEST_UNREACH ||
type != ICMPV6_PKT_TOOBIG)
return;
x = xfrm6_state_lookup(&iph->daddr, ah->spi, IPPROTO_AH);
if (!x)
return;
printk(KERN_DEBUG "pmtu discvovery on SA AH/%08x/"
"%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
ntohl(ah->spi), NIP6(iph->daddr));
xfrm_state_put(x);
}
static int ah6_init_state(struct xfrm_state *x, void *args)
{
struct ah_data *ahp = NULL;
struct xfrm_algo_desc *aalg_desc;
/* null auth can use a zero length key */
if (x->aalg->alg_key_len > 512)
goto error;
ahp = kmalloc(sizeof(*ahp), GFP_KERNEL);
if (ahp == NULL)
return -ENOMEM;
memset(ahp, 0, sizeof(*ahp));
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);
if (!ahp->tfm)
goto error;
ahp->icv = ah_hmac_digest;
/*
* Lookup the algorithm description maintained by xfrm_algo,
* verify crypto transform properties, and store information
* we need for AH processing. This lookup cannot fail here
* after a successful crypto_alloc_tfm().
*/
aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name);
BUG_ON(!aalg_desc);
if (aalg_desc->uinfo.auth.icv_fullbits/8 !=
crypto_tfm_alg_digestsize(ahp->tfm)) {
printk(KERN_INFO "AH: %s digestsize %u != %hu\n",
x->aalg->alg_name, crypto_tfm_alg_digestsize(ahp->tfm),
aalg_desc->uinfo.auth.icv_fullbits/8);
goto error;
}
ahp->icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/8;
ahp->icv_trunc_len = aalg_desc->uinfo.auth.icv_truncbits/8;
ahp->work_icv = kmalloc(ahp->icv_full_len, GFP_KERNEL);
if (!ahp->work_icv)
goto error;
x->props.header_len = XFRM_ALIGN8(ahp->icv_trunc_len + AH_HLEN_NOICV);
if (x->props.mode)
x->props.header_len += 20;
x->data = ahp;
return 0;
error:
if (ahp) {
if (ahp->work_icv)
kfree(ahp->work_icv);
if (ahp->tfm)
crypto_free_tfm(ahp->tfm);
kfree(ahp);
}
return -EINVAL;
}
static void ah6_destroy(struct xfrm_state *x)
{
struct ah_data *ahp = x->data;
if (ahp->work_icv) {
kfree(ahp->work_icv);
ahp->work_icv = NULL;
}
if (ahp->tfm) {
crypto_free_tfm(ahp->tfm);
ahp->tfm = NULL;
}
}
static struct xfrm_type ah6_type =
{
.description = "AH6",
.proto = IPPROTO_AH,
.init_state = ah6_init_state,
.destructor = ah6_destroy,
.input = ah6_input,
.output = ah6_output
};
static struct inet6_protocol ah6_protocol = {
.handler = xfrm6_rcv,
.err_handler = ah6_err,
};
int __init ah6_init(void)
{
SET_MODULE_OWNER(&ah6_type);
if (xfrm6_register_type(&ah6_type) < 0) {
printk(KERN_INFO "ipv6 ah init: can't add xfrm type\n");
return -EAGAIN;
}
if (inet6_add_protocol(&ah6_protocol, IPPROTO_AH) < 0) {
printk(KERN_INFO "ipv6 ah init: can't add protocol\n");
xfrm6_unregister_type(&ah6_type);
return -EAGAIN;
}
return 0;
}
static void __exit ah6_fini(void)
{
if (inet6_del_protocol(&ah6_protocol, IPPROTO_AH) < 0)
printk(KERN_INFO "ipv6 ah close: can't remove protocol\n");
if (xfrm6_unregister_type(&ah6_type) < 0)
printk(KERN_INFO "ipv6 ah close: can't remove xfrm type\n");
}
module_init(ah6_init);
module_exit(ah6_fini);
MODULE_LICENSE("GPL");
/*
* Copyright (C)2002 USAGI/WIDE Project
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Authors
*
* Mitsuru KANDA @USAGI : IPv6 Support
* Kazunori MIYAZAWA @USAGI :
* Kunihiro Ishiguro :
*
* This file is derived from net/ipv4/esp.c
*/
#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>
#include <net/ipv6.h>
#include <linux/icmpv6.h>
#define MAX_SG_ONSTACK 4
/* BUGS:
* - we assume replay seqno is always present.
*/
/* Move to common area: it is shared with AH. */
/* Common with AH after some work on arguments. */
/* XXX no ipv6 esp specific */
#define NIP6(addr) \
ntohs((addr).s6_addr16[0]),\
ntohs((addr).s6_addr16[1]),\
ntohs((addr).s6_addr16[2]),\
ntohs((addr).s6_addr16[3]),\
ntohs((addr).s6_addr16[4]),\
ntohs((addr).s6_addr16[5]),\
ntohs((addr).s6_addr16[6]),\
ntohs((addr).s6_addr16[7])
static int get_offset(u8 *packet, u32 packet_len, u8 *nexthdr, struct ipv6_opt_hdr **prevhdr)
{
u16 offset = sizeof(struct ipv6hdr);
struct ipv6_opt_hdr *exthdr = (struct ipv6_opt_hdr*)(packet + offset);
u8 nextnexthdr;
*nexthdr = ((struct ipv6hdr*)packet)->nexthdr;
while (offset + 1 < packet_len) {
switch (*nexthdr) {
case NEXTHDR_HOP:
case NEXTHDR_ROUTING:
offset += ipv6_optlen(exthdr);
*nexthdr = exthdr->nexthdr;
*prevhdr = exthdr;
exthdr = (struct ipv6_opt_hdr*)(packet + offset);
break;
case NEXTHDR_DEST:
nextnexthdr =
((struct ipv6_opt_hdr*)(packet + offset + ipv6_optlen(exthdr)))->nexthdr;
/* XXX We know the option is inner dest opt
with next next header check. */
if (nextnexthdr != NEXTHDR_HOP &&
nextnexthdr != NEXTHDR_ROUTING &&
nextnexthdr != NEXTHDR_DEST) {
return offset;
}
offset += ipv6_optlen(exthdr);
*nexthdr = exthdr->nexthdr;
*prevhdr = exthdr;
exthdr = (struct ipv6_opt_hdr*)(packet + offset);
break;
default :
return offset;
}
}
return offset;
}
int esp6_output(struct sk_buff *skb)
{
int err;
int hdr_len = 0;
struct dst_entry *dst = skb->dst;
struct xfrm_state *x = dst->xfrm;
struct ipv6hdr *iph = NULL, *top_iph;
struct ip_esp_hdr *esph;
struct crypto_tfm *tfm;
struct esp_data *esp;
struct sk_buff *trailer;
struct ipv6_opt_hdr *prevhdr = NULL;
int blksize;
int clen;
int alen;
int nfrags;
u8 nexthdr;
printk(KERN_DEBUG "%s\n", __FUNCTION__);
/* 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) {
hdr_len = get_offset(skb->nh.raw, skb->len, &nexthdr, &prevhdr);
iph = kmalloc(hdr_len, GFP_ATOMIC);
if (!iph) {
err = -ENOMEM;
goto error;
}
memcpy(iph, skb->nh.raw, hdr_len);
__skb_pull(skb, hdr_len);
}
/* Now skb is pure payload to encrypt */
/* Round to block size */
clen = skb->len;
esp = x->data;
alen = esp->auth.icv_trunc_len;
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+alen, &trailer)) < 0) {
if (!x->props.mode && iph) kfree(iph);
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);
if (x->props.mode) {
iph = skb->nh.ipv6h;
top_iph = (struct ipv6hdr*)skb_push(skb, x->props.header_len);
esph = (struct ip_esp_hdr*)(top_iph+1);
*(u8*)(trailer->tail - 1) = IPPROTO_IPV6;
top_iph->version = 6;
top_iph->priority = iph->priority;
top_iph->flow_lbl[0] = iph->flow_lbl[0];
top_iph->flow_lbl[1] = iph->flow_lbl[1];
top_iph->flow_lbl[2] = iph->flow_lbl[2];
top_iph->nexthdr = IPPROTO_ESP;
top_iph->payload_len = htons(skb->len + alen);
top_iph->hop_limit = iph->hop_limit;
memcpy(&top_iph->saddr, (struct in6_addr *)&x->props.saddr, sizeof(struct ipv6hdr));
memcpy(&top_iph->daddr, (struct in6_addr *)&x->id.daddr, sizeof(struct ipv6hdr));
} else {
/* XXX exthdr */
esph = (struct ip_esp_hdr*)skb_push(skb, x->props.header_len);
skb->h.raw = (unsigned char*)esph;
top_iph = (struct ipv6hdr*)skb_push(skb, hdr_len);
memcpy(top_iph, iph, hdr_len);
kfree(iph);
top_iph->payload_len = htons(skb->len + alen - sizeof(struct ipv6hdr));
if (prevhdr) {
prevhdr->nexthdr = IPPROTO_ESP;
} else {
top_iph->nexthdr = IPPROTO_ESP;
}
*(u8*)(trailer->tail - 1) = nexthdr;
}
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, sg, clen);
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.icv_full_len) {
esp->auth.icv(esp, skb, (u8*)esph-skb->data,
8+esp->conf.ivlen+clen, trailer->tail);
pskb_put(skb, trailer, alen);
}
skb->nh.raw = skb->data;
x->curlft.bytes += skb->len;
x->curlft.packets++;
spin_unlock_bh(&x->lock);
if ((skb->dst = dst_pop(dst)) == NULL)
goto error_nolock;
return NET_XMIT_BYPASS;
error:
spin_unlock_bh(&x->lock);
error_nolock:
kfree_skb(skb);
return err;
}
int esp6_input(struct xfrm_state *x, struct sk_buff *skb)
{
struct ipv6hdr *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 alen = esp->auth.icv_trunc_len;
int elen = skb->len - 8 - esp->conf.ivlen - alen;
int hdr_len = skb->h.raw - skb->nh.raw;
int nfrags;
u8 ret_nexthdr = 0;
unsigned char *tmp_hdr = NULL;
if (!pskb_may_pull(skb, sizeof(struct ip_esp_hdr)))
goto out;
if (elen <= 0 || (elen & (blksize-1)))
goto out;
tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC);
if (!tmp_hdr)
goto out;
memcpy(tmp_hdr, skb->nh.raw, hdr_len);
/* If integrity check is required, do this. */
if (esp->auth.icv_full_len) {
u8 sum[esp->auth.icv_full_len];
u8 sum1[alen];
esp->auth.icv(esp, skb, 0, skb->len-alen, sum);
if (skb_copy_bits(skb, skb->len-alen, sum1, alen))
BUG();
if (unlikely(memcmp(sum, sum1, alen))) {
x->stats.integrity_failed++;
goto out;
}
}
if ((nfrags = skb_cow_data(skb, 0, &trailer)) < 0)
goto out;
skb->ip_summed = CHECKSUM_NONE;
esph = (struct ip_esp_hdr*)skb->data;
iph = skb->nh.ipv6h;
/* 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 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, sg, elen);
if (unlikely(sg != sgbuf))
kfree(sg);
if (skb_copy_bits(skb, skb->len-alen-2, nexthdr, 2))
BUG();
padlen = nexthdr[0];
if (padlen+2 >= elen) {
if (net_ratelimit()) {
printk(KERN_WARNING "ipsec esp packet is garbage padlen=%d, elen=%d\n", padlen+2, elen);
}
goto out;
}
/* ... check padding bits here. Silly. :-) */
ret_nexthdr = nexthdr[1];
pskb_trim(skb, skb->len - alen - padlen - 2);
skb->h.raw = skb_pull(skb, 8 + esp->conf.ivlen);
skb->nh.raw += 8 + esp->conf.ivlen;
memcpy(skb->nh.raw, tmp_hdr, hdr_len);
}
kfree(tmp_hdr);
return ret_nexthdr;
out:
return -EINVAL;
}
static u32 esp6_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.icv_full_len;
}
void esp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
int type, int code, int offset, __u32 info)
{
struct ipv6hdr *iph = (struct ipv6hdr*)skb->data;
struct ip_esp_hdr *esph = (struct ip_esp_hdr*)(skb->data+offset);
struct xfrm_state *x;
if (type != ICMPV6_DEST_UNREACH ||
type != ICMPV6_PKT_TOOBIG)
return;
x = xfrm6_state_lookup(&iph->daddr, esph->spi, IPPROTO_ESP);
if (!x)
return;
printk(KERN_DEBUG "pmtu discvovery on SA ESP/%08x/"
"%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
ntohl(esph->spi), NIP6(iph->daddr));
xfrm_state_put(x);
}
void esp6_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;
}
if (esp->auth.work_icv) {
kfree(esp->auth.work_icv);
esp->auth.work_icv = NULL;
}
}
int esp6_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;
memset(esp, 0, sizeof(*esp));
if (x->aalg) {
struct xfrm_algo_desc *aalg_desc;
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.icv = esp_hmac_digest;
aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name);
BUG_ON(!aalg_desc);
if (aalg_desc->uinfo.auth.icv_fullbits/8 !=
crypto_tfm_alg_digestsize(esp->auth.tfm)) {
printk(KERN_INFO "ESP: %s digestsize %u != %hu\n",
x->aalg->alg_name,
crypto_tfm_alg_digestsize(esp->auth.tfm),
aalg_desc->uinfo.auth.icv_fullbits/8);
goto error;
}
esp->auth.icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/8;
esp->auth.icv_trunc_len = aalg_desc->uinfo.auth.icv_truncbits/8;
esp->auth.work_icv = kmalloc(esp->auth.icv_full_len, GFP_KERNEL);
if (!esp->auth.work_icv)
goto error;
}
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 += 40; /* XXX ext hdr */
x->data = esp;
return 0;
error:
if (esp) {
if (esp->auth.tfm)
crypto_free_tfm(esp->auth.tfm);
if (esp->auth.work_icv)
kfree(esp->auth.work_icv);
if (esp->conf.tfm)
crypto_free_tfm(esp->conf.tfm);
kfree(esp);
}
return -EINVAL;
}
static struct xfrm_type esp6_type =
{
.description = "ESP6",
.proto = IPPROTO_ESP,
.init_state = esp6_init_state,
.destructor = esp6_destroy,
.get_max_size = esp6_get_max_size,
.input = esp6_input,
.output = esp6_output
};
static struct inet6_protocol esp6_protocol = {
.handler = xfrm6_rcv,
.err_handler = esp6_err,
};
int __init esp6_init(void)
{
SET_MODULE_OWNER(&esp6_type);
if (xfrm6_register_type(&esp6_type) < 0) {
printk(KERN_INFO "ipv6 esp init: can't add xfrm type\n");
return -EAGAIN;
}
if (inet6_add_protocol(&esp6_protocol, IPPROTO_ESP) < 0) {
printk(KERN_INFO "ipv6 esp init: can't add protocol\n");
xfrm6_unregister_type(&esp6_type);
return -EAGAIN;
}
return 0;
}
static void __exit esp6_fini(void)
{
if (inet6_del_protocol(&esp6_protocol, IPPROTO_ESP) < 0)
printk(KERN_INFO "ipv6 esp close: can't remove protocol\n");
if (xfrm6_unregister_type(&esp6_type) < 0)
printk(KERN_INFO "ipv6 esp close: can't remove xfrm type\n");
}
module_init(esp6_init);
module_exit(esp6_fini);
MODULE_LICENSE("GPL");
...@@ -150,7 +150,8 @@ static inline int ip6_input_finish(struct sk_buff *skb) ...@@ -150,7 +150,8 @@ static inline int ip6_input_finish(struct sk_buff *skb)
It would be stupid to detect for optional headers, It would be stupid to detect for optional headers,
which are missing with probability of 200% which are missing with probability of 200%
*/ */
if (nexthdr != IPPROTO_TCP && nexthdr != IPPROTO_UDP) { if (nexthdr != IPPROTO_TCP && nexthdr != IPPROTO_UDP &&
nexthdr != NEXTHDR_AUTH && nexthdr != NEXTHDR_ESP) {
nhoff = ipv6_parse_exthdrs(&skb, nhoff); nhoff = ipv6_parse_exthdrs(&skb, nhoff);
if (nhoff < 0) if (nhoff < 0)
return 0; return 0;
......
...@@ -192,6 +192,11 @@ int ip6_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl, ...@@ -192,6 +192,11 @@ int ip6_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl,
int seg_len = skb->len; int seg_len = skb->len;
int hlimit; int hlimit;
u32 mtu; u32 mtu;
int err = 0;
if ((err = xfrm_lookup(&skb->dst, fl, sk, 0)) < 0) {
return err;
}
if (opt) { if (opt) {
int head_room; int head_room;
...@@ -576,6 +581,13 @@ int ip6_build_xmit(struct sock *sk, inet_getfrag_t getfrag, const void *data, ...@@ -576,6 +581,13 @@ int ip6_build_xmit(struct sock *sk, inet_getfrag_t getfrag, const void *data,
} }
pktlength = length; pktlength = length;
if (dst) {
if ((err = xfrm_lookup(&dst, fl, sk, 0)) < 0) {
dst_release(dst);
return -ENETUNREACH;
}
}
if (hlimit < 0) { if (hlimit < 0) {
if (ipv6_addr_is_multicast(fl->fl6_dst)) if (ipv6_addr_is_multicast(fl->fl6_dst))
hlimit = np->mcast_hops; hlimit = np->mcast_hops;
...@@ -630,10 +642,8 @@ int ip6_build_xmit(struct sock *sk, inet_getfrag_t getfrag, const void *data, ...@@ -630,10 +642,8 @@ int ip6_build_xmit(struct sock *sk, inet_getfrag_t getfrag, const void *data,
err = 0; err = 0;
if (flags&MSG_PROBE) if (flags&MSG_PROBE)
goto out; goto out;
/* alloc skb with mtu as we do in the IPv4 stack for IPsec */
skb = sock_alloc_send_skb(sk, pktlength + 15 + skb = sock_alloc_send_skb(sk, mtu, flags & MSG_DONTWAIT, &err);
dev->hard_header_len,
flags & MSG_DONTWAIT, &err);
if (skb == NULL) { if (skb == NULL) {
IP6_INC_STATS(Ip6OutDiscards); IP6_INC_STATS(Ip6OutDiscards);
...@@ -663,6 +673,8 @@ int ip6_build_xmit(struct sock *sk, inet_getfrag_t getfrag, const void *data, ...@@ -663,6 +673,8 @@ int ip6_build_xmit(struct sock *sk, inet_getfrag_t getfrag, const void *data,
err = getfrag(data, &hdr->saddr, err = getfrag(data, &hdr->saddr,
((char *) hdr) + (pktlength - length), ((char *) hdr) + (pktlength - length),
0, length); 0, length);
if (!opt || !opt->dst1opt)
skb->h.raw = ((char *) hdr) + (pktlength - length);
if (!err) { if (!err) {
IP6_INC_STATS(Ip6OutRequests); IP6_INC_STATS(Ip6OutRequests);
......
...@@ -71,6 +71,7 @@ ...@@ -71,6 +71,7 @@
#include <net/addrconf.h> #include <net/addrconf.h>
#include <net/icmp.h> #include <net/icmp.h>
#include <net/flow.h>
#include <net/checksum.h> #include <net/checksum.h>
#include <linux/proc_fs.h> #include <linux/proc_fs.h>
...@@ -335,8 +336,6 @@ ndisc_build_ll_hdr(struct sk_buff *skb, struct net_device *dev, ...@@ -335,8 +336,6 @@ ndisc_build_ll_hdr(struct sk_buff *skb, struct net_device *dev,
unsigned char ha[MAX_ADDR_LEN]; unsigned char ha[MAX_ADDR_LEN];
unsigned char *h_dest = NULL; unsigned char *h_dest = NULL;
skb_reserve(skb, (dev->hard_header_len + 15) & ~15);
if (dev->hard_header) { if (dev->hard_header) {
if (ipv6_addr_type(daddr) & IPV6_ADDR_MULTICAST) { if (ipv6_addr_type(daddr) & IPV6_ADDR_MULTICAST) {
ndisc_mc_map(daddr, ha, dev, 1); ndisc_mc_map(daddr, ha, dev, 1);
...@@ -373,10 +372,50 @@ ndisc_build_ll_hdr(struct sk_buff *skb, struct net_device *dev, ...@@ -373,10 +372,50 @@ ndisc_build_ll_hdr(struct sk_buff *skb, struct net_device *dev,
* Send a Neighbour Advertisement * Send a Neighbour Advertisement
*/ */
int ndisc_output(struct sk_buff *skb)
{
if (skb) {
struct neighbour *neigh = (skb->dst ? skb->dst->neighbour : NULL);
if (ndisc_build_ll_hdr(skb, skb->dev, &skb->nh.ipv6h->daddr, neigh, skb->len) == 0) {
kfree_skb(skb);
return -EINVAL;
}
dev_queue_xmit(skb);
return 0;
}
return -EINVAL;
}
static inline void ndisc_rt_init(struct rt6_info *rt, struct net_device *dev,
struct neighbour *neigh)
{
rt->rt6i_dev = dev;
rt->rt6i_nexthop = neigh;
rt->rt6i_expires = 0;
rt->rt6i_flags = RTF_LOCAL;
rt->rt6i_metric = 0;
rt->rt6i_hoplimit = 255;
rt->u.dst.output = ndisc_output;
}
static inline void ndisc_flow_init(struct flowi *fl, u8 type,
struct in6_addr *saddr, struct in6_addr *daddr)
{
memset(fl, 0, sizeof(*fl));
fl->fl6_src = saddr;
fl->fl6_dst = daddr;
fl->proto = IPPROTO_ICMPV6;
fl->uli_u.icmpt.type = type;
fl->uli_u.icmpt.code = 0;
}
static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh, static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
struct in6_addr *daddr, struct in6_addr *solicited_addr, struct in6_addr *daddr, struct in6_addr *solicited_addr,
int router, int solicited, int override, int inc_opt) int router, int solicited, int override, int inc_opt)
{ {
struct flowi fl;
struct rt6_info *rt = NULL;
struct dst_entry* dst;
struct sock *sk = ndisc_socket->sk; struct sock *sk = ndisc_socket->sk;
struct nd_msg *msg; struct nd_msg *msg;
int len; int len;
...@@ -385,6 +424,22 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh, ...@@ -385,6 +424,22 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr); len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr);
rt = ndisc_get_dummy_rt();
if (!rt)
return;
ndisc_flow_init(&fl, NDISC_NEIGHBOUR_ADVERTISEMENT, solicited_addr, daddr);
ndisc_rt_init(rt, dev, neigh);
dst = (struct dst_entry*)rt;
dst_clone(dst);
err = xfrm_lookup(&dst, &fl, NULL, 0);
if (err < 0) {
dst_release(dst);
return;
}
if (inc_opt) { if (inc_opt) {
if (dev->addr_len) if (dev->addr_len)
len += NDISC_OPT_SPACE(dev->addr_len); len += NDISC_OPT_SPACE(dev->addr_len);
...@@ -400,14 +455,10 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh, ...@@ -400,14 +455,10 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
return; return;
} }
if (ndisc_build_ll_hdr(skb, dev, daddr, neigh, len) == 0) { skb_reserve(skb, (dev->hard_header_len + 15) & ~15);
kfree_skb(skb);
return;
}
ip6_nd_hdr(sk, skb, dev, solicited_addr, daddr, IPPROTO_ICMPV6, len); ip6_nd_hdr(sk, skb, dev, solicited_addr, daddr, IPPROTO_ICMPV6, len);
msg = (struct nd_msg *) skb_put(skb, len); skb->h.raw = (unsigned char*) msg = (struct nd_msg *) skb_put(skb, len);
msg->icmph.icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT; msg->icmph.icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT;
msg->icmph.icmp6_code = 0; msg->icmph.icmp6_code = 0;
...@@ -430,7 +481,9 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh, ...@@ -430,7 +481,9 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
csum_partial((__u8 *) msg, csum_partial((__u8 *) msg,
len, 0)); len, 0));
dev_queue_xmit(skb); dst_clone(dst);
skb->dst = dst;
dst_output(skb);
ICMP6_INC_STATS(Icmp6OutNeighborAdvertisements); ICMP6_INC_STATS(Icmp6OutNeighborAdvertisements);
ICMP6_INC_STATS(Icmp6OutMsgs); ICMP6_INC_STATS(Icmp6OutMsgs);
...@@ -440,6 +493,9 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh, ...@@ -440,6 +493,9 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh,
struct in6_addr *solicit, struct in6_addr *solicit,
struct in6_addr *daddr, struct in6_addr *saddr) struct in6_addr *daddr, struct in6_addr *saddr)
{ {
struct flowi fl;
struct rt6_info *rt = NULL;
struct dst_entry* dst;
struct sock *sk = ndisc_socket->sk; struct sock *sk = ndisc_socket->sk;
struct sk_buff *skb; struct sk_buff *skb;
struct nd_msg *msg; struct nd_msg *msg;
...@@ -454,6 +510,22 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh, ...@@ -454,6 +510,22 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh,
saddr = &addr_buf; saddr = &addr_buf;
} }
rt = ndisc_get_dummy_rt();
if (!rt)
return;
ndisc_flow_init(&fl, NDISC_NEIGHBOUR_SOLICITATION, saddr, daddr);
ndisc_rt_init(rt, dev, neigh);
dst = (struct dst_entry*)rt;
dst_clone(dst);
err = xfrm_lookup(&dst, &fl, NULL, 0);
if (err < 0) {
dst_release(dst);
return;
}
len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr); len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr);
send_llinfo = dev->addr_len && ipv6_addr_type(saddr) != IPV6_ADDR_ANY; send_llinfo = dev->addr_len && ipv6_addr_type(saddr) != IPV6_ADDR_ANY;
if (send_llinfo) if (send_llinfo)
...@@ -466,14 +538,10 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh, ...@@ -466,14 +538,10 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh,
return; return;
} }
if (ndisc_build_ll_hdr(skb, dev, daddr, neigh, len) == 0) { skb_reserve(skb, (dev->hard_header_len + 15) & ~15);
kfree_skb(skb);
return;
}
ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len); ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len);
msg = (struct nd_msg *)skb_put(skb, len); skb->h.raw = (unsigned char*) msg = (struct nd_msg *)skb_put(skb, len);
msg->icmph.icmp6_type = NDISC_NEIGHBOUR_SOLICITATION; msg->icmph.icmp6_type = NDISC_NEIGHBOUR_SOLICITATION;
msg->icmph.icmp6_code = 0; msg->icmph.icmp6_code = 0;
msg->icmph.icmp6_cksum = 0; msg->icmph.icmp6_cksum = 0;
...@@ -492,7 +560,9 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh, ...@@ -492,7 +560,9 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh,
csum_partial((__u8 *) msg, csum_partial((__u8 *) msg,
len, 0)); len, 0));
/* send it! */ /* send it! */
dev_queue_xmit(skb); dst_clone(dst);
skb->dst = dst;
dst_output(skb);
ICMP6_INC_STATS(Icmp6OutNeighborSolicits); ICMP6_INC_STATS(Icmp6OutNeighborSolicits);
ICMP6_INC_STATS(Icmp6OutMsgs); ICMP6_INC_STATS(Icmp6OutMsgs);
...@@ -501,6 +571,9 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh, ...@@ -501,6 +571,9 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh,
void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr, void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr,
struct in6_addr *daddr) struct in6_addr *daddr)
{ {
struct flowi fl;
struct rt6_info *rt = NULL;
struct dst_entry* dst;
struct sock *sk = ndisc_socket->sk; struct sock *sk = ndisc_socket->sk;
struct sk_buff *skb; struct sk_buff *skb;
struct icmp6hdr *hdr; struct icmp6hdr *hdr;
...@@ -508,6 +581,22 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr, ...@@ -508,6 +581,22 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr,
int len; int len;
int err; int err;
rt = ndisc_get_dummy_rt();
if (!rt)
return;
ndisc_flow_init(&fl, NDISC_ROUTER_SOLICITATION, saddr, daddr);
ndisc_rt_init(rt, dev, NULL);
dst = (struct dst_entry*)rt;
dst_clone(dst);
err = xfrm_lookup(&dst, &fl, NULL, 0);
if (err < 0) {
dst_release(dst);
return;
}
len = sizeof(struct icmp6hdr); len = sizeof(struct icmp6hdr);
if (dev->addr_len) if (dev->addr_len)
len += NDISC_OPT_SPACE(dev->addr_len); len += NDISC_OPT_SPACE(dev->addr_len);
...@@ -519,14 +608,10 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr, ...@@ -519,14 +608,10 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr,
return; return;
} }
if (ndisc_build_ll_hdr(skb, dev, daddr, NULL, len) == 0) { skb_reserve(skb, (dev->hard_header_len + 15) & ~15);
kfree_skb(skb);
return;
}
ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len); ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len);
hdr = (struct icmp6hdr *) skb_put(skb, len); skb->h.raw = (unsigned char*) hdr = (struct icmp6hdr *) skb_put(skb, len);
hdr->icmp6_type = NDISC_ROUTER_SOLICITATION; hdr->icmp6_type = NDISC_ROUTER_SOLICITATION;
hdr->icmp6_code = 0; hdr->icmp6_code = 0;
hdr->icmp6_cksum = 0; hdr->icmp6_cksum = 0;
...@@ -543,7 +628,9 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr, ...@@ -543,7 +628,9 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr,
csum_partial((__u8 *) hdr, len, 0)); csum_partial((__u8 *) hdr, len, 0));
/* send it! */ /* send it! */
dev_queue_xmit(skb); dst_clone(dst);
skb->dst = dst;
dst_output(skb);
ICMP6_INC_STATS(Icmp6OutRouterSolicits); ICMP6_INC_STATS(Icmp6OutRouterSolicits);
ICMP6_INC_STATS(Icmp6OutMsgs); ICMP6_INC_STATS(Icmp6OutMsgs);
...@@ -1125,6 +1212,8 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, ...@@ -1125,6 +1212,8 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
struct in6_addr *addrp; struct in6_addr *addrp;
struct net_device *dev; struct net_device *dev;
struct rt6_info *rt; struct rt6_info *rt;
struct dst_entry *dst;
struct flowi fl;
u8 *opt; u8 *opt;
int rd_len; int rd_len;
int err; int err;
...@@ -1136,6 +1225,22 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, ...@@ -1136,6 +1225,22 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
if (rt == NULL) if (rt == NULL)
return; return;
dst = (struct dst_entry*)rt;
if (ipv6_get_lladdr(dev, &saddr_buf)) {
ND_PRINTK1("redirect: no link_local addr for dev\n");
return;
}
ndisc_flow_init(&fl, NDISC_REDIRECT, &saddr_buf, &skb->nh.ipv6h->saddr);
dst_clone(dst);
err = xfrm_lookup(&dst, &fl, NULL, 0);
if (err) {
dst_release(dst);
return;
}
if (rt->rt6i_flags & RTF_GATEWAY) { if (rt->rt6i_flags & RTF_GATEWAY) {
ND_PRINTK1("ndisc_send_redirect: not a neighbour\n"); ND_PRINTK1("ndisc_send_redirect: not a neighbour\n");
dst_release(&rt->u.dst); dst_release(&rt->u.dst);
...@@ -1164,11 +1269,6 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, ...@@ -1164,11 +1269,6 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
rd_len &= ~0x7; rd_len &= ~0x7;
len += rd_len; len += rd_len;
if (ipv6_get_lladdr(dev, &saddr_buf)) {
ND_PRINTK1("redirect: no link_local addr for dev\n");
return;
}
buff = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15, buff = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15,
0, &err); 0, &err);
if (buff == NULL) { if (buff == NULL) {
...@@ -1178,15 +1278,11 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, ...@@ -1178,15 +1278,11 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
hlen = 0; hlen = 0;
if (ndisc_build_ll_hdr(buff, dev, &skb->nh.ipv6h->saddr, NULL, len) == 0) { skb_reserve(skb, (dev->hard_header_len + 15) & ~15);
kfree_skb(buff);
return;
}
ip6_nd_hdr(sk, buff, dev, &saddr_buf, &skb->nh.ipv6h->saddr, ip6_nd_hdr(sk, buff, dev, &saddr_buf, &skb->nh.ipv6h->saddr,
IPPROTO_ICMPV6, len); IPPROTO_ICMPV6, len);
icmph = (struct icmp6hdr *) skb_put(buff, len); skb->h.raw = (unsigned char*) icmph = (struct icmp6hdr *) skb_put(buff, len);
memset(icmph, 0, sizeof(struct icmp6hdr)); memset(icmph, 0, sizeof(struct icmp6hdr));
icmph->icmp6_type = NDISC_REDIRECT; icmph->icmp6_type = NDISC_REDIRECT;
...@@ -1224,7 +1320,8 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, ...@@ -1224,7 +1320,8 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
len, IPPROTO_ICMPV6, len, IPPROTO_ICMPV6,
csum_partial((u8 *) icmph, len, 0)); csum_partial((u8 *) icmph, len, 0));
dev_queue_xmit(buff); skb->dst = dst;
dst_output(skb);
ICMP6_INC_STATS(Icmp6OutRedirects); ICMP6_INC_STATS(Icmp6OutRedirects);
ICMP6_INC_STATS(Icmp6OutMsgs); ICMP6_INC_STATS(Icmp6OutMsgs);
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
#include <net/inet_common.h> #include <net/inet_common.h>
#include <net/rawv6.h> #include <net/rawv6.h>
#include <net/xfrm.h>
struct sock *raw_v6_htable[RAWV6_HTABLE_SIZE]; struct sock *raw_v6_htable[RAWV6_HTABLE_SIZE];
rwlock_t raw_v6_lock = RW_LOCK_UNLOCKED; rwlock_t raw_v6_lock = RW_LOCK_UNLOCKED;
...@@ -304,6 +305,11 @@ int rawv6_rcv(struct sock *sk, struct sk_buff *skb) ...@@ -304,6 +305,11 @@ int rawv6_rcv(struct sock *sk, struct sk_buff *skb)
struct inet_opt *inet = inet_sk(sk); struct inet_opt *inet = inet_sk(sk);
struct raw6_opt *raw_opt = raw6_sk(sk); struct raw6_opt *raw_opt = raw6_sk(sk);
if (!xfrm6_policy_check(sk, XFRM_POLICY_IN, skb)) {
kfree_skb(skb);
return NET_RX_DROP;
}
if (!raw_opt->checksum) if (!raw_opt->checksum)
skb->ip_summed = CHECKSUM_UNNECESSARY; skb->ip_summed = CHECKSUM_UNNECESSARY;
......
...@@ -49,6 +49,8 @@ ...@@ -49,6 +49,8 @@
#include <net/addrconf.h> #include <net/addrconf.h>
#include <net/tcp.h> #include <net/tcp.h>
#include <linux/rtnetlink.h> #include <linux/rtnetlink.h>
#include <net/dst.h>
#include <net/xfrm.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
...@@ -128,6 +130,12 @@ struct fib6_node ip6_routing_table = { ...@@ -128,6 +130,12 @@ struct fib6_node ip6_routing_table = {
rwlock_t rt6_lock = RW_LOCK_UNLOCKED; rwlock_t rt6_lock = RW_LOCK_UNLOCKED;
/* Dummy rt for ndisc */
struct rt6_info *ndisc_get_dummy_rt()
{
return dst_alloc(&ip6_dst_ops);
}
/* /*
* Route lookup. Any rt6_lock is implied. * Route lookup. Any rt6_lock is implied.
*/ */
...@@ -1859,6 +1867,14 @@ ctl_table ipv6_route_table[] = { ...@@ -1859,6 +1867,14 @@ ctl_table ipv6_route_table[] = {
#endif #endif
int xfrm6_dst_lookup(struct xfrm_dst **dst, struct flowi *fl)
{
int err = 0;
*dst = (struct xfrm_dst*)ip6_route_output(NULL, fl);
if (!*dst)
err = -ENETUNREACH;
return err;
}
void __init ip6_route_init(void) void __init ip6_route_init(void)
{ {
...@@ -1867,6 +1883,7 @@ void __init ip6_route_init(void) ...@@ -1867,6 +1883,7 @@ void __init ip6_route_init(void)
0, SLAB_HWCACHE_ALIGN, 0, SLAB_HWCACHE_ALIGN,
NULL, NULL); NULL, NULL);
fib6_init(); fib6_init();
xfrm_dst_lookup_register(xfrm6_dst_lookup, AF_INET6);
#ifdef CONFIG_PROC_FS #ifdef CONFIG_PROC_FS
proc_net_create("ipv6_route", 0, rt6_proc_info); proc_net_create("ipv6_route", 0, rt6_proc_info);
proc_net_create("rt6_stats", 0, rt6_proc_stats); proc_net_create("rt6_stats", 0, rt6_proc_stats);
...@@ -1880,7 +1897,7 @@ void ip6_route_cleanup(void) ...@@ -1880,7 +1897,7 @@ void ip6_route_cleanup(void)
proc_net_remove("ipv6_route"); proc_net_remove("ipv6_route");
proc_net_remove("rt6_stats"); proc_net_remove("rt6_stats");
#endif #endif
xfrm_dst_lookup_unregister(AF_INET6);
rt6_ifdown(NULL); rt6_ifdown(NULL);
fib6_gc_cleanup(); fib6_gc_cleanup();
} }
......
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
#include <net/ip6_route.h> #include <net/ip6_route.h>
#include <net/inet_ecn.h> #include <net/inet_ecn.h>
#include <net/protocol.h> #include <net/protocol.h>
#include <net/xfrm.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
...@@ -677,6 +678,9 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, ...@@ -677,6 +678,9 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
fl.nl_u.ip6_u.daddr = rt0->addr; fl.nl_u.ip6_u.daddr = rt0->addr;
} }
if (!fl.fl6_src)
fl.fl6_src = &np->saddr;
dst = ip6_route_output(sk, &fl); dst = ip6_route_output(sk, &fl);
if ((err = dst->error) != 0) { if ((err = dst->error) != 0) {
...@@ -1637,6 +1641,9 @@ static int tcp_v6_rcv(struct sk_buff *skb) ...@@ -1637,6 +1641,9 @@ static int tcp_v6_rcv(struct sk_buff *skb)
if (sk_filter(sk, skb, 0)) if (sk_filter(sk, skb, 0))
goto discard_and_relse; goto discard_and_relse;
if (!xfrm6_policy_check(sk, XFRM_POLICY_IN, skb))
goto discard_it;
skb->dev = NULL; skb->dev = NULL;
bh_lock_sock(sk); bh_lock_sock(sk);
...@@ -1652,6 +1659,9 @@ static int tcp_v6_rcv(struct sk_buff *skb) ...@@ -1652,6 +1659,9 @@ static int tcp_v6_rcv(struct sk_buff *skb)
return ret; return ret;
no_tcp_socket: no_tcp_socket:
if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb))
goto discard_and_relse;
if (skb->len < (th->doff<<2) || tcp_checksum_complete(skb)) { if (skb->len < (th->doff<<2) || tcp_checksum_complete(skb)) {
bad_packet: bad_packet:
TCP_INC_STATS_BH(TcpInErrs); TCP_INC_STATS_BH(TcpInErrs);
...@@ -1673,6 +1683,9 @@ static int tcp_v6_rcv(struct sk_buff *skb) ...@@ -1673,6 +1683,9 @@ static int tcp_v6_rcv(struct sk_buff *skb)
goto discard_it; goto discard_it;
do_time_wait: do_time_wait:
if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb))
goto discard_and_relse;
if (skb->len < (th->doff<<2) || tcp_checksum_complete(skb)) { if (skb->len < (th->doff<<2) || tcp_checksum_complete(skb)) {
TCP_INC_STATS_BH(TcpInErrs); TCP_INC_STATS_BH(TcpInErrs);
sock_put(sk); sock_put(sk);
......
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
#include <net/inet_common.h> #include <net/inet_common.h>
#include <net/checksum.h> #include <net/checksum.h>
#include <net/xfrm.h>
DEFINE_SNMP_STAT(struct udp_mib, udp_stats_in6); DEFINE_SNMP_STAT(struct udp_mib, udp_stats_in6);
...@@ -541,6 +542,11 @@ static void udpv6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -541,6 +542,11 @@ static void udpv6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
static inline int udpv6_queue_rcv_skb(struct sock * sk, struct sk_buff *skb) static inline int udpv6_queue_rcv_skb(struct sock * sk, struct sk_buff *skb)
{ {
if (!xfrm6_policy_check(sk, XFRM_POLICY_IN, skb)) {
kfree_skb(skb);
return -1;
}
#if defined(CONFIG_FILTER) #if defined(CONFIG_FILTER)
if (sk->filter && skb->ip_summed != CHECKSUM_UNNECESSARY) { if (sk->filter && skb->ip_summed != CHECKSUM_UNNECESSARY) {
if ((unsigned short)csum_fold(skb_checksum(skb, 0, skb->len, skb->csum))) { if ((unsigned short)csum_fold(skb_checksum(skb, 0, skb->len, skb->csum))) {
...@@ -646,6 +652,9 @@ static int udpv6_rcv(struct sk_buff *skb) ...@@ -646,6 +652,9 @@ static int udpv6_rcv(struct sk_buff *skb)
if (!pskb_may_pull(skb, sizeof(struct udphdr))) if (!pskb_may_pull(skb, sizeof(struct udphdr)))
goto short_packet; goto short_packet;
if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb))
goto discard;
saddr = &skb->nh.ipv6h->saddr; saddr = &skb->nh.ipv6h->saddr;
daddr = &skb->nh.ipv6h->daddr; daddr = &skb->nh.ipv6h->daddr;
uh = skb->h.uh; uh = skb->h.uh;
......
...@@ -550,7 +550,7 @@ static struct xfrm_state *pfkey_xfrm_state_lookup(struct sadb_msg *hdr, void ** ...@@ -550,7 +550,7 @@ static struct xfrm_state *pfkey_xfrm_state_lookup(struct sadb_msg *hdr, void **
switch (((struct sockaddr *)(addr + 1))->sa_family) { switch (((struct sockaddr *)(addr + 1))->sa_family) {
case AF_INET: case AF_INET:
x = xfrm_state_lookup(((struct sockaddr_in *)(addr + 1))->sin_addr.s_addr, x = xfrm4_state_lookup(((struct sockaddr_in *)(addr + 1))->sin_addr.s_addr,
sa->sadb_sa_spi, proto); sa->sadb_sa_spi, proto);
break; break;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
...@@ -1097,18 +1097,7 @@ static int pfkey_getspi(struct sock *sk, struct sk_buff *skb, struct sadb_msg *h ...@@ -1097,18 +1097,7 @@ static int pfkey_getspi(struct sock *sk, struct sk_buff *skb, struct sadb_msg *h
min_spi = htonl(0x100); min_spi = htonl(0x100);
max_spi = htonl(0x0fffffff); max_spi = htonl(0x0fffffff);
} }
switch (x->props.family) {
case AF_INET:
xfrm_alloc_spi(x, min_spi, max_spi); xfrm_alloc_spi(x, min_spi, max_spi);
break;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
case AF_INET6:
xfrm6_alloc_spi(x, min_spi, max_spi);
break;
#endif
default:
break;
}
if (x->id.spi) if (x->id.spi)
resp_skb = pfkey_xfrm_state2msg(x, 0, 3); resp_skb = pfkey_xfrm_state2msg(x, 0, 3);
} }
......
...@@ -296,11 +296,11 @@ EXPORT_SYMBOL(__xfrm_policy_check); ...@@ -296,11 +296,11 @@ EXPORT_SYMBOL(__xfrm_policy_check);
EXPORT_SYMBOL(__xfrm_route_forward); EXPORT_SYMBOL(__xfrm_route_forward);
EXPORT_SYMBOL(xfrm_state_alloc); EXPORT_SYMBOL(xfrm_state_alloc);
EXPORT_SYMBOL(__xfrm_state_destroy); EXPORT_SYMBOL(__xfrm_state_destroy);
EXPORT_SYMBOL(xfrm_state_find); EXPORT_SYMBOL(xfrm4_state_find);
EXPORT_SYMBOL(xfrm_state_insert); EXPORT_SYMBOL(xfrm_state_insert);
EXPORT_SYMBOL(xfrm_state_check_expire); EXPORT_SYMBOL(xfrm_state_check_expire);
EXPORT_SYMBOL(xfrm_state_check_space); EXPORT_SYMBOL(xfrm_state_check_space);
EXPORT_SYMBOL(xfrm_state_lookup); EXPORT_SYMBOL(xfrm4_state_lookup);
EXPORT_SYMBOL(xfrm_replay_check); EXPORT_SYMBOL(xfrm_replay_check);
EXPORT_SYMBOL(xfrm_replay_advance); EXPORT_SYMBOL(xfrm_replay_advance);
EXPORT_SYMBOL(xfrm_check_selectors); EXPORT_SYMBOL(xfrm_check_selectors);
...@@ -324,13 +324,17 @@ EXPORT_SYMBOL(xfrm_policy_walk); ...@@ -324,13 +324,17 @@ EXPORT_SYMBOL(xfrm_policy_walk);
EXPORT_SYMBOL(xfrm_policy_flush); EXPORT_SYMBOL(xfrm_policy_flush);
EXPORT_SYMBOL(xfrm_policy_byid); EXPORT_SYMBOL(xfrm_policy_byid);
EXPORT_SYMBOL(xfrm_policy_list); EXPORT_SYMBOL(xfrm_policy_list);
EXPORT_SYMBOL(xfrm_dst_lookup_register);
EXPORT_SYMBOL(xfrm_dst_lookup_unregister);
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
EXPORT_SYMBOL(xfrm6_state_find);
EXPORT_SYMBOL(xfrm6_rcv);
EXPORT_SYMBOL(xfrm6_state_lookup); EXPORT_SYMBOL(xfrm6_state_lookup);
EXPORT_SYMBOL(xfrm6_find_acq); EXPORT_SYMBOL(xfrm6_find_acq);
EXPORT_SYMBOL(xfrm6_alloc_spi);
EXPORT_SYMBOL(xfrm6_register_type); EXPORT_SYMBOL(xfrm6_register_type);
EXPORT_SYMBOL(xfrm6_unregister_type); EXPORT_SYMBOL(xfrm6_unregister_type);
EXPORT_SYMBOL(xfrm6_get_type); EXPORT_SYMBOL(xfrm6_get_type);
EXPORT_SYMBOL(xfrm6_clear_mutable_options);
#endif #endif
EXPORT_SYMBOL_GPL(xfrm_probe_algs); EXPORT_SYMBOL_GPL(xfrm_probe_algs);
...@@ -342,6 +346,15 @@ EXPORT_SYMBOL_GPL(xfrm_aalg_get_byid); ...@@ -342,6 +346,15 @@ EXPORT_SYMBOL_GPL(xfrm_aalg_get_byid);
EXPORT_SYMBOL_GPL(xfrm_ealg_get_byid); EXPORT_SYMBOL_GPL(xfrm_ealg_get_byid);
EXPORT_SYMBOL_GPL(xfrm_aalg_get_byname); EXPORT_SYMBOL_GPL(xfrm_aalg_get_byname);
EXPORT_SYMBOL_GPL(xfrm_ealg_get_byname); EXPORT_SYMBOL_GPL(xfrm_ealg_get_byname);
#if defined(CONFIG_INET_AH) || defined(CONFIG_INET_AH_MODULE) || defined(CONFIG_INET6_AH) || defined(CONFIG_INET6_AH_MODULE)
EXPORT_SYMBOL_GPL(skb_ah_walk);
#endif
#if defined(CONFIG_INET_ESP) || defined(CONFIG_INET_ESP_MODULE) || defined(CONFIG_INET6_ESP) || defined(CONFIG_INET6_ESP_MODULE)
EXPORT_SYMBOL_GPL(skb_cow_data);
EXPORT_SYMBOL_GPL(pskb_put);
EXPORT_SYMBOL_GPL(skb_icv_walk);
EXPORT_SYMBOL_GPL(skb_to_sgvec);
#endif
#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