1 /*
2 * (C) 2014 by Giuseppe Longo <[email protected]>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 */
9
10 #include <stddef.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <netdb.h>
15 #include <net/if.h>
16 //#include <net/if_arp.h>
17 #include <netinet/if_ether.h>
18
19 #include <libnftnl/rule.h>
20 #include <libnftnl/expr.h>
21 #include <libnftnl/set.h>
22
23 #include <xtables.h>
24
25 #include "nft.h" /* just for nft_set_batch_lookup_byid? */
26 #include "nft-bridge.h"
27 #include "nft-cache.h"
28 #include "nft-shared.h"
29 #include "nft-ruleparse.h"
30 #include "xshared.h"
31
nft_bridge_parse_meta(struct nft_xt_ctx * ctx,const struct nft_xt_ctx_reg * reg,struct nftnl_expr * e,struct iptables_command_state * cs)32 static void nft_bridge_parse_meta(struct nft_xt_ctx *ctx,
33 const struct nft_xt_ctx_reg *reg,
34 struct nftnl_expr *e,
35 struct iptables_command_state *cs)
36 {
37 struct ebt_entry *fw = &cs->eb;
38 uint8_t invflags = 0;
39 char iifname[IFNAMSIZ] = {}, oifname[IFNAMSIZ] = {};
40
41 switch (reg->meta_dreg.key) {
42 case NFT_META_PROTOCOL:
43 return;
44 }
45
46 if (parse_meta(ctx, e, reg->meta_dreg.key, iifname, NULL, oifname, NULL, &invflags) < 0) {
47 ctx->errmsg = "unknown meta key";
48 return;
49 }
50
51 switch (reg->meta_dreg.key) {
52 case NFT_META_BRI_IIFNAME:
53 if (invflags & IPT_INV_VIA_IN)
54 cs->eb.invflags |= EBT_ILOGICALIN;
55 snprintf(fw->logical_in, sizeof(fw->logical_in), "%s", iifname);
56 break;
57 case NFT_META_IIFNAME:
58 if (invflags & IPT_INV_VIA_IN)
59 cs->eb.invflags |= EBT_IIN;
60 snprintf(fw->in, sizeof(fw->in), "%s", iifname);
61 break;
62 case NFT_META_BRI_OIFNAME:
63 if (invflags & IPT_INV_VIA_OUT)
64 cs->eb.invflags |= EBT_ILOGICALOUT;
65 snprintf(fw->logical_out, sizeof(fw->logical_out), "%s", oifname);
66 break;
67 case NFT_META_OIFNAME:
68 if (invflags & IPT_INV_VIA_OUT)
69 cs->eb.invflags |= EBT_IOUT;
70 snprintf(fw->out, sizeof(fw->out), "%s", oifname);
71 break;
72 default:
73 ctx->errmsg = "unknown bridge meta key";
74 break;
75 }
76 }
77
nft_bridge_parse_payload(struct nft_xt_ctx * ctx,const struct nft_xt_ctx_reg * reg,struct nftnl_expr * e,struct iptables_command_state * cs)78 static void nft_bridge_parse_payload(struct nft_xt_ctx *ctx,
79 const struct nft_xt_ctx_reg *reg,
80 struct nftnl_expr *e,
81 struct iptables_command_state *cs)
82 {
83 struct ebt_entry *fw = &cs->eb;
84 unsigned char addr[ETH_ALEN];
85 unsigned short int ethproto;
86 uint8_t op;
87 bool inv;
88 int i;
89
90 switch (reg->payload.offset) {
91 case offsetof(struct ethhdr, h_dest):
92 get_cmp_data(e, addr, sizeof(addr), &inv);
93 for (i = 0; i < ETH_ALEN; i++)
94 fw->destmac[i] = addr[i];
95 if (inv)
96 fw->invflags |= EBT_IDEST;
97
98 if (reg->bitwise.set)
99 memcpy(fw->destmsk, reg->bitwise.mask, ETH_ALEN);
100 else
101 memset(&fw->destmsk, 0xff,
102 min(reg->payload.len, ETH_ALEN));
103 fw->bitmask |= EBT_IDEST;
104 break;
105 case offsetof(struct ethhdr, h_source):
106 get_cmp_data(e, addr, sizeof(addr), &inv);
107 for (i = 0; i < ETH_ALEN; i++)
108 fw->sourcemac[i] = addr[i];
109 if (inv)
110 fw->invflags |= EBT_ISOURCE;
111 if (reg->bitwise.set)
112 memcpy(fw->sourcemsk, reg->bitwise.mask, ETH_ALEN);
113 else
114 memset(&fw->sourcemsk, 0xff,
115 min(reg->payload.len, ETH_ALEN));
116 fw->bitmask |= EBT_ISOURCE;
117 break;
118 case offsetof(struct ethhdr, h_proto):
119 __get_cmp_data(e, ðproto, sizeof(ethproto), &op);
120 if (ethproto == htons(0x0600)) {
121 fw->bitmask |= EBT_802_3;
122 inv = (op == NFT_CMP_GTE);
123 } else {
124 fw->ethproto = ethproto;
125 inv = (op == NFT_CMP_NEQ);
126 }
127 if (inv)
128 fw->invflags |= EBT_IPROTO;
129 fw->bitmask &= ~EBT_NOPROTO;
130 break;
131 default:
132 DEBUGP("unknown payload offset %d\n", reg->payload.offset);
133 ctx->errmsg = "unknown payload offset";
134 break;
135 }
136 }
137
138 /* return 0 if saddr, 1 if daddr, -1 on error */
139 static int
lookup_check_ether_payload(uint32_t base,uint32_t offset,uint32_t len)140 lookup_check_ether_payload(uint32_t base, uint32_t offset, uint32_t len)
141 {
142 if (base != 0 || len != ETH_ALEN)
143 return -1;
144
145 switch (offset) {
146 case offsetof(struct ether_header, ether_dhost):
147 return 1;
148 case offsetof(struct ether_header, ether_shost):
149 return 0;
150 default:
151 return -1;
152 }
153 }
154
155 /* return 0 if saddr, 1 if daddr, -1 on error */
156 static int
lookup_check_iphdr_payload(uint32_t base,uint32_t offset,uint32_t len)157 lookup_check_iphdr_payload(uint32_t base, uint32_t offset, uint32_t len)
158 {
159 if (base != 1 || len != 4)
160 return -1;
161
162 switch (offset) {
163 case offsetof(struct iphdr, daddr):
164 return 1;
165 case offsetof(struct iphdr, saddr):
166 return 0;
167 default:
168 return -1;
169 }
170 }
171
172 /* Make sure previous payload expression(s) is/are consistent and extract if
173 * matching on source or destination address and if matching on MAC and IP or
174 * only MAC address. */
lookup_analyze_payloads(struct nft_xt_ctx * ctx,enum nft_registers sreg,uint32_t key_len,bool * dst,bool * ip)175 static int lookup_analyze_payloads(struct nft_xt_ctx *ctx,
176 enum nft_registers sreg,
177 uint32_t key_len,
178 bool *dst, bool *ip)
179 {
180 const struct nft_xt_ctx_reg *reg;
181 int val, val2 = -1;
182
183 reg = nft_xt_ctx_get_sreg(ctx, sreg);
184 if (!reg)
185 return -1;
186
187 if (reg->type != NFT_XT_REG_PAYLOAD) {
188 ctx->errmsg = "lookup reg is not payload type";
189 return -1;
190 }
191
192 switch (key_len) {
193 case 12: /* ether + ipv4addr */
194 val = lookup_check_ether_payload(reg->payload.base,
195 reg->payload.offset,
196 reg->payload.len);
197 if (val < 0) {
198 DEBUGP("unknown payload base/offset/len %d/%d/%d\n",
199 reg->payload.base, reg->payload.offset,
200 reg->payload.len);
201 return -1;
202 }
203
204 sreg = nft_get_next_reg(sreg, ETH_ALEN);
205
206 reg = nft_xt_ctx_get_sreg(ctx, sreg);
207 if (!reg) {
208 ctx->errmsg = "next lookup register is invalid";
209 return -1;
210 }
211
212 if (reg->type != NFT_XT_REG_PAYLOAD) {
213 ctx->errmsg = "next lookup reg is not payload type";
214 return -1;
215 }
216
217 val2 = lookup_check_iphdr_payload(reg->payload.base,
218 reg->payload.offset,
219 reg->payload.len);
220 if (val2 < 0) {
221 DEBUGP("unknown payload base/offset/len %d/%d/%d\n",
222 reg->payload.base, reg->payload.offset,
223 reg->payload.len);
224 return -1;
225 } else if (val != val2) {
226 DEBUGP("mismatching payload match offsets\n");
227 return -1;
228 }
229 break;
230 case 6: /* ether */
231 val = lookup_check_ether_payload(reg->payload.base,
232 reg->payload.offset,
233 reg->payload.len);
234 if (val < 0) {
235 DEBUGP("unknown payload base/offset/len %d/%d/%d\n",
236 reg->payload.base, reg->payload.offset,
237 reg->payload.len);
238 return -1;
239 }
240 break;
241 default:
242 ctx->errmsg = "unsupported lookup key length";
243 return -1;
244 }
245
246 if (dst)
247 *dst = (val == 1);
248 if (ip)
249 *ip = (val2 != -1);
250 return 0;
251 }
252
set_elems_to_among_pairs(struct nft_among_pair * pairs,const struct nftnl_set * s,int cnt)253 static int set_elems_to_among_pairs(struct nft_among_pair *pairs,
254 const struct nftnl_set *s, int cnt)
255 {
256 struct nftnl_set_elems_iter *iter = nftnl_set_elems_iter_create(s);
257 struct nftnl_set_elem *elem;
258 size_t tmpcnt = 0;
259 const void *data;
260 uint32_t datalen;
261 int ret = -1;
262
263 if (!iter) {
264 fprintf(stderr, "BUG: set elems iter allocation failed\n");
265 return ret;
266 }
267
268 while ((elem = nftnl_set_elems_iter_next(iter))) {
269 data = nftnl_set_elem_get(elem, NFTNL_SET_ELEM_KEY, &datalen);
270 if (!data) {
271 fprintf(stderr, "BUG: set elem without key\n");
272 goto err;
273 }
274 if (datalen > sizeof(*pairs)) {
275 fprintf(stderr, "BUG: overlong set elem\n");
276 goto err;
277 }
278 nft_among_insert_pair(pairs, &tmpcnt, data);
279 }
280 ret = 0;
281 err:
282 nftnl_set_elems_iter_destroy(iter);
283 return ret;
284 }
285
set_from_lookup_expr(struct nft_xt_ctx * ctx,const struct nftnl_expr * e)286 static struct nftnl_set *set_from_lookup_expr(struct nft_xt_ctx *ctx,
287 const struct nftnl_expr *e)
288 {
289 const char *set_name = nftnl_expr_get_str(e, NFTNL_EXPR_LOOKUP_SET);
290 uint32_t set_id = nftnl_expr_get_u32(e, NFTNL_EXPR_LOOKUP_SET_ID);
291 struct nftnl_set_list *slist;
292 struct nftnl_set *set;
293
294 slist = nft_set_list_get(ctx->h, ctx->table, set_name);
295 if (slist) {
296 set = nftnl_set_list_lookup_byname(slist, set_name);
297 if (set)
298 return set;
299
300 set = nft_set_batch_lookup_byid(ctx->h, set_id);
301 if (set)
302 return set;
303 }
304
305 return NULL;
306 }
307
nft_bridge_parse_lookup(struct nft_xt_ctx * ctx,struct nftnl_expr * e)308 static void nft_bridge_parse_lookup(struct nft_xt_ctx *ctx,
309 struct nftnl_expr *e)
310 {
311 struct xtables_match *match = NULL;
312 struct nft_among_data *among_data;
313 bool is_dst, have_ip, inv;
314 struct ebt_match *ematch;
315 struct nftnl_set *s;
316 size_t poff, size;
317 uint32_t cnt;
318
319 s = set_from_lookup_expr(ctx, e);
320 if (!s)
321 xtables_error(OTHER_PROBLEM,
322 "BUG: lookup expression references unknown set");
323
324 if (lookup_analyze_payloads(ctx,
325 nftnl_expr_get_u32(e, NFTNL_EXPR_LOOKUP_SREG),
326 nftnl_set_get_u32(s, NFTNL_SET_KEY_LEN),
327 &is_dst, &have_ip))
328 return;
329
330 cnt = nftnl_set_get_u32(s, NFTNL_SET_DESC_SIZE);
331
332 for (ematch = ctx->cs->match_list; ematch; ematch = ematch->next) {
333 if (!ematch->ismatch || strcmp(ematch->u.match->name, "among"))
334 continue;
335
336 match = ematch->u.match;
337 among_data = (struct nft_among_data *)match->m->data;
338
339 size = cnt + among_data->src.cnt + among_data->dst.cnt;
340 size *= sizeof(struct nft_among_pair);
341
342 size += XT_ALIGN(sizeof(struct xt_entry_match)) +
343 sizeof(struct nft_among_data);
344
345 match->m = xtables_realloc(match->m, size);
346 break;
347 }
348 if (!match) {
349 match = xtables_find_match("among", XTF_TRY_LOAD,
350 &ctx->cs->matches);
351
352 size = cnt * sizeof(struct nft_among_pair);
353 size += XT_ALIGN(sizeof(struct xt_entry_match)) +
354 sizeof(struct nft_among_data);
355
356 match->m = xtables_calloc(1, size);
357 strcpy(match->m->u.user.name, match->name);
358 match->m->u.user.revision = match->revision;
359 xs_init_match(match);
360
361 if (ctx->h->ops->rule_parse->match != NULL)
362 ctx->h->ops->rule_parse->match(match, ctx->cs);
363 }
364 if (!match)
365 return;
366
367 match->m->u.match_size = size;
368
369 inv = !!(nftnl_expr_get_u32(e, NFTNL_EXPR_LOOKUP_FLAGS) &
370 NFT_LOOKUP_F_INV);
371
372 among_data = (struct nft_among_data *)match->m->data;
373 poff = nft_among_prepare_data(among_data, is_dst, cnt, inv, have_ip);
374 if (set_elems_to_among_pairs(among_data->pairs + poff, s, cnt))
375 xtables_error(OTHER_PROBLEM,
376 "ebtables among pair parsing failed");
377 }
378
parse_watcher(void * object,struct ebt_match ** match_list,bool ismatch)379 static void parse_watcher(void *object, struct ebt_match **match_list,
380 bool ismatch)
381 {
382 struct ebt_match *m = xtables_calloc(1, sizeof(struct ebt_match));
383
384 if (ismatch)
385 m->u.match = object;
386 else
387 m->u.watcher = object;
388
389 m->ismatch = ismatch;
390 if (*match_list == NULL)
391 *match_list = m;
392 else
393 (*match_list)->next = m;
394 }
395
nft_bridge_parse_match(struct xtables_match * m,struct iptables_command_state * cs)396 static void nft_bridge_parse_match(struct xtables_match *m,
397 struct iptables_command_state *cs)
398 {
399 parse_watcher(m, &cs->match_list, true);
400 }
401
nft_bridge_parse_target(struct xtables_target * t,struct iptables_command_state * cs)402 static void nft_bridge_parse_target(struct xtables_target *t,
403 struct iptables_command_state *cs)
404 {
405 /* harcoded names :-( */
406 if (strcmp(t->name, "log") == 0 ||
407 strcmp(t->name, "nflog") == 0) {
408 parse_watcher(t, &cs->match_list, false);
409 cs->jumpto = NULL;
410 cs->target = NULL;
411 return;
412 }
413 }
414
415 struct nft_ruleparse_ops nft_ruleparse_ops_bridge = {
416 .meta = nft_bridge_parse_meta,
417 .payload = nft_bridge_parse_payload,
418 .lookup = nft_bridge_parse_lookup,
419 .match = nft_bridge_parse_match,
420 .target = nft_bridge_parse_target,
421 };
422