[IPSEC]: Add ICMP host relookup support
authorHerbert Xu <herbert@gondor.apana.org.au>
Wed, 12 Dec 2007 18:44:43 +0000 (10:44 -0800)
committerDavid S. Miller <davem@davemloft.net>
Mon, 28 Jan 2008 22:57:23 +0000 (14:57 -0800)
RFC 4301 requires us to relookup ICMP traffic that does not match any
policies using the reverse of its payload.  This patch implements this
for ICMP traffic that originates from or terminates on localhost.

This is activated on outbound with the new policy flag XFRM_POLICY_ICMP,
and on inbound by the new state flag XFRM_STATE_ICMP.

On inbound the policy check is now performed by the ICMP protocol so
that it can repeat the policy check where necessary.

Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/xfrm.h
include/net/dst.h
net/ipv4/af_inet.c
net/ipv4/icmp.c
net/ipv6/icmp.c
net/xfrm/xfrm_policy.c

index c0e41e02234fe3d346a7468a961fbe40c1d3faad..1131eabfaa2ab568ba6ae0eaac48988ded5ca10b 100644 (file)
@@ -329,6 +329,7 @@ struct xfrm_usersa_info {
 #define XFRM_STATE_DECAP_DSCP  2
 #define XFRM_STATE_NOPMTUDISC  4
 #define XFRM_STATE_WILDRECV    8
+#define XFRM_STATE_ICMP                16
 };
 
 struct xfrm_usersa_id {
@@ -363,6 +364,8 @@ struct xfrm_userpolicy_info {
 #define XFRM_POLICY_BLOCK      1
        __u8                            flags;
 #define XFRM_POLICY_LOCALOK    1       /* Allow user to override global policy */
+       /* Automatically expand selector to include matching ICMP payloads. */
+#define XFRM_POLICY_ICMP       2
        __u8                            share;
 };
 
index aaa2dbb501793097d524c04df8d5bd4f74f7020c..31468c9aa87774dae2883de50d2c72bfe645bdbc 100644 (file)
@@ -268,6 +268,7 @@ extern void         dst_init(void);
 /* Flags for xfrm_lookup flags argument. */
 enum {
        XFRM_LOOKUP_WAIT = 1 << 0,
+       XFRM_LOOKUP_ICMP = 1 << 1,
 };
 
 struct flowi;
index 7f8b27ff94ff04448e34be59ba84f4ec79123587..5089a369e99c73c308ec075ff13e1d9f89c17635 100644 (file)
@@ -1291,6 +1291,7 @@ static struct net_protocol udp_protocol = {
 
 static struct net_protocol icmp_protocol = {
        .handler =      icmp_rcv,
+       .no_policy =    1,
 };
 
 static int __init init_ipv4_mibs(void)
index 13d74598d3e47c4f0ee97838b9d7273cb51ff8b9..3c41a6f7e6ec7c62ae56e8b9844a460ce5430fcb 100644 (file)
@@ -92,6 +92,7 @@
 #include <asm/system.h>
 #include <asm/uaccess.h>
 #include <net/checksum.h>
+#include <net/xfrm.h>
 
 /*
  *     Build xmit assembly blocks
@@ -563,11 +564,71 @@ void icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info)
                                }
                        }
                };
+               int err;
+               struct rtable *rt2;
+
                security_skb_classify_flow(skb_in, &fl);
-               if (ip_route_output_key(&rt, &fl))
+               if (__ip_route_output_key(&rt, &fl))
+                       goto out_unlock;
+
+               /* No need to clone since we're just using its address. */
+               rt2 = rt;
+
+               err = xfrm_lookup((struct dst_entry **)&rt, &fl, NULL, 0);
+               switch (err) {
+               case 0:
+                       if (rt != rt2)
+                               goto route_done;
+                       break;
+               case -EPERM:
+                       rt = NULL;
+                       break;
+               default:
+                       goto out_unlock;
+               }
+
+               if (xfrm_decode_session_reverse(skb_in, &fl, AF_INET))
+                       goto out_unlock;
+
+               if (inet_addr_type(fl.fl4_src) == RTN_LOCAL)
+                       err = __ip_route_output_key(&rt2, &fl);
+               else {
+                       struct flowi fl2 = {};
+                       struct dst_entry *odst;
+
+                       fl2.fl4_dst = fl.fl4_src;
+                       if (ip_route_output_key(&rt2, &fl2))
+                               goto out_unlock;
+
+                       /* Ugh! */
+                       odst = skb_in->dst;
+                       err = ip_route_input(skb_in, fl.fl4_dst, fl.fl4_src,
+                                            RT_TOS(tos), rt2->u.dst.dev);
+
+                       dst_release(&rt2->u.dst);
+                       rt2 = (struct rtable *)skb_in->dst;
+                       skb_in->dst = odst;
+               }
+
+               if (err)
+                       goto out_unlock;
+
+               err = xfrm_lookup((struct dst_entry **)&rt2, &fl, NULL,
+                                 XFRM_LOOKUP_ICMP);
+               if (err == -ENOENT) {
+                       if (!rt)
+                               goto out_unlock;
+                       goto route_done;
+               }
+
+               dst_release(&rt->u.dst);
+               rt = rt2;
+
+               if (err)
                        goto out_unlock;
        }
 
+route_done:
        if (!icmpv4_xrlim_allow(rt, type, code))
                goto ende;
 
@@ -916,6 +977,22 @@ int icmp_rcv(struct sk_buff *skb)
        struct icmphdr *icmph;
        struct rtable *rt = (struct rtable *)skb->dst;
 
+       if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb) &&
+           skb->sp->xvec[skb->sp->len - 1]->props.flags & XFRM_STATE_ICMP) {
+               int nh;
+
+               if (!pskb_may_pull(skb, sizeof(*icmph) + sizeof(struct iphdr)))
+                       goto drop;
+
+               nh = skb_network_offset(skb);
+               skb_set_network_header(skb, sizeof(*icmph));
+
+               if (!xfrm4_policy_check_reverse(NULL, XFRM_POLICY_IN, skb))
+                       goto drop;
+
+               skb_set_network_header(skb, nh);
+       }
+
        ICMP_INC_STATS_BH(ICMP_MIB_INMSGS);
 
        switch (skb->ip_summed) {
@@ -929,8 +1006,7 @@ int icmp_rcv(struct sk_buff *skb)
                        goto error;
        }
 
