Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next-2.6
[pandora-kernel.git] / net / sctp / ipv6.c
index 185fe05..0bb0d7c 100644 (file)
 
 #include <asm/uaccess.h>
 
+static inline int sctp_v6_addr_match_len(union sctp_addr *s1,
+                                        union sctp_addr *s2);
+static void sctp_v6_to_addr(union sctp_addr *addr, struct in6_addr *saddr,
+                             __be16 port);
+static int sctp_v6_cmp_addr(const union sctp_addr *addr1,
+                           const union sctp_addr *addr2);
+
 /* Event handler for inet6 address addition/deletion events.
  * The sctp_local_addr_list needs to be protocted by a spin lock since
  * multiple notifiers (say IPv4 and IPv6) may be running at the same
@@ -240,37 +247,107 @@ static int sctp_v6_xmit(struct sk_buff *skb, struct sctp_transport *transport)
 /* Returns the dst cache entry for the given source and destination ip
  * addresses.
  */
-static struct dst_entry *sctp_v6_get_dst(struct sctp_association *asoc,
-                                        union sctp_addr *daddr,
-                                        union sctp_addr *saddr)
+static void sctp_v6_get_dst(struct sctp_transport *t, union sctp_addr *saddr,
+                           struct flowi *fl, struct sock *sk)
 {
-       struct dst_entry *dst;
-       struct flowi6 fl6;
+       struct sctp_association *asoc = t->asoc;
+       struct dst_entry *dst = NULL;
+       struct flowi6 *fl6 = &fl->u.ip6;
+       struct sctp_bind_addr *bp;
+       struct sctp_sockaddr_entry *laddr;
+       union sctp_addr *baddr = NULL;
+       union sctp_addr *daddr = &t->ipaddr;
+       union sctp_addr dst_saddr;
+       __u8 matchlen = 0;
+       __u8 bmatchlen;
+       sctp_scope_t scope;
 
-       memset(&fl6, 0, sizeof(fl6));
-       ipv6_addr_copy(&fl6.daddr, &daddr->v6.sin6_addr);
+       memset(fl6, 0, sizeof(struct flowi6));
+       ipv6_addr_copy(&fl6->daddr, &daddr->v6.sin6_addr);
+       fl6->fl6_dport = daddr->v6.sin6_port;
+       fl6->flowi6_proto = IPPROTO_SCTP;
        if (ipv6_addr_type(&daddr->v6.sin6_addr) & IPV6_ADDR_LINKLOCAL)
-               fl6.flowi6_oif = daddr->v6.sin6_scope_id;
+               fl6->flowi6_oif = daddr->v6.sin6_scope_id;
 
+       SCTP_DEBUG_PRINTK("%s: DST=%pI6 ", __func__, &fl6->daddr);
 
-       SCTP_DEBUG_PRINTK("%s: DST=%pI6 ", __func__, &fl6.daddr);
+       if (asoc)
+               fl6->fl6_sport = htons(asoc->base.bind_addr.port);
 
        if (saddr) {
-               ipv6_addr_copy(&fl6.saddr, &saddr->v6.sin6_addr);
-               SCTP_DEBUG_PRINTK("SRC=%pI6 - ", &fl6.saddr);
+               ipv6_addr_copy(&fl6->saddr, &saddr->v6.sin6_addr);
+               fl6->fl6_sport = saddr->v6.sin6_port;
+               SCTP_DEBUG_PRINTK("SRC=%pI6 - ", &fl6->saddr);
+       }
+
+       dst = ip6_dst_lookup_flow(sk, fl6, NULL, false);
+       if (!asoc || saddr)
+               goto out;
+
+       bp = &asoc->base.bind_addr;
+       scope = sctp_scope(daddr);
+       /* ip6_dst_lookup has filled in the fl6->saddr for us.  Check
+        * to see if we can use it.
+        */
+       if (!IS_ERR(dst)) {
+               /* Walk through the bind address list and look for a bind
+                * address that matches the source address of the returned dst.
+                */
+               sctp_v6_to_addr(&dst_saddr, &fl6->saddr, htons(bp->port));
+               rcu_read_lock();
+               list_for_each_entry_rcu(laddr, &bp->address_list, list) {
+                       if (!laddr->valid || (laddr->state != SCTP_ADDR_SRC))
+                               continue;
+
+                       /* Do not compare against v4 addrs */
+                       if ((laddr->a.sa.sa_family == AF_INET6) &&
+                           (sctp_v6_cmp_addr(&dst_saddr, &laddr->a))) {
+                               rcu_read_unlock();
+                               goto out;
+                       }
+               }
+               rcu_read_unlock();
+               /* None of the bound addresses match the source address of the
+                * dst. So release it.
+                */
+               dst_release(dst);
+               dst = NULL;
        }
 
-       dst = ip6_route_output(&init_net, NULL, &fl6);
-       if (!dst->error) {
+       /* Walk through the bind address list and try to get the
+        * best source address for a given destination.
+        */
+       rcu_read_lock();
+       list_for_each_entry_rcu(laddr, &bp->address_list, list) {
+               if (!laddr->valid && laddr->state != SCTP_ADDR_SRC)
+                       continue;
+               if ((laddr->a.sa.sa_family == AF_INET6) &&
+                   (scope <= sctp_scope(&laddr->a))) {
+                       bmatchlen = sctp_v6_addr_match_len(daddr, &laddr->a);
+                       if (!baddr || (matchlen < bmatchlen)) {
+                               baddr = &laddr->a;
+                               matchlen = bmatchlen;
+                       }
+               }
+       }
+       rcu_read_unlock();
+       if (baddr) {
+               ipv6_addr_copy(&fl6->saddr, &baddr->v6.sin6_addr);
+               fl6->fl6_sport = baddr->v6.sin6_port;
+               dst = ip6_dst_lookup_flow(sk, fl6, NULL, false);
+       }
+
+out:
+       if (!IS_ERR(dst)) {
                struct rt6_info *rt;
                rt = (struct rt6_info *)dst;
+               t->dst = dst;
                SCTP_DEBUG_PRINTK("rt6_dst:%pI6 rt6_src:%pI6\n",
-                       &rt->rt6i_dst.addr, &rt->rt6i_src.addr);
-               return dst;
+                       &rt->rt6i_dst.addr, &fl6->saddr);
+       } else {
+               t->dst = NULL;
+               SCTP_DEBUG_PRINTK("NO ROUTE\n");
        }
-       SCTP_DEBUG_PRINTK("NO ROUTE\n");
-       dst_release(dst);
-       return NULL;
 }
 
 /* Returns the number of consecutive initial bits that match in the 2 ipv6
@@ -286,64 +363,18 @@ static inline int sctp_v6_addr_match_len(union sctp_addr *s1,
  * and asoc's bind address list.
  */
 static void sctp_v6_get_saddr(struct sctp_sock *sk,
-                             struct sctp_association *asoc,
-                             struct dst_entry *dst,
-                             union sctp_addr *daddr,
-                             union sctp_addr *saddr)
+                             struct sctp_transport *t,
+                             struct flowi *fl)
 {
-       struct sctp_bind_addr *bp;
-       struct sctp_sockaddr_entry *laddr;
-       sctp_scope_t scope;
-       union sctp_addr *baddr = NULL;
-       __u8 matchlen = 0;
-       __u8 bmatchlen;
+       struct flowi6 *fl6 = &fl->u.ip6;
+       union sctp_addr *saddr = &t->saddr;
 
-       SCTP_DEBUG_PRINTK("%s: asoc:%p dst:%p daddr:%pI6 ",
-                         __func__, asoc, dst, &daddr->v6.sin6_addr);
-
-       if (!asoc) {
-               ipv6_dev_get_saddr(sock_net(sctp_opt2sk(sk)),
-                                  dst ? ip6_dst_idev(dst)->dev : NULL,
-                                  &daddr->v6.sin6_addr,
-                                  inet6_sk(&sk->inet.sk)->srcprefs,
-                                  &saddr->v6.sin6_addr);
-               SCTP_DEBUG_PRINTK("saddr from ipv6_get_saddr: %pI6\n",
-                                 &saddr->v6.sin6_addr);
-               return;
-       }
-
-       scope = sctp_scope(daddr);
-
-       bp = &asoc->base.bind_addr;
+       SCTP_DEBUG_PRINTK("%s: asoc:%p dst:%p\n", __func__, t->asoc, t->dst);
 
-       /* Go through the bind address list and find the best source address
-        * that matches the scope of the destination address.
-        */
-       rcu_read_lock();
-       list_for_each_entry_rcu(laddr, &bp->address_list, list) {
-               if (!laddr->valid)
-                       continue;
-               if ((laddr->state == SCTP_ADDR_SRC) &&
-                   (laddr->a.sa.sa_family == AF_INET6) &&
-                   (scope <= sctp_scope(&laddr->a))) {
-                       bmatchlen = sctp_v6_addr_match_len(daddr, &laddr->a);
-                       if (!baddr || (matchlen < bmatchlen)) {
-                               baddr = &laddr->a;
-                               matchlen = bmatchlen;
-                       }
-               }
-       }
-
-       if (baddr) {
-               memcpy(saddr, baddr, sizeof(union sctp_addr));
-               SCTP_DEBUG_PRINTK("saddr: %pI6\n", &saddr->v6.sin6_addr);
-       } else {
-               pr_err("%s: asoc:%p Could not find a valid source "
-                      "address for the dest:%pI6\n",
-                      __func__, asoc, &daddr->v6.sin6_addr);
+       if (t->dst) {
+               saddr->v6.sin6_family = AF_INET6;
+               ipv6_addr_copy(&saddr->v6.sin6_addr, &fl6->saddr);
        }
-
-       rcu_read_unlock();
 }
 
 /* Make a copy of all potential local addresses. */
@@ -465,14 +496,13 @@ static int sctp_v6_to_addr_param(const union sctp_addr *addr,
        return length;
 }
 
-/* Initialize a sctp_addr from a dst_entry. */
-static void sctp_v6_dst_saddr(union sctp_addr *addr, struct dst_entry *dst,
+/* Initialize a sctp_addr from struct in6_addr. */
+static void sctp_v6_to_addr(union sctp_addr *addr, struct in6_addr *saddr,
                              __be16 port)
 {
-       struct rt6_info *rt = (struct rt6_info *)dst;
        addr->sa.sa_family = AF_INET6;
        addr->v6.sin6_port = port;
-       ipv6_addr_copy(&addr->v6.sin6_addr, &rt->rt6i_src.addr);
+       ipv6_addr_copy(&addr->v6.sin6_addr, saddr);
 }
 
 /* Compare addresses exactly.
@@ -531,7 +561,7 @@ static int sctp_v6_is_any(const union sctp_addr *addr)
 static int sctp_v6_available(union sctp_addr *addr, struct sctp_sock *sp)
 {
        int type;
-       struct in6_addr *in6 = (struct in6_addr *)&addr->v6.sin6_addr;
+       const struct in6_addr *in6 = (const struct in6_addr *)&addr->v6.sin6_addr;
 
        type = ipv6_addr_type(in6);
        if (IPV6_ADDR_ANY == type)
@@ -959,7 +989,6 @@ static struct sctp_af sctp_af_inet6 = {
        .to_sk_daddr       = sctp_v6_to_sk_daddr,
        .from_addr_param   = sctp_v6_from_addr_param,
        .to_addr_param     = sctp_v6_to_addr_param,
-       .dst_saddr         = sctp_v6_dst_saddr,
        .cmp_addr          = sctp_v6_cmp_addr,
        .scope             = sctp_v6_scope,
        .addr_valid        = sctp_v6_addr_valid,