blob: 17101369f782ad44426a6c74c1265fcde17ab3cc [file] [log] [blame]
/*
* (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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <net/if_arp.h>
#include <xtables.h>
#include <libiptc/libxtc.h>
#include <net/if_arp.h>
#include <netinet/if_ether.h>
#include <linux/netfilter_arp/arp_tables.h>
#include <linux/netfilter/nf_tables.h>
#include "nft-shared.h"
#include "nft.h"
/* a few names */
char *opcodes[] =
{
"Request",
"Reply",
"Request_Reverse",
"Reply_Reverse",
"DRARP_Request",
"DRARP_Reply",
"DRARP_Error",
"InARP_Request",
"ARP_NAK",
};
static char *
addr_to_dotted(const struct in_addr *addrp)
{
static char buf[20];
const unsigned char *bytep;
bytep = (const unsigned char *) &(addrp->s_addr);
sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]);
return buf;
}
static char *
addr_to_host(const struct in_addr *addr)
{
struct hostent *host;
if ((host = gethostbyaddr((char *) addr,
sizeof(struct in_addr), AF_INET)) != NULL)
return (char *) host->h_name;
return (char *) NULL;
}
static char *
addr_to_network(const struct in_addr *addr)
{
struct netent *net;
if ((net = getnetbyaddr((long) ntohl(addr->s_addr), AF_INET)) != NULL)
return (char *) net->n_name;
return (char *) NULL;
}
static char *
addr_to_anyname(const struct in_addr *addr)
{
char *name;
if ((name = addr_to_host(addr)) != NULL ||
(name = addr_to_network(addr)) != NULL)
return name;
return addr_to_dotted(addr);
}
static char *
mask_to_dotted(const struct in_addr *mask)
{
int i;
static char buf[20];
u_int32_t maskaddr, bits;
maskaddr = ntohl(mask->s_addr);
if (maskaddr == 0xFFFFFFFFL)
/* we don't want to see "/32" */
return "";
i = 32;
bits = 0xFFFFFFFEL;
while (--i >= 0 && maskaddr != bits)
bits <<= 1;
if (i >= 0)
sprintf(buf, "/%d", i);
else
/* mask was not a decent combination of 1's and 0's */
sprintf(buf, "/%s", addr_to_dotted(mask));
return buf;
}
static void print_mac(const unsigned char *mac, int l)
{
int j;
for (j = 0; j < l; j++)
printf("%02x%s", mac[j],
(j==l-1) ? "" : ":");
}
static void print_mac_and_mask(const unsigned char *mac, const unsigned char *mask, int l)
{
int i;
print_mac(mac, l);
for (i = 0; i < l ; i++)
if (mask[i] != 255)
break;
if (i == l)
return;
printf("/");
print_mac(mask, l);
}
static uint8_t arpt_to_ipt_flags(uint16_t invflags)
{
uint8_t result = 0;
if (invflags & ARPT_INV_VIA_IN)
result |= IPT_INV_VIA_IN;
if (invflags & ARPT_INV_VIA_OUT)
result |= IPT_INV_VIA_OUT;
if (invflags & ARPT_INV_SRCIP)
result |= IPT_INV_SRCIP;
if (invflags & ARPT_INV_TGTIP)
result |= IPT_INV_DSTIP;
if (invflags & ARPT_INV_ARPPRO)
result |= IPT_INV_PROTO;
if (invflags & ARPT_INV_MASK)
result |= IPT_INV_MASK;
return result;
}
static int nft_arp_add(struct nft_rule *r, void *data)
{
struct arpt_entry *fw = data;
uint8_t flags = arpt_to_ipt_flags(fw->arp.invflags);
struct xt_entry_target *t;
char *targname;
int ret;
if (fw->arp.iniface[0] != '\0')
add_iniface(r, fw->arp.iniface, flags);
if (fw->arp.outiface[0] != '\0')
add_outiface(r, fw->arp.outiface, flags);
if (fw->arp.arhrd != 0) {
add_payload(r, offsetof(struct arphdr, ar_hrd), 2);
add_cmp_u16(r, fw->arp.arhrd, NFT_CMP_EQ);
}
if (fw->arp.arpro != 0) {
add_payload(r, offsetof(struct arphdr, ar_pro), 2);
add_cmp_u16(r, fw->arp.arpro, NFT_CMP_EQ);
}
if (fw->arp.arhln != 0)
add_proto(r, offsetof(struct arphdr, ar_hln), 1,
fw->arp.arhln, flags);
add_proto(r, offsetof(struct arphdr, ar_pln), 1, 4, 0);
if (fw->arp.arpop != 0) {
add_payload(r, offsetof(struct arphdr, ar_op), 2);
add_cmp_u16(r, fw->arp.arpop, NFT_CMP_EQ);
}
if (fw->arp.src_devaddr.addr[0] != '\0') {
add_payload(r, sizeof(struct arphdr), fw->arp.arhln);
add_cmp_ptr(r, NFT_CMP_EQ, fw->arp.src_devaddr.addr, fw->arp.arhln);
}
if (fw->arp.src.s_addr != 0)
add_addr(r, sizeof(struct arphdr) + fw->arp.arhln,
&fw->arp.src.s_addr, 4, flags);
if (fw->arp.tgt_devaddr.addr[0] != '\0') {
add_payload(r, sizeof(struct arphdr) + fw->arp.arhln + 4, fw->arp.arhln);
add_cmp_ptr(r, NFT_CMP_EQ, fw->arp.tgt_devaddr.addr, fw->arp.arhln);
}
if (fw->arp.tgt.s_addr != 0)
add_addr(r, sizeof(struct arphdr) + fw->arp.arhln + sizeof(struct in_addr),
&fw->arp.tgt.s_addr, 4, flags);
/* Counters need to me added before the target, otherwise they are
* increased for each rule because of the way nf_tables works.
*/
if (add_counters(r, fw->counters.pcnt, fw->counters.bcnt) < 0)
return -1;
t = nft_arp_get_target(fw);
targname = t->u.user.name;
/* Standard target? */
if (strcmp(targname, XTC_LABEL_ACCEPT) == 0)
ret = add_verdict(r, NF_ACCEPT);
else if (strcmp(targname, XTC_LABEL_DROP) == 0)
ret = add_verdict(r, NF_DROP);
else if (strcmp(targname, XTC_LABEL_RETURN) == 0)
ret = add_verdict(r, NFT_RETURN);
else if (xtables_find_target(targname, XTF_TRY_LOAD) != NULL)
ret = add_target(r, t);
else
ret = add_jumpto(r, targname, NFT_JUMP);
return ret;
}
static uint16_t ipt_to_arpt_flags(uint8_t invflags)
{
uint16_t result = 0;
if (invflags & IPT_INV_VIA_IN)
result |= ARPT_INV_VIA_IN;
if (invflags & IPT_INV_VIA_OUT)
result |= ARPT_INV_VIA_OUT;
if (invflags & IPT_INV_SRCIP)
result |= ARPT_INV_SRCIP;
if (invflags & IPT_INV_DSTIP)
result |= ARPT_INV_TGTIP;
if (invflags & IPT_INV_PROTO)
result |= ARPT_INV_ARPPRO;
if (invflags & IPT_INV_MASK)
result |= ARPT_INV_MASK;
return result;
}
static void nft_arp_parse_meta(struct nft_rule_expr *e, uint8_t key,
void *data)
{
struct arpt_entry *fw = data;
uint8_t flags = 0;
parse_meta(e, key, fw->arp.iniface, fw->arp.iniface_mask,
fw->arp.outiface, fw->arp.outiface_mask,
&flags);
fw->arp.invflags |= ipt_to_arpt_flags(flags);
}
static void nft_arp_parse_target(struct xtables_target *target, void *data)
{
struct arpt_entry *fw = data;
struct xt_entry_target **t;
fw->target_offset = offsetof(struct arpt_entry, elems);
fw->next_offset = fw->target_offset + target->t->u.target_size;
t = (void *) &fw->elems;
*t = target->t;
}
static void nft_arp_parse_immediate(const char *jumpto, bool nft_goto,
void *data)
{
struct xtables_target *target;
size_t size;
target = xtables_find_target(XT_STANDARD_TARGET,
XTF_LOAD_MUST_SUCCEED);
size = XT_ALIGN(sizeof(struct xt_entry_target)) + target->size;
target->t = xtables_calloc(1, size);
target->t->u.target_size = size;
strcpy(target->t->u.user.name, jumpto);
target->t->u.user.revision = target->revision;
nft_arp_parse_target(target, data);
}
static void nft_arp_parse_payload(struct nft_rule_expr_iter *iter,
uint32_t offset, void *data)
{
struct arpt_entry *fw = data;
struct in_addr addr;
unsigned short int ar_hrd, ar_pro, ar_op, ar_hln;
bool inv;
switch (offset) {
case offsetof(struct arphdr, ar_hrd):
get_cmp_data(iter, &ar_hrd, sizeof(ar_hrd), &inv);
fw->arp.arhrd = ar_hrd;
fw->arp.arhrd_mask = 0xffff;
if (inv)
fw->arp.invflags |= ARPT_INV_ARPHRD;
break;
case offsetof(struct arphdr, ar_pro):
get_cmp_data(iter, &ar_pro, sizeof(ar_pro), &inv);
fw->arp.arpro = ar_pro;
fw->arp.arpro_mask = 0xffff;
if (inv)
fw->arp.invflags |= ARPT_INV_ARPPRO;
break;
case offsetof(struct arphdr, ar_op):
get_cmp_data(iter, &ar_op, sizeof(ar_op), &inv);
fw->arp.arpop = ar_op;
fw->arp.arpop_mask = 0xffff;
if (inv)
fw->arp.invflags |= ARPT_INV_ARPOP;
break;
case offsetof(struct arphdr, ar_hln):
get_cmp_data(iter, &ar_hln, sizeof(ar_op), &inv);
fw->arp.arhln = ar_hln;
fw->arp.arhln_mask = 0xff;
if (inv)
fw->arp.invflags |= ARPT_INV_ARPOP;
break;
default:
if (!fw->arp.arhln)
break;
if (offset == sizeof(struct arphdr) + fw->arp.arhln) {
get_cmp_data(iter, &addr, sizeof(addr), &inv);
fw->arp.src.s_addr = addr.s_addr;
fw->arp.smsk.s_addr = 0xffffffff;
if (inv)
fw->arp.invflags |= ARPT_INV_SRCIP;
} else if (offset == sizeof(struct arphdr) + fw->arp.arhln + sizeof(struct in_addr)) {
get_cmp_data(iter, &addr, sizeof(addr), &inv);
fw->arp.tgt.s_addr = addr.s_addr;
fw->arp.tmsk.s_addr = 0xffffffff;
if (inv)
fw->arp.invflags |= ARPT_INV_TGTIP;
}
break;
}
}
void nft_rule_to_arpt_entry(struct nft_rule *r, struct arpt_entry *fw)
{
struct nft_rule_expr_iter *iter;
struct nft_rule_expr *expr;
int family = nft_rule_attr_get_u8(r, NFT_RULE_ATTR_FAMILY);
iter = nft_rule_expr_iter_create(r);
if (iter == NULL)
return;
expr = nft_rule_expr_iter_next(iter);
while (expr != NULL) {
const char *name =
nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME);
if (strcmp(name, "counter") == 0)
nft_parse_counter(expr, iter, &fw->counters);
else if (strcmp(name, "payload") == 0)
nft_parse_payload(expr, iter, family, fw);
else if (strcmp(name, "meta") == 0)
nft_parse_meta(expr, iter, family, fw);
else if (strcmp(name, "immediate") == 0)
nft_parse_immediate(expr, iter, family, fw);
else if (strcmp(name, "target") == 0)
nft_parse_target(expr, iter, family, fw);
expr = nft_rule_expr_iter_next(iter);
}
nft_rule_expr_iter_destroy(iter);
}
static void
nft_arp_print_firewall(struct nft_rule *r, unsigned int num,
unsigned int format)
{
struct arpt_entry fw = {};
const char *targname;
struct xtables_target *target = NULL;
const struct xt_entry_target *t;
char buf[BUFSIZ];
int i;
char iface[IFNAMSIZ+2];
int print_iface = 0;
nft_rule_to_arpt_entry(r, &fw);
if (format & FMT_LINENUMBERS)
printf("%u ", num);
if (fw.target_offset) {
t = nft_arp_get_target(&fw);
targname = t->u.user.name;
target = xtables_find_target(targname, XTF_TRY_LOAD);
if (!(format & FMT_NOTARGET))
printf("-j %s ", targname);
}
iface[0] = '\0';
if (fw.arp.iniface[0] != '\0') {
strcat(iface, fw.arp.iniface);
print_iface = 1;
}
else if (format & FMT_VIA) {
print_iface = 1;
if (format & FMT_NUMERIC) strcat(iface, "*");
else strcat(iface, "any");
}
if (print_iface)
printf("%s-i %s ", fw.arp.invflags & ARPT_INV_VIA_IN ? "! ": "", iface);
print_iface = 0;
iface[0] = '\0';
if (fw.arp.outiface[0] != '\0') {
strcat(iface, fw.arp.outiface);
print_iface = 1;
}
else if (format & FMT_VIA) {
print_iface = 1;
if (format & FMT_NUMERIC) strcat(iface, "*");
else strcat(iface, "any");
}
if (print_iface)
printf("%s-o %s ", fw.arp.invflags & ARPT_INV_VIA_OUT ? "! " : "", iface);
if (fw.arp.smsk.s_addr != 0L) {
printf("%s", fw.arp.invflags & ARPT_INV_SRCIP
? "! " : "");
if (format & FMT_NUMERIC)
sprintf(buf, "%s", addr_to_dotted(&(fw.arp.src)));
else
sprintf(buf, "%s", addr_to_anyname(&(fw.arp.src)));
strncat(buf, mask_to_dotted(&(fw.arp.smsk)),
sizeof(buf) - strlen(buf) - 1);
printf("-s %s ", buf);
}
for (i = 0; i < ARPT_DEV_ADDR_LEN_MAX; i++)
if (fw.arp.src_devaddr.mask[i] != 0)
break;
if (i == ARPT_DEV_ADDR_LEN_MAX)
goto after_devsrc;
printf("%s", fw.arp.invflags & ARPT_INV_SRCDEVADDR
? "! " : "");
printf("--src-mac ");
print_mac_and_mask((unsigned char *)fw.arp.src_devaddr.addr,
(unsigned char *)fw.arp.src_devaddr.mask, ETH_ALEN);
printf(" ");
after_devsrc:
if (fw.arp.tmsk.s_addr != 0L) {
printf("%s",fw.arp.invflags & ARPT_INV_TGTIP
? "! " : "");
if (format & FMT_NUMERIC)
sprintf(buf, "%s", addr_to_dotted(&(fw.arp.tgt)));
else
sprintf(buf, "%s", addr_to_anyname(&(fw.arp.tgt)));
strncat(buf, mask_to_dotted(&(fw.arp.tmsk)),
sizeof(buf) - strlen(buf) - 1);
printf("-d %s ", buf);
}
for (i = 0; i <ARPT_DEV_ADDR_LEN_MAX; i++)
if (fw.arp.tgt_devaddr.mask[i] != 0)
break;
if (i == ARPT_DEV_ADDR_LEN_MAX)
goto after_devdst;
printf("%s",fw.arp.invflags & ARPT_INV_TGTDEVADDR
? "! " : "");
printf("--dst-mac ");
print_mac_and_mask((unsigned char *)fw.arp.tgt_devaddr.addr,
(unsigned char *)fw.arp.tgt_devaddr.mask, ETH_ALEN);
printf(" ");
after_devdst:
if (fw.arp.arhln_mask != 0) {
printf("%s",fw.arp.invflags & ARPT_INV_ARPHLN
? "! " : "");
printf("--h-length %d", fw.arp.arhln);
if (fw.arp.arhln_mask != 255)
printf("/%d", fw.arp.arhln_mask);
printf(" ");
}
if (fw.arp.arpop_mask != 0) {
int tmp = ntohs(fw.arp.arpop);
printf("%s",fw.arp.invflags & ARPT_INV_ARPOP
? "! " : "");
if (tmp <= NUMOPCODES && !(format & FMT_NUMERIC))
printf("--opcode %s", opcodes[tmp-1]);
else
printf("--opcode %d", tmp);
if (fw.arp.arpop_mask != 65535)
printf("/%d", ntohs(fw.arp.arpop_mask));
printf(" ");
}
if (fw.arp.arhrd_mask != 0) {
uint16_t tmp = ntohs(fw.arp.arhrd);
printf("%s", fw.arp.invflags & ARPT_INV_ARPHRD
? "! " : "");
if (tmp == 1 && !(format & FMT_NUMERIC))
printf("--h-type %s", "Ethernet");
else
printf("--h-type %u", tmp);
if (fw.arp.arhrd_mask != 65535)
printf("/%d", ntohs(fw.arp.arhrd_mask));
printf(" ");
}
if (fw.arp.arpro_mask != 0) {
int tmp = ntohs(fw.arp.arpro);
printf("%s", fw.arp.invflags & ARPT_INV_ARPPRO
? "! " : "");
if (tmp == 0x0800 && !(format & FMT_NUMERIC))
printf("--proto-type %s", "IPv4");
else
printf("--proto-type 0x%x", tmp);
if (fw.arp.arpro_mask != 65535)
printf("/%x", ntohs(fw.arp.arpro_mask));
printf(" ");
}
if (target) {
if (target->print)
/* Print the target information. */
target->print(&fw.arp, t, format & FMT_NUMERIC);
}
if (!(format & FMT_NOCOUNTS)) {
printf(", pcnt=");
xtables_print_num(fw.counters.pcnt, format);
printf("-- bcnt=");
xtables_print_num(fw.counters.bcnt, format);
}
if (!(format & FMT_NONEWLINE))
fputc('\n', stdout);
}
static bool nft_arp_is_same(const void *data_a,
const void *data_b)
{
const struct arpt_entry *a = data_a;
const struct arpt_entry *b = data_b;
if (a->arp.src.s_addr != b->arp.src.s_addr
|| a->arp.tgt.s_addr != b->arp.tgt.s_addr
|| a->arp.smsk.s_addr != b->arp.tmsk.s_addr
|| a->arp.arpro != b->arp.arpro
|| a->arp.flags != b->arp.flags
|| a->arp.invflags != b->arp.invflags) {
DEBUGP("different src/dst/proto/flags/invflags\n");
return false;
}
return is_same_interfaces(a->arp.src_devaddr.addr,
a->arp.tgt_devaddr.addr,
(unsigned char*)a->arp.src_devaddr.mask,
(unsigned char*)a->arp.tgt_devaddr.mask,
b->arp.src_devaddr.addr,
a->arp.tgt_devaddr.addr,
(unsigned char*)b->arp.src_devaddr.mask,
(unsigned char*)b->arp.tgt_devaddr.mask);
}
static bool nft_arp_rule_find(struct nft_family_ops *ops, struct nft_rule *r,
void *data)
{
struct arpt_entry *fw = data;
struct xt_entry_target *t_fw, *t_this;
char *targname_fw, *targname_this;
struct arpt_entry this = {};
/* Delete by matching rule case */
nft_rule_to_arpt_entry(r, &this);
if (!ops->is_same(fw, &this))
return false;
t_fw = nft_arp_get_target(fw);
t_this = nft_arp_get_target(&this);
targname_fw = t_fw->u.user.name;
targname_this = t_this->u.user.name;
if (!strcmp(targname_fw, targname_this) &&
(!strcmp(targname_fw, "mangle") ||
!strcmp(targname_fw, "CLASSIFY"))) {
if (memcmp(t_fw->data, t_this->data,
t_fw->u.user.target_size - sizeof(*t_fw)) != 0) {
DEBUGP("Different target\n");
return false;
}
return true;
}
if (strcmp(targname_fw, targname_this) != 0) {
DEBUGP("Different verdict\n");
return false;
}
return true;
}
struct nft_family_ops nft_family_ops_arp = {
.add = nft_arp_add,
.is_same = nft_arp_is_same,
.print_payload = NULL,
.parse_meta = nft_arp_parse_meta,
.parse_payload = nft_arp_parse_payload,
.parse_immediate = nft_arp_parse_immediate,
.print_firewall = nft_arp_print_firewall,
.post_parse = NULL,
.rule_find = nft_arp_rule_find,
.parse_target = nft_arp_parse_target,
};