-       if (!pskb_pull(skb, sizeof(struct icmphdr)))
-               goto error;
+       __skb_pull(skb, sizeof(*icmph));
 
        icmph = icmp_hdr(skb);
 
index 93c96cfd5ee19052f27ecc2e559c2446ff4ef295..c0bea7bfaa8a713ebc55c5663327e635a97a4b60 100644 (file)
@@ -63,6 +63,7 @@
 #include <net/ip6_route.h>
 #include <net/addrconf.h>
 #include <net/icmp.h>
+#include <net/xfrm.h>
 
 #include <asm/uaccess.h>
 #include <asm/system.h>
@@ -86,7 +87,7 @@ static int icmpv6_rcv(struct sk_buff *skb);
 
 static struct inet6_protocol icmpv6_protocol = {
        .handler        =       icmpv6_rcv,
-       .flags          =       INET6_PROTO_FINAL,
+       .flags          =       INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
 };
 
 static __inline__ int icmpv6_xmit_lock(void)
@@ -310,8 +311,10 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info,
        struct ipv6_pinfo *np;
        struct in6_addr *saddr = NULL;
        struct dst_entry *dst;
+       struct dst_entry *dst2;
        struct icmp6hdr tmp_hdr;
        struct flowi fl;
+       struct flowi fl2;
        struct icmpv6_msg msg;
        int iif = 0;
        int addr_type = 0;
@@ -418,9 +421,42 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info,
                goto out_dst_release;
        }
 
