[XFRM]: Fix oops in xfrm4_dst_destroy()
[pandora-kernel.git] / net / ipv4 / xfrm4_policy.c
index 1bed0cd..5d51a2a 100644 (file)
@@ -1,11 +1,11 @@
-/* 
+/*
  * xfrm4_policy.c
  *
  * Changes:
  *     Kazunori MIYAZAWA @USAGI
  *     YOSHIFUJI Hideaki @USAGI
  *             Split up af-specific portion
- *     
+ *
  */
 
 #include <linux/compiler.h>
@@ -50,8 +50,8 @@ __xfrm4_find_bundle(struct flowi *fl, struct xfrm_policy *policy)
                struct xfrm_dst *xdst = (struct xfrm_dst*)dst;
                if (xdst->u.rt.fl.oif == fl->oif &&     /*XXX*/
                    xdst->u.rt.fl.fl4_dst == fl->fl4_dst &&
-                   xdst->u.rt.fl.fl4_src == fl->fl4_src &&
-                   xdst->u.rt.fl.fl4_tos == fl->fl4_tos &&
+                   xdst->u.rt.fl.fl4_src == fl->fl4_src &&
+                   xdst->u.rt.fl.fl4_tos == fl->fl4_tos &&
                    xfrm_bundle_ok(policy, xdst, fl, AF_INET, 0)) {
                        dst_clone(dst);
                        break;
@@ -72,13 +72,11 @@ __xfrm4_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int
        struct dst_entry *dst, *dst_prev;
        struct rtable *rt0 = (struct rtable*)(*dst_p);
        struct rtable *rt = rt0;
-       u32 remote = fl->fl4_dst;
-       u32 local  = fl->fl4_src;
        struct flowi fl_tunnel = {
                .nl_u = {
                        .ip4_u = {
-                               .saddr = local,
-                               .daddr = remote,
+                               .saddr = fl->fl4_src,
+                               .daddr = fl->fl4_dst,
                                .tos = fl->fl4_tos
                        }
                }
@@ -94,7 +92,6 @@ __xfrm4_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int
        for (i = 0; i < nx; i++) {
                struct dst_entry *dst1 = dst_alloc(&xfrm4_dst_ops);
                struct xfrm_dst *xdst;
-               int tunnel = 0;
 
                if (unlikely(dst1 == NULL)) {
                        err = -ENOBUFS;
@@ -116,19 +113,28 @@ __xfrm4_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int
 
                dst1->next = dst_prev;
                dst_prev = dst1;
-               if (xfrm[i]->props.mode != XFRM_MODE_TRANSPORT) {
-                       remote = xfrm[i]->id.daddr.a4;
-                       local  = xfrm[i]->props.saddr.a4;
-                       tunnel = 1;
-               }
+
                header_len += xfrm[i]->props.header_len;
                trailer_len += xfrm[i]->props.trailer_len;
 
-               if (tunnel) {
-                       fl_tunnel.fl4_src = local;
-                       fl_tunnel.fl4_dst = remote;
+               if (xfrm[i]->props.mode == XFRM_MODE_TUNNEL) {
+                       unsigned short encap_family = xfrm[i]->props.family;
+                       switch(encap_family) {
+                       case AF_INET:
+                               fl_tunnel.fl4_dst = xfrm[i]->id.daddr.a4;
+                               fl_tunnel.fl4_src = xfrm[i]->props.saddr.a4;
+                               break;
+#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
+                       case AF_INET6:
+                               ipv6_addr_copy(&fl_tunnel.fl6_dst, (struct in6_addr*)&xfrm[i]->id.daddr.a6);
+                               ipv6_addr_copy(&fl_tunnel.fl6_src, (struct in6_addr*)&xfrm[i]->props.saddr.a6);
+                               break;
+#endif
+                       default:
+                               BUG_ON(1);
+                       }
                        err = xfrm_dst_lookup((struct xfrm_dst **)&rt,
-                                             &fl_tunnel, AF_INET);
+                                             &fl_tunnel, encap_family);
                        if (err)
                                goto error;
                } else
@@ -145,6 +151,7 @@ __xfrm4_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int
        i = 0;
        for (; dst_prev != &rt->u.dst; dst_prev = dst_prev->child) {
                struct xfrm_dst *x = (struct xfrm_dst*)dst_prev;
+               struct xfrm_state_afinfo *afinfo;
                x->u.rt.fl = *fl;
 
                dst_prev->xfrm = xfrm[i++];
@@ -162,8 +169,18 @@ __xfrm4_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int
                /* Copy neighbout for reachability confirmation */
                dst_prev->neighbour     = neigh_clone(rt->u.dst.neighbour);
                dst_prev->input         = rt->u.dst.input;
-               dst_prev->output        = xfrm4_output;
-               if (rt->peer)
+               /* XXX: When IPv6 module can be unloaded, we should manage reference
+                * to xfrm6_output in afinfo->output. Miyazawa
+                * */
+               afinfo = xfrm_state_get_afinfo(dst_prev->xfrm->props.family);
+               if (!afinfo) {
+                       dst = *dst_p;
+                       err = -EAFNOSUPPORT;
+                       goto error;
+               }
+               dst_prev->output = afinfo->output;
+               xfrm_state_put_afinfo(afinfo);
+               if (dst_prev->xfrm->props.family == AF_INET && rt->peer)
                        atomic_inc(&rt->peer->refcnt);
                x->u.rt.peer = rt->peer;
                /* Sheit... I remember I did this right. Apparently,
@@ -199,11 +216,12 @@ _decode_session4(struct sk_buff *skb, struct flowi *fl)
        if (!(iph->frag_off & htons(IP_MF | IP_OFFSET))) {
                switch (iph->protocol) {
                case IPPROTO_UDP:
+               case IPPROTO_UDPLITE:
                case IPPROTO_TCP:
                case IPPROTO_SCTP:
                case IPPROTO_DCCP:
                        if (pskb_may_pull(skb, xprth + 4 - skb->data)) {
-                               u16 *ports = (u16 *)xprth;
+                               __be16 *ports = (__be16 *)xprth;
 
                                fl->fl_ip_sport = ports[0];
                                fl->fl_ip_dport = ports[1];
@@ -273,6 +291,8 @@ static void xfrm4_dst_destroy(struct dst_entry *dst)
 
        if (likely(xdst->u.rt.idev))
                in_dev_put(xdst->u.rt.idev);
+       if (dst->xfrm && dst->xfrm->props.family == AF_INET && likely(xdst->u.rt.peer))
+               inet_putpeer(xdst->u.rt.peer);
        xfrm_dst_destroy(xdst);
 }