blob: edbbfa40e9c43bfe8583455b9ab44b5b1b4a55b2 [file] [log] [blame]
/*
* (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org>
* (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.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 <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <linux/netfilter/nf_log.h>
#include <linux/netfilter/xt_limit.h>
#include <linux/netfilter/xt_mark.h>
#include <linux/netfilter/xt_NFLOG.h>
#include <linux/netfilter/xt_pkttype.h>
#include <linux/netfilter_ipv6/ip6t_hl.h>
#include <libnftnl/rule.h>
#include <libnftnl/expr.h>
#include <xtables.h>
#include "nft-ruleparse.h"
#include "nft.h"
static struct xtables_match *
nft_find_match_in_cs(struct iptables_command_state *cs, const char *name)
{
struct xtables_rule_match *rm;
struct ebt_match *ebm;
for (ebm = cs->match_list; ebm; ebm = ebm->next) {
if (ebm->ismatch &&
!strcmp(ebm->u.match->m->u.user.name, name))
return ebm->u.match;
}
for (rm = cs->matches; rm; rm = rm->next) {
if (!strcmp(rm->match->m->u.user.name, name))
return rm->match;
}
return NULL;
}
void *
nft_create_match(struct nft_xt_ctx *ctx,
struct iptables_command_state *cs,
const char *name, bool reuse)
{
struct xtables_match *match;
struct xt_entry_match *m;
unsigned int size;
if (reuse) {
match = nft_find_match_in_cs(cs, name);
if (match)
return match->m->data;
}
match = xtables_find_match(name, XTF_TRY_LOAD,
&cs->matches);
if (!match)
return NULL;
size = XT_ALIGN(sizeof(struct xt_entry_match)) + match->size;
m = xtables_calloc(1, size);
m->u.match_size = size;
m->u.user.revision = match->revision;
strcpy(m->u.user.name, match->name);
match->m = m;
xs_init_match(match);
if (ctx->h->ops->rule_parse->match)
ctx->h->ops->rule_parse->match(match, cs);
return match->m->data;
}
static void nft_parse_counter(struct nftnl_expr *e, struct xt_counters *counters)
{
counters->pcnt = nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_PACKETS);
counters->bcnt = nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_BYTES);
}
static void nft_parse_payload(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
{
enum nft_registers regnum = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_DREG);
struct nft_xt_ctx_reg *reg = nft_xt_ctx_get_dreg(ctx, regnum);
if (!reg)
return;
reg->type = NFT_XT_REG_PAYLOAD;
reg->payload.base = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_BASE);
reg->payload.offset = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET);
reg->payload.len = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_LEN);
}
static bool nft_parse_meta_set_common(struct nft_xt_ctx* ctx,
struct nft_xt_ctx_reg *sreg)
{
if ((sreg->type != NFT_XT_REG_IMMEDIATE)) {
ctx->errmsg = "meta sreg is not an immediate";
return false;
}
if (sreg->immediate.data[0] == 0) {
ctx->errmsg = "meta sreg immediate is 0";
return false;
}
return true;
}
static void nft_parse_meta_set(struct nft_xt_ctx *ctx,
struct nftnl_expr *e)
{
struct xtables_target *target;
struct nft_xt_ctx_reg *sreg;
enum nft_registers sregnum;
struct xt_entry_target *t;
unsigned int size;
const char *targname;
sregnum = nftnl_expr_get_u32(e, NFTNL_EXPR_META_SREG);
sreg = nft_xt_ctx_get_sreg(ctx, sregnum);
if (!sreg)
return;
switch (nftnl_expr_get_u32(e, NFTNL_EXPR_META_KEY)) {
case NFT_META_NFTRACE:
if (!nft_parse_meta_set_common(ctx, sreg))
return;
targname = "TRACE";
break;
case NFT_META_BRI_BROUTE:
if (!nft_parse_meta_set_common(ctx, sreg))
return;
ctx->cs->jumpto = "DROP";
return;
default:
ctx->errmsg = "meta sreg key not supported";
return;
}
target = xtables_find_target(targname, XTF_TRY_LOAD);
if (target == NULL) {
ctx->errmsg = "target TRACE not found";
return;
}
size = XT_ALIGN(sizeof(struct xt_entry_target)) + target->size;
t = xtables_calloc(1, size);
t->u.target_size = size;
t->u.user.revision = target->revision;
strcpy(t->u.user.name, targname);
target->t = t;
ctx->h->ops->rule_parse->target(target, ctx->cs);
}
static void nft_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
{
struct nft_xt_ctx_reg *reg;
if (nftnl_expr_is_set(e, NFTNL_EXPR_META_SREG)) {
nft_parse_meta_set(ctx, e);
return;
}
reg = nft_xt_ctx_get_dreg(ctx, nftnl_expr_get_u32(e, NFTNL_EXPR_META_DREG));
if (!reg)
return;
reg->meta_dreg.key = nftnl_expr_get_u32(e, NFTNL_EXPR_META_KEY);
reg->type = NFT_XT_REG_META_DREG;
}
static void nft_parse_bitwise(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
{
enum nft_registers sregnum = nftnl_expr_get_u32(e, NFTNL_EXPR_BITWISE_SREG);
enum nft_registers dregnum = nftnl_expr_get_u32(e, NFTNL_EXPR_BITWISE_DREG);
struct nft_xt_ctx_reg *sreg = nft_xt_ctx_get_sreg(ctx, sregnum);
struct nft_xt_ctx_reg *dreg = sreg;
const void *data;
uint32_t len;
if (!sreg)
return;
if (sregnum != dregnum) {
dreg = nft_xt_ctx_get_sreg(ctx, dregnum); /* sreg, do NOT clear ... */
if (!dreg)
return;
*dreg = *sreg; /* .. and copy content instead */
}
data = nftnl_expr_get(e, NFTNL_EXPR_BITWISE_XOR, &len);
if (len > sizeof(dreg->bitwise.xor)) {
ctx->errmsg = "bitwise xor too large";
return;
}
memcpy(dreg->bitwise.xor, data, len);
data = nftnl_expr_get(e, NFTNL_EXPR_BITWISE_MASK, &len);
if (len > sizeof(dreg->bitwise.mask)) {
ctx->errmsg = "bitwise mask too large";
return;
}
memcpy(dreg->bitwise.mask, data, len);
dreg->bitwise.set = true;
}
static void nft_parse_icmp(struct nft_xt_ctx *ctx,
struct iptables_command_state *cs,
struct nft_xt_ctx_reg *sreg,
uint8_t op, const char *data, size_t dlen)
{
struct ipt_icmp icmp = {
.type = UINT8_MAX,
.code = { 0, UINT8_MAX },
}, *icmpp;
if (dlen < 1)
goto out_err_len;
switch (sreg->payload.offset) {
case 0:
icmp.type = data[0];
if (dlen == 1)
break;
dlen--;
data++;
/* fall through */
case 1:
if (dlen > 1)
goto out_err_len;
icmp.code[0] = icmp.code[1] = data[0];
break;
default:
ctx->errmsg = "unexpected payload offset";
return;
}
switch (ctx->h->family) {
case NFPROTO_IPV4:
icmpp = nft_create_match(ctx, cs, "icmp", false);
break;
case NFPROTO_IPV6:
if (icmp.type == UINT8_MAX) {
ctx->errmsg = "icmp6 code with any type match not supported";
return;
}
icmpp = nft_create_match(ctx, cs, "icmp6", false);
break;
default:
ctx->errmsg = "unexpected family for icmp match";
return;
}
if (!icmpp) {
ctx->errmsg = "icmp match extension not found";
return;
}
memcpy(icmpp, &icmp, sizeof(icmp));
return;
out_err_len:
ctx->errmsg = "unexpected RHS data length";
}
static void port_match_single_to_range(__u16 *ports, __u8 *invflags,
uint8_t op, int port, __u8 invflag)
{
if (port < 0)
return;
switch (op) {
case NFT_CMP_NEQ:
*invflags |= invflag;
/* fallthrough */
case NFT_CMP_EQ:
ports[0] = port;
ports[1] = port;
break;
case NFT_CMP_LT:
ports[1] = max(port - 1, 1);
break;
case NFT_CMP_LTE:
ports[1] = port;
break;
case NFT_CMP_GT:
ports[0] = min(port + 1, UINT16_MAX);
break;
case NFT_CMP_GTE:
ports[0] = port;
break;
}
}
static void nft_parse_udp(struct nft_xt_ctx *ctx,
struct iptables_command_state *cs,
int sport, int dport,
uint8_t op)
{
struct xt_udp *udp = nft_create_match(ctx, cs, "udp", true);
if (!udp) {
ctx->errmsg = "udp match extension not found";
return;
}
port_match_single_to_range(udp->spts, &udp->invflags,
op, sport, XT_UDP_INV_SRCPT);
port_match_single_to_range(udp->dpts, &udp->invflags,
op, dport, XT_UDP_INV_DSTPT);
}
static void nft_parse_tcp(struct nft_xt_ctx *ctx,
struct iptables_command_state *cs,
int sport, int dport,
uint8_t op)
{
struct xt_tcp *tcp = nft_create_match(ctx, cs, "tcp", true);
if (!tcp) {
ctx->errmsg = "tcp match extension not found";
return;
}
port_match_single_to_range(tcp->spts, &tcp->invflags,
op, sport, XT_TCP_INV_SRCPT);
port_match_single_to_range(tcp->dpts, &tcp->invflags,
op, dport, XT_TCP_INV_DSTPT);
}
static void nft_parse_th_port(struct nft_xt_ctx *ctx,
struct iptables_command_state *cs,
uint8_t proto,
int sport, int dport, uint8_t op)
{
switch (proto) {
case IPPROTO_UDP:
nft_parse_udp(ctx, cs, sport, dport, op);
break;
case IPPROTO_TCP:
nft_parse_tcp(ctx, cs, sport, dport, op);
break;
default:
ctx->errmsg = "unknown layer 4 protocol for TH match";
}
}
static void nft_parse_tcp_flags(struct nft_xt_ctx *ctx,
struct iptables_command_state *cs,
uint8_t op, uint8_t flags, uint8_t mask)
{
struct xt_tcp *tcp = nft_create_match(ctx, cs, "tcp", true);
if (!tcp) {
ctx->errmsg = "tcp match extension not found";
return;
}
if (op == NFT_CMP_NEQ)
tcp->invflags |= XT_TCP_INV_FLAGS;
tcp->flg_cmp = flags;
tcp->flg_mask = mask;
}
static void nft_parse_transport(struct nft_xt_ctx *ctx,
struct nftnl_expr *e,
struct iptables_command_state *cs)
{
struct nft_xt_ctx_reg *sreg;
enum nft_registers reg;
uint32_t sdport;
uint16_t port;
uint8_t proto, op;
unsigned int len;
switch (ctx->h->family) {
case NFPROTO_IPV4:
proto = ctx->cs->fw.ip.proto;
break;
case NFPROTO_IPV6:
proto = ctx->cs->fw6.ipv6.proto;
break;
default:
ctx->errmsg = "invalid family for TH match";
return;
}
nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &len);
op = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP);
reg = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_SREG);
sreg = nft_xt_ctx_get_sreg(ctx, reg);
if (!sreg)
return;
if (sreg->type != NFT_XT_REG_PAYLOAD) {
ctx->errmsg = "sgreg not payload";
return;
}
switch (proto) {
case IPPROTO_UDP:
case IPPROTO_TCP:
break;
case IPPROTO_ICMP:
case IPPROTO_ICMPV6:
nft_parse_icmp(ctx, cs, sreg, op,
nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &len),
len);
return;
default:
ctx->errmsg = "unsupported layer 4 protocol value";
return;
}
switch(sreg->payload.offset) {
case 0: /* th->sport */
switch (len) {
case 2: /* load sport only */
port = ntohs(nftnl_expr_get_u16(e, NFTNL_EXPR_CMP_DATA));
nft_parse_th_port(ctx, cs, proto, port, -1, op);
return;
case 4: /* load both src and dst port */
sdport = ntohl(nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_DATA));
nft_parse_th_port(ctx, cs, proto, sdport >> 16, sdport & 0xffff, op);
return;
}
break;
case 2: /* th->dport */
switch (len) {
case 2:
port = ntohs(nftnl_expr_get_u16(e, NFTNL_EXPR_CMP_DATA));
nft_parse_th_port(ctx, cs, proto, -1, port, op);
return;
}
break;
case 13: /* th->flags */
if (len == 1 && proto == IPPROTO_TCP) {
uint8_t flags = nftnl_expr_get_u8(e, NFTNL_EXPR_CMP_DATA);
uint8_t mask = ~0;
if (sreg->bitwise.set)
memcpy(&mask, &sreg->bitwise.mask, sizeof(mask));
nft_parse_tcp_flags(ctx, cs, op, flags, mask);
}
return;
}
}
static void nft_parse_cmp(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
{
struct nft_xt_ctx_reg *sreg;
uint32_t reg;
reg = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_SREG);
sreg = nft_xt_ctx_get_sreg(ctx, reg);
if (!sreg)
return;
switch (sreg->type) {
case NFT_XT_REG_UNDEF:
ctx->errmsg = "cmp sreg undef";
break;
case NFT_XT_REG_META_DREG:
ctx->h->ops->rule_parse->meta(ctx, sreg, e, ctx->cs);
break;
case NFT_XT_REG_PAYLOAD:
switch (sreg->payload.base) {
case NFT_PAYLOAD_LL_HEADER:
if (ctx->h->family == NFPROTO_BRIDGE)
ctx->h->ops->rule_parse->payload(ctx, sreg, e, ctx->cs);
break;
case NFT_PAYLOAD_NETWORK_HEADER:
ctx->h->ops->rule_parse->payload(ctx, sreg, e, ctx->cs);
break;
case NFT_PAYLOAD_TRANSPORT_HEADER:
nft_parse_transport(ctx, e, ctx->cs);
break;
}
break;
default:
ctx->errmsg = "cmp sreg has unknown type";
break;
}
}
static void nft_parse_immediate(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
{
const char *chain = nftnl_expr_get_str(e, NFTNL_EXPR_IMM_CHAIN);
struct iptables_command_state *cs = ctx->cs;
struct xt_entry_target *t;
uint32_t size;
int verdict;
if (nftnl_expr_is_set(e, NFTNL_EXPR_IMM_DATA)) {
struct nft_xt_ctx_reg *dreg;
const void *imm_data;
uint32_t len;
imm_data = nftnl_expr_get(e, NFTNL_EXPR_IMM_DATA, &len);
dreg = nft_xt_ctx_get_dreg(ctx, nftnl_expr_get_u32(e, NFTNL_EXPR_IMM_DREG));
if (!dreg)
return;
if (len > sizeof(dreg->immediate.data)) {
ctx->errmsg = "oversized immediate data";
return;
}
memcpy(dreg->immediate.data, imm_data, len);
dreg->immediate.len = len;
dreg->type = NFT_XT_REG_IMMEDIATE;
return;
}
verdict = nftnl_expr_get_u32(e, NFTNL_EXPR_IMM_VERDICT);
/* Standard target? */
switch(verdict) {
case NF_ACCEPT:
if (cs->jumpto && strcmp(ctx->table, "broute") == 0)
break;
cs->jumpto = "ACCEPT";
break;
case NF_DROP:
cs->jumpto = "DROP";
break;
case NFT_RETURN:
cs->jumpto = "RETURN";
break;;
case NFT_GOTO:
if (ctx->h->ops->set_goto_flag)
ctx->h->ops->set_goto_flag(cs);
/* fall through */
case NFT_JUMP:
cs->jumpto = chain;
/* fall through */
default:
return;
}
cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD);
if (!cs->target) {
ctx->errmsg = "verdict extension not found";
return;
}
size = XT_ALIGN(sizeof(struct xt_entry_target)) + cs->target->size;
t = xtables_calloc(1, size);
t->u.target_size = size;
t->u.user.revision = cs->target->revision;
strcpy(t->u.user.name, cs->jumpto);
cs->target->t = t;
}
static void nft_parse_match(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
{
uint32_t mt_len;
const char *mt_name = nftnl_expr_get_str(e, NFTNL_EXPR_MT_NAME);
const void *mt_info = nftnl_expr_get(e, NFTNL_EXPR_MT_INFO, &mt_len);
struct xtables_match *match;
struct xtables_rule_match **matches;
struct xt_entry_match *m;
switch (ctx->h->family) {
case NFPROTO_IPV4:
case NFPROTO_IPV6:
case NFPROTO_BRIDGE:
matches = &ctx->cs->matches;
break;
default:
fprintf(stderr, "BUG: nft_parse_match() unknown family %d\n",
ctx->h->family);
exit(EXIT_FAILURE);
}
match = xtables_find_match(mt_name, XTF_TRY_LOAD, matches);
if (match == NULL) {
ctx->errmsg = "match extension not found";
return;
}
m = xtables_calloc(1, sizeof(struct xt_entry_match) + mt_len);
memcpy(&m->data, mt_info, mt_len);
m->u.match_size = mt_len + XT_ALIGN(sizeof(struct xt_entry_match));
m->u.user.revision = nftnl_expr_get_u32(e, NFTNL_EXPR_TG_REV);
strcpy(m->u.user.name, match->name);
match->m = m;
if (ctx->h->ops->rule_parse->match != NULL)
ctx->h->ops->rule_parse->match(match, ctx->cs);
}
static void nft_parse_target(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
{
uint32_t tg_len;
const char *targname = nftnl_expr_get_str(e, NFTNL_EXPR_TG_NAME);
const void *targinfo = nftnl_expr_get(e, NFTNL_EXPR_TG_INFO, &tg_len);
struct xtables_target *target;
struct xt_entry_target *t;
size_t size;
target = xtables_find_target(targname, XTF_TRY_LOAD);
if (target == NULL) {
ctx->errmsg = "target extension not found";
return;
}
size = XT_ALIGN(sizeof(struct xt_entry_target)) + tg_len;
t = xtables_calloc(1, size);
memcpy(&t->data, targinfo, tg_len);
t->u.target_size = size;
t->u.user.revision = nftnl_expr_get_u32(e, NFTNL_EXPR_TG_REV);
strcpy(t->u.user.name, target->name);
target->t = t;
ctx->h->ops->rule_parse->target(target, ctx->cs);
}
static void nft_parse_limit(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
{
__u32 burst = nftnl_expr_get_u32(e, NFTNL_EXPR_LIMIT_BURST);
__u64 unit = nftnl_expr_get_u64(e, NFTNL_EXPR_LIMIT_UNIT);
__u64 rate = nftnl_expr_get_u64(e, NFTNL_EXPR_LIMIT_RATE);
struct xt_rateinfo *rinfo;
switch (ctx->h->family) {
case NFPROTO_IPV4:
case NFPROTO_IPV6:
case NFPROTO_BRIDGE:
break;
default:
fprintf(stderr, "BUG: nft_parse_limit() unknown family %d\n",
ctx->h->family);
exit(EXIT_FAILURE);
}
rinfo = nft_create_match(ctx, ctx->cs, "limit", false);
if (!rinfo) {
ctx->errmsg = "limit match extension not found";
return;
}
rinfo->avg = XT_LIMIT_SCALE * unit / rate;
rinfo->burst = burst;
}
static void nft_parse_lookup(struct nft_xt_ctx *ctx, struct nft_handle *h,
struct nftnl_expr *e)
{
if (ctx->h->ops->rule_parse->lookup)
ctx->h->ops->rule_parse->lookup(ctx, e);
}
static void nft_parse_log(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
{
struct xtables_target *target;
struct xt_entry_target *t;
size_t target_size;
/*
* In order to handle the longer log-prefix supported by nft, instead of
* using struct xt_nflog_info, we use a struct with a compatible layout, but
* a larger buffer for the prefix.
*/
struct xt_nflog_info_nft {
__u32 len;
__u16 group;
__u16 threshold;
__u16 flags;
__u16 pad;
char prefix[NF_LOG_PREFIXLEN];
} info = {
.group = nftnl_expr_get_u16(e, NFTNL_EXPR_LOG_GROUP),
.threshold = nftnl_expr_get_u16(e, NFTNL_EXPR_LOG_QTHRESHOLD),
};
if (nftnl_expr_is_set(e, NFTNL_EXPR_LOG_SNAPLEN)) {
info.len = nftnl_expr_get_u32(e, NFTNL_EXPR_LOG_SNAPLEN);
info.flags = XT_NFLOG_F_COPY_LEN;
}
if (nftnl_expr_is_set(e, NFTNL_EXPR_LOG_PREFIX))
snprintf(info.prefix, sizeof(info.prefix), "%s",
nftnl_expr_get_str(e, NFTNL_EXPR_LOG_PREFIX));
target = xtables_find_target("NFLOG", XTF_TRY_LOAD);
if (target == NULL) {
ctx->errmsg = "NFLOG target extension not found";
return;
}
target_size = XT_ALIGN(sizeof(struct xt_entry_target)) +
XT_ALIGN(sizeof(struct xt_nflog_info_nft));
t = xtables_calloc(1, target_size);
t->u.target_size = target_size;
strcpy(t->u.user.name, target->name);
t->u.user.revision = target->revision;
target->t = t;
memcpy(&target->t->data, &info, sizeof(info));
ctx->h->ops->rule_parse->target(target, ctx->cs);
}
static void nft_parse_udp_range(struct nft_xt_ctx *ctx,
struct iptables_command_state *cs,
int sport_from, int sport_to,
int dport_from, int dport_to,
uint8_t op)
{
struct xt_udp *udp = nft_create_match(ctx, cs, "udp", true);
if (!udp) {
ctx->errmsg = "udp match extension not found";
return;
}
if (sport_from >= 0) {
switch (op) {
case NFT_RANGE_NEQ:
udp->invflags |= XT_UDP_INV_SRCPT;
/* fallthrough */
case NFT_RANGE_EQ:
udp->spts[0] = sport_from;
udp->spts[1] = sport_to;
break;
}
}
if (dport_to >= 0) {
switch (op) {
case NFT_CMP_NEQ:
udp->invflags |= XT_UDP_INV_DSTPT;
/* fallthrough */
case NFT_CMP_EQ:
udp->dpts[0] = dport_from;
udp->dpts[1] = dport_to;
break;
}
}
}
static void nft_parse_tcp_range(struct nft_xt_ctx *ctx,
struct iptables_command_state *cs,
int sport_from, int sport_to,
int dport_from, int dport_to,
uint8_t op)
{
struct xt_tcp *tcp = nft_create_match(ctx, cs, "tcp", true);
if (!tcp) {
ctx->errmsg = "tcp match extension not found";
return;
}
if (sport_from >= 0) {
switch (op) {
case NFT_RANGE_NEQ:
tcp->invflags |= XT_TCP_INV_SRCPT;
/* fallthrough */
case NFT_RANGE_EQ:
tcp->spts[0] = sport_from;
tcp->spts[1] = sport_to;
break;
}
}
if (dport_to >= 0) {
switch (op) {
case NFT_CMP_NEQ:
tcp->invflags |= XT_TCP_INV_DSTPT;
/* fallthrough */
case NFT_CMP_EQ:
tcp->dpts[0] = dport_from;
tcp->dpts[1] = dport_to;
break;
}
}
}
static void nft_parse_th_port_range(struct nft_xt_ctx *ctx,
struct iptables_command_state *cs,
uint8_t proto,
int sport_from, int sport_to,
int dport_from, int dport_to, uint8_t op)
{
switch (proto) {
case IPPROTO_UDP:
nft_parse_udp_range(ctx, cs, sport_from, sport_to, dport_from, dport_to, op);
break;
case IPPROTO_TCP:
nft_parse_tcp_range(ctx, cs, sport_from, sport_to, dport_from, dport_to, op);
break;
}
}
static void nft_parse_transport_range(struct nft_xt_ctx *ctx,
const struct nft_xt_ctx_reg *sreg,
struct nftnl_expr *e,
struct iptables_command_state *cs)
{
unsigned int len_from, len_to;
uint8_t proto, op;
uint16_t from, to;
switch (ctx->h->family) {
case NFPROTO_IPV4:
proto = ctx->cs->fw.ip.proto;
break;
case NFPROTO_IPV6:
proto = ctx->cs->fw6.ipv6.proto;
break;
default:
proto = 0;
break;
}
nftnl_expr_get(e, NFTNL_EXPR_RANGE_FROM_DATA, &len_from);
nftnl_expr_get(e, NFTNL_EXPR_RANGE_FROM_DATA, &len_to);
if (len_to != len_from || len_to != 2)
return;
op = nftnl_expr_get_u32(e, NFTNL_EXPR_RANGE_OP);
from = ntohs(nftnl_expr_get_u16(e, NFTNL_EXPR_RANGE_FROM_DATA));
to = ntohs(nftnl_expr_get_u16(e, NFTNL_EXPR_RANGE_TO_DATA));
switch (sreg->payload.offset) {
case 0:
nft_parse_th_port_range(ctx, cs, proto, from, to, -1, -1, op);
return;
case 2:
to = ntohs(nftnl_expr_get_u16(e, NFTNL_EXPR_RANGE_TO_DATA));
nft_parse_th_port_range(ctx, cs, proto, -1, -1, from, to, op);
return;
}
}
static void nft_parse_range(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
{
struct nft_xt_ctx_reg *sreg;
uint32_t reg;
reg = nftnl_expr_get_u32(e, NFTNL_EXPR_RANGE_SREG);
sreg = nft_xt_ctx_get_sreg(ctx, reg);
switch (sreg->type) {
case NFT_XT_REG_UNDEF:
ctx->errmsg = "range sreg undef";
break;
case NFT_XT_REG_PAYLOAD:
switch (sreg->payload.base) {
case NFT_PAYLOAD_TRANSPORT_HEADER:
nft_parse_transport_range(ctx, sreg, e, ctx->cs);
break;
default:
ctx->errmsg = "range with unknown payload base";
break;
}
break;
default:
ctx->errmsg = "range sreg type unsupported";
break;
}
}
bool nft_rule_to_iptables_command_state(struct nft_handle *h,
const struct nftnl_rule *r,
struct iptables_command_state *cs)
{
struct nftnl_expr_iter *iter;
struct nftnl_expr *expr;
struct nft_xt_ctx ctx = {
.cs = cs,
.h = h,
.table = nftnl_rule_get_str(r, NFTNL_RULE_TABLE),
};
bool ret = true;
iter = nftnl_expr_iter_create(r);
if (iter == NULL)
return false;
ctx.iter = iter;
expr = nftnl_expr_iter_next(iter);
while (expr != NULL) {
const char *name =
nftnl_expr_get_str(expr, NFTNL_EXPR_NAME);
if (strcmp(name, "counter") == 0)
nft_parse_counter(expr, &ctx.cs->counters);
else if (strcmp(name, "payload") == 0)
nft_parse_payload(&ctx, expr);
else if (strcmp(name, "meta") == 0)
nft_parse_meta(&ctx, expr);
else if (strcmp(name, "bitwise") == 0)
nft_parse_bitwise(&ctx, expr);
else if (strcmp(name, "cmp") == 0)
nft_parse_cmp(&ctx, expr);
else if (strcmp(name, "immediate") == 0)
nft_parse_immediate(&ctx, expr);
else if (strcmp(name, "match") == 0)
nft_parse_match(&ctx, expr);
else if (strcmp(name, "target") == 0)
nft_parse_target(&ctx, expr);
else if (strcmp(name, "limit") == 0)
nft_parse_limit(&ctx, expr);
else if (strcmp(name, "lookup") == 0)
nft_parse_lookup(&ctx, h, expr);
else if (strcmp(name, "log") == 0)
nft_parse_log(&ctx, expr);
else if (strcmp(name, "range") == 0)
nft_parse_range(&ctx, expr);
if (ctx.errmsg) {
fprintf(stderr, "Error: %s\n", ctx.errmsg);
ctx.errmsg = NULL;
ret = false;
}
expr = nftnl_expr_iter_next(iter);
}
nftnl_expr_iter_destroy(iter);
if (nftnl_rule_is_set(r, NFTNL_RULE_USERDATA)) {
const void *data;
uint32_t len, size;
const char *comment;
data = nftnl_rule_get_data(r, NFTNL_RULE_USERDATA, &len);
comment = get_comment(data, len);
if (comment) {
struct xtables_match *match;
struct xt_entry_match *m;
match = xtables_find_match("comment", XTF_TRY_LOAD,
&cs->matches);
if (match == NULL)
return false;
size = XT_ALIGN(sizeof(struct xt_entry_match))
+ match->size;
m = xtables_calloc(1, size);
strncpy((char *)m->data, comment, match->size - 1);
m->u.match_size = size;
m->u.user.revision = 0;
strcpy(m->u.user.name, match->name);
match->m = m;
}
}
if (!cs->jumpto)
cs->jumpto = "";
if (!ret)
xtables_error(VERSION_PROBLEM, "Parsing nftables rule failed");
return ret;
}
static void parse_ifname(const char *name, unsigned int len,
char *dst, unsigned char *mask)
{
if (len == 0)
return;
memcpy(dst, name, len);
if (name[len - 1] == '\0') {
if (mask)
memset(mask, 0xff, strlen(name) + 1);
return;
}
if (len >= IFNAMSIZ)
return;
/* wildcard */
dst[len++] = '+';
if (len >= IFNAMSIZ)
return;
dst[len++] = 0;
if (mask)
memset(mask, 0xff, len - 2);
}
static void parse_invalid_iface(char *iface, unsigned char *mask,
uint8_t *invflags, uint8_t invbit)
{
if (*invflags & invbit || strcmp(iface, "INVAL/D"))
return;
/* nft's poor "! -o +" excuse */
*invflags |= invbit;
iface[0] = '+';
iface[1] = '\0';
mask[0] = 0xff;
mask[1] = 0xff;
memset(mask + 2, 0, IFNAMSIZ - 2);
}
static uint32_t get_meta_mask(struct nft_xt_ctx *ctx, enum nft_registers sreg)
{
struct nft_xt_ctx_reg *reg = nft_xt_ctx_get_sreg(ctx, sreg);
if (reg->bitwise.set)
return reg->bitwise.mask[0];
return ~0u;
}
static int parse_meta_mark(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
{
struct xt_mark_mtinfo1 *mark;
uint32_t value;
mark = nft_create_match(ctx, ctx->cs, "mark", false);
if (!mark)
return -1;
if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
mark->invert = 1;
value = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_DATA);
mark->mark = value;
mark->mask = get_meta_mask(ctx, nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_SREG));
return 0;
}
static int parse_meta_pkttype(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
{
struct xt_pkttype_info *pkttype;
uint8_t value;
pkttype = nft_create_match(ctx, ctx->cs, "pkttype", false);
if (!pkttype)
return -1;
if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
pkttype->invert = 1;
value = nftnl_expr_get_u8(e, NFTNL_EXPR_CMP_DATA);
pkttype->pkttype = value;
return 0;
}
int parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e, uint8_t key,
char *iniface, unsigned char *iniface_mask,
char *outiface, unsigned char *outiface_mask, uint8_t *invflags)
{
uint32_t value;
const void *ifname;
uint32_t len;
switch(key) {
case NFT_META_IIF:
value = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_DATA);
if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
*invflags |= IPT_INV_VIA_IN;
if_indextoname(value, iniface);
memset(iniface_mask, 0xff, strlen(iniface)+1);
break;
case NFT_META_OIF:
value = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_DATA);
if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
*invflags |= IPT_INV_VIA_OUT;
if_indextoname(value, outiface);
memset(outiface_mask, 0xff, strlen(outiface)+1);
break;
case NFT_META_BRI_IIFNAME:
case NFT_META_IIFNAME:
ifname = nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &len);
if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
*invflags |= IPT_INV_VIA_IN;
parse_ifname(ifname, len, iniface, iniface_mask);
parse_invalid_iface(iniface, iniface_mask,
invflags, IPT_INV_VIA_IN);
break;
case NFT_META_BRI_OIFNAME:
case NFT_META_OIFNAME:
ifname = nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &len);
if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
*invflags |= IPT_INV_VIA_OUT;
parse_ifname(ifname, len, outiface, outiface_mask);
parse_invalid_iface(outiface, outiface_mask,
invflags, IPT_INV_VIA_OUT);
break;
case NFT_META_MARK:
parse_meta_mark(ctx, e);
break;
case NFT_META_PKTTYPE:
parse_meta_pkttype(ctx, e);
break;
default:
return -1;
}
return 0;
}
void nft_ipv46_parse_target(struct xtables_target *t,
struct iptables_command_state *cs)
{
cs->target = t;
cs->jumpto = t->name;
}
int nft_parse_hl(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
struct iptables_command_state *cs)
{
struct ip6t_hl_info *info;
uint8_t hl, mode;
int op;
hl = nftnl_expr_get_u8(e, NFTNL_EXPR_CMP_DATA);
op = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP);
switch (op) {
case NFT_CMP_NEQ:
mode = IP6T_HL_NE;
break;
case NFT_CMP_EQ:
mode = IP6T_HL_EQ;
break;
case NFT_CMP_LT:
mode = IP6T_HL_LT;
break;
case NFT_CMP_GT:
mode = IP6T_HL_GT;
break;
case NFT_CMP_LTE:
mode = IP6T_HL_LT;
if (hl == 255)
return -1;
hl++;
break;
case NFT_CMP_GTE:
mode = IP6T_HL_GT;
if (hl == 0)
return -1;
hl--;
break;
default:
return -1;
}
/* ipt_ttl_info and ip6t_hl_info have same layout,
* IPT_TTL_x and IP6T_HL_x are aliases as well, so
* just use HL for both ipv4 and ipv6.
*/
switch (ctx->h->family) {
case NFPROTO_IPV4:
info = nft_create_match(ctx, ctx->cs, "ttl", false);
break;
case NFPROTO_IPV6:
info = nft_create_match(ctx, ctx->cs, "hl", false);
break;
default:
return -1;
}
if (!info)
return -1;
info->hop_limit = hl;
info->mode = mode;
return 0;
}