diff options
-rw-r--r-- | net/sctp/ipv6.c | 44 |
1 files changed, 43 insertions, 1 deletions
diff --git a/net/sctp/ipv6.c b/net/sctp/ipv6.c index 321f175055bf..3a571d6614f9 100644 --- a/net/sctp/ipv6.c +++ b/net/sctp/ipv6.c | |||
@@ -80,6 +80,9 @@ | |||
80 | 80 | ||
81 | #include <asm/uaccess.h> | 81 | #include <asm/uaccess.h> |
82 | 82 | ||
83 | static inline int sctp_v6_addr_match_len(union sctp_addr *s1, | ||
84 | union sctp_addr *s2); | ||
85 | |||
83 | /* Event handler for inet6 address addition/deletion events. | 86 | /* Event handler for inet6 address addition/deletion events. |
84 | * The sctp_local_addr_list needs to be protocted by a spin lock since | 87 | * The sctp_local_addr_list needs to be protocted by a spin lock since |
85 | * multiple notifiers (say IPv4 and IPv6) may be running at the same | 88 | * multiple notifiers (say IPv4 and IPv6) may be running at the same |
@@ -244,8 +247,14 @@ static struct dst_entry *sctp_v6_get_dst(struct sctp_association *asoc, | |||
244 | union sctp_addr *daddr, | 247 | union sctp_addr *daddr, |
245 | union sctp_addr *saddr) | 248 | union sctp_addr *saddr) |
246 | { | 249 | { |
247 | struct dst_entry *dst; | 250 | struct dst_entry *dst = NULL; |
248 | struct flowi6 fl6; | 251 | struct flowi6 fl6; |
252 | struct sctp_bind_addr *bp; | ||
253 | struct sctp_sockaddr_entry *laddr; | ||
254 | union sctp_addr *baddr = NULL; | ||
255 | __u8 matchlen = 0; | ||
256 | __u8 bmatchlen; | ||
257 | sctp_scope_t scope; | ||
249 | 258 | ||
250 | memset(&fl6, 0, sizeof(fl6)); | 259 | memset(&fl6, 0, sizeof(fl6)); |
251 | ipv6_addr_copy(&fl6.daddr, &daddr->v6.sin6_addr); | 260 | ipv6_addr_copy(&fl6.daddr, &daddr->v6.sin6_addr); |
@@ -261,6 +270,39 @@ static struct dst_entry *sctp_v6_get_dst(struct sctp_association *asoc, | |||
261 | } | 270 | } |
262 | 271 | ||
263 | dst = ip6_route_output(&init_net, NULL, &fl6); | 272 | dst = ip6_route_output(&init_net, NULL, &fl6); |
273 | if (!asoc || saddr) | ||
274 | goto out; | ||
275 | |||
276 | if (dst->error) { | ||
277 | dst_release(dst); | ||
278 | dst = NULL; | ||
279 | bp = &asoc->base.bind_addr; | ||
280 | scope = sctp_scope(daddr); | ||
281 | /* Walk through the bind address list and try to get a dst that | ||
282 | * matches a bind address as the source address. | ||
283 | */ | ||
284 | rcu_read_lock(); | ||
285 | list_for_each_entry_rcu(laddr, &bp->address_list, list) { | ||
286 | if (!laddr->valid) | ||
287 | continue; | ||
288 | if ((laddr->state == SCTP_ADDR_SRC) && | ||
289 | (laddr->a.sa.sa_family == AF_INET6) && | ||
290 | (scope <= sctp_scope(&laddr->a))) { | ||
291 | bmatchlen = sctp_v6_addr_match_len(daddr, | ||
292 | &laddr->a); | ||
293 | if (!baddr || (matchlen < bmatchlen)) { | ||
294 | baddr = &laddr->a; | ||
295 | matchlen = bmatchlen; | ||
296 | } | ||
297 | } | ||
298 | } | ||
299 | rcu_read_unlock(); | ||
300 | if (baddr) { | ||
301 | ipv6_addr_copy(&fl6.saddr, &baddr->v6.sin6_addr); | ||
302 | dst = ip6_route_output(&init_net, NULL, &fl6); | ||
303 | } | ||
304 | } | ||
305 | out: | ||
264 | if (!dst->error) { | 306 | if (!dst->error) { |
265 | struct rt6_info *rt; | 307 | struct rt6_info *rt; |
266 | rt = (struct rt6_info *)dst; | 308 | rt = (struct rt6_info *)dst; |