-       if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0)
+       /* No need to clone since we're just using its address. */
+       dst2 = dst;
+
+       err = xfrm_lookup(&dst, &fl, sk, 0);
+       switch (err) {
+       case 0:
+               if (dst != dst2)
+                       goto route_done;
+               break;
+       case -EPERM:
+               dst = NULL;
+               break;
+       default:
+               goto out;
+       }
+
+       if (xfrm_decode_session_reverse(skb, &fl2, AF_INET6))
+               goto out;
+
+       if (ip6_dst_lookup(sk, &dst2, &fl))
                goto out;
 
+       err = xfrm_lookup(&dst2, &fl, sk, XFRM_LOOKUP_ICMP);
+       if (err == -ENOENT) {
+               if (!dst)
+                       goto out;
+               goto route_done;
+       }
+
+       dst_release(dst);
+       dst = dst2;
+
+       if (err)
+               goto out;
+
+route_done:
        if (ipv6_addr_is_multicast(&fl.fl6_dst))
                hlimit = np->mcast_hops;
        else
@@ -608,6 +644,22 @@ static int icmpv6_rcv(struct sk_buff *skb)
        struct icmp6hdr *hdr;
        int type;
 
+       if (xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb) &&
+           skb->sp->xvec[skb->sp->len - 1]->props.flags & XFRM_STATE_ICMP) {
+               int nh;
+
+               if (!pskb_may_pull(skb, sizeof(*hdr) + sizeof(*orig_hdr)))
+                       goto drop_no_count;
+
+               nh = skb_network_offset(skb);
+               skb_set_network_header(skb, sizeof(*hdr));
+
+               if (!xfrm6_policy_check_reverse(NULL, XFRM_POLICY_IN, skb))
+                       goto drop_no_count;
+
+               skb_set_network_header(skb, nh);
+       }
+
        ICMP6_INC_STATS_BH(idev, ICMP6_MIB_INMSGS);
 
        saddr = &ipv6_hdr(skb)->saddr;
@@ -630,8 +682,7 @@ static int icmpv6_rcv(struct sk_buff *skb)
                }
        }
 
-       if (!pskb_pull(skb, sizeof(struct icmp6hdr)))
-               goto discard_it;
+       __skb_pull(skb, sizeof(*hdr));
 
        hdr = icmp6_hdr(skb);
 
@@ -717,6 +768,7 @@ static int icmpv6_rcv(struct sk_buff *skb)
 
 discard_it:
        ICMP6_INC_STATS_BH(idev, ICMP6_MIB_INERRORS);
+drop_no_count:
        kfree_skb(skb);
        return 0;
 }
index 2e10d46c0e8cc1373fc810e49b2c03b6e1769f42..a83b5e1349ed4985c77c8ce361059a01d646f91b 100644 (file)
@@ -1469,11 +1469,13 @@ restart:
                        goto dropdst;
        }
 
+       err = -ENOENT;
+
        if (!policy) {
                /* To accelerate a bit...  */
                if ((dst_orig->flags & DST_NOXFRM) ||
                    !xfrm_policy_count[XFRM_POLICY_OUT])
-                       return 0;
+                       goto nopol;
 
                policy = flow_cache_lookup(fl, dst_orig->ops->family,
                                           dir, xfrm_policy_lookup);
@@ -1483,14 +1485,18 @@ restart:
        }
 
        if (!policy)
-               return 0;
+               goto nopol;
 
        family = dst_orig->ops->family;
-       policy->curlft.use_time = get_seconds();
        pols[0] = policy;
        npols ++;
        xfrm_nr += pols[0]->xfrm_nr;
 
+       if ((flags & XFRM_LOOKUP_ICMP) && !(policy->flags & XFRM_POLICY_ICMP))
+               goto error;
+
+       policy->curlft.use_time = get_seconds();
+
        switch (policy->action) {
        default:
        case XFRM_POLICY_BLOCK:
@@ -1649,6 +1655,11 @@ dropdst:
        dst_release(dst_orig);
        *dst_p = NULL;
        return err;
+
+nopol:
+       if (flags & XFRM_LOOKUP_ICMP)
+               goto dropdst;
+       return 0;
 }
 EXPORT_SYMBOL(__xfrm_lookup);