summaryrefslogtreecommitdiffstats
path: root/iptables/nft-ruleparse-arp.c
blob: b68fb06d8e0f96b977df153275bba851ffd51821 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/*
 * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org>
 * (C) 2013 by Giuseppe Longo <giuseppelng@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
 */

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netinet/if_ether.h>

#include <libnftnl/rule.h>
#include <libnftnl/expr.h>

#include "nft-shared.h"
#include "nft-ruleparse.h"
#include "xshared.h"

static void nft_arp_parse_meta(struct nft_xt_ctx *ctx,
			       const struct nft_xt_ctx_reg *reg,
			       struct nftnl_expr *e,
			       struct iptables_command_state *cs)
{
	struct arpt_entry *fw = &cs->arp;
	uint8_t flags = 0;

	if (parse_meta(ctx, e, reg->meta_dreg.key, fw->arp.iniface, fw->arp.iniface_mask,
		   fw->arp.outiface, fw->arp.outiface_mask,
		   &flags) == 0) {
		fw->arp.invflags |= flags;
		return;
	}

	ctx->errmsg = "Unknown arp meta key";
}

static void parse_mask_ipv4(const struct nft_xt_ctx_reg *reg, struct in_addr *mask)
{
	mask->s_addr = reg->bitwise.mask[0];
}

static bool nft_arp_parse_devaddr(const struct nft_xt_ctx_reg *reg,
				  struct nftnl_expr *e,
				  struct arpt_devaddr_info *info)
{
	uint32_t hlen;
	bool inv;

	nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &hlen);

	if (hlen != ETH_ALEN)
		return false;

	get_cmp_data(e, info->addr, ETH_ALEN, &inv);

	if (reg->bitwise.set)
		memcpy(info->mask, reg->bitwise.mask, ETH_ALEN);
	else
		memset(info->mask, 0xff,
		       min(reg->payload.len, ETH_ALEN));

	return inv;
}

static void nft_arp_parse_payload(struct nft_xt_ctx *ctx,
				  const struct nft_xt_ctx_reg *reg,
				  struct nftnl_expr *e,
				  struct iptables_command_state *cs)
{
	struct arpt_entry *fw = &cs->arp;
	struct in_addr addr;
	uint16_t ar_hrd, ar_pro, ar_op;
	uint8_t ar_hln, ar_pln;
	bool inv;

	switch (reg->payload.offset) {
	case offsetof(struct arphdr, ar_hrd):
		get_cmp_data(e, &ar_hrd, sizeof(ar_hrd), &inv);
		fw->arp.arhrd = ar_hrd;
		fw->arp.arhrd_mask = 0xffff;
		if (inv)
			fw->arp.invflags |= IPT_INV_ARPHRD;
		break;
	case offsetof(struct arphdr, ar_pro):
		get_cmp_data(e, &ar_pro, sizeof(ar_pro), &inv);
		fw->arp.arpro = ar_pro;
		fw->arp.arpro_mask = 0xffff;
		if (inv)
			fw->arp.invflags |= IPT_INV_PROTO;
		break;
	case offsetof(struct arphdr, ar_op):
		get_cmp_data(e, &ar_op, sizeof(ar_op), &inv);
		fw->arp.arpop = ar_op;
		fw->arp.arpop_mask = 0xffff;
		if (inv)
			fw->arp.invflags |= IPT_INV_ARPOP;
		break;
	case offsetof(struct arphdr, ar_hln):
		get_cmp_data(e, &ar_hln, sizeof(ar_hln), &inv);
		fw->arp.arhln = ar_hln;
		fw->arp.arhln_mask = 0xff;
		if (inv)
			fw->arp.invflags |= IPT_INV_ARPHLN;
		break;
	case offsetof(struct arphdr, ar_pln):
		get_cmp_data(e, &ar_pln, sizeof(ar_pln), &inv);
		if (ar_pln != 4 || inv)
			ctx->errmsg = "unexpected ARP protocol length match";
		break;
	default:
		if (reg->payload.offset == sizeof(struct arphdr)) {
			if (nft_arp_parse_devaddr(reg, e, &fw->arp.src_devaddr))
				fw->arp.invflags |= IPT_INV_SRCDEVADDR;
		} else if (reg->payload.offset == sizeof(struct arphdr) +
					   fw->arp.arhln) {
			get_cmp_data(e, &addr, sizeof(addr), &inv);
			fw->arp.src.s_addr = addr.s_addr;
			if (reg->bitwise.set)
				parse_mask_ipv4(reg, &fw->arp.smsk);
			else
				memset(&fw->arp.smsk, 0xff,
				       min(reg->payload.len,
					   sizeof(struct in_addr)));

			if (inv)
				fw->arp.invflags |= IPT_INV_SRCIP;
		} else if (reg->payload.offset == sizeof(struct arphdr) +
						  fw->arp.arhln +
						  sizeof(struct in_addr)) {
			if (nft_arp_parse_devaddr(reg, e, &fw->arp.tgt_devaddr))
				fw->arp.invflags |= IPT_INV_TGTDEVADDR;
		} else if (reg->payload.offset == sizeof(struct arphdr) +
						  fw->arp.arhln +
						  sizeof(struct in_addr) +
						  fw->arp.arhln) {
			get_cmp_data(e, &addr, sizeof(addr), &inv);
			fw->arp.tgt.s_addr = addr.s_addr;
			if (reg->bitwise.set)
				parse_mask_ipv4(reg, &fw->arp.tmsk);
			else
				memset(&fw->arp.tmsk, 0xff,
				       min(reg->payload.len,
					   sizeof(struct in_addr)));

			if (inv)
				fw->arp.invflags |= IPT_INV_DSTIP;
		} else {
			ctx->errmsg = "unknown payload offset";
		}
		break;
	}
}

struct nft_ruleparse_ops nft_ruleparse_ops_arp = {
	.meta		= nft_arp_parse_meta,
	.payload	= nft_arp_parse_payload,
	.target		= nft_ipv46_parse_target,
};