diff options
Diffstat (limited to 'net/sctp/ipv6.c')
| -rw-r--r-- | net/sctp/ipv6.c | 187 | 
1 files changed, 108 insertions, 79 deletions
diff --git a/net/sctp/ipv6.c b/net/sctp/ipv6.c index 865ce7ba4e14..0bb0d7cb9f10 100644 --- a/net/sctp/ipv6.c +++ b/net/sctp/ipv6.c @@ -80,6 +80,13 @@  #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 @@ -123,7 +130,7 @@ static int sctp_inet6addr_event(struct notifier_block *this, unsigned long ev,  		}  		spin_unlock_bh(&sctp_local_addr_lock);  		if (found) -			call_rcu(&addr->rcu, sctp_local_addr_free); +			kfree_rcu(addr, rcu);  		break;  	} @@ -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,  |