![Herbert Xu](/assets/img/avatar_default.png)
This patch makes the IPv4 x->type->input functions return the next protocol instead of setting it directly. This is identical to how we do things in IPv6 and will help us merge common code on the input path. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Signed-off-by: David S. Miller <davem@davemloft.net>
273 lines
6.1 KiB
C
273 lines
6.1 KiB
C
/*
|
|
* xfrm4_input.c
|
|
*
|
|
* Changes:
|
|
* YOSHIFUJI Hideaki @USAGI
|
|
* Split up af-specific portion
|
|
* Derek Atkins <derek@ihtfp.com>
|
|
* Add Encapsulation support
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/string.h>
|
|
#include <linux/netfilter.h>
|
|
#include <linux/netfilter_ipv4.h>
|
|
#include <net/ip.h>
|
|
#include <net/xfrm.h>
|
|
|
|
static int xfrm4_parse_spi(struct sk_buff *skb, u8 nexthdr, __be32 *spi, __be32 *seq)
|
|
{
|
|
switch (nexthdr) {
|
|
case IPPROTO_IPIP:
|
|
case IPPROTO_IPV6:
|
|
*spi = ip_hdr(skb)->saddr;
|
|
*seq = 0;
|
|
return 0;
|
|
}
|
|
|
|
return xfrm_parse_spi(skb, nexthdr, spi, seq);
|
|
}
|
|
|
|
#ifdef CONFIG_NETFILTER
|
|
static inline int xfrm4_rcv_encap_finish(struct sk_buff *skb)
|
|
{
|
|
if (skb->dst == NULL) {
|
|
const struct iphdr *iph = ip_hdr(skb);
|
|
|
|
if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,
|
|
skb->dev))
|
|
goto drop;
|
|
}
|
|
return dst_input(skb);
|
|
drop:
|
|
kfree_skb(skb);
|
|
return NET_RX_DROP;
|
|
}
|
|
#endif
|
|
|
|
static int xfrm4_rcv_encap(struct sk_buff *skb, __u16 encap_type)
|
|
{
|
|
__be32 spi, seq;
|
|
struct xfrm_state *xfrm_vec[XFRM_MAX_DEPTH];
|
|
struct xfrm_state *x;
|
|
int xfrm_nr = 0;
|
|
int decaps = 0;
|
|
int err = xfrm4_parse_spi(skb, ip_hdr(skb)->protocol, &spi, &seq);
|
|
unsigned int nhoff = offsetof(struct iphdr, protocol);
|
|
|
|
if (err != 0)
|
|
goto drop;
|
|
|
|
do {
|
|
const struct iphdr *iph = ip_hdr(skb);
|
|
int nexthdr;
|
|
|
|
if (xfrm_nr == XFRM_MAX_DEPTH)
|
|
goto drop;
|
|
|
|
x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi,
|
|
iph->protocol != IPPROTO_IPV6 ? iph->protocol : IPPROTO_IPIP, AF_INET);
|
|
if (x == NULL)
|
|
goto drop;
|
|
|
|
spin_lock(&x->lock);
|
|
if (unlikely(x->km.state != XFRM_STATE_VALID))
|
|
goto drop_unlock;
|
|
|
|
if ((x->encap ? x->encap->encap_type : 0) != encap_type)
|
|
goto drop_unlock;
|
|
|
|
if (x->props.replay_window && xfrm_replay_check(x, seq))
|
|
goto drop_unlock;
|
|
|
|
if (xfrm_state_check_expire(x))
|
|
goto drop_unlock;
|
|
|
|
nexthdr = x->type->input(x, skb);
|
|
if (nexthdr <= 0)
|
|
goto drop_unlock;
|
|
|
|
skb_network_header(skb)[nhoff] = nexthdr;
|
|
|
|
/* only the first xfrm gets the encap type */
|
|
encap_type = 0;
|
|
|
|
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;
|
|
|
|
if (x->mode->input(x, skb))
|
|
goto drop;
|
|
|
|
if (x->props.mode == XFRM_MODE_TUNNEL) {
|
|
decaps = 1;
|
|
break;
|
|
}
|
|
|
|
err = xfrm_parse_spi(skb, ip_hdr(skb)->protocol, &spi, &seq);
|
|
if (err < 0)
|
|
goto drop;
|
|
} while (!err);
|
|
|
|
/* Allocate new secpath or COW existing one. */
|
|
|
|
if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) {
|
|
struct sec_path *sp;
|
|
sp = secpath_dup(skb->sp);
|
|
if (!sp)
|
|
goto drop;
|
|
if (skb->sp)
|
|
secpath_put(skb->sp);
|
|
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(xfrm_vec[0]));
|
|
skb->sp->len += xfrm_nr;
|
|
|
|
nf_reset(skb);
|
|
|
|
if (decaps) {
|
|
dst_release(skb->dst);
|
|
skb->dst = NULL;
|
|
netif_rx(skb);
|
|
return 0;
|
|
} else {
|
|
#ifdef CONFIG_NETFILTER
|
|
__skb_push(skb, skb->data - skb_network_header(skb));
|
|
ip_hdr(skb)->tot_len = htons(skb->len);
|
|
ip_send_check(ip_hdr(skb));
|
|
|
|
NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, skb->dev, NULL,
|
|
xfrm4_rcv_encap_finish);
|
|
return 0;
|
|
#else
|
|
return -ip_hdr(skb)->protocol;
|
|
#endif
|
|
}
|
|
|
|
drop_unlock:
|
|
spin_unlock(&x->lock);
|
|
xfrm_state_put(x);
|
|
drop:
|
|
while (--xfrm_nr >= 0)
|
|
xfrm_state_put(xfrm_vec[xfrm_nr]);
|
|
|
|
kfree_skb(skb);
|
|
return 0;
|
|
}
|
|
|
|
/* If it's a keepalive packet, then just eat it.
|
|
* If it's an encapsulated packet, then pass it to the
|
|
* IPsec xfrm input.
|
|
* Returns 0 if skb passed to xfrm or was dropped.
|
|
* Returns >0 if skb should be passed to UDP.
|
|
* Returns <0 if skb should be resubmitted (-ret is protocol)
|
|
*/
|
|
int xfrm4_udp_encap_rcv(struct sock *sk, struct sk_buff *skb)
|
|
{
|
|
struct udp_sock *up = udp_sk(sk);
|
|
struct udphdr *uh;
|
|
struct iphdr *iph;
|
|
int iphlen, len;
|
|
int ret;
|
|
|
|
__u8 *udpdata;
|
|
__be32 *udpdata32;
|
|
__u16 encap_type = up->encap_type;
|
|
|
|
/* if this is not encapsulated socket, then just return now */
|
|
if (!encap_type)
|
|
return 1;
|
|
|
|
/* If this is a paged skb, make sure we pull up
|
|
* whatever data we need to look at. */
|
|
len = skb->len - sizeof(struct udphdr);
|
|
if (!pskb_may_pull(skb, sizeof(struct udphdr) + min(len, 8)))
|
|
return 1;
|
|
|
|
/* Now we can get the pointers */
|
|
uh = udp_hdr(skb);
|
|
udpdata = (__u8 *)uh + sizeof(struct udphdr);
|
|
udpdata32 = (__be32 *)udpdata;
|
|
|
|
switch (encap_type) {
|
|
default:
|
|
case UDP_ENCAP_ESPINUDP:
|
|
/* Check if this is a keepalive packet. If so, eat it. */
|
|
if (len == 1 && udpdata[0] == 0xff) {
|
|
goto drop;
|
|
} else if (len > sizeof(struct ip_esp_hdr) && udpdata32[0] != 0) {
|
|
/* ESP Packet without Non-ESP header */
|
|
len = sizeof(struct udphdr);
|
|
} else
|
|
/* Must be an IKE packet.. pass it through */
|
|
return 1;
|
|
break;
|
|
case UDP_ENCAP_ESPINUDP_NON_IKE:
|
|
/* Check if this is a keepalive packet. If so, eat it. */
|
|
if (len == 1 && udpdata[0] == 0xff) {
|
|
goto drop;
|
|
} else if (len > 2 * sizeof(u32) + sizeof(struct ip_esp_hdr) &&
|
|
udpdata32[0] == 0 && udpdata32[1] == 0) {
|
|
|
|
/* ESP Packet with Non-IKE marker */
|
|
len = sizeof(struct udphdr) + 2 * sizeof(u32);
|
|
} else
|
|
/* Must be an IKE packet.. pass it through */
|
|
return 1;
|
|
break;
|
|
}
|
|
|
|
/* At this point we are sure that this is an ESPinUDP packet,
|
|
* so we need to remove 'len' bytes from the packet (the UDP
|
|
* header and optional ESP marker bytes) and then modify the
|
|
* protocol to ESP, and then call into the transform receiver.
|
|
*/
|
|
if (skb_cloned(skb) && pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
|
|
goto drop;
|
|
|
|
/* Now we can update and verify the packet length... */
|
|
iph = ip_hdr(skb);
|
|
iphlen = iph->ihl << 2;
|
|
iph->tot_len = htons(ntohs(iph->tot_len) - len);
|
|
if (skb->len < iphlen + len) {
|
|
/* packet is too small!?! */
|
|
goto drop;
|
|
}
|
|
|
|
/* pull the data buffer up to the ESP header and set the
|
|
* transport header to point to ESP. Keep UDP on the stack
|
|
* for later.
|
|
*/
|
|
__skb_pull(skb, len);
|
|
skb_reset_transport_header(skb);
|
|
|
|
/* modify the protocol (it's ESP!) */
|
|
iph->protocol = IPPROTO_ESP;
|
|
|
|
/* process ESP */
|
|
ret = xfrm4_rcv_encap(skb, encap_type);
|
|
return ret;
|
|
|
|
drop:
|
|
kfree_skb(skb);
|
|
return 0;
|
|
}
|
|
|
|
int xfrm4_rcv(struct sk_buff *skb)
|
|
{
|
|
return xfrm4_rcv_encap(skb, 0);
|
|
}
|
|
|
|
EXPORT_SYMBOL(xfrm4_rcv);
|