diff options
Diffstat (limited to 'net/ipv4/inet_hashtables.c')
-rw-r--r-- | net/ipv4/inet_hashtables.c | 28 |
1 files changed, 22 insertions, 6 deletions
diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c index fa3ae8148710..0ce0595d9861 100644 --- a/net/ipv4/inet_hashtables.c +++ b/net/ipv4/inet_hashtables.c | |||
@@ -39,6 +39,7 @@ struct inet_bind_bucket *inet_bind_bucket_create(struct kmem_cache *cachep, | |||
39 | write_pnet(&tb->ib_net, hold_net(net)); | 39 | write_pnet(&tb->ib_net, hold_net(net)); |
40 | tb->port = snum; | 40 | tb->port = snum; |
41 | tb->fastreuse = 0; | 41 | tb->fastreuse = 0; |
42 | tb->fastreuseport = 0; | ||
42 | tb->num_owners = 0; | 43 | tb->num_owners = 0; |
43 | INIT_HLIST_HEAD(&tb->owners); | 44 | INIT_HLIST_HEAD(&tb->owners); |
44 | hlist_add_head(&tb->node, &head->chain); | 45 | hlist_add_head(&tb->node, &head->chain); |
@@ -151,16 +152,16 @@ static inline int compute_score(struct sock *sk, struct net *net, | |||
151 | if (net_eq(sock_net(sk), net) && inet->inet_num == hnum && | 152 | if (net_eq(sock_net(sk), net) && inet->inet_num == hnum && |
152 | !ipv6_only_sock(sk)) { | 153 | !ipv6_only_sock(sk)) { |
153 | __be32 rcv_saddr = inet->inet_rcv_saddr; | 154 | __be32 rcv_saddr = inet->inet_rcv_saddr; |
154 | score = sk->sk_family == PF_INET ? 1 : 0; | 155 | score = sk->sk_family == PF_INET ? 2 : 1; |
155 | if (rcv_saddr) { | 156 | if (rcv_saddr) { |
156 | if (rcv_saddr != daddr) | 157 | if (rcv_saddr != daddr) |
157 | return -1; | 158 | return -1; |
158 | score += 2; | 159 | score += 4; |
159 | } | 160 | } |
160 | if (sk->sk_bound_dev_if) { | 161 | if (sk->sk_bound_dev_if) { |
161 | if (sk->sk_bound_dev_if != dif) | 162 | if (sk->sk_bound_dev_if != dif) |
162 | return -1; | 163 | return -1; |
163 | score += 2; | 164 | score += 4; |
164 | } | 165 | } |
165 | } | 166 | } |
166 | return score; | 167 | return score; |
@@ -176,6 +177,7 @@ static inline int compute_score(struct sock *sk, struct net *net, | |||
176 | 177 | ||
177 | struct sock *__inet_lookup_listener(struct net *net, | 178 | struct sock *__inet_lookup_listener(struct net *net, |
178 | struct inet_hashinfo *hashinfo, | 179 | struct inet_hashinfo *hashinfo, |
180 | const __be32 saddr, __be16 sport, | ||
179 | const __be32 daddr, const unsigned short hnum, | 181 | const __be32 daddr, const unsigned short hnum, |
180 | const int dif) | 182 | const int dif) |
181 | { | 183 | { |
@@ -183,17 +185,29 @@ struct sock *__inet_lookup_listener(struct net *net, | |||
183 | struct hlist_nulls_node *node; | 185 | struct hlist_nulls_node *node; |
184 | unsigned int hash = inet_lhashfn(net, hnum); | 186 | unsigned int hash = inet_lhashfn(net, hnum); |
185 | struct inet_listen_hashbucket *ilb = &hashinfo->listening_hash[hash]; | 187 | struct inet_listen_hashbucket *ilb = &hashinfo->listening_hash[hash]; |
186 | int score, hiscore; | 188 | int score, hiscore, matches = 0, reuseport = 0; |
189 | u32 phash = 0; | ||
187 | 190 | ||
188 | rcu_read_lock(); | 191 | rcu_read_lock(); |
189 | begin: | 192 | begin: |
190 | result = NULL; | 193 | result = NULL; |
191 | hiscore = -1; | 194 | hiscore = 0; |
192 | sk_nulls_for_each_rcu(sk, node, &ilb->head) { | 195 | sk_nulls_for_each_rcu(sk, node, &ilb->head) { |
193 | score = compute_score(sk, net, hnum, daddr, dif); | 196 | score = compute_score(sk, net, hnum, daddr, dif); |
194 | if (score > hiscore) { | 197 | if (score > hiscore) { |
195 | result = sk; | 198 | result = sk; |
196 | hiscore = score; | 199 | hiscore = score; |
200 | reuseport = sk->sk_reuseport; | ||
201 | if (reuseport) { | ||
202 | phash = inet_ehashfn(net, daddr, hnum, | ||
203 | saddr, sport); | ||
204 | matches = 1; | ||
205 | } | ||
206 | } else if (score == hiscore && reuseport) { | ||
207 | matches++; | ||
208 | if (((u64)phash * matches) >> 32 == 0) | ||
209 | result = sk; | ||
210 | phash = next_pseudo_random32(phash); | ||
197 | } | 211 | } |
198 | } | 212 | } |
199 | /* | 213 | /* |
@@ -501,7 +515,8 @@ int __inet_hash_connect(struct inet_timewait_death_row *death_row, | |||
501 | inet_bind_bucket_for_each(tb, node, &head->chain) { | 515 | inet_bind_bucket_for_each(tb, node, &head->chain) { |
502 | if (net_eq(ib_net(tb), net) && | 516 | if (net_eq(ib_net(tb), net) && |
503 | tb->port == port) { | 517 | tb->port == port) { |
504 | if (tb->fastreuse >= 0) | 518 | if (tb->fastreuse >= 0 || |
519 | tb->fastreuseport >= 0) | ||
505 | goto next_port; | 520 | goto next_port; |
506 | WARN_ON(hlist_empty(&tb->owners)); | 521 | WARN_ON(hlist_empty(&tb->owners)); |
507 | if (!check_established(death_row, sk, | 522 | if (!check_established(death_row, sk, |
@@ -518,6 +533,7 @@ int __inet_hash_connect(struct inet_timewait_death_row *death_row, | |||
518 | break; | 533 | break; |
519 | } | 534 | } |
520 | tb->fastreuse = -1; | 535 | tb->fastreuse = -1; |
536 | tb->fastreuseport = -1; | ||
521 | goto ok; | 537 | goto ok; |
522 | 538 | ||
523 | next_port: | 539 | next_port: |