1 /* 2 * (C) 2012-2014 by Pablo Neira Ayuso <pablo (at) netfilter.org> 3 * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka (at) 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 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 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 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 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 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 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 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 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 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 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 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 */ 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 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 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 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 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 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 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 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