1/* 2 * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org> 3 * (C) 2013 by Giuseppe Longo <giuseppelng@gmail.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 <stdio.h> 14#include <stdlib.h> 15#include <string.h> 16#include <netdb.h> 17#include <net/if_arp.h> 18 19#include <xtables.h> 20#include <libiptc/libxtc.h> 21#include <net/if_arp.h> 22#include <netinet/if_ether.h> 23 24#include <linux/netfilter_arp/arp_tables.h> 25#include <linux/netfilter/nf_tables.h> 26 27#include "nft-shared.h" 28#include "nft-arp.h" 29#include "nft.h" 30 31/* a few names */ 32char *opcodes[] = 33{ 34 "Request", 35 "Reply", 36 "Request_Reverse", 37 "Reply_Reverse", 38 "DRARP_Request", 39 "DRARP_Reply", 40 "DRARP_Error", 41 "InARP_Request", 42 "ARP_NAK", 43}; 44 45static char * 46addr_to_dotted(const struct in_addr *addrp) 47{ 48 static char buf[20]; 49 const unsigned char *bytep; 50 51 bytep = (const unsigned char *) &(addrp->s_addr); 52 sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]); 53 return buf; 54} 55 56static char * 57addr_to_host(const struct in_addr *addr) 58{ 59 struct hostent *host; 60 61 if ((host = gethostbyaddr((char *) addr, 62 sizeof(struct in_addr), AF_INET)) != NULL) 63 return (char *) host->h_name; 64 65 return (char *) NULL; 66} 67 68static char * 69addr_to_network(const struct in_addr *addr) 70{ 71 struct netent *net; 72 73 if ((net = getnetbyaddr((long) ntohl(addr->s_addr), AF_INET)) != NULL) 74 return (char *) net->n_name; 75 76 return (char *) NULL; 77} 78 79static char * 80addr_to_anyname(const struct in_addr *addr) 81{ 82 char *name; 83 84 if ((name = addr_to_host(addr)) != NULL || 85 (name = addr_to_network(addr)) != NULL) 86 return name; 87 88 return addr_to_dotted(addr); 89} 90 91static char * 92mask_to_dotted(const struct in_addr *mask) 93{ 94 int i; 95 static char buf[20]; 96 u_int32_t maskaddr, bits; 97 98 maskaddr = ntohl(mask->s_addr); 99 100 if (maskaddr == 0xFFFFFFFFL) 101 /* we don't want to see "/32" */ 102 return ""; 103 104 i = 32; 105 bits = 0xFFFFFFFEL; 106 while (--i >= 0 && maskaddr != bits) 107 bits <<= 1; 108 if (i >= 0) 109 sprintf(buf, "/%d", i); 110 else 111 /* mask was not a decent combination of 1's and 0's */ 112 sprintf(buf, "/%s", addr_to_dotted(mask)); 113 114 return buf; 115} 116 117static void print_mac(const unsigned char *mac, int l) 118{ 119 int j; 120 121 for (j = 0; j < l; j++) 122 printf("%02x%s", mac[j], 123 (j==l-1) ? "" : ":"); 124} 125 126static void print_mac_and_mask(const unsigned char *mac, const unsigned char *mask, int l) 127{ 128 int i; 129 130 print_mac(mac, l); 131 for (i = 0; i < l ; i++) 132 if (mask[i] != 255) 133 break; 134 if (i == l) 135 return; 136 printf("/"); 137 print_mac(mask, l); 138} 139 140static int nft_arp_add(struct nftnl_rule *r, void *data) 141{ 142 struct arptables_command_state *cs = data; 143 struct arpt_entry *fw = &cs->fw; 144 uint32_t op; 145 int ret = 0; 146 147 if (fw->arp.iniface[0] != '\0') { 148 op = nft_invflags2cmp(fw->arp.invflags, ARPT_INV_VIA_IN); 149 add_iniface(r, fw->arp.iniface, op); 150 } 151 152 if (fw->arp.outiface[0] != '\0') { 153 op = nft_invflags2cmp(fw->arp.invflags, ARPT_INV_VIA_OUT); 154 add_outiface(r, fw->arp.outiface, op); 155 } 156 157 if (fw->arp.arhrd != 0) { 158 op = nft_invflags2cmp(fw->arp.invflags, ARPT_INV_ARPHRD); 159 add_payload(r, offsetof(struct arphdr, ar_hrd), 2, 160 NFT_PAYLOAD_NETWORK_HEADER); 161 add_cmp_u16(r, fw->arp.arhrd, op); 162 } 163 164 if (fw->arp.arpro != 0) { 165 op = nft_invflags2cmp(fw->arp.invflags, ARPT_INV_ARPPRO); 166 add_payload(r, offsetof(struct arphdr, ar_pro), 2, 167 NFT_PAYLOAD_NETWORK_HEADER); 168 add_cmp_u16(r, fw->arp.arpro, op); 169 } 170 171 if (fw->arp.arhln != 0) { 172 op = nft_invflags2cmp(fw->arp.invflags, ARPT_INV_ARPHLN); 173 add_proto(r, offsetof(struct arphdr, ar_hln), 1, 174 fw->arp.arhln, op); 175 } 176 177 add_proto(r, offsetof(struct arphdr, ar_pln), 1, 4, NFT_CMP_EQ); 178 179 if (fw->arp.arpop != 0) { 180 op = nft_invflags2cmp(fw->arp.invflags, ARPT_INV_ARPOP); 181 add_payload(r, offsetof(struct arphdr, ar_op), 2, 182 NFT_PAYLOAD_NETWORK_HEADER); 183 add_cmp_u16(r, fw->arp.arpop, op); 184 } 185 186 if (fw->arp.src_devaddr.addr[0] != '\0') { 187 op = nft_invflags2cmp(fw->arp.invflags, ARPT_INV_SRCDEVADDR); 188 add_payload(r, sizeof(struct arphdr), fw->arp.arhln, 189 NFT_PAYLOAD_NETWORK_HEADER); 190 add_cmp_ptr(r, op, fw->arp.src_devaddr.addr, fw->arp.arhln); 191 } 192 193 if (fw->arp.src.s_addr != 0) { 194 op = nft_invflags2cmp(fw->arp.invflags, ARPT_INV_SRCIP); 195 add_addr(r, sizeof(struct arphdr) + fw->arp.arhln, 196 &fw->arp.src.s_addr, &fw->arp.smsk.s_addr, 197 sizeof(struct in_addr), op); 198 } 199 200 if (fw->arp.tgt_devaddr.addr[0] != '\0') { 201 op = nft_invflags2cmp(fw->arp.invflags, ARPT_INV_TGTDEVADDR); 202 add_payload(r, sizeof(struct arphdr) + fw->arp.arhln + 4, 203 fw->arp.arhln, NFT_PAYLOAD_NETWORK_HEADER); 204 add_cmp_ptr(r, op, fw->arp.tgt_devaddr.addr, fw->arp.arhln); 205 } 206 207 if (fw->arp.tgt.s_addr != 0) { 208 op = nft_invflags2cmp(fw->arp.invflags, ARPT_INV_TGTIP); 209 add_addr(r, sizeof(struct arphdr) + fw->arp.arhln + sizeof(struct in_addr), 210 &fw->arp.tgt.s_addr, &fw->arp.tmsk.s_addr, 211 sizeof(struct in_addr), op); 212 } 213 214 /* Counters need to me added before the target, otherwise they are 215 * increased for each rule because of the way nf_tables works. 216 */ 217 if (add_counters(r, fw->counters.pcnt, fw->counters.bcnt) < 0) 218 return -1; 219 220 if (cs->target != NULL) { 221 /* Standard target? */ 222 if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0) 223 ret = add_verdict(r, NF_ACCEPT); 224 else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0) 225 ret = add_verdict(r, NF_DROP); 226 else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0) 227 ret = add_verdict(r, NFT_RETURN); 228 else 229 ret = add_target(r, cs->target->t); 230 } else if (strlen(cs->jumpto) > 0) { 231 /* No goto in arptables */ 232 ret = add_jumpto(r, cs->jumpto, NFT_JUMP); 233 } 234 235 return ret; 236} 237 238static uint16_t ipt_to_arpt_flags(uint8_t invflags) 239{ 240 uint16_t result = 0; 241 242 if (invflags & IPT_INV_VIA_IN) 243 result |= ARPT_INV_VIA_IN; 244 245 if (invflags & IPT_INV_VIA_OUT) 246 result |= ARPT_INV_VIA_OUT; 247 248 if (invflags & IPT_INV_SRCIP) 249 result |= ARPT_INV_SRCIP; 250 251 if (invflags & IPT_INV_DSTIP) 252 result |= ARPT_INV_TGTIP; 253 254 if (invflags & IPT_INV_PROTO) 255 result |= ARPT_INV_ARPPRO; 256 257 return result; 258} 259 260static void nft_arp_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e, 261 void *data) 262{ 263 struct arptables_command_state *cs = data; 264 struct arpt_entry *fw = &cs->fw; 265 uint8_t flags = 0; 266 267 parse_meta(e, ctx->meta.key, fw->arp.iniface, fw->arp.iniface_mask, 268 fw->arp.outiface, fw->arp.outiface_mask, 269 &flags); 270 271 fw->arp.invflags |= ipt_to_arpt_flags(flags); 272} 273 274static void nft_arp_parse_target(struct xtables_target *target, void *data) 275{ 276 struct arptables_command_state *cs = data; 277 278 cs->target = target; 279} 280 281static void nft_arp_parse_immediate(const char *jumpto, bool nft_goto, 282 void *data) 283{ 284 struct arptables_command_state *cs = data; 285 286 cs->jumpto = jumpto; 287} 288 289static void parse_mask_ipv4(struct nft_xt_ctx *ctx, struct in_addr *mask) 290{ 291 mask->s_addr = ctx->bitwise.mask[0]; 292} 293 294static void nft_arp_parse_payload(struct nft_xt_ctx *ctx, 295 struct nftnl_expr *e, void *data) 296{ 297 struct arptables_command_state *cs = data; 298 struct arpt_entry *fw = &cs->fw; 299 struct in_addr addr; 300 unsigned short int ar_hrd, ar_pro, ar_op, ar_hln; 301 bool inv; 302 303 switch (ctx->payload.offset) { 304 case offsetof(struct arphdr, ar_hrd): 305 get_cmp_data(e, &ar_hrd, sizeof(ar_hrd), &inv); 306 fw->arp.arhrd = ar_hrd; 307 fw->arp.arhrd_mask = 0xffff; 308 if (inv) 309 fw->arp.invflags |= ARPT_INV_ARPHRD; 310 break; 311 case offsetof(struct arphdr, ar_pro): 312 get_cmp_data(e, &ar_pro, sizeof(ar_pro), &inv); 313 fw->arp.arpro = ar_pro; 314 fw->arp.arpro_mask = 0xffff; 315 if (inv) 316 fw->arp.invflags |= ARPT_INV_ARPPRO; 317 break; 318 case offsetof(struct arphdr, ar_op): 319 get_cmp_data(e, &ar_op, sizeof(ar_op), &inv); 320 fw->arp.arpop = ar_op; 321 fw->arp.arpop_mask = 0xffff; 322 if (inv) 323 fw->arp.invflags |= ARPT_INV_ARPOP; 324 break; 325 case offsetof(struct arphdr, ar_hln): 326 get_cmp_data(e, &ar_hln, sizeof(ar_op), &inv); 327 fw->arp.arhln = ar_hln; 328 fw->arp.arhln_mask = 0xff; 329 if (inv) 330 fw->arp.invflags |= ARPT_INV_ARPOP; 331 break; 332 default: 333 if (fw->arp.arhln < 0) 334 break; 335 336 if (ctx->payload.offset == sizeof(struct arphdr) + 337 fw->arp.arhln) { 338 get_cmp_data(e, &addr, sizeof(addr), &inv); 339 fw->arp.src.s_addr = addr.s_addr; 340 if (ctx->flags & NFT_XT_CTX_BITWISE) { 341 parse_mask_ipv4(ctx, &fw->arp.smsk); 342 ctx->flags &= ~NFT_XT_CTX_BITWISE; 343 } else { 344 fw->arp.smsk.s_addr = 0xffffffff; 345 } 346 347 if (inv) 348 fw->arp.invflags |= ARPT_INV_SRCIP; 349 } else if (ctx->payload.offset == sizeof(struct arphdr) + 350 fw->arp.arhln + 351 sizeof(struct in_addr)) { 352 get_cmp_data(e, &addr, sizeof(addr), &inv); 353 fw->arp.tgt.s_addr = addr.s_addr; 354 if (ctx->flags & NFT_XT_CTX_BITWISE) { 355 parse_mask_ipv4(ctx, &fw->arp.tmsk); 356 ctx->flags &= ~NFT_XT_CTX_BITWISE; 357 } else { 358 fw->arp.tmsk.s_addr = 0xffffffff; 359 } 360 361 if (inv) 362 fw->arp.invflags |= ARPT_INV_TGTIP; 363 } 364 break; 365 } 366} 367 368void nft_rule_to_arptables_command_state(struct nftnl_rule *r, 369 struct arptables_command_state *cs) 370{ 371 struct nftnl_expr_iter *iter; 372 struct nftnl_expr *expr; 373 int family = nftnl_rule_get_u32(r, NFTNL_RULE_FAMILY); 374 struct nft_xt_ctx ctx = { 375 .state.cs_arp = cs, 376 .family = family, 377 }; 378 379 iter = nftnl_expr_iter_create(r); 380 if (iter == NULL) 381 return; 382 383 ctx.iter = iter; 384 expr = nftnl_expr_iter_next(iter); 385 while (expr != NULL) { 386 const char *name = 387 nftnl_expr_get_str(expr, NFTNL_EXPR_NAME); 388 389 if (strcmp(name, "counter") == 0) 390 nft_parse_counter(expr, &ctx.state.cs_arp->fw.counters); 391 else if (strcmp(name, "payload") == 0) 392 nft_parse_payload(&ctx, expr); 393 else if (strcmp(name, "meta") == 0) 394 nft_parse_meta(&ctx, expr); 395 else if (strcmp(name, "bitwise") == 0) 396 nft_parse_bitwise(&ctx, expr); 397 else if (strcmp(name, "cmp") == 0) 398 nft_parse_cmp(&ctx, expr); 399 else if (strcmp(name, "immediate") == 0) 400 nft_parse_immediate(&ctx, expr); 401 else if (strcmp(name, "target") == 0) 402 nft_parse_target(&ctx, expr); 403 404 expr = nftnl_expr_iter_next(iter); 405 } 406 407 nftnl_expr_iter_destroy(iter); 408 409 if (cs->jumpto != NULL) 410 return; 411 412 if (cs->target != NULL && cs->target->name != NULL) 413 cs->target = xtables_find_target(cs->target->name, XTF_TRY_LOAD); 414 else 415 cs->jumpto = ""; 416} 417 418static void nft_arp_print_header(unsigned int format, const char *chain, 419 const char *pol, 420 const struct xt_counters *counters, 421 bool basechain, uint32_t refs) 422{ 423 printf("Chain %s", chain); 424 if (pol) { 425 printf(" (policy %s", pol); 426 if (!(format & FMT_NOCOUNTS)) { 427 fputc(' ', stdout); 428 xtables_print_num(counters->pcnt, (format|FMT_NOTABLE)); 429 fputs("packets, ", stdout); 430 xtables_print_num(counters->bcnt, (format|FMT_NOTABLE)); 431 fputs("bytes", stdout); 432 } 433 printf(")\n"); 434 } else { 435 printf(" (%u references)\n", refs); 436 } 437} 438 439static void print_fw_details(struct arpt_entry *fw, unsigned int format) 440{ 441 char buf[BUFSIZ]; 442 char iface[IFNAMSIZ+2]; 443 int print_iface = 0; 444 int i; 445 446 iface[0] = '\0'; 447 448 if (fw->arp.iniface[0] != '\0') { 449 strcat(iface, fw->arp.iniface); 450 print_iface = 1; 451 } 452 else if (format & FMT_VIA) { 453 print_iface = 1; 454 if (format & FMT_NUMERIC) strcat(iface, "*"); 455 else strcat(iface, "any"); 456 } 457 if (print_iface) 458 printf("%s-i %s ", fw->arp.invflags & ARPT_INV_VIA_IN ? 459 "! " : "", iface); 460 461 print_iface = 0; 462 iface[0] = '\0'; 463 464 if (fw->arp.outiface[0] != '\0') { 465 strcat(iface, fw->arp.outiface); 466 print_iface = 1; 467 } 468 else if (format & FMT_VIA) { 469 print_iface = 1; 470 if (format & FMT_NUMERIC) strcat(iface, "*"); 471 else strcat(iface, "any"); 472 } 473 if (print_iface) 474 printf("%s-o %s ", fw->arp.invflags & ARPT_INV_VIA_OUT ? 475 "! " : "", iface); 476 477 if (fw->arp.smsk.s_addr != 0L) { 478 printf("%s", fw->arp.invflags & ARPT_INV_SRCIP 479 ? "! " : ""); 480 if (format & FMT_NUMERIC) 481 sprintf(buf, "%s", addr_to_dotted(&(fw->arp.src))); 482 else 483 sprintf(buf, "%s", addr_to_anyname(&(fw->arp.src))); 484 strncat(buf, mask_to_dotted(&(fw->arp.smsk)), 485 sizeof(buf) - strlen(buf) - 1); 486 printf("-s %s ", buf); 487 } 488 489 for (i = 0; i < ARPT_DEV_ADDR_LEN_MAX; i++) 490 if (fw->arp.src_devaddr.mask[i] != 0) 491 break; 492 if (i == ARPT_DEV_ADDR_LEN_MAX) 493 goto after_devsrc; 494 printf("%s", fw->arp.invflags & ARPT_INV_SRCDEVADDR 495 ? "! " : ""); 496 printf("--src-mac "); 497 print_mac_and_mask((unsigned char *)fw->arp.src_devaddr.addr, 498 (unsigned char *)fw->arp.src_devaddr.mask, ETH_ALEN); 499 printf(" "); 500after_devsrc: 501 502 if (fw->arp.tmsk.s_addr != 0L) { 503 printf("%s", fw->arp.invflags & ARPT_INV_TGTIP 504 ? "! " : ""); 505 if (format & FMT_NUMERIC) 506 sprintf(buf, "%s", addr_to_dotted(&(fw->arp.tgt))); 507 else 508 sprintf(buf, "%s", addr_to_anyname(&(fw->arp.tgt))); 509 strncat(buf, mask_to_dotted(&(fw->arp.tmsk)), 510 sizeof(buf) - strlen(buf) - 1); 511 printf("-d %s ", buf); 512 } 513 514 for (i = 0; i <ARPT_DEV_ADDR_LEN_MAX; i++) 515 if (fw->arp.tgt_devaddr.mask[i] != 0) 516 break; 517 if (i == ARPT_DEV_ADDR_LEN_MAX) 518 goto after_devdst; 519 printf("%s", fw->arp.invflags & ARPT_INV_TGTDEVADDR 520 ? "! " : ""); 521 printf("--dst-mac "); 522 print_mac_and_mask((unsigned char *)fw->arp.tgt_devaddr.addr, 523 (unsigned char *)fw->arp.tgt_devaddr.mask, ETH_ALEN); 524 printf(" "); 525 526after_devdst: 527 528 if (fw->arp.arhln_mask != 0) { 529 printf("%s", fw->arp.invflags & ARPT_INV_ARPHLN 530 ? "! " : ""); 531 printf("--h-length %d", fw->arp.arhln); 532 if (fw->arp.arhln_mask != 255) 533 printf("/%d", fw->arp.arhln_mask); 534 printf(" "); 535 } 536 537 if (fw->arp.arpop_mask != 0) { 538 int tmp = ntohs(fw->arp.arpop); 539 540 printf("%s", fw->arp.invflags & ARPT_INV_ARPOP 541 ? "! " : ""); 542 if (tmp <= NUMOPCODES && !(format & FMT_NUMERIC)) 543 printf("--opcode %s", opcodes[tmp-1]); 544 else 545 546 if (fw->arp.arpop_mask != 65535) 547 printf("/%d", ntohs(fw->arp.arpop_mask)); 548 printf(" "); 549 } 550 551 if (fw->arp.arhrd_mask != 0) { 552 uint16_t tmp = ntohs(fw->arp.arhrd); 553 554 printf("%s", fw->arp.invflags & ARPT_INV_ARPHRD 555 ? "! " : ""); 556 if (tmp == 1 && !(format & FMT_NUMERIC)) 557 printf("--h-type %s", "Ethernet"); 558 else 559 printf("--h-type %u", tmp); 560 if (fw->arp.arhrd_mask != 65535) 561 printf("/%d", ntohs(fw->arp.arhrd_mask)); 562 printf(" "); 563 } 564 565 if (fw->arp.arpro_mask != 0) { 566 int tmp = ntohs(fw->arp.arpro); 567 568 printf("%s", fw->arp.invflags & ARPT_INV_ARPPRO 569 ? "! " : ""); 570 if (tmp == 0x0800 && !(format & FMT_NUMERIC)) 571 printf("--proto-type %s", "IPv4"); 572 else 573 printf("--proto-type 0x%x", tmp); 574 if (fw->arp.arpro_mask != 65535) 575 printf("/%x", ntohs(fw->arp.arpro_mask)); 576 printf(" "); 577 } 578} 579 580static void 581nft_arp_print_firewall(struct nftnl_rule *r, unsigned int num, 582 unsigned int format) 583{ 584 struct arptables_command_state cs = {}; 585 586 nft_rule_to_arptables_command_state(r, &cs); 587 588 if (format & FMT_LINENUMBERS) 589 printf("%u ", num); 590 591 print_fw_details(&cs.fw, format); 592 593 if (cs.jumpto != NULL && strcmp(cs.jumpto, "") != 0) { 594 printf("-j %s", cs.jumpto); 595 } else if (cs.target) { 596 printf("-j %s", cs.target->name); 597 cs.target->print(&cs.fw, cs.target->t, format & FMT_NUMERIC); 598 } 599 600 if (!(format & FMT_NOCOUNTS)) { 601 printf(", pcnt="); 602 xtables_print_num(cs.fw.counters.pcnt, format); 603 printf("-- bcnt="); 604 xtables_print_num(cs.fw.counters.bcnt, format); 605 } 606 607 if (!(format & FMT_NONEWLINE)) 608 fputc('\n', stdout); 609} 610 611static bool nft_arp_is_same(const void *data_a, 612 const void *data_b) 613{ 614 const struct arpt_entry *a = data_a; 615 const struct arpt_entry *b = data_b; 616 617 if (a->arp.src.s_addr != b->arp.src.s_addr 618 || a->arp.tgt.s_addr != b->arp.tgt.s_addr 619 || a->arp.smsk.s_addr != b->arp.tmsk.s_addr 620 || a->arp.arpro != b->arp.arpro 621 || a->arp.flags != b->arp.flags 622 || a->arp.invflags != b->arp.invflags) { 623 DEBUGP("different src/dst/proto/flags/invflags\n"); 624 return false; 625 } 626 627 return is_same_interfaces(a->arp.iniface, 628 a->arp.outiface, 629 (unsigned char *)a->arp.iniface_mask, 630 (unsigned char *)a->arp.outiface_mask, 631 b->arp.iniface, 632 b->arp.outiface, 633 (unsigned char *)b->arp.iniface_mask, 634 (unsigned char *)b->arp.outiface_mask); 635} 636 637static bool nft_arp_rule_find(struct nft_family_ops *ops, struct nftnl_rule *r, 638 void *data) 639{ 640 const struct arptables_command_state *cs = data; 641 struct arptables_command_state this = {}; 642 643 /* Delete by matching rule case */ 644 nft_rule_to_arptables_command_state(r, &this); 645 646 if (!nft_arp_is_same(cs, &this)) 647 return false; 648 649 if (!compare_targets(cs->target, this.target)) 650 return false; 651 652 if (strcmp(cs->jumpto, this.jumpto) != 0) 653 return false; 654 655 return true; 656} 657 658struct nft_family_ops nft_family_ops_arp = { 659 .add = nft_arp_add, 660 .is_same = nft_arp_is_same, 661 .print_payload = NULL, 662 .parse_meta = nft_arp_parse_meta, 663 .parse_payload = nft_arp_parse_payload, 664 .parse_immediate = nft_arp_parse_immediate, 665 .print_header = nft_arp_print_header, 666 .print_firewall = nft_arp_print_firewall, 667 .save_firewall = NULL, 668 .save_counters = NULL, 669 .post_parse = NULL, 670 .rule_find = nft_arp_rule_find, 671 .parse_target = nft_arp_parse_target, 672}; 673