blob: 5817d03105b23a35de565c4d31433d6b5232f40a [file] [log] [blame]
Hans Schillstromcf308a12012-05-02 07:49:47 +00001/*
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
28MODULE_LICENSE("GPL");
29MODULE_AUTHOR("Hans Schillstrom <hans.schillstrom@ericsson.com>");
30MODULE_DESCRIPTION("Xtables: packet marking using hash calculation");
31MODULE_ALIAS("ipt_HMARK");
32MODULE_ALIAS("ip6t_HMARK");
33
34struct hmark_tuple {
35 u32 src;
36 u32 dst;
37 union hmark_ports uports;
38 uint8_t proto;
39};
40
41static 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
49static inline u32
50hmark_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
61static int
62hmark_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
101static inline u32
102hmark_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
115static void
116hmark_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)
137static 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
152static int
153hmark_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 }
180noicmp:
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
198static unsigned int
199hmark_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
219static 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);
Dan Carpenter58618112012-05-12 01:00:03 +0000226 if (icmph == NULL || icmph->type > NR_ICMP_TYPES)
Hans Schillstromcf308a12012-05-02 07:49:47 +0000227 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
241static int
242hmark_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
282static unsigned int
283hmark_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
302static 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
330static 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
351static int __init hmark_tg_init(void)
352{
353 return xt_register_targets(hmark_tg_reg, ARRAY_SIZE(hmark_tg_reg));
354}
355
356static void __exit hmark_tg_exit(void)
357{
358 xt_unregister_targets(hmark_tg_reg, ARRAY_SIZE(hmark_tg_reg));
359}
360
361module_init(hmark_tg_init);
362module_exit(hmark_tg_exit);