1 /*
2 * (C) 2012-2014 by Pablo Neira Ayuso <pablo@netfilter.org>
3 * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
11 */
12
13 #include <string.h>
14 #include <stdio.h>
15
16 #include <sys/types.h>
17 #include <sys/socket.h>
18 #include <arpa/inet.h>
19 #include <netinet/ip6.h>
20 #include <netdb.h>
21
22 #include <xtables.h>
23
24 #include <linux/netfilter/nf_tables.h>
25 #include "nft.h"
26 #include "nft-shared.h"
27
nft_ipv6_add(struct nftnl_rule * r,void * data)28 static int nft_ipv6_add(struct nftnl_rule *r, void *data)
29 {
30 struct iptables_command_state *cs = data;
31 struct xtables_rule_match *matchp;
32 uint32_t op;
33 int ret;
34
35 if (cs->fw6.ipv6.iniface[0] != '\0') {
36 op = nft_invflags2cmp(cs->fw6.ipv6.invflags, IPT_INV_VIA_IN);
37 add_iniface(r, cs->fw6.ipv6.iniface, op);
38 }
39
40 if (cs->fw6.ipv6.outiface[0] != '\0') {
41 op = nft_invflags2cmp(cs->fw6.ipv6.invflags, IPT_INV_VIA_OUT);
42 add_outiface(r, cs->fw6.ipv6.outiface, op);
43 }
44
45 if (cs->fw6.ipv6.proto != 0) {
46 op = nft_invflags2cmp(cs->fw6.ipv6.invflags, XT_INV_PROTO);
47 add_proto(r, offsetof(struct ip6_hdr, ip6_nxt), 1,
48 cs->fw6.ipv6.proto, op);
49 }
50
51 if (!IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.src)) {
52 op = nft_invflags2cmp(cs->fw6.ipv6.invflags, IPT_INV_SRCIP);
53 add_addr(r, offsetof(struct ip6_hdr, ip6_src),
54 &cs->fw6.ipv6.src, &cs->fw6.ipv6.smsk,
55 sizeof(struct in6_addr), op);
56 }
57 if (!IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.dst)) {
58 op = nft_invflags2cmp(cs->fw6.ipv6.invflags, IPT_INV_DSTIP);
59 add_addr(r, offsetof(struct ip6_hdr, ip6_dst),
60 &cs->fw6.ipv6.dst, &cs->fw6.ipv6.dmsk,
61 sizeof(struct in6_addr), op);
62 }
63 add_compat(r, cs->fw6.ipv6.proto, cs->fw6.ipv6.invflags);
64
65 for (matchp = cs->matches; matchp; matchp = matchp->next) {
66 /* Use nft built-in comments support instead of comment match */
67 if (strcmp(matchp->match->name, "comment") == 0) {
68 ret = add_comment(r, (char *)matchp->match->m->data);
69 if (ret < 0)
70 return ret;
71 } else {
72 ret = add_match(r, matchp->match->m);
73 if (ret < 0)
74 return ret;
75 }
76 }
77
78 /* Counters need to me added before the target, otherwise they are
79 * increased for each rule because of the way nf_tables works.
80 */
81 if (add_counters(r, cs->counters.pcnt, cs->counters.bcnt) < 0)
82 return -1;
83
84 return add_action(r, cs, !!(cs->fw6.ipv6.flags & IP6T_F_GOTO));
85 }
86
nft_ipv6_is_same(const void * data_a,const void * data_b)87 static bool nft_ipv6_is_same(const void *data_a,
88 const void *data_b)
89 {
90 const struct iptables_command_state *a = data_a;
91 const struct iptables_command_state *b = data_b;
92
93 if (memcmp(a->fw6.ipv6.src.s6_addr, b->fw6.ipv6.src.s6_addr,
94 sizeof(struct in6_addr)) != 0
95 || memcmp(a->fw6.ipv6.dst.s6_addr, b->fw6.ipv6.dst.s6_addr,
96 sizeof(struct in6_addr)) != 0
97 || a->fw6.ipv6.proto != b->fw6.ipv6.proto
98 || a->fw6.ipv6.flags != b->fw6.ipv6.flags
99 || a->fw6.ipv6.invflags != b->fw6.ipv6.invflags) {
100 DEBUGP("different src/dst/proto/flags/invflags\n");
101 return false;
102 }
103
104 return is_same_interfaces(a->fw6.ipv6.iniface, a->fw6.ipv6.outiface,
105 a->fw6.ipv6.iniface_mask,
106 a->fw6.ipv6.outiface_mask,
107 b->fw6.ipv6.iniface, b->fw6.ipv6.outiface,
108 b->fw6.ipv6.iniface_mask,
109 b->fw6.ipv6.outiface_mask);
110 }
111
nft_ipv6_parse_meta(struct nft_xt_ctx * ctx,struct nftnl_expr * e,void * data)112 static void nft_ipv6_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
113 void *data)
114 {
115 struct iptables_command_state *cs = data;
116
117 parse_meta(e, ctx->meta.key, cs->fw6.ipv6.iniface,
118 cs->fw6.ipv6.iniface_mask, cs->fw6.ipv6.outiface,
119 cs->fw6.ipv6.outiface_mask, &cs->fw6.ipv6.invflags);
120 }
121
parse_mask_ipv6(struct nft_xt_ctx * ctx,struct in6_addr * mask)122 static void parse_mask_ipv6(struct nft_xt_ctx *ctx, struct in6_addr *mask)
123 {
124 memcpy(mask, ctx->bitwise.mask, sizeof(struct in6_addr));
125 }
126
nft_ipv6_parse_payload(struct nft_xt_ctx * ctx,struct nftnl_expr * e,void * data)127 static void nft_ipv6_parse_payload(struct nft_xt_ctx *ctx,
128 struct nftnl_expr *e, void *data)
129 {
130 struct iptables_command_state *cs = data;
131 struct in6_addr addr;
132 uint8_t proto;
133 bool inv;
134
135 switch (ctx->payload.offset) {
136 case offsetof(struct ip6_hdr, ip6_src):
137 get_cmp_data(e, &addr, sizeof(addr), &inv);
138 memcpy(cs->fw6.ipv6.src.s6_addr, &addr, sizeof(addr));
139 if (ctx->flags & NFT_XT_CTX_BITWISE) {
140 parse_mask_ipv6(ctx, &cs->fw6.ipv6.smsk);
141 ctx->flags &= ~NFT_XT_CTX_BITWISE;
142 } else {
143 memset(&cs->fw.ip.smsk, 0xff, sizeof(struct in6_addr));
144 }
145
146 if (inv)
147 cs->fw6.ipv6.invflags |= IP6T_INV_SRCIP;
148 break;
149 case offsetof(struct ip6_hdr, ip6_dst):
150 get_cmp_data(e, &addr, sizeof(addr), &inv);
151 memcpy(cs->fw6.ipv6.dst.s6_addr, &addr, sizeof(addr));
152 if (ctx->flags & NFT_XT_CTX_BITWISE) {
153 parse_mask_ipv6(ctx, &cs->fw6.ipv6.dmsk);
154 ctx->flags &= ~NFT_XT_CTX_BITWISE;
155 } else {
156 memset(&cs->fw.ip.dmsk, 0xff, sizeof(struct in6_addr));
157 }
158
159 if (inv)
160 cs->fw6.ipv6.invflags |= IP6T_INV_DSTIP;
161 break;
162 case offsetof(struct ip6_hdr, ip6_nxt):
163 get_cmp_data(e, &proto, sizeof(proto), &inv);
164 cs->fw6.ipv6.flags |= IP6T_F_PROTO;
165 cs->fw6.ipv6.proto = proto;
166 if (inv)
167 cs->fw6.ipv6.invflags |= IP6T_INV_PROTO;
168 default:
169 DEBUGP("unknown payload offset %d\n", ctx->payload.offset);
170 break;
171 }
172 }
173
nft_ipv6_parse_immediate(const char * jumpto,bool nft_goto,void * data)174 static void nft_ipv6_parse_immediate(const char *jumpto, bool nft_goto,
175 void *data)
176 {
177 struct iptables_command_state *cs = data;
178
179 cs->jumpto = jumpto;
180
181 if (nft_goto)
182 cs->fw6.ipv6.flags |= IP6T_F_GOTO;
183 }
184
nft_ipv6_print_header(unsigned int format,const char * chain,const char * pol,const struct xt_counters * counters,bool basechain,uint32_t refs)185 static void nft_ipv6_print_header(unsigned int format, const char *chain,
186 const char *pol,
187 const struct xt_counters *counters,
188 bool basechain, uint32_t refs)
189 {
190 print_header(format, chain, pol, counters, basechain, refs);
191 }
192
print_ipv6_addr(const struct iptables_command_state * cs,unsigned int format)193 static void print_ipv6_addr(const struct iptables_command_state *cs,
194 unsigned int format)
195 {
196 char buf[BUFSIZ];
197
198 fputc(cs->fw6.ipv6.invflags & IP6T_INV_SRCIP ? '!' : ' ', stdout);
199 if (IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.src)
200 && !(format & FMT_NUMERIC))
201 printf(FMT("%-19s ","%s "), "anywhere");
202 else {
203 if (format & FMT_NUMERIC)
204 strcpy(buf,
205 xtables_ip6addr_to_numeric(&cs->fw6.ipv6.src));
206 else
207 strcpy(buf,
208 xtables_ip6addr_to_anyname(&cs->fw6.ipv6.src));
209 strcat(buf, xtables_ip6mask_to_numeric(&cs->fw6.ipv6.smsk));
210 printf(FMT("%-19s ","%s "), buf);
211 }
212
213
214 fputc(cs->fw6.ipv6.invflags & IP6T_INV_DSTIP ? '!' : ' ', stdout);
215 if (IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.dst)
216 && !(format & FMT_NUMERIC))
217 printf(FMT("%-19s ","-> %s"), "anywhere");
218 else {
219 if (format & FMT_NUMERIC)
220 strcpy(buf,
221 xtables_ip6addr_to_numeric(&cs->fw6.ipv6.dst));
222 else
223 strcpy(buf,
224 xtables_ip6addr_to_anyname(&cs->fw6.ipv6.dst));
225 strcat(buf, xtables_ip6mask_to_numeric(&cs->fw6.ipv6.dmsk));
226 printf(FMT("%-19s ","-> %s"), buf);
227 }
228 }
229
nft_ipv6_print_firewall(struct nftnl_rule * r,unsigned int num,unsigned int format)230 static void nft_ipv6_print_firewall(struct nftnl_rule *r, unsigned int num,
231 unsigned int format)
232 {
233 struct iptables_command_state cs = {};
234
235 nft_rule_to_iptables_command_state(r, &cs);
236
237 print_firewall_details(&cs, cs.jumpto, cs.fw6.ipv6.flags,
238 cs.fw6.ipv6.invflags, cs.fw6.ipv6.proto,
239 num, format);
240 print_ifaces(cs.fw6.ipv6.iniface, cs.fw6.ipv6.outiface,
241 cs.fw6.ipv6.invflags, format);
242 print_ipv6_addr(&cs, format);
243
244 if (format & FMT_NOTABLE)
245 fputs(" ", stdout);
246
247 if (cs.fw6.ipv6.flags & IP6T_F_GOTO)
248 printf("[goto] ");
249
250 print_matches_and_target(&cs, format);
251
252 if (!(format & FMT_NONEWLINE))
253 fputc('\n', stdout);
254 }
255
save_ipv6_addr(char letter,const struct in6_addr * addr,int invert)256 static void save_ipv6_addr(char letter, const struct in6_addr *addr,
257 int invert)
258 {
259 char addr_str[INET6_ADDRSTRLEN];
260
261 if (!invert && IN6_IS_ADDR_UNSPECIFIED(addr))
262 return;
263
264 inet_ntop(AF_INET6, addr, addr_str, INET6_ADDRSTRLEN);
265 printf("%s-%c %s ", invert ? "! " : "", letter, addr_str);
266 }
267
nft_ipv6_save_firewall(const void * data,unsigned int format)268 static void nft_ipv6_save_firewall(const void *data, unsigned int format)
269 {
270 const struct iptables_command_state *cs = data;
271
272 save_firewall_details(cs, cs->fw6.ipv6.invflags, cs->fw6.ipv6.proto,
273 cs->fw6.ipv6.iniface, cs->fw6.ipv6.iniface_mask,
274 cs->fw6.ipv6.outiface,
275 cs->fw6.ipv6.outiface_mask);
276
277 save_ipv6_addr('s', &cs->fw6.ipv6.src,
278 cs->fw6.ipv6.invflags & IP6T_INV_SRCIP);
279 save_ipv6_addr('d', &cs->fw6.ipv6.dst,
280 cs->fw6.ipv6.invflags & IP6T_INV_DSTIP);
281
282 save_matches_and_target(cs->matches, cs->target,
283 cs->jumpto, cs->fw6.ipv6.flags, &cs->fw6);
284
285 if (cs->target == NULL && strlen(cs->jumpto) > 0) {
286 printf("-%c %s", cs->fw6.ipv6.flags & IP6T_F_GOTO ? 'g' : 'j',
287 cs->jumpto);
288 }
289 printf("\n");
290 }
291
292 /* These are invalid numbers as upper layer protocol */
is_exthdr(uint16_t proto)293 static int is_exthdr(uint16_t proto)
294 {
295 return (proto == IPPROTO_ROUTING ||
296 proto == IPPROTO_FRAGMENT ||
297 proto == IPPROTO_AH ||
298 proto == IPPROTO_DSTOPTS);
299 }
300
nft_ipv6_proto_parse(struct iptables_command_state * cs,struct xtables_args * args)301 static void nft_ipv6_proto_parse(struct iptables_command_state *cs,
302 struct xtables_args *args)
303 {
304 cs->fw6.ipv6.proto = args->proto;
305 cs->fw6.ipv6.invflags = args->invflags;
306
307 if (is_exthdr(cs->fw6.ipv6.proto)
308 && (cs->fw6.ipv6.invflags & XT_INV_PROTO) == 0)
309 fprintf(stderr,
310 "Warning: never matched protocol: %s. "
311 "use extension match instead.\n",
312 cs->protocol);
313 }
314
nft_ipv6_post_parse(int command,struct iptables_command_state * cs,struct xtables_args * args)315 static void nft_ipv6_post_parse(int command, struct iptables_command_state *cs,
316 struct xtables_args *args)
317 {
318 if (args->proto != 0)
319 args->flags |= IP6T_F_PROTO;
320
321 cs->fw6.ipv6.flags = args->flags;
322 /* We already set invflags in proto_parse, but we need to refresh it
323 * to include new parsed options.
324 */
325 cs->fw6.ipv6.invflags = args->invflags;
326
327 strncpy(cs->fw6.ipv6.iniface, args->iniface, IFNAMSIZ);
328 memcpy(cs->fw6.ipv6.iniface_mask,
329 args->iniface_mask, IFNAMSIZ*sizeof(unsigned char));
330
331 strncpy(cs->fw6.ipv6.outiface, args->outiface, IFNAMSIZ);
332 memcpy(cs->fw6.ipv6.outiface_mask,
333 args->outiface_mask, IFNAMSIZ*sizeof(unsigned char));
334
335 if (args->goto_set)
336 cs->fw6.ipv6.flags |= IP6T_F_GOTO;
337
338 cs->fw6.counters.pcnt = args->pcnt_cnt;
339 cs->fw6.counters.bcnt = args->bcnt_cnt;
340
341 if (command & (CMD_REPLACE | CMD_INSERT |
342 CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
343 if (!(cs->options & OPT_DESTINATION))
344 args->dhostnetworkmask = "::0/0";
345 if (!(cs->options & OPT_SOURCE))
346 args->shostnetworkmask = "::0/0";
347 }
348
349 if (args->shostnetworkmask)
350 xtables_ip6parse_multiple(args->shostnetworkmask,
351 &args->s.addr.v6,
352 &args->s.mask.v6,
353 &args->s.naddrs);
354 if (args->dhostnetworkmask)
355 xtables_ip6parse_multiple(args->dhostnetworkmask,
356 &args->d.addr.v6,
357 &args->d.mask.v6,
358 &args->d.naddrs);
359
360 if ((args->s.naddrs > 1 || args->d.naddrs > 1) &&
361 (cs->fw6.ipv6.invflags & (IP6T_INV_SRCIP | IP6T_INV_DSTIP)))
362 xtables_error(PARAMETER_PROBLEM,
363 "! not allowed with multiple"
364 " source or destination IP addresses");
365 }
366
nft_ipv6_parse_target(struct xtables_target * t,void * data)367 static void nft_ipv6_parse_target(struct xtables_target *t, void *data)
368 {
369 struct iptables_command_state *cs = data;
370
371 cs->target = t;
372 }
373
nft_ipv6_rule_find(struct nft_family_ops * ops,struct nftnl_rule * r,void * data)374 static bool nft_ipv6_rule_find(struct nft_family_ops *ops,
375 struct nftnl_rule *r, void *data)
376 {
377 struct iptables_command_state *cs = data;
378
379 return nft_ipv46_rule_find(ops, r, cs);
380 }
381
nft_ipv6_save_counters(const void * data)382 static void nft_ipv6_save_counters(const void *data)
383 {
384 const struct iptables_command_state *cs = data;
385
386 save_counters(cs->counters.pcnt, cs->counters.bcnt);
387 }
388
xlate_ipv6_addr(const char * selector,const struct in6_addr * addr,const struct in6_addr * mask,int invert,struct xt_xlate * xl)389 static void xlate_ipv6_addr(const char *selector, const struct in6_addr *addr,
390 const struct in6_addr *mask,
391 int invert, struct xt_xlate *xl)
392 {
393 char addr_str[INET6_ADDRSTRLEN];
394
395 if (!invert && IN6_IS_ADDR_UNSPECIFIED(addr))
396 return;
397
398 inet_ntop(AF_INET6, addr, addr_str, INET6_ADDRSTRLEN);
399 xt_xlate_add(xl, "%s %s%s%s ", selector, invert ? "!= " : "", addr_str,
400 xtables_ip6mask_to_numeric(mask));
401 }
402
nft_ipv6_xlate(const void * data,struct xt_xlate * xl)403 static int nft_ipv6_xlate(const void *data, struct xt_xlate *xl)
404 {
405 const struct iptables_command_state *cs = data;
406 const char *comment;
407 int ret;
408
409 xlate_ifname(xl, "iifname", cs->fw6.ipv6.iniface,
410 cs->fw6.ipv6.invflags & IP6T_INV_VIA_IN);
411 xlate_ifname(xl, "oifname", cs->fw6.ipv6.outiface,
412 cs->fw6.ipv6.invflags & IP6T_INV_VIA_OUT);
413
414 if (cs->fw6.ipv6.proto != 0) {
415 const struct protoent *pent =
416 getprotobynumber(cs->fw6.ipv6.proto);
417 char protonum[strlen("255") + 1];
418
419 if (!xlate_find_match(cs, pent->p_name)) {
420 snprintf(protonum, sizeof(protonum), "%u",
421 cs->fw6.ipv6.proto);
422 protonum[sizeof(protonum) - 1] = '\0';
423 xt_xlate_add(xl, "meta l4proto %s%s ",
424 cs->fw6.ipv6.invflags & IP6T_INV_PROTO ?
425 "!= " : "",
426 pent ? pent->p_name : protonum);
427 }
428 }
429
430 xlate_ipv6_addr("ip6 saddr", &cs->fw6.ipv6.src, &cs->fw6.ipv6.smsk,
431 cs->fw6.ipv6.invflags & IP6T_INV_SRCIP, xl);
432 xlate_ipv6_addr("ip6 daddr", &cs->fw6.ipv6.dst, &cs->fw6.ipv6.dmsk,
433 cs->fw6.ipv6.invflags & IP6T_INV_DSTIP, xl);
434
435 ret = xlate_matches(cs, xl);
436 if (!ret)
437 return ret;
438
439 /* Always add counters per rule, as in iptables */
440 xt_xlate_add(xl, "counter ");
441
442 comment = xt_xlate_get_comment(xl);
443 if (comment)
444 xt_xlate_add(xl, "comment %s", comment);
445
446 ret = xlate_action(cs, !!(cs->fw6.ipv6.flags & IP6T_F_GOTO), xl);
447
448 return ret;
449 }
450
451 struct nft_family_ops nft_family_ops_ipv6 = {
452 .add = nft_ipv6_add,
453 .is_same = nft_ipv6_is_same,
454 .parse_meta = nft_ipv6_parse_meta,
455 .parse_payload = nft_ipv6_parse_payload,
456 .parse_immediate = nft_ipv6_parse_immediate,
457 .print_header = nft_ipv6_print_header,
458 .print_firewall = nft_ipv6_print_firewall,
459 .save_firewall = nft_ipv6_save_firewall,
460 .save_counters = nft_ipv6_save_counters,
461 .proto_parse = nft_ipv6_proto_parse,
462 .post_parse = nft_ipv6_post_parse,
463 .parse_target = nft_ipv6_parse_target,
464 .rule_find = nft_ipv6_rule_find,
465 .xlate = nft_ipv6_xlate,
466 };
467