diff options
-rw-r--r-- | net/netfilter/ipset/Kconfig | 9 | ||||
-rw-r--r-- | net/netfilter/ipset/Makefile | 1 | ||||
-rw-r--r-- | net/netfilter/ipset/ip_set_hash_net.c | 461 |
3 files changed, 471 insertions, 0 deletions
diff --git a/net/netfilter/ipset/Kconfig b/net/netfilter/ipset/Kconfig index e2fbaa9d902..8d85de00e9e 100644 --- a/net/netfilter/ipset/Kconfig +++ b/net/netfilter/ipset/Kconfig | |||
@@ -89,4 +89,13 @@ config IP_SET_HASH_IPPORTNET | |||
89 | 89 | ||
90 | To compile it as a module, choose M here. If unsure, say N. | 90 | To compile it as a module, choose M here. If unsure, say N. |
91 | 91 | ||
92 | config IP_SET_HASH_NET | ||
93 | tristate "hash:net set support" | ||
94 | depends on IP_SET | ||
95 | help | ||
96 | This option adds the hash:net set type support, by which | ||
97 | one can store IPv4/IPv6 network address/prefix elements in a set. | ||
98 | |||
99 | To compile it as a module, choose M here. If unsure, say N. | ||
100 | |||
92 | endif # IP_SET | 101 | endif # IP_SET |
diff --git a/net/netfilter/ipset/Makefile b/net/netfilter/ipset/Makefile index 9c5d857511c..fd5eeb6a7e5 100644 --- a/net/netfilter/ipset/Makefile +++ b/net/netfilter/ipset/Makefile | |||
@@ -17,3 +17,4 @@ obj-$(CONFIG_IP_SET_HASH_IP) += ip_set_hash_ip.o | |||
17 | obj-$(CONFIG_IP_SET_HASH_IPPORT) += ip_set_hash_ipport.o | 17 | obj-$(CONFIG_IP_SET_HASH_IPPORT) += ip_set_hash_ipport.o |
18 | obj-$(CONFIG_IP_SET_HASH_IPPORTIP) += ip_set_hash_ipportip.o | 18 | obj-$(CONFIG_IP_SET_HASH_IPPORTIP) += ip_set_hash_ipportip.o |
19 | obj-$(CONFIG_IP_SET_HASH_IPPORTNET) += ip_set_hash_ipportnet.o | 19 | obj-$(CONFIG_IP_SET_HASH_IPPORTNET) += ip_set_hash_ipportnet.o |
20 | obj-$(CONFIG_IP_SET_HASH_NET) += ip_set_hash_net.o | ||
diff --git a/net/netfilter/ipset/ip_set_hash_net.c b/net/netfilter/ipset/ip_set_hash_net.c new file mode 100644 index 00000000000..fb0e6a68d13 --- /dev/null +++ b/net/netfilter/ipset/ip_set_hash_net.c | |||
@@ -0,0 +1,461 @@ | |||
1 | /* Copyright (C) 2003-2011 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> | ||
2 | * | ||
3 | * This program is free software; you can redistribute it and/or modify | ||
4 | * it under the terms of the GNU General Public License version 2 as | ||
5 | * published by the Free Software Foundation. | ||
6 | */ | ||
7 | |||
8 | /* Kernel module implementing an IP set type: the hash:net type */ | ||
9 | |||
10 | #include <linux/jhash.h> | ||
11 | #include <linux/module.h> | ||
12 | #include <linux/ip.h> | ||
13 | #include <linux/skbuff.h> | ||
14 | #include <linux/errno.h> | ||
15 | #include <linux/uaccess.h> | ||
16 | #include <linux/bitops.h> | ||
17 | #include <linux/spinlock.h> | ||
18 | #include <linux/random.h> | ||
19 | #include <net/ip.h> | ||
20 | #include <net/ipv6.h> | ||
21 | #include <net/netlink.h> | ||
22 | |||
23 | #include <linux/netfilter.h> | ||
24 | #include <linux/netfilter/ipset/pfxlen.h> | ||
25 | #include <linux/netfilter/ipset/ip_set.h> | ||
26 | #include <linux/netfilter/ipset/ip_set_timeout.h> | ||
27 | #include <linux/netfilter/ipset/ip_set_hash.h> | ||
28 | |||
29 | MODULE_LICENSE("GPL"); | ||
30 | MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>"); | ||
31 | MODULE_DESCRIPTION("hash:net type of IP sets"); | ||
32 | MODULE_ALIAS("ip_set_hash:net"); | ||
33 | |||
34 | /* Type specific function prefix */ | ||
35 | #define TYPE hash_net | ||
36 | |||
37 | static bool | ||
38 | hash_net_same_set(const struct ip_set *a, const struct ip_set *b); | ||
39 | |||
40 | #define hash_net4_same_set hash_net_same_set | ||
41 | #define hash_net6_same_set hash_net_same_set | ||
42 | |||
43 | /* The type variant functions: IPv4 */ | ||
44 | |||
45 | /* Member elements without timeout */ | ||
46 | struct hash_net4_elem { | ||
47 | __be32 ip; | ||
48 | u16 padding0; | ||
49 | u8 padding1; | ||
50 | u8 cidr; | ||
51 | }; | ||
52 | |||
53 | /* Member elements with timeout support */ | ||
54 | struct hash_net4_telem { | ||
55 | __be32 ip; | ||
56 | u16 padding0; | ||
57 | u8 padding1; | ||
58 | u8 cidr; | ||
59 | unsigned long timeout; | ||
60 | }; | ||
61 | |||
62 | static inline bool | ||
63 | hash_net4_data_equal(const struct hash_net4_elem *ip1, | ||
64 | const struct hash_net4_elem *ip2) | ||
65 | { | ||
66 | return ip1->ip == ip2->ip && ip1->cidr == ip2->cidr; | ||
67 | } | ||
68 | |||
69 | static inline bool | ||
70 | hash_net4_data_isnull(const struct hash_net4_elem *elem) | ||
71 | { | ||
72 | return elem->cidr == 0; | ||
73 | } | ||
74 | |||
75 | static inline void | ||
76 | hash_net4_data_copy(struct hash_net4_elem *dst, | ||
77 | const struct hash_net4_elem *src) | ||
78 | { | ||
79 | dst->ip = src->ip; | ||
80 | dst->cidr = src->cidr; | ||
81 | } | ||
82 | |||
83 | static inline void | ||
84 | hash_net4_data_netmask(struct hash_net4_elem *elem, u8 cidr) | ||
85 | { | ||
86 | elem->ip &= ip_set_netmask(cidr); | ||
87 | elem->cidr = cidr; | ||
88 | } | ||
89 | |||
90 | /* Zero CIDR values cannot be stored */ | ||
91 | static inline void | ||
92 | hash_net4_data_zero_out(struct hash_net4_elem *elem) | ||
93 | { | ||
94 | elem->cidr = 0; | ||
95 | } | ||
96 | |||
97 | static bool | ||
98 | hash_net4_data_list(struct sk_buff *skb, const struct hash_net4_elem *data) | ||
99 | { | ||
100 | NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, data->ip); | ||
101 | NLA_PUT_U8(skb, IPSET_ATTR_CIDR, data->cidr); | ||
102 | return 0; | ||
103 | |||
104 | nla_put_failure: | ||
105 | return 1; | ||
106 | } | ||
107 | |||
108 | static bool | ||
109 | hash_net4_data_tlist(struct sk_buff *skb, const struct hash_net4_elem *data) | ||
110 | { | ||
111 | const struct hash_net4_telem *tdata = | ||
112 | (const struct hash_net4_telem *)data; | ||
113 | |||
114 | NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, tdata->ip); | ||
115 | NLA_PUT_U8(skb, IPSET_ATTR_CIDR, tdata->cidr); | ||
116 | NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, | ||
117 | htonl(ip_set_timeout_get(tdata->timeout))); | ||
118 | |||
119 | return 0; | ||
120 | |||
121 | nla_put_failure: | ||
122 | return 1; | ||
123 | } | ||
124 | |||
125 | #define IP_SET_HASH_WITH_NETS | ||
126 | |||
127 | #define PF 4 | ||
128 | #define HOST_MASK 32 | ||
129 | #include <linux/netfilter/ipset/ip_set_ahash.h> | ||
130 | |||
131 | static int | ||
132 | hash_net4_kadt(struct ip_set *set, const struct sk_buff *skb, | ||
133 | enum ipset_adt adt, u8 pf, u8 dim, u8 flags) | ||
134 | { | ||
135 | const struct ip_set_hash *h = set->data; | ||
136 | ipset_adtfn adtfn = set->variant->adt[adt]; | ||
137 | struct hash_net4_elem data = { .cidr = h->nets[0].cidr || HOST_MASK }; | ||
138 | |||
139 | if (data.cidr == 0) | ||
140 | return -EINVAL; | ||
141 | if (adt == IPSET_TEST) | ||
142 | data.cidr = HOST_MASK; | ||
143 | |||
144 | ip4addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip); | ||
145 | data.ip &= ip_set_netmask(data.cidr); | ||
146 | |||
147 | return adtfn(set, &data, h->timeout); | ||
148 | } | ||
149 | |||
150 | static int | ||
151 | hash_net4_uadt(struct ip_set *set, struct nlattr *tb[], | ||
152 | enum ipset_adt adt, u32 *lineno, u32 flags) | ||
153 | { | ||
154 | const struct ip_set_hash *h = set->data; | ||
155 | ipset_adtfn adtfn = set->variant->adt[adt]; | ||
156 | struct hash_net4_elem data = { .cidr = HOST_MASK }; | ||
157 | u32 timeout = h->timeout; | ||
158 | int ret; | ||
159 | |||
160 | if (unlikely(!tb[IPSET_ATTR_IP] || | ||
161 | !ip_set_optattr_netorder(tb, IPSET_ATTR_TIMEOUT))) | ||
162 | return -IPSET_ERR_PROTOCOL; | ||
163 | |||
164 | if (tb[IPSET_ATTR_LINENO]) | ||
165 | *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); | ||
166 | |||
167 | ret = ip_set_get_ipaddr4(tb[IPSET_ATTR_IP], &data.ip); | ||
168 | if (ret) | ||
169 | return ret; | ||
170 | |||
171 | if (tb[IPSET_ATTR_CIDR]) | ||
172 | data.cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); | ||
173 | |||
174 | if (!data.cidr) | ||
175 | return -IPSET_ERR_INVALID_CIDR; | ||
176 | |||
177 | data.ip &= ip_set_netmask(data.cidr); | ||
178 | |||
179 | if (tb[IPSET_ATTR_TIMEOUT]) { | ||
180 | if (!with_timeout(h->timeout)) | ||
181 | return -IPSET_ERR_TIMEOUT; | ||
182 | timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); | ||
183 | } | ||
184 | |||
185 | ret = adtfn(set, &data, timeout); | ||
186 | |||
187 | return ip_set_eexist(ret, flags) ? 0 : ret; | ||
188 | } | ||
189 | |||
190 | static bool | ||
191 | hash_net_same_set(const struct ip_set *a, const struct ip_set *b) | ||
192 | { | ||
193 | const struct ip_set_hash *x = a->data; | ||
194 | const struct ip_set_hash *y = b->data; | ||
195 | |||
196 | /* Resizing changes htable_bits, so we ignore it */ | ||
197 | return x->maxelem == y->maxelem && | ||
198 | x->timeout == y->timeout; | ||
199 | } | ||
200 | |||
201 | /* The type variant functions: IPv6 */ | ||
202 | |||
203 | struct hash_net6_elem { | ||
204 | union nf_inet_addr ip; | ||
205 | u16 padding0; | ||
206 | u8 padding1; | ||
207 | u8 cidr; | ||
208 | }; | ||
209 | |||
210 | struct hash_net6_telem { | ||
211 | union nf_inet_addr ip; | ||
212 | u16 padding0; | ||
213 | u8 padding1; | ||
214 | u8 cidr; | ||
215 | unsigned long timeout; | ||
216 | }; | ||
217 | |||
218 | static inline bool | ||
219 | hash_net6_data_equal(const struct hash_net6_elem *ip1, | ||
220 | const struct hash_net6_elem *ip2) | ||
221 | { | ||
222 | return ipv6_addr_cmp(&ip1->ip.in6, &ip2->ip.in6) == 0 && | ||
223 | ip1->cidr == ip2->cidr; | ||
224 | } | ||
225 | |||
226 | static inline bool | ||
227 | hash_net6_data_isnull(const struct hash_net6_elem *elem) | ||
228 | { | ||
229 | return elem->cidr == 0; | ||
230 | } | ||
231 | |||
232 | static inline void | ||
233 | hash_net6_data_copy(struct hash_net6_elem *dst, | ||
234 | const struct hash_net6_elem *src) | ||
235 | { | ||
236 | ipv6_addr_copy(&dst->ip.in6, &src->ip.in6); | ||
237 | dst->cidr = src->cidr; | ||
238 | } | ||
239 | |||
240 | static inline void | ||
241 | hash_net6_data_zero_out(struct hash_net6_elem *elem) | ||
242 | { | ||
243 | elem->cidr = 0; | ||
244 | } | ||
245 | |||
246 | static inline void | ||
247 | ip6_netmask(union nf_inet_addr *ip, u8 prefix) | ||
248 | { | ||
249 | ip->ip6[0] &= ip_set_netmask6(prefix)[0]; | ||
250 | ip->ip6[1] &= ip_set_netmask6(prefix)[1]; | ||
251 | ip->ip6[2] &= ip_set_netmask6(prefix)[2]; | ||
252 | ip->ip6[3] &= ip_set_netmask6(prefix)[3]; | ||
253 | } | ||
254 | |||
255 | static inline void | ||
256 | hash_net6_data_netmask(struct hash_net6_elem *elem, u8 cidr) | ||
257 | { | ||
258 | ip6_netmask(&elem->ip, cidr); | ||
259 | elem->cidr = cidr; | ||
260 | } | ||
261 | |||
262 | static bool | ||
263 | hash_net6_data_list(struct sk_buff *skb, const struct hash_net6_elem *data) | ||
264 | { | ||
265 | NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &data->ip); | ||
266 | NLA_PUT_U8(skb, IPSET_ATTR_CIDR, data->cidr); | ||
267 | return 0; | ||
268 | |||
269 | nla_put_failure: | ||
270 | return 1; | ||
271 | } | ||
272 | |||
273 | static bool | ||
274 | hash_net6_data_tlist(struct sk_buff *skb, const struct hash_net6_elem *data) | ||
275 | { | ||
276 | const struct hash_net6_telem *e = | ||
277 | (const struct hash_net6_telem *)data; | ||
278 | |||
279 | NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &e->ip); | ||
280 | NLA_PUT_U8(skb, IPSET_ATTR_CIDR, e->cidr); | ||
281 | NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, | ||
282 | htonl(ip_set_timeout_get(e->timeout))); | ||
283 | return 0; | ||
284 | |||
285 | nla_put_failure: | ||
286 | return 1; | ||
287 | } | ||
288 | |||
289 | #undef PF | ||
290 | #undef HOST_MASK | ||
291 | |||
292 | #define PF 6 | ||
293 | #define HOST_MASK 128 | ||
294 | #include <linux/netfilter/ipset/ip_set_ahash.h> | ||
295 | |||
296 | static int | ||
297 | hash_net6_kadt(struct ip_set *set, const struct sk_buff *skb, | ||
298 | enum ipset_adt adt, u8 pf, u8 dim, u8 flags) | ||
299 | { | ||
300 | const struct ip_set_hash *h = set->data; | ||
301 | ipset_adtfn adtfn = set->variant->adt[adt]; | ||
302 | struct hash_net6_elem data = { .cidr = h->nets[0].cidr || HOST_MASK }; | ||
303 | |||
304 | if (data.cidr == 0) | ||
305 | return -EINVAL; | ||
306 | if (adt == IPSET_TEST) | ||
307 | data.cidr = HOST_MASK; | ||
308 | |||
309 | ip6addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip.in6); | ||
310 | ip6_netmask(&data.ip, data.cidr); | ||
311 | |||
312 | return adtfn(set, &data, h->timeout); | ||
313 | } | ||
314 | |||
315 | static int | ||
316 | hash_net6_uadt(struct ip_set *set, struct nlattr *tb[], | ||
317 | enum ipset_adt adt, u32 *lineno, u32 flags) | ||
318 | { | ||
319 | const struct ip_set_hash *h = set->data; | ||
320 | ipset_adtfn adtfn = set->variant->adt[adt]; | ||
321 | struct hash_net6_elem data = { .cidr = HOST_MASK }; | ||
322 | u32 timeout = h->timeout; | ||
323 | int ret; | ||
324 | |||
325 | if (unlikely(!tb[IPSET_ATTR_IP] || | ||
326 | !ip_set_optattr_netorder(tb, IPSET_ATTR_TIMEOUT))) | ||
327 | return -IPSET_ERR_PROTOCOL; | ||
328 | |||
329 | if (tb[IPSET_ATTR_LINENO]) | ||
330 | *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); | ||
331 | |||
332 | ret = ip_set_get_ipaddr6(tb[IPSET_ATTR_IP], &data.ip); | ||
333 | if (ret) | ||
334 | return ret; | ||
335 | |||
336 | if (tb[IPSET_ATTR_CIDR]) | ||
337 | data.cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); | ||
338 | |||
339 | if (!data.cidr) | ||
340 | return -IPSET_ERR_INVALID_CIDR; | ||
341 | |||
342 | ip6_netmask(&data.ip, data.cidr); | ||
343 | |||
344 | if (tb[IPSET_ATTR_TIMEOUT]) { | ||
345 | if (!with_timeout(h->timeout)) | ||
346 | return -IPSET_ERR_TIMEOUT; | ||
347 | timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); | ||
348 | } | ||
349 | |||
350 | ret = adtfn(set, &data, timeout); | ||
351 | |||
352 | return ip_set_eexist(ret, flags) ? 0 : ret; | ||
353 | } | ||
354 | |||
355 | /* Create hash:ip type of sets */ | ||
356 | |||
357 | static int | ||
358 | hash_net_create(struct ip_set *set, struct nlattr *tb[], u32 flags) | ||
359 | { | ||
360 | u32 hashsize = IPSET_DEFAULT_HASHSIZE, maxelem = IPSET_DEFAULT_MAXELEM; | ||
361 | struct ip_set_hash *h; | ||
362 | u8 hbits; | ||
363 | |||
364 | if (!(set->family == AF_INET || set->family == AF_INET6)) | ||
365 | return -IPSET_ERR_INVALID_FAMILY; | ||
366 | |||
367 | if (unlikely(!ip_set_optattr_netorder(tb, IPSET_ATTR_HASHSIZE) || | ||
368 | !ip_set_optattr_netorder(tb, IPSET_ATTR_MAXELEM) || | ||
369 | !ip_set_optattr_netorder(tb, IPSET_ATTR_TIMEOUT))) | ||
370 | return -IPSET_ERR_PROTOCOL; | ||
371 | |||
372 | if (tb[IPSET_ATTR_HASHSIZE]) { | ||
373 | hashsize = ip_set_get_h32(tb[IPSET_ATTR_HASHSIZE]); | ||
374 | if (hashsize < IPSET_MIMINAL_HASHSIZE) | ||
375 | hashsize = IPSET_MIMINAL_HASHSIZE; | ||
376 | } | ||
377 | |||
378 | if (tb[IPSET_ATTR_MAXELEM]) | ||
379 | maxelem = ip_set_get_h32(tb[IPSET_ATTR_MAXELEM]); | ||
380 | |||
381 | h = kzalloc(sizeof(*h) | ||
382 | + sizeof(struct ip_set_hash_nets) | ||
383 | * (set->family == AF_INET ? 32 : 128), GFP_KERNEL); | ||
384 | if (!h) | ||
385 | return -ENOMEM; | ||
386 | |||
387 | h->maxelem = maxelem; | ||
388 | get_random_bytes(&h->initval, sizeof(h->initval)); | ||
389 | h->timeout = IPSET_NO_TIMEOUT; | ||
390 | |||
391 | hbits = htable_bits(hashsize); | ||
392 | h->table = ip_set_alloc( | ||
393 | sizeof(struct htable) | ||
394 | + jhash_size(hbits) * sizeof(struct hbucket)); | ||
395 | if (!h->table) { | ||
396 | kfree(h); | ||
397 | return -ENOMEM; | ||
398 | } | ||
399 | h->table->htable_bits = hbits; | ||
400 | |||
401 | set->data = h; | ||
402 | |||
403 | if (tb[IPSET_ATTR_TIMEOUT]) { | ||
404 | h->timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); | ||
405 | |||
406 | set->variant = set->family == AF_INET | ||
407 | ? &hash_net4_tvariant : &hash_net6_tvariant; | ||
408 | |||
409 | if (set->family == AF_INET) | ||
410 | hash_net4_gc_init(set); | ||
411 | else | ||
412 | hash_net6_gc_init(set); | ||
413 | } else { | ||
414 | set->variant = set->family == AF_INET | ||
415 | ? &hash_net4_variant : &hash_net6_variant; | ||
416 | } | ||
417 | |||
418 | pr_debug("create %s hashsize %u (%u) maxelem %u: %p(%p)\n", | ||
419 | set->name, jhash_size(h->table->htable_bits), | ||
420 | h->table->htable_bits, h->maxelem, set->data, h->table); | ||
421 | |||
422 | return 0; | ||
423 | } | ||
424 | |||
425 | static struct ip_set_type hash_net_type __read_mostly = { | ||
426 | .name = "hash:net", | ||
427 | .protocol = IPSET_PROTOCOL, | ||
428 | .features = IPSET_TYPE_IP, | ||
429 | .dimension = IPSET_DIM_ONE, | ||
430 | .family = AF_UNSPEC, | ||
431 | .revision = 0, | ||
432 | .create = hash_net_create, | ||
433 | .create_policy = { | ||
434 | [IPSET_ATTR_HASHSIZE] = { .type = NLA_U32 }, | ||
435 | [IPSET_ATTR_MAXELEM] = { .type = NLA_U32 }, | ||
436 | [IPSET_ATTR_PROBES] = { .type = NLA_U8 }, | ||
437 | [IPSET_ATTR_RESIZE] = { .type = NLA_U8 }, | ||
438 | [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, | ||
439 | }, | ||
440 | .adt_policy = { | ||
441 | [IPSET_ATTR_IP] = { .type = NLA_NESTED }, | ||
442 | [IPSET_ATTR_CIDR] = { .type = NLA_U8 }, | ||
443 | [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, | ||
444 | }, | ||
445 | .me = THIS_MODULE, | ||
446 | }; | ||
447 | |||
448 | static int __init | ||
449 | hash_net_init(void) | ||
450 | { | ||
451 | return ip_set_type_register(&hash_net_type); | ||
452 | } | ||
453 | |||
454 | static void __exit | ||
455 | hash_net_fini(void) | ||
456 | { | ||
457 | ip_set_type_unregister(&hash_net_type); | ||
458 | } | ||
459 | |||
460 | module_init(hash_net_init); | ||
461 | module_exit(hash_net_fini); | ||