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/socket.h>
17#include <netinet/in.h>
18#include <arpa/inet.h>
19#include <netinet/ip.h>
20#include <netdb.h>
21
22#include <xtables.h>
23
24#include <linux/netfilter/nf_tables.h>
25
26#include "nft.h"
27#include "nft-shared.h"
28
29static int nft_ipv4_add(struct nftnl_rule *r, void *data)
30{
31	struct iptables_command_state *cs = data;
32	struct xtables_rule_match *matchp;
33	uint32_t op;
34	int ret;
35
36	if (cs->fw.ip.iniface[0] != '\0') {
37		op = nft_invflags2cmp(cs->fw.ip.invflags, IPT_INV_VIA_IN);
38		add_iniface(r, cs->fw.ip.iniface, op);
39	}
40
41	if (cs->fw.ip.outiface[0] != '\0') {
42		op = nft_invflags2cmp(cs->fw.ip.invflags, IPT_INV_VIA_OUT);
43		add_outiface(r, cs->fw.ip.outiface, op);
44	}
45
46	if (cs->fw.ip.proto != 0) {
47		op = nft_invflags2cmp(cs->fw.ip.invflags, XT_INV_PROTO);
48		add_proto(r, offsetof(struct iphdr, protocol), 1,
49			  cs->fw.ip.proto, op);
50	}
51
52	if (cs->fw.ip.src.s_addr != 0) {
53		op = nft_invflags2cmp(cs->fw.ip.invflags, IPT_INV_SRCIP);
54		add_addr(r, offsetof(struct iphdr, saddr),
55			 &cs->fw.ip.src.s_addr, &cs->fw.ip.smsk.s_addr,
56			 sizeof(struct in_addr), op);
57	}
58	if (cs->fw.ip.dst.s_addr != 0) {
59		op = nft_invflags2cmp(cs->fw.ip.invflags, IPT_INV_DSTIP);
60		add_addr(r, offsetof(struct iphdr, daddr),
61			 &cs->fw.ip.dst.s_addr, &cs->fw.ip.dmsk.s_addr,
62			 sizeof(struct in_addr), op);
63	}
64	if (cs->fw.ip.flags & IPT_F_FRAG) {
65		add_payload(r, offsetof(struct iphdr, frag_off), 2,
66			    NFT_PAYLOAD_NETWORK_HEADER);
67		/* get the 13 bits that contain the fragment offset */
68		add_bitwise_u16(r, 0x1fff, !0x1fff);
69
70		/* if offset is non-zero, this is a fragment */
71		op = nft_invflags2cmp(cs->fw.ip.invflags, IPT_INV_FRAG);
72		add_cmp_u16(r, 0, op);
73	}
74
75	add_compat(r, cs->fw.ip.proto, cs->fw.ip.invflags);
76
77	for (matchp = cs->matches; matchp; matchp = matchp->next) {
78		/* Use nft built-in comments support instead of comment match */
79		if (strcmp(matchp->match->name, "comment") == 0) {
80			ret = add_comment(r, (char *)matchp->match->m->data);
81			if (ret < 0)
82				return ret;
83		} else {
84			ret = add_match(r, matchp->match->m);
85			if (ret < 0)
86				return ret;
87		}
88	}
89
90	/* Counters need to me added before the target, otherwise they are
91	 * increased for each rule because of the way nf_tables works.
92	 */
93	if (add_counters(r, cs->counters.pcnt, cs->counters.bcnt) < 0)
94		return -1;
95
96	return add_action(r, cs, !!(cs->fw.ip.flags & IPT_F_GOTO));
97}
98
99static bool nft_ipv4_is_same(const void *data_a,
100			     const void *data_b)
101{
102	const struct iptables_command_state *a = data_a;
103	const struct iptables_command_state *b = data_b;
104
105	if (a->fw.ip.src.s_addr != b->fw.ip.src.s_addr
106	    || a->fw.ip.dst.s_addr != b->fw.ip.dst.s_addr
107	    || a->fw.ip.smsk.s_addr != b->fw.ip.smsk.s_addr
108	    || a->fw.ip.dmsk.s_addr != b->fw.ip.dmsk.s_addr
109	    || a->fw.ip.proto != b->fw.ip.proto
110	    || a->fw.ip.flags != b->fw.ip.flags
111	    || a->fw.ip.invflags != b->fw.ip.invflags) {
112		DEBUGP("different src/dst/proto/flags/invflags\n");
113		return false;
114	}
115
116	return is_same_interfaces(a->fw.ip.iniface, a->fw.ip.outiface,
117				  a->fw.ip.iniface_mask, a->fw.ip.outiface_mask,
118				  b->fw.ip.iniface, b->fw.ip.outiface,
119				  b->fw.ip.iniface_mask, b->fw.ip.outiface_mask);
120}
121
122static void get_frag(struct nft_xt_ctx *ctx, struct nftnl_expr *e, bool *inv)
123{
124	uint8_t op;
125
126	/* we assume correct mask and xor */
127	if (!(ctx->flags & NFT_XT_CTX_BITWISE))
128		return;
129
130	/* we assume correct data */
131	op = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP);
132	if (op == NFT_CMP_EQ)
133		*inv = true;
134	else
135		*inv = false;
136
137	ctx->flags &= ~NFT_XT_CTX_BITWISE;
138}
139
140static const char *mask_to_str(uint32_t mask)
141{
142	static char mask_str[sizeof("255.255.255.255")];
143	uint32_t bits, hmask = ntohl(mask);
144	struct in_addr mask_addr = {
145		.s_addr = mask,
146	};
147	int i;
148
149	if (mask == 0xFFFFFFFFU) {
150		sprintf(mask_str, "32");
151		return mask_str;
152	}
153
154	i    = 32;
155	bits = 0xFFFFFFFEU;
156	while (--i >= 0 && hmask != bits)
157		bits <<= 1;
158	if (i >= 0)
159		sprintf(mask_str, "%u", i);
160	else
161		sprintf(mask_str, "%s", inet_ntoa(mask_addr));
162
163	return mask_str;
164}
165
166static void nft_ipv4_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
167				void *data)
168{
169	struct iptables_command_state *cs = data;
170
171	parse_meta(e, ctx->meta.key, cs->fw.ip.iniface, cs->fw.ip.iniface_mask,
172		   cs->fw.ip.outiface, cs->fw.ip.outiface_mask,
173		   &cs->fw.ip.invflags);
174}
175
176static void parse_mask_ipv4(struct nft_xt_ctx *ctx, struct in_addr *mask)
177{
178	mask->s_addr = ctx->bitwise.mask[0];
179}
180
181static void nft_ipv4_parse_payload(struct nft_xt_ctx *ctx,
182				   struct nftnl_expr *e, void *data)
183{
184	struct iptables_command_state *cs = data;
185	struct in_addr addr;
186	uint8_t proto;
187	bool inv;
188
189	switch(ctx->payload.offset) {
190	case offsetof(struct iphdr, saddr):
191		get_cmp_data(e, &addr, sizeof(addr), &inv);
192		cs->fw.ip.src.s_addr = addr.s_addr;
193		if (ctx->flags & NFT_XT_CTX_BITWISE) {
194			parse_mask_ipv4(ctx, &cs->fw.ip.smsk);
195			ctx->flags &= ~NFT_XT_CTX_BITWISE;
196		} else {
197			cs->fw.ip.smsk.s_addr = 0xffffffff;
198		}
199
200		if (inv)
201			cs->fw.ip.invflags |= IPT_INV_SRCIP;
202		break;
203	case offsetof(struct iphdr, daddr):
204		get_cmp_data(e, &addr, sizeof(addr), &inv);
205		cs->fw.ip.dst.s_addr = addr.s_addr;
206		if (ctx->flags & NFT_XT_CTX_BITWISE) {
207			parse_mask_ipv4(ctx, &cs->fw.ip.dmsk);
208			ctx->flags &= ~NFT_XT_CTX_BITWISE;
209		} else {
210			cs->fw.ip.dmsk.s_addr = 0xffffffff;
211		}
212
213		if (inv)
214			cs->fw.ip.invflags |= IPT_INV_DSTIP;
215		break;
216	case offsetof(struct iphdr, protocol):
217		get_cmp_data(e, &proto, sizeof(proto), &inv);
218		cs->fw.ip.proto = proto;
219		if (inv)
220			cs->fw.ip.invflags |= IPT_INV_PROTO;
221		break;
222	case offsetof(struct iphdr, frag_off):
223		cs->fw.ip.flags |= IPT_F_FRAG;
224		get_frag(ctx, e, &inv);
225		if (inv)
226			cs->fw.ip.invflags |= IPT_INV_FRAG;
227		break;
228	default:
229		DEBUGP("unknown payload offset %d\n", ctx->payload.offset);
230		break;
231	}
232}
233
234static void nft_ipv4_parse_immediate(const char *jumpto, bool nft_goto,
235				     void *data)
236{
237	struct iptables_command_state *cs = data;
238
239	cs->jumpto = jumpto;
240
241	if (nft_goto)
242		cs->fw.ip.flags |= IPT_F_GOTO;
243}
244
245static void nft_ipv4_print_header(unsigned int format, const char *chain,
246				  const char *pol,
247				  const struct xt_counters *counters,
248				  bool basechain, uint32_t refs)
249{
250	print_header(format, chain, pol, counters, basechain, refs);
251}
252
253static void print_ipv4_addr(const struct iptables_command_state *cs,
254			    unsigned int format)
255{
256	char buf[BUFSIZ];
257
258	fputc(cs->fw.ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout);
259	if (cs->fw.ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC))
260		printf(FMT("%-19s ","%s "), "anywhere");
261	else {
262		if (format & FMT_NUMERIC)
263			strcpy(buf, xtables_ipaddr_to_numeric(&cs->fw.ip.src));
264		else
265			strcpy(buf, xtables_ipaddr_to_anyname(&cs->fw.ip.src));
266		strcat(buf, xtables_ipmask_to_numeric(&cs->fw.ip.smsk));
267		printf(FMT("%-19s ","%s "), buf);
268	}
269
270	fputc(cs->fw.ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout);
271	if (cs->fw.ip.dmsk.s_addr == 0L && !(format & FMT_NUMERIC))
272		printf(FMT("%-19s ","-> %s"), "anywhere");
273	else {
274		if (format & FMT_NUMERIC)
275			strcpy(buf, xtables_ipaddr_to_numeric(&cs->fw.ip.dst));
276		else
277			strcpy(buf, xtables_ipaddr_to_anyname(&cs->fw.ip.dst));
278		strcat(buf, xtables_ipmask_to_numeric(&cs->fw.ip.dmsk));
279		printf(FMT("%-19s ","-> %s"), buf);
280	}
281}
282
283static void print_fragment(unsigned int flags, unsigned int invflags,
284			   unsigned int format)
285{
286	if (!(format & FMT_OPTIONS))
287		return;
288
289	if (format & FMT_NOTABLE)
290		fputs("opt ", stdout);
291	fputc(invflags & IPT_INV_FRAG ? '!' : '-', stdout);
292	fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout);
293	fputc(' ', stdout);
294}
295
296static void nft_ipv4_print_firewall(struct nftnl_rule *r, unsigned int num,
297				    unsigned int format)
298{
299	struct iptables_command_state cs = {};
300
301	nft_rule_to_iptables_command_state(r, &cs);
302
303	print_firewall_details(&cs, cs.jumpto, cs.fw.ip.flags,
304			       cs.fw.ip.invflags, cs.fw.ip.proto,
305			       num, format);
306	print_fragment(cs.fw.ip.flags, cs.fw.ip.invflags, format);
307	print_ifaces(cs.fw.ip.iniface, cs.fw.ip.outiface, cs.fw.ip.invflags,
308		     format);
309	print_ipv4_addr(&cs, format);
310
311	if (format & FMT_NOTABLE)
312		fputs("  ", stdout);
313
314#ifdef IPT_F_GOTO
315	if (cs.fw.ip.flags & IPT_F_GOTO)
316		printf("[goto] ");
317#endif
318
319	print_matches_and_target(&cs, format);
320
321	if (!(format & FMT_NONEWLINE))
322		fputc('\n', stdout);
323}
324
325static void save_ipv4_addr(char letter, const struct in_addr *addr,
326			   uint32_t mask, int invert)
327{
328	if (!mask && !invert && !addr->s_addr)
329		return;
330
331	printf("%s-%c %s/%s ", invert ? "! " : "", letter, inet_ntoa(*addr),
332	       mask_to_str(mask));
333}
334
335static void nft_ipv4_save_firewall(const void *data, unsigned int format)
336{
337	const struct iptables_command_state *cs = data;
338
339	save_firewall_details(cs, cs->fw.ip.invflags, cs->fw.ip.proto,
340			      cs->fw.ip.iniface, cs->fw.ip.iniface_mask,
341			      cs->fw.ip.outiface, cs->fw.ip.outiface_mask);
342
343	if (cs->fw.ip.flags & IPT_F_FRAG) {
344		if (cs->fw.ip.invflags & IPT_INV_FRAG)
345			printf("! ");
346		printf("-f ");
347	}
348
349	save_ipv4_addr('s', &cs->fw.ip.src, cs->fw.ip.smsk.s_addr,
350		       cs->fw.ip.invflags & IPT_INV_SRCIP);
351	save_ipv4_addr('d', &cs->fw.ip.dst, cs->fw.ip.dmsk.s_addr,
352		       cs->fw.ip.invflags & IPT_INV_DSTIP);
353
354	save_matches_and_target(cs->matches, cs->target,
355				cs->jumpto, cs->fw.ip.flags, &cs->fw);
356
357	if (cs->target == NULL && strlen(cs->jumpto) > 0) {
358		printf("-%c %s", cs->fw.ip.flags & IPT_F_GOTO ? 'g' : 'j',
359		       cs->jumpto);
360	}
361	printf("\n");
362}
363
364static void nft_ipv4_proto_parse(struct iptables_command_state *cs,
365				 struct xtables_args *args)
366{
367	cs->fw.ip.proto = args->proto;
368	cs->fw.ip.invflags = args->invflags;
369}
370
371static void nft_ipv4_post_parse(int command,
372				struct iptables_command_state *cs,
373				struct xtables_args *args)
374{
375	cs->fw.ip.flags = args->flags;
376	/* We already set invflags in proto_parse, but we need to refresh it
377	 * to include new parsed options.
378	 */
379	cs->fw.ip.invflags = args->invflags;
380
381	strncpy(cs->fw.ip.iniface, args->iniface, IFNAMSIZ);
382	memcpy(cs->fw.ip.iniface_mask,
383	       args->iniface_mask, IFNAMSIZ*sizeof(unsigned char));
384
385	strncpy(cs->fw.ip.outiface, args->outiface, IFNAMSIZ);
386	memcpy(cs->fw.ip.outiface_mask,
387	       args->outiface_mask, IFNAMSIZ*sizeof(unsigned char));
388
389	if (args->goto_set)
390		cs->fw.ip.flags |= IPT_F_GOTO;
391
392	cs->counters.pcnt = args->pcnt_cnt;
393	cs->counters.bcnt = args->bcnt_cnt;
394
395	if (command & (CMD_REPLACE | CMD_INSERT |
396			CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
397		if (!(cs->options & OPT_DESTINATION))
398			args->dhostnetworkmask = "0.0.0.0/0";
399		if (!(cs->options & OPT_SOURCE))
400			args->shostnetworkmask = "0.0.0.0/0";
401	}
402
403	if (args->shostnetworkmask)
404		xtables_ipparse_multiple(args->shostnetworkmask,
405					 &args->s.addr.v4, &args->s.mask.v4,
406					 &args->s.naddrs);
407	if (args->dhostnetworkmask)
408		xtables_ipparse_multiple(args->dhostnetworkmask,
409					 &args->d.addr.v4, &args->d.mask.v4,
410					 &args->d.naddrs);
411
412	if ((args->s.naddrs > 1 || args->d.naddrs > 1) &&
413	    (cs->fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP)))
414		xtables_error(PARAMETER_PROBLEM,
415			      "! not allowed with multiple"
416			      " source or destination IP addresses");
417}
418
419static void nft_ipv4_parse_target(struct xtables_target *t, void *data)
420{
421	struct iptables_command_state *cs = data;
422
423	cs->target = t;
424}
425
426static bool nft_ipv4_rule_find(struct nft_family_ops *ops,
427			       struct nftnl_rule *r, void *data)
428{
429	struct iptables_command_state *cs = data;
430
431	return nft_ipv46_rule_find(ops, r, cs);
432}
433
434static void nft_ipv4_save_counters(const void *data)
435{
436	const struct iptables_command_state *cs = data;
437
438	save_counters(cs->counters.pcnt, cs->counters.bcnt);
439}
440
441static int nft_ipv4_xlate(const void *data, struct xt_xlate *xl)
442{
443	const struct iptables_command_state *cs = data;
444	const char *comment;
445	int ret;
446
447	xlate_ifname(xl, "iifname", cs->fw.ip.iniface,
448		     cs->fw.ip.invflags & IPT_INV_VIA_IN);
449	xlate_ifname(xl, "oifname", cs->fw.ip.outiface,
450		     cs->fw.ip.invflags & IPT_INV_VIA_OUT);
451
452	if (cs->fw.ip.flags & IPT_F_FRAG) {
453		xt_xlate_add(xl, "ip frag-off %s%x ",
454			   cs->fw.ip.invflags & IPT_INV_FRAG? "" : "!= ", 0);
455	}
456
457	if (cs->fw.ip.proto != 0) {
458		const struct protoent *pent =
459			getprotobynumber(cs->fw.ip.proto);
460		char protonum[strlen("255") + 1];
461
462		if (!xlate_find_match(cs, pent->p_name)) {
463			snprintf(protonum, sizeof(protonum), "%u",
464				 cs->fw.ip.proto);
465			protonum[sizeof(protonum) - 1] = '\0';
466			xt_xlate_add(xl, "ip protocol %s%s ",
467				   cs->fw.ip.invflags & IPT_INV_PROTO ?
468					"!= " : "",
469				   pent ? pent->p_name : protonum);
470		}
471	}
472
473	if (cs->fw.ip.src.s_addr != 0) {
474		xt_xlate_add(xl, "ip saddr %s%s%s ",
475			   cs->fw.ip.invflags & IPT_INV_SRCIP ? "!= " : "",
476			   inet_ntoa(cs->fw.ip.src),
477			   xtables_ipmask_to_numeric(&cs->fw.ip.smsk));
478	}
479	if (cs->fw.ip.dst.s_addr != 0) {
480		xt_xlate_add(xl, "ip daddr %s%s%s ",
481			   cs->fw.ip.invflags & IPT_INV_DSTIP ? "!= " : "",
482			   inet_ntoa(cs->fw.ip.dst),
483			   xtables_ipmask_to_numeric(&cs->fw.ip.dmsk));
484	}
485
486	ret = xlate_matches(cs, xl);
487	if (!ret)
488		return ret;
489
490	/* Always add counters per rule, as in iptables */
491	xt_xlate_add(xl, "counter ");
492
493	comment = xt_xlate_get_comment(xl);
494	if (comment)
495		xt_xlate_add(xl, "comment %s", comment);
496
497	ret = xlate_action(cs, !!(cs->fw.ip.flags & IPT_F_GOTO), xl);
498
499	return ret;
500}
501
502struct nft_family_ops nft_family_ops_ipv4 = {
503	.add			= nft_ipv4_add,
504	.is_same		= nft_ipv4_is_same,
505	.parse_meta		= nft_ipv4_parse_meta,
506	.parse_payload		= nft_ipv4_parse_payload,
507	.parse_immediate	= nft_ipv4_parse_immediate,
508	.print_header		= nft_ipv4_print_header,
509	.print_firewall		= nft_ipv4_print_firewall,
510	.save_firewall		= nft_ipv4_save_firewall,
511	.save_counters		= nft_ipv4_save_counters,
512	.proto_parse		= nft_ipv4_proto_parse,
513	.post_parse		= nft_ipv4_post_parse,
514	.parse_target		= nft_ipv4_parse_target,
515	.rule_find		= nft_ipv4_rule_find,
516	.xlate			= nft_ipv4_xlate,
517};
518