diff options
Diffstat (limited to 'net/ipv6')
| -rw-r--r-- | net/ipv6/inet6_hashtables.c | 183 | ||||
| -rw-r--r-- | net/ipv6/tcp_ipv6.c | 173 |
2 files changed, 182 insertions, 174 deletions
diff --git a/net/ipv6/inet6_hashtables.c b/net/ipv6/inet6_hashtables.c index 01d5f46d4e40..4154f3a8b6cf 100644 --- a/net/ipv6/inet6_hashtables.c +++ b/net/ipv6/inet6_hashtables.c | |||
| @@ -5,7 +5,8 @@ | |||
| 5 | * | 5 | * |
| 6 | * Generic INET6 transport hashtables | 6 | * Generic INET6 transport hashtables |
| 7 | * | 7 | * |
| 8 | * Authors: Lotsa people, from code originally in tcp | 8 | * Authors: Lotsa people, from code originally in tcp, generalised here |
| 9 | * by Arnaldo Carvalho de Melo <acme@mandriva.com> | ||
| 9 | * | 10 | * |
| 10 | * This program is free software; you can redistribute it and/or | 11 | * This program is free software; you can redistribute it and/or |
| 11 | * modify it under the terms of the GNU General Public License | 12 | * modify it under the terms of the GNU General Public License |
| @@ -14,12 +15,13 @@ | |||
| 14 | */ | 15 | */ |
| 15 | 16 | ||
| 16 | #include <linux/config.h> | 17 | #include <linux/config.h> |
| 17 | |||
| 18 | #include <linux/module.h> | 18 | #include <linux/module.h> |
| 19 | #include <linux/random.h> | ||
| 19 | 20 | ||
| 20 | #include <net/inet_connection_sock.h> | 21 | #include <net/inet_connection_sock.h> |
| 21 | #include <net/inet_hashtables.h> | 22 | #include <net/inet_hashtables.h> |
| 22 | #include <net/inet6_hashtables.h> | 23 | #include <net/inet6_hashtables.h> |
| 24 | #include <net/ip.h> | ||
| 23 | 25 | ||
| 24 | struct sock *inet6_lookup_listener(struct inet_hashinfo *hashinfo, | 26 | struct sock *inet6_lookup_listener(struct inet_hashinfo *hashinfo, |
| 25 | const struct in6_addr *daddr, | 27 | const struct in6_addr *daddr, |
| @@ -79,3 +81,180 @@ struct sock *inet6_lookup(struct inet_hashinfo *hashinfo, | |||
| 79 | } | 81 | } |
| 80 | 82 | ||
| 81 | EXPORT_SYMBOL_GPL(inet6_lookup); | 83 | EXPORT_SYMBOL_GPL(inet6_lookup); |
| 84 | |||
| 85 | static int __inet6_check_established(struct inet_timewait_death_row *death_row, | ||
| 86 | struct sock *sk, const __u16 lport, | ||
| 87 | struct inet_timewait_sock **twp) | ||
| 88 | { | ||
| 89 | struct inet_hashinfo *hinfo = death_row->hashinfo; | ||
| 90 | const struct inet_sock *inet = inet_sk(sk); | ||
| 91 | const struct ipv6_pinfo *np = inet6_sk(sk); | ||
| 92 | const struct in6_addr *daddr = &np->rcv_saddr; | ||
| 93 | const struct in6_addr *saddr = &np->daddr; | ||
| 94 | const int dif = sk->sk_bound_dev_if; | ||
| 95 | const u32 ports = INET_COMBINED_PORTS(inet->dport, lport); | ||
| 96 | const unsigned int hash = inet6_ehashfn(daddr, inet->num, saddr, | ||
| 97 | inet->dport); | ||
| 98 | struct inet_ehash_bucket *head = inet_ehash_bucket(hinfo, hash); | ||
| 99 | struct sock *sk2; | ||
| 100 | const struct hlist_node *node; | ||
| 101 | struct inet_timewait_sock *tw; | ||
| 102 | |||
| 103 | prefetch(head->chain.first); | ||
| 104 | write_lock(&head->lock); | ||
| 105 | |||
| 106 | /* Check TIME-WAIT sockets first. */ | ||
| 107 | sk_for_each(sk2, node, &(head + hinfo->ehash_size)->chain) { | ||
| 108 | const struct inet6_timewait_sock *tw6 = inet6_twsk(sk2); | ||
| 109 | |||
| 110 | tw = inet_twsk(sk2); | ||
| 111 | |||
| 112 | if(*((__u32 *)&(tw->tw_dport)) == ports && | ||
| 113 | sk2->sk_family == PF_INET6 && | ||
| 114 | ipv6_addr_equal(&tw6->tw_v6_daddr, saddr) && | ||
| 115 | ipv6_addr_equal(&tw6->tw_v6_rcv_saddr, daddr) && | ||
| 116 | sk2->sk_bound_dev_if == sk->sk_bound_dev_if) { | ||
| 117 | if (twsk_unique(sk, sk2, twp)) | ||
| 118 | goto unique; | ||
| 119 | else | ||
| 120 | goto not_unique; | ||
| 121 | } | ||
| 122 | } | ||
| 123 | tw = NULL; | ||
| 124 | |||
| 125 | /* And established part... */ | ||
| 126 | sk_for_each(sk2, node, &head->chain) { | ||
| 127 | if (INET6_MATCH(sk2, hash, saddr, daddr, ports, dif)) | ||
| 128 | goto not_unique; | ||
| 129 | } | ||
| 130 | |||
| 131 | unique: | ||
| 132 | BUG_TRAP(sk_unhashed(sk)); | ||
| 133 | __sk_add_node(sk, &head->chain); | ||
| 134 | sk->sk_hash = hash; | ||
| 135 | sock_prot_inc_use(sk->sk_prot); | ||
| 136 | write_unlock(&head->lock); | ||
| 137 | |||
| 138 | if (twp != NULL) { | ||
| 139 | *twp = tw; | ||
| 140 | NET_INC_STATS_BH(LINUX_MIB_TIMEWAITRECYCLED); | ||
| 141 | } else if (tw != NULL) { | ||
| 142 | /* Silly. Should hash-dance instead... */ | ||
| 143 | inet_twsk_deschedule(tw, death_row); | ||
| 144 | NET_INC_STATS_BH(LINUX_MIB_TIMEWAITRECYCLED); | ||
| 145 | |||
| 146 | inet_twsk_put(tw); | ||
| 147 | } | ||
| 148 | return 0; | ||
| 149 | |||
| 150 | not_unique: | ||
| 151 | write_unlock(&head->lock); | ||
| 152 | return -EADDRNOTAVAIL; | ||
| 153 | } | ||
| 154 | |||
| 155 | static inline u32 inet6_sk_port_offset(const struct sock *sk) | ||
| 156 | { | ||
| 157 | const struct inet_sock *inet = inet_sk(sk); | ||
| 158 | const struct ipv6_pinfo *np = inet6_sk(sk); | ||
| 159 | return secure_ipv6_port_ephemeral(np->rcv_saddr.s6_addr32, | ||
| 160 | np->daddr.s6_addr32, | ||
| 161 | inet->dport); | ||
| 162 | } | ||
| 163 | |||
| 164 | int inet6_hash_connect(struct inet_timewait_death_row *death_row, | ||
| 165 | struct sock *sk) | ||
| 166 | { | ||
| 167 | struct inet_hashinfo *hinfo = death_row->hashinfo; | ||
| 168 | const unsigned short snum = inet_sk(sk)->num; | ||
| 169 | struct inet_bind_hashbucket *head; | ||
| 170 | struct inet_bind_bucket *tb; | ||
| 171 | int ret; | ||
| 172 | |||
| 173 | if (snum == 0) { | ||
| 174 | const int low = sysctl_local_port_range[0]; | ||
| 175 | const int high = sysctl_local_port_range[1]; | ||
| 176 | const int range = high - low; | ||
| 177 | int i, port; | ||
| 178 | static u32 hint; | ||
| 179 | const u32 offset = hint + inet6_sk_port_offset(sk); | ||
| 180 | struct hlist_node *node; | ||
| 181 | struct inet_timewait_sock *tw = NULL; | ||
| 182 | |||
| 183 | local_bh_disable(); | ||
| 184 | for (i = 1; i <= range; i++) { | ||
| 185 | port = low + (i + offset) % range; | ||
| 186 | head = &hinfo->bhash[inet_bhashfn(port, hinfo->bhash_size)]; | ||
| 187 | spin_lock(&head->lock); | ||
| 188 | |||
| 189 | /* Does not bother with rcv_saddr checks, | ||
| 190 | * because the established check is already | ||
| 191 | * unique enough. | ||
| 192 | */ | ||
| 193 | inet_bind_bucket_for_each(tb, node, &head->chain) { | ||
| 194 | if (tb->port == port) { | ||
| 195 | BUG_TRAP(!hlist_empty(&tb->owners)); | ||
| 196 | if (tb->fastreuse >= 0) | ||
| 197 | goto next_port; | ||
| 198 | if (!__inet6_check_established(death_row, | ||
| 199 | sk, port, | ||
| 200 | &tw)) | ||
| 201 | goto ok; | ||
| 202 | goto next_port; | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep, | ||
| 207 | head, port); | ||
| 208 | if (!tb) { | ||
| 209 | spin_unlock(&head->lock); | ||
| 210 | break; | ||
| 211 | } | ||
| 212 | tb->fastreuse = -1; | ||
| 213 | goto ok; | ||
| 214 | |||
| 215 | next_port: | ||
| 216 | spin_unlock(&head->lock); | ||
| 217 | } | ||
| 218 | local_bh_enable(); | ||
| 219 | |||
| 220 | return -EADDRNOTAVAIL; | ||
| 221 | |||
| 222 | ok: | ||
| 223 | hint += i; | ||
| 224 | |||
| 225 | /* Head lock still held and bh's disabled */ | ||
| 226 | inet_bind_hash(sk, tb, port); | ||
| 227 | if (sk_unhashed(sk)) { | ||
| 228 | inet_sk(sk)->sport = htons(port); | ||
| 229 | __inet6_hash(hinfo, sk); | ||
| 230 | } | ||
| 231 | spin_unlock(&head->lock); | ||
| 232 | |||
| 233 | if (tw) { | ||
| 234 | inet_twsk_deschedule(tw, death_row); | ||
| 235 | inet_twsk_put(tw); | ||
| 236 | } | ||
| 237 | |||
| 238 | ret = 0; | ||
| 239 | goto out; | ||
| 240 | } | ||
| 241 | |||
| 242 | head = &hinfo->bhash[inet_bhashfn(snum, hinfo->bhash_size)]; | ||
| 243 | tb = inet_csk(sk)->icsk_bind_hash; | ||
| 244 | spin_lock_bh(&head->lock); | ||
| 245 | |||
| 246 | if (sk_head(&tb->owners) == sk && sk->sk_bind_node.next == NULL) { | ||
| 247 | __inet6_hash(hinfo, sk); | ||
| 248 | spin_unlock_bh(&head->lock); | ||
| 249 | return 0; | ||
| 250 | } else { | ||
| 251 | spin_unlock(&head->lock); | ||
| 252 | /* No definite answer... Walk to established hash table */ | ||
| 253 | ret = __inet6_check_established(death_row, sk, snum, NULL); | ||
| 254 | out: | ||
| 255 | local_bh_enable(); | ||
| 256 | return ret; | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 260 | EXPORT_SYMBOL_GPL(inet6_hash_connect); | ||
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index 514b57bb80b7..a682eb9093e1 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c | |||
| @@ -119,177 +119,6 @@ static __u32 tcp_v6_init_sequence(struct sock *sk, struct sk_buff *skb) | |||
| 119 | } | 119 | } |
| 120 | } | 120 | } |
| 121 | 121 | ||
| 122 | static int __tcp_v6_check_established(struct sock *sk, const __u16 lport, | ||
| 123 | struct inet_timewait_sock **twp) | ||
| 124 | { | ||
| 125 | struct inet_sock *inet = inet_sk(sk); | ||
| 126 | const struct ipv6_pinfo *np = inet6_sk(sk); | ||
| 127 | const struct in6_addr *daddr = &np->rcv_saddr; | ||
| 128 | const struct in6_addr *saddr = &np->daddr; | ||
| 129 | const int dif = sk->sk_bound_dev_if; | ||
| 130 | const u32 ports = INET_COMBINED_PORTS(inet->dport, lport); | ||
| 131 | unsigned int hash = inet6_ehashfn(daddr, inet->num, saddr, inet->dport); | ||
| 132 | struct inet_ehash_bucket *head = inet_ehash_bucket(&tcp_hashinfo, hash); | ||
| 133 | struct sock *sk2; | ||
| 134 | const struct hlist_node *node; | ||
| 135 | struct inet_timewait_sock *tw; | ||
| 136 | |||
| 137 | prefetch(head->chain.first); | ||
| 138 | write_lock(&head->lock); | ||
| 139 | |||
| 140 | /* Check TIME-WAIT sockets first. */ | ||
| 141 | sk_for_each(sk2, node, &(head + tcp_hashinfo.ehash_size)->chain) { | ||
| 142 | const struct inet6_timewait_sock *tw6 = inet6_twsk(sk2); | ||
| 143 | |||
| 144 | tw = inet_twsk(sk2); | ||
| 145 | |||
| 146 | if(*((__u32 *)&(tw->tw_dport)) == ports && | ||
| 147 | sk2->sk_family == PF_INET6 && | ||
| 148 | ipv6_addr_equal(&tw6->tw_v6_daddr, saddr) && | ||
| 149 | ipv6_addr_equal(&tw6->tw_v6_rcv_saddr, daddr) && | ||
| 150 | sk2->sk_bound_dev_if == sk->sk_bound_dev_if) { | ||
| 151 | if (twsk_unique(sk, sk2, twp)) | ||
| 152 | goto unique; | ||
| 153 | else | ||
| 154 | goto not_unique; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | tw = NULL; | ||
| 158 | |||
| 159 | /* And established part... */ | ||
| 160 | sk_for_each(sk2, node, &head->chain) { | ||
| 161 | if (INET6_MATCH(sk2, hash, saddr, daddr, ports, dif)) | ||
| 162 | goto not_unique; | ||
| 163 | } | ||
| 164 | |||
| 165 | unique: | ||
| 166 | BUG_TRAP(sk_unhashed(sk)); | ||
| 167 | __sk_add_node(sk, &head->chain); | ||
| 168 | sk->sk_hash = hash; | ||
| 169 | sock_prot_inc_use(sk->sk_prot); | ||
| 170 | write_unlock(&head->lock); | ||
| 171 | |||
| 172 | if (twp) { | ||
| 173 | *twp = tw; | ||
| 174 | NET_INC_STATS_BH(LINUX_MIB_TIMEWAITRECYCLED); | ||
| 175 | } else if (tw) { | ||
| 176 | /* Silly. Should hash-dance instead... */ | ||
| 177 | inet_twsk_deschedule(tw, &tcp_death_row); | ||
| 178 | NET_INC_STATS_BH(LINUX_MIB_TIMEWAITRECYCLED); | ||
| 179 | |||
| 180 | inet_twsk_put(tw); | ||
| 181 | } | ||
| 182 | return 0; | ||
| 183 | |||
| 184 | not_unique: | ||
| 185 | write_unlock(&head->lock); | ||
| 186 | return -EADDRNOTAVAIL; | ||
| 187 | } | ||
| 188 | |||
| 189 | static inline u32 tcpv6_port_offset(const struct sock *sk) | ||
| 190 | { | ||
| 191 | const struct inet_sock *inet = inet_sk(sk); | ||
| 192 | const struct ipv6_pinfo *np = inet6_sk(sk); | ||
| 193 | |||
| 194 | return secure_tcpv6_port_ephemeral(np->rcv_saddr.s6_addr32, | ||
| 195 | np->daddr.s6_addr32, | ||
| 196 | inet->dport); | ||
| 197 | } | ||
| 198 | |||
| 199 | static int tcp_v6_hash_connect(struct sock *sk) | ||
| 200 | { | ||
| 201 | unsigned short snum = inet_sk(sk)->num; | ||
| 202 | struct inet_bind_hashbucket *head; | ||
| 203 | struct inet_bind_bucket *tb; | ||
| 204 | int ret; | ||
| 205 | |||
| 206 | if (!snum) { | ||
| 207 | int low = sysctl_local_port_range[0]; | ||
| 208 | int high = sysctl_local_port_range[1]; | ||
| 209 | int range = high - low; | ||
| 210 | int i; | ||
| 211 | int port; | ||
| 212 | static u32 hint; | ||
| 213 | u32 offset = hint + tcpv6_port_offset(sk); | ||
| 214 | struct hlist_node *node; | ||
| 215 | struct inet_timewait_sock *tw = NULL; | ||
| 216 | |||
| 217 | local_bh_disable(); | ||
| 218 | for (i = 1; i <= range; i++) { | ||
| 219 | port = low + (i + offset) % range; | ||
| 220 | head = &tcp_hashinfo.bhash[inet_bhashfn(port, tcp_hashinfo.bhash_size)]; | ||
| 221 | spin_lock(&head->lock); | ||
| 222 | |||
| 223 | /* Does not bother with rcv_saddr checks, | ||
| 224 | * because the established check is already | ||
| 225 | * unique enough. | ||
| 226 | */ | ||
| 227 | inet_bind_bucket_for_each(tb, node, &head->chain) { | ||
| 228 | if (tb->port == port) { | ||
| 229 | BUG_TRAP(!hlist_empty(&tb->owners)); | ||
| 230 | if (tb->fastreuse >= 0) | ||
| 231 | goto next_port; | ||
| 232 | if (!__tcp_v6_check_established(sk, | ||
| 233 | port, | ||
| 234 | &tw)) | ||
| 235 | goto ok; | ||
| 236 | goto next_port; | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | tb = inet_bind_bucket_create(tcp_hashinfo.bind_bucket_cachep, head, port); | ||
| 241 | if (!tb) { | ||
| 242 | spin_unlock(&head->lock); | ||
| 243 | break; | ||
| 244 | } | ||
| 245 | tb->fastreuse = -1; | ||
| 246 | goto ok; | ||
| 247 | |||
| 248 | next_port: | ||
| 249 | spin_unlock(&head->lock); | ||
| 250 | } | ||
| 251 | local_bh_enable(); | ||
| 252 | |||
| 253 | return -EADDRNOTAVAIL; | ||
| 254 | |||
| 255 | ok: | ||
| 256 | hint += i; | ||
| 257 | |||
| 258 | /* Head lock still held and bh's disabled */ | ||
| 259 | inet_bind_hash(sk, tb, port); | ||
| 260 | if (sk_unhashed(sk)) { | ||
| 261 | inet_sk(sk)->sport = htons(port); | ||
| 262 | __inet6_hash(&tcp_hashinfo, sk); | ||
| 263 | } | ||
| 264 | spin_unlock(&head->lock); | ||
| 265 | |||
| 266 | if (tw) { | ||
| 267 | inet_twsk_deschedule(tw, &tcp_death_row); | ||
| 268 | inet_twsk_put(tw); | ||
| 269 | } | ||
| 270 | |||
| 271 | ret = 0; | ||
| 272 | goto out; | ||
| 273 | } | ||
| 274 | |||
| 275 | head = &tcp_hashinfo.bhash[inet_bhashfn(snum, tcp_hashinfo.bhash_size)]; | ||
| 276 | tb = inet_csk(sk)->icsk_bind_hash; | ||
| 277 | spin_lock_bh(&head->lock); | ||
| 278 | |||
| 279 | if (sk_head(&tb->owners) == sk && !sk->sk_bind_node.next) { | ||
| 280 | __inet6_hash(&tcp_hashinfo, sk); | ||
| 281 | spin_unlock_bh(&head->lock); | ||
| 282 | return 0; | ||
| 283 | } else { | ||
| 284 | spin_unlock(&head->lock); | ||
| 285 | /* No definite answer... Walk to established hash table */ | ||
| 286 | ret = __tcp_v6_check_established(sk, snum, NULL); | ||
| 287 | out: | ||
| 288 | local_bh_enable(); | ||
| 289 | return ret; | ||
| 290 | } | ||
| 291 | } | ||
| 292 | |||
| 293 | static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, | 122 | static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, |
| 294 | int addr_len) | 123 | int addr_len) |
| 295 | { | 124 | { |
| @@ -450,7 +279,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, | |||
| 450 | inet->dport = usin->sin6_port; | 279 | inet->dport = usin->sin6_port; |
| 451 | 280 | ||
| 452 | tcp_set_state(sk, TCP_SYN_SENT); | 281 | tcp_set_state(sk, TCP_SYN_SENT); |
| 453 | err = tcp_v6_hash_connect(sk); | 282 | err = inet6_hash_connect(&tcp_death_row, sk); |
| 454 | if (err) | 283 | if (err) |
| 455 | goto late_failure; | 284 | goto late_failure; |
| 456 | 285 | ||
