diff options
author | Hans Schillstrom <hans.schillstrom@ericsson.com> | 2012-05-02 03:49:47 -0400 |
---|---|---|
committer | Pablo Neira Ayuso <pablo@netfilter.org> | 2012-05-09 06:54:05 -0400 |
commit | cf308a1fae432f315989e2da6878bfaa3daa22b1 (patch) | |
tree | 1997e797a147cc51df1b51eaf9744e84aa956838 | |
parent | 84018f55ab883f03d41ec3c9ac7f0cc80830b20f (diff) |
netfilter: add xt_hmark target for hash-based skb marking
The target allows you to create rules in the "raw" and "mangle" tables
which set the skbuff mark by means of hash calculation within a given
range. The nfmark can influence the routing method (see "Use netfilter
MARK value as routing key") and can also be used by other subsystems to
change their behaviour.
[ Part of this patch has been refactorized and modified by Pablo Neira Ayuso ]
Signed-off-by: Hans Schillstrom <hans.schillstrom@ericsson.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
-rw-r--r-- | include/linux/netfilter/xt_HMARK.h | 45 | ||||
-rw-r--r-- | net/netfilter/Kconfig | 15 | ||||
-rw-r--r-- | net/netfilter/Makefile | 1 | ||||
-rw-r--r-- | net/netfilter/xt_HMARK.c | 362 |
4 files changed, 423 insertions, 0 deletions
diff --git a/include/linux/netfilter/xt_HMARK.h b/include/linux/netfilter/xt_HMARK.h new file mode 100644 index 000000000000..abb1650940d2 --- /dev/null +++ b/include/linux/netfilter/xt_HMARK.h | |||
@@ -0,0 +1,45 @@ | |||
1 | #ifndef XT_HMARK_H_ | ||
2 | #define XT_HMARK_H_ | ||
3 | |||
4 | #include <linux/types.h> | ||
5 | |||
6 | enum { | ||
7 | XT_HMARK_SADDR_MASK, | ||
8 | XT_HMARK_DADDR_MASK, | ||
9 | XT_HMARK_SPI, | ||
10 | XT_HMARK_SPI_MASK, | ||
11 | XT_HMARK_SPORT, | ||
12 | XT_HMARK_DPORT, | ||
13 | XT_HMARK_SPORT_MASK, | ||
14 | XT_HMARK_DPORT_MASK, | ||
15 | XT_HMARK_PROTO_MASK, | ||
16 | XT_HMARK_RND, | ||
17 | XT_HMARK_MODULUS, | ||
18 | XT_HMARK_OFFSET, | ||
19 | XT_HMARK_CT, | ||
20 | XT_HMARK_METHOD_L3, | ||
21 | XT_HMARK_METHOD_L3_4, | ||
22 | }; | ||
23 | #define XT_HMARK_FLAG(flag) (1 << flag) | ||
24 | |||
25 | union hmark_ports { | ||
26 | struct { | ||
27 | __u16 src; | ||
28 | __u16 dst; | ||
29 | } p16; | ||
30 | __u32 v32; | ||
31 | }; | ||
32 | |||
33 | struct xt_hmark_info { | ||
34 | union nf_inet_addr src_mask; | ||
35 | union nf_inet_addr dst_mask; | ||
36 | union hmark_ports port_mask; | ||
37 | union hmark_ports port_set; | ||
38 | __u32 flags; | ||
39 | __u16 proto_mask; | ||
40 | __u32 hashrnd; | ||
41 | __u32 hmodulus; | ||
42 | __u32 hoffset; /* Mark offset to start from */ | ||
43 | }; | ||
44 | |||
45 | #endif /* XT_HMARK_H_ */ | ||
diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig index 0c6f67e8f2e5..209c1ed43368 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig | |||
@@ -509,6 +509,21 @@ config NETFILTER_XT_TARGET_HL | |||
509 | since you can easily create immortal packets that loop | 509 | since you can easily create immortal packets that loop |
510 | forever on the network. | 510 | forever on the network. |
511 | 511 | ||
512 | config NETFILTER_XT_TARGET_HMARK | ||
513 | tristate '"HMARK" target support' | ||
514 | depends on (IP6_NF_IPTABLES || IP6_NF_IPTABLES=n) | ||
515 | depends on NETFILTER_ADVANCED | ||
516 | ---help--- | ||
517 | This option adds the "HMARK" target. | ||
518 | |||
519 | The target allows you to create rules in the "raw" and "mangle" tables | ||
520 | which set the skbuff mark by means of hash calculation within a given | ||
521 | range. The nfmark can influence the routing method (see "Use netfilter | ||
522 | MARK value as routing key") and can also be used by other subsystems to | ||
523 | change their behaviour. | ||
524 | |||
525 | To compile it as a module, choose M here. If unsure, say N. | ||
526 | |||
512 | config NETFILTER_XT_TARGET_IDLETIMER | 527 | config NETFILTER_XT_TARGET_IDLETIMER |
513 | tristate "IDLETIMER target support" | 528 | tristate "IDLETIMER target support" |
514 | depends on NETFILTER_ADVANCED | 529 | depends on NETFILTER_ADVANCED |
diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile index ca3676586f51..4e7960cc7b97 100644 --- a/net/netfilter/Makefile +++ b/net/netfilter/Makefile | |||
@@ -59,6 +59,7 @@ obj-$(CONFIG_NETFILTER_XT_TARGET_CONNSECMARK) += xt_CONNSECMARK.o | |||
59 | obj-$(CONFIG_NETFILTER_XT_TARGET_CT) += xt_CT.o | 59 | obj-$(CONFIG_NETFILTER_XT_TARGET_CT) += xt_CT.o |
60 | obj-$(CONFIG_NETFILTER_XT_TARGET_DSCP) += xt_DSCP.o | 60 | obj-$(CONFIG_NETFILTER_XT_TARGET_DSCP) += xt_DSCP.o |
61 | obj-$(CONFIG_NETFILTER_XT_TARGET_HL) += xt_HL.o | 61 | obj-$(CONFIG_NETFILTER_XT_TARGET_HL) += xt_HL.o |
62 | obj-$(CONFIG_NETFILTER_XT_TARGET_HMARK) += xt_HMARK.o | ||
62 | obj-$(CONFIG_NETFILTER_XT_TARGET_LED) += xt_LED.o | 63 | obj-$(CONFIG_NETFILTER_XT_TARGET_LED) += xt_LED.o |
63 | obj-$(CONFIG_NETFILTER_XT_TARGET_LOG) += xt_LOG.o | 64 | obj-$(CONFIG_NETFILTER_XT_TARGET_LOG) += xt_LOG.o |
64 | obj-$(CONFIG_NETFILTER_XT_TARGET_NFLOG) += xt_NFLOG.o | 65 | obj-$(CONFIG_NETFILTER_XT_TARGET_NFLOG) += xt_NFLOG.o |
diff --git a/net/netfilter/xt_HMARK.c b/net/netfilter/xt_HMARK.c new file mode 100644 index 000000000000..32fbd735d02b --- /dev/null +++ b/net/netfilter/xt_HMARK.c | |||
@@ -0,0 +1,362 @@ | |||
1 | /* | ||
2 | * xt_HMARK - Netfilter module to set mark by means of hashing | ||
3 | * | ||
4 | * (C) 2012 by Hans Schillstrom <hans.schillstrom@ericsson.com> | ||
5 | * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms of the GNU General Public License version 2 as published by | ||
9 | * the Free Software Foundation. | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/skbuff.h> | ||
14 | #include <linux/icmp.h> | ||
15 | |||
16 | #include <linux/netfilter/x_tables.h> | ||
17 | #include <linux/netfilter/xt_HMARK.h> | ||
18 | |||
19 | #include <net/ip.h> | ||
20 | #if IS_ENABLED(CONFIG_NF_CONNTRACK) | ||
21 | #include <net/netfilter/nf_conntrack.h> | ||
22 | #endif | ||
23 | #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) | ||
24 | #include <net/ipv6.h> | ||
25 | #include <linux/netfilter_ipv6/ip6_tables.h> | ||
26 | #endif | ||
27 | |||
28 | MODULE_LICENSE("GPL"); | ||
29 | MODULE_AUTHOR("Hans Schillstrom <hans.schillstrom@ericsson.com>"); | ||
30 | MODULE_DESCRIPTION("Xtables: packet marking using hash calculation"); | ||
31 | MODULE_ALIAS("ipt_HMARK"); | ||
32 | MODULE_ALIAS("ip6t_HMARK"); | ||
33 | |||
34 | struct hmark_tuple { | ||
35 | u32 src; | ||
36 | u32 dst; | ||
37 | union hmark_ports uports; | ||
38 | uint8_t proto; | ||
39 | }; | ||
40 | |||
41 | static inline u32 hmark_addr6_mask(const __u32 *addr32, const __u32 *mask) | ||
42 | { | ||
43 | return (addr32[0] & mask[0]) ^ | ||
44 | (addr32[1] & mask[1]) ^ | ||
45 | (addr32[2] & mask[2]) ^ | ||
46 | (addr32[3] & mask[3]); | ||
47 | } | ||
48 | |||
49 | static inline u32 | ||
50 | hmark_addr_mask(int l3num, const __u32 *addr32, const __u32 *mask) | ||
51 | { | ||
52 | switch (l3num) { | ||
53 | case AF_INET: | ||
54 | return *addr32 & *mask; | ||
55 | case AF_INET6: | ||
56 | return hmark_addr6_mask(addr32, mask); | ||
57 | } | ||
58 | return 0; | ||
59 | } | ||
60 | |||
61 | static int | ||
62 | hmark_ct_set_htuple(const struct sk_buff *skb, struct hmark_tuple *t, | ||
63 | const struct xt_hmark_info *info) | ||
64 | { | ||
65 | #if IS_ENABLED(CONFIG_NF_CONNTRACK) | ||
66 | enum ip_conntrack_info ctinfo; | ||
67 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | ||
68 | struct nf_conntrack_tuple *otuple; | ||
69 | struct nf_conntrack_tuple *rtuple; | ||
70 | |||
71 | if (ct == NULL || nf_ct_is_untracked(ct)) | ||
72 | return -1; | ||
73 | |||
74 | otuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; | ||
75 | rtuple = &ct->tuplehash[IP_CT_DIR_REPLY].tuple; | ||
76 | |||
77 | t->src = hmark_addr_mask(otuple->src.l3num, otuple->src.u3.all, | ||
78 | info->src_mask.all); | ||
79 | t->dst = hmark_addr_mask(otuple->src.l3num, rtuple->src.u3.all, | ||
80 | info->dst_mask.all); | ||
81 | |||
82 | if (info->flags & XT_HMARK_FLAG(XT_HMARK_METHOD_L3)) | ||
83 | return 0; | ||
84 | |||
85 | t->proto = nf_ct_protonum(ct); | ||
86 | if (t->proto != IPPROTO_ICMP) { | ||
87 | t->uports.p16.src = otuple->src.u.all; | ||
88 | t->uports.p16.dst = rtuple->src.u.all; | ||
89 | t->uports.v32 = (t->uports.v32 & info->port_mask.v32) | | ||
90 | info->port_set.v32; | ||
91 | if (t->uports.p16.dst < t->uports.p16.src) | ||
92 | swap(t->uports.p16.dst, t->uports.p16.src); | ||
93 | } | ||
94 | |||
95 | return 0; | ||
96 | #else | ||
97 | return -1; | ||
98 | #endif | ||
99 | } | ||
100 | |||
101 | static inline u32 | ||
102 | hmark_hash(struct hmark_tuple *t, const struct xt_hmark_info *info) | ||
103 | { | ||
104 | u32 hash; | ||
105 | |||
106 | if (t->dst < t->src) | ||
107 | swap(t->src, t->dst); | ||
108 | |||
109 | hash = jhash_3words(t->src, t->dst, t->uports.v32, info->hashrnd); | ||
110 | hash = hash ^ (t->proto & info->proto_mask); | ||
111 | |||
112 | return (hash % info->hmodulus) + info->hoffset; | ||
113 | } | ||
114 | |||
115 | static void | ||
116 | hmark_set_tuple_ports(const struct sk_buff *skb, unsigned int nhoff, | ||
117 | struct hmark_tuple *t, const struct xt_hmark_info *info) | ||
118 | { | ||
119 | int protoff; | ||
120 | |||
121 | protoff = proto_ports_offset(t->proto); | ||
122 | if (protoff < 0) | ||
123 | return; | ||
124 | |||
125 | nhoff += protoff; | ||
126 | if (skb_copy_bits(skb, nhoff, &t->uports, sizeof(t->uports)) < 0) | ||
127 | return; | ||
128 | |||
129 | t->uports.v32 = (t->uports.v32 & info->port_mask.v32) | | ||
130 | info->port_set.v32; | ||
131 | |||
132 | if (t->uports.p16.dst < t->uports.p16.src) | ||
133 | swap(t->uports.p16.dst, t->uports.p16.src); | ||
134 | } | ||
135 | |||
136 | #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) | ||
137 | static int get_inner6_hdr(const struct sk_buff *skb, int *offset) | ||
138 | { | ||
139 | struct icmp6hdr *icmp6h, _ih6; | ||
140 | |||
141 | icmp6h = skb_header_pointer(skb, *offset, sizeof(_ih6), &_ih6); | ||
142 | if (icmp6h == NULL) | ||
143 | return 0; | ||
144 | |||
145 | if (icmp6h->icmp6_type && icmp6h->icmp6_type < 128) { | ||
146 | *offset += sizeof(struct icmp6hdr); | ||
147 | return 1; | ||
148 | } | ||
149 | return 0; | ||
150 | } | ||
151 | |||
152 | static int | ||
153 | hmark_pkt_set_htuple_ipv6(const struct sk_buff *skb, struct hmark_tuple *t, | ||
154 | const struct xt_hmark_info *info) | ||
155 | { | ||
156 | struct ipv6hdr *ip6, _ip6; | ||
157 | int flag = IP6T_FH_F_AUTH; | ||
158 | unsigned int nhoff = 0; | ||
159 | u16 fragoff = 0; | ||
160 | int nexthdr; | ||
161 | |||
162 | ip6 = (struct ipv6hdr *) (skb->data + skb_network_offset(skb)); | ||
163 | nexthdr = ipv6_find_hdr(skb, &nhoff, -1, &fragoff, &flag); | ||
164 | if (nexthdr < 0) | ||
165 | return 0; | ||
166 | /* No need to check for icmp errors on fragments */ | ||
167 | if ((flag & IP6T_FH_F_FRAG) || (nexthdr != IPPROTO_ICMPV6)) | ||
168 | goto noicmp; | ||
169 | /* Use inner header in case of ICMP errors */ | ||
170 | if (get_inner6_hdr(skb, &nhoff)) { | ||
171 | ip6 = skb_header_pointer(skb, nhoff, sizeof(_ip6), &_ip6); | ||
172 | if (ip6 == NULL) | ||
173 | return -1; | ||
174 | /* If AH present, use SPI like in ESP. */ | ||
175 | flag = IP6T_FH_F_AUTH; | ||
176 | nexthdr = ipv6_find_hdr(skb, &nhoff, -1, &fragoff, &flag); | ||
177 | if (nexthdr < 0) | ||
178 | return -1; | ||
179 | } | ||
180 | noicmp: | ||
181 | t->src = hmark_addr6_mask(ip6->saddr.s6_addr32, info->src_mask.all); | ||
182 | t->dst = hmark_addr6_mask(ip6->daddr.s6_addr32, info->dst_mask.all); | ||
183 | |||
184 | if (info->flags & XT_HMARK_FLAG(XT_HMARK_METHOD_L3)) | ||
185 | return 0; | ||
186 | |||
187 | t->proto = nexthdr; | ||
188 | if (t->proto == IPPROTO_ICMPV6) | ||
189 | return 0; | ||
190 | |||
191 | if (flag & IP6T_FH_F_FRAG) | ||
192 | return 0; | ||
193 | |||
194 | hmark_set_tuple_ports(skb, nhoff, t, info); | ||
195 | return 0; | ||
196 | } | ||
197 | |||
198 | static unsigned int | ||
199 | hmark_tg_v6(struct sk_buff *skb, const struct xt_action_param *par) | ||
200 | { | ||
201 | const struct xt_hmark_info *info = par->targinfo; | ||
202 | struct hmark_tuple t; | ||
203 | |||
204 | memset(&t, 0, sizeof(struct hmark_tuple)); | ||
205 | |||
206 | if (info->flags & XT_HMARK_FLAG(XT_HMARK_CT)) { | ||
207 | if (hmark_ct_set_htuple(skb, &t, info) < 0) | ||
208 | return XT_CONTINUE; | ||
209 | } else { | ||
210 | if (hmark_pkt_set_htuple_ipv6(skb, &t, info) < 0) | ||
211 | return XT_CONTINUE; | ||
212 | } | ||
213 | |||
214 | skb->mark = hmark_hash(&t, info); | ||
215 | return XT_CONTINUE; | ||
216 | } | ||
217 | #endif | ||
218 | |||
219 | static int get_inner_hdr(const struct sk_buff *skb, int iphsz, int *nhoff) | ||
220 | { | ||
221 | const struct icmphdr *icmph; | ||
222 | struct icmphdr _ih; | ||
223 | |||
224 | /* Not enough header? */ | ||
225 | icmph = skb_header_pointer(skb, *nhoff + iphsz, sizeof(_ih), &_ih); | ||
226 | if (icmph == NULL && icmph->type > NR_ICMP_TYPES) | ||
227 | return 0; | ||
228 | |||
229 | /* Error message? */ | ||
230 | if (icmph->type != ICMP_DEST_UNREACH && | ||
231 | icmph->type != ICMP_SOURCE_QUENCH && | ||
232 | icmph->type != ICMP_TIME_EXCEEDED && | ||
233 | icmph->type != ICMP_PARAMETERPROB && | ||
234 | icmph->type != ICMP_REDIRECT) | ||
235 | return 0; | ||
236 | |||
237 | *nhoff += iphsz + sizeof(_ih); | ||
238 | return 1; | ||
239 | } | ||
240 | |||
241 | static int | ||
242 | hmark_pkt_set_htuple_ipv4(const struct sk_buff *skb, struct hmark_tuple *t, | ||
243 | const struct xt_hmark_info *info) | ||
244 | { | ||
245 | struct iphdr *ip, _ip; | ||
246 | int nhoff = skb_network_offset(skb); | ||
247 | |||
248 | ip = (struct iphdr *) (skb->data + nhoff); | ||
249 | if (ip->protocol == IPPROTO_ICMP) { | ||
250 | /* Use inner header in case of ICMP errors */ | ||
251 | if (get_inner_hdr(skb, ip->ihl * 4, &nhoff)) { | ||
252 | ip = skb_header_pointer(skb, nhoff, sizeof(_ip), &_ip); | ||
253 | if (ip == NULL) | ||
254 | return -1; | ||
255 | } | ||
256 | } | ||
257 | |||
258 | t->src = (__force u32) ip->saddr; | ||
259 | t->dst = (__force u32) ip->daddr; | ||
260 | |||
261 | t->src &= info->src_mask.ip; | ||
262 | t->dst &= info->dst_mask.ip; | ||
263 | |||
264 | if (info->flags & XT_HMARK_FLAG(XT_HMARK_METHOD_L3)) | ||
265 | return 0; | ||
266 | |||
267 | t->proto = ip->protocol; | ||
268 | |||
269 | /* ICMP has no ports, skip */ | ||
270 | if (t->proto == IPPROTO_ICMP) | ||
271 | return 0; | ||
272 | |||
273 | /* follow-up fragments don't contain ports, skip all fragments */ | ||
274 | if (ip->frag_off & htons(IP_MF | IP_OFFSET)) | ||
275 | return 0; | ||
276 | |||
277 | hmark_set_tuple_ports(skb, (ip->ihl * 4) + nhoff, t, info); | ||
278 | |||
279 | return 0; | ||
280 | } | ||
281 | |||
282 | static unsigned int | ||
283 | hmark_tg_v4(struct sk_buff *skb, const struct xt_action_param *par) | ||
284 | { | ||
285 | const struct xt_hmark_info *info = par->targinfo; | ||
286 | struct hmark_tuple t; | ||
287 | |||
288 | memset(&t, 0, sizeof(struct hmark_tuple)); | ||
289 | |||
290 | if (info->flags & XT_HMARK_FLAG(XT_HMARK_CT)) { | ||
291 | if (hmark_ct_set_htuple(skb, &t, info) < 0) | ||
292 | return XT_CONTINUE; | ||
293 | } else { | ||
294 | if (hmark_pkt_set_htuple_ipv4(skb, &t, info) < 0) | ||
295 | return XT_CONTINUE; | ||
296 | } | ||
297 | |||
298 | skb->mark = hmark_hash(&t, info); | ||
299 | return XT_CONTINUE; | ||
300 | } | ||
301 | |||
302 | static int hmark_tg_check(const struct xt_tgchk_param *par) | ||
303 | { | ||
304 | const struct xt_hmark_info *info = par->targinfo; | ||
305 | |||
306 | if (!info->hmodulus) { | ||
307 | pr_info("xt_HMARK: hash modulus can't be zero\n"); | ||
308 | return -EINVAL; | ||
309 | } | ||
310 | if (info->proto_mask && | ||
311 | (info->flags & XT_HMARK_FLAG(XT_HMARK_METHOD_L3))) { | ||
312 | pr_info("xt_HMARK: proto mask must be zero with L3 mode\n"); | ||
313 | return -EINVAL; | ||
314 | } | ||
315 | if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPI_MASK) && | ||
316 | (info->flags & (XT_HMARK_FLAG(XT_HMARK_SPORT_MASK) | | ||
317 | XT_HMARK_FLAG(XT_HMARK_DPORT_MASK)))) { | ||
318 | pr_info("xt_HMARK: spi-mask and port-mask can't be combined\n"); | ||
319 | return -EINVAL; | ||
320 | } | ||
321 | if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPI) && | ||
322 | (info->flags & (XT_HMARK_FLAG(XT_HMARK_SPORT) | | ||
323 | XT_HMARK_FLAG(XT_HMARK_DPORT)))) { | ||
324 | pr_info("xt_HMARK: spi-set and port-set can't be combined\n"); | ||
325 | return -EINVAL; | ||
326 | } | ||
327 | return 0; | ||
328 | } | ||
329 | |||
330 | static struct xt_target hmark_tg_reg[] __read_mostly = { | ||
331 | { | ||
332 | .name = "HMARK", | ||
333 | .family = NFPROTO_IPV4, | ||
334 | .target = hmark_tg_v4, | ||
335 | .targetsize = sizeof(struct xt_hmark_info), | ||
336 | .checkentry = hmark_tg_check, | ||
337 | .me = THIS_MODULE, | ||
338 | }, | ||
339 | #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) | ||
340 | { | ||
341 | .name = "HMARK", | ||
342 | .family = NFPROTO_IPV6, | ||
343 | .target = hmark_tg_v6, | ||
344 | .targetsize = sizeof(struct xt_hmark_info), | ||
345 | .checkentry = hmark_tg_check, | ||
346 | .me = THIS_MODULE, | ||
347 | }, | ||
348 | #endif | ||
349 | }; | ||
350 | |||
351 | static int __init hmark_tg_init(void) | ||
352 | { | ||
353 | return xt_register_targets(hmark_tg_reg, ARRAY_SIZE(hmark_tg_reg)); | ||
354 | } | ||
355 | |||
356 | static void __exit hmark_tg_exit(void) | ||
357 | { | ||
358 | xt_unregister_targets(hmark_tg_reg, ARRAY_SIZE(hmark_tg_reg)); | ||
359 | } | ||
360 | |||
361 | module_init(hmark_tg_init); | ||
362 | module_exit(hmark_tg_exit); | ||