diff options
| -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) { |
