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