diff options
Diffstat (limited to 'net')
-rw-r--r-- | net/ipv6/udp.c | 112 |
1 files changed, 109 insertions, 3 deletions
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index 1e5fadd997b7..f580cf925112 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c | |||
@@ -146,6 +146,88 @@ static inline int compute_score(struct sock *sk, struct net *net, | |||
146 | return score; | 146 | return score; |
147 | } | 147 | } |
148 | 148 | ||
149 | #define SCORE2_MAX (1 + 1 + 1) | ||
150 | static inline int compute_score2(struct sock *sk, struct net *net, | ||
151 | const struct in6_addr *saddr, __be16 sport, | ||
152 | const struct in6_addr *daddr, unsigned short hnum, | ||
153 | int dif) | ||
154 | { | ||
155 | int score = -1; | ||
156 | |||
157 | if (net_eq(sock_net(sk), net) && udp_sk(sk)->udp_port_hash == hnum && | ||
158 | sk->sk_family == PF_INET6) { | ||
159 | struct ipv6_pinfo *np = inet6_sk(sk); | ||
160 | struct inet_sock *inet = inet_sk(sk); | ||
161 | |||
162 | if (!ipv6_addr_equal(&np->rcv_saddr, daddr)) | ||
163 | return -1; | ||
164 | score = 0; | ||
165 | if (inet->inet_dport) { | ||
166 | if (inet->inet_dport != sport) | ||
167 | return -1; | ||
168 | score++; | ||
169 | } | ||
170 | if (!ipv6_addr_any(&np->daddr)) { | ||
171 | if (!ipv6_addr_equal(&np->daddr, saddr)) | ||
172 | return -1; | ||
173 | score++; | ||
174 | } | ||
175 | if (sk->sk_bound_dev_if) { | ||
176 | if (sk->sk_bound_dev_if != dif) | ||
177 | return -1; | ||
178 | score++; | ||
179 | } | ||
180 | } | ||
181 | return score; | ||
182 | } | ||
183 | |||
184 | #define udp_portaddr_for_each_entry_rcu(__sk, node, list) \ | ||
185 | hlist_nulls_for_each_entry_rcu(__sk, node, list, __sk_common.skc_portaddr_node) | ||
186 | |||
187 | /* called with read_rcu_lock() */ | ||
188 | static struct sock *udp6_lib_lookup2(struct net *net, | ||
189 | const struct in6_addr *saddr, __be16 sport, | ||
190 | const struct in6_addr *daddr, unsigned int hnum, int dif, | ||
191 | struct udp_hslot *hslot2, unsigned int slot2) | ||
192 | { | ||
193 | struct sock *sk, *result; | ||
194 | struct hlist_nulls_node *node; | ||
195 | int score, badness; | ||
196 | |||
197 | begin: | ||
198 | result = NULL; | ||
199 | badness = -1; | ||
200 | udp_portaddr_for_each_entry_rcu(sk, node, &hslot2->head) { | ||
201 | score = compute_score2(sk, net, saddr, sport, | ||
202 | daddr, hnum, dif); | ||
203 | if (score > badness) { | ||
204 | result = sk; | ||
205 | badness = score; | ||
206 | if (score == SCORE2_MAX) | ||
207 | goto exact_match; | ||
208 | } | ||
209 | } | ||
210 | /* | ||
211 | * if the nulls value we got at the end of this lookup is | ||
212 | * not the expected one, we must restart lookup. | ||
213 | * We probably met an item that was moved to another chain. | ||
214 | */ | ||
215 | if (get_nulls_value(node) != slot2) | ||
216 | goto begin; | ||
217 | |||
218 | if (result) { | ||
219 | exact_match: | ||
220 | if (unlikely(!atomic_inc_not_zero(&result->sk_refcnt))) | ||
221 | result = NULL; | ||
222 | else if (unlikely(compute_score2(result, net, saddr, sport, | ||
223 | daddr, hnum, dif) < badness)) { | ||
224 | sock_put(result); | ||
225 | goto begin; | ||
226 | } | ||
227 | } | ||
228 | return result; | ||
229 | } | ||
230 | |||
149 | static struct sock *__udp6_lib_lookup(struct net *net, | 231 | static struct sock *__udp6_lib_lookup(struct net *net, |
150 | struct in6_addr *saddr, __be16 sport, | 232 | struct in6_addr *saddr, __be16 sport, |
151 | struct in6_addr *daddr, __be16 dport, | 233 | struct in6_addr *daddr, __be16 dport, |
@@ -154,11 +236,35 @@ static struct sock *__udp6_lib_lookup(struct net *net, | |||
154 | struct sock *sk, *result; | 236 | struct sock *sk, *result; |
155 | struct hlist_nulls_node *node; | 237 | struct hlist_nulls_node *node; |
156 | unsigned short hnum = ntohs(dport); | 238 | unsigned short hnum = ntohs(dport); |
157 | unsigned int hash = udp_hashfn(net, hnum, udptable->mask); | 239 | unsigned int hash2, slot2, slot = udp_hashfn(net, hnum, udptable->mask); |
158 | struct udp_hslot *hslot = &udptable->hash[hash]; | 240 | struct udp_hslot *hslot2, *hslot = &udptable->hash[slot]; |
159 | int score, badness; | 241 | int score, badness; |
160 | 242 | ||
161 | rcu_read_lock(); | 243 | rcu_read_lock(); |
244 | if (hslot->count > 10) { | ||
245 | hash2 = udp6_portaddr_hash(net, daddr, hnum); | ||
246 | slot2 = hash2 & udptable->mask; | ||
247 | hslot2 = &udptable->hash2[slot2]; | ||
248 | if (hslot->count < hslot2->count) | ||
249 | goto begin; | ||
250 | |||
251 | result = udp6_lib_lookup2(net, saddr, sport, | ||
252 | daddr, hnum, dif, | ||
253 | hslot2, slot2); | ||
254 | if (!result) { | ||
255 | hash2 = udp6_portaddr_hash(net, &in6addr_any, hnum); | ||
256 | slot2 = hash2 & udptable->mask; | ||
257 | hslot2 = &udptable->hash2[slot2]; | ||
258 | if (hslot->count < hslot2->count) | ||
259 | goto begin; | ||
260 | |||
261 | result = udp6_lib_lookup2(net, &in6addr_any, sport, | ||
262 | daddr, hnum, dif, | ||
263 | hslot2, slot2); | ||
264 | } | ||
265 | rcu_read_unlock(); | ||
266 | return result; | ||
267 | } | ||
162 | begin: | 268 | begin: |
163 | result = NULL; | 269 | result = NULL; |
164 | badness = -1; | 270 | badness = -1; |
@@ -174,7 +280,7 @@ begin: | |||
174 | * not the expected one, we must restart lookup. | 280 | * not the expected one, we must restart lookup. |
175 | * We probably met an item that was moved to another chain. | 281 | * We probably met an item that was moved to another chain. |
176 | */ | 282 | */ |
177 | if (get_nulls_value(node) != hash) | 283 | if (get_nulls_value(node) != slot) |
178 | goto begin; | 284 | goto begin; |
179 | 285 | ||
180 | if (result) { | 286 | if (result) { |