mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-27 12:57:53 +00:00
8daa8fde3f
Analogous to NFT_MSG_GETOBJ_RESET, but for rules: Reset stateful expressions like counters or quotas. The latter two are the only consumers, adjust their 'dump' callbacks to respect the parameter introduced earlier. Signed-off-by: Phil Sutter <phil@nwl.cc> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
385 lines
8.6 KiB
C
385 lines
8.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2022 Pablo Neira Ayuso <pablo@netfilter.org>
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/if_vlan.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/netlink.h>
|
|
#include <linux/netfilter.h>
|
|
#include <linux/netfilter/nf_tables.h>
|
|
#include <net/netfilter/nf_tables_core.h>
|
|
#include <net/netfilter/nf_tables.h>
|
|
#include <net/netfilter/nft_meta.h>
|
|
#include <net/netfilter/nf_tables_offload.h>
|
|
#include <linux/tcp.h>
|
|
#include <linux/udp.h>
|
|
#include <net/gre.h>
|
|
#include <net/geneve.h>
|
|
#include <net/ip.h>
|
|
#include <linux/icmpv6.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/ipv6.h>
|
|
|
|
static DEFINE_PER_CPU(struct nft_inner_tun_ctx, nft_pcpu_tun_ctx);
|
|
|
|
/* Same layout as nft_expr but it embeds the private expression data area. */
|
|
struct __nft_expr {
|
|
const struct nft_expr_ops *ops;
|
|
union {
|
|
struct nft_payload payload;
|
|
struct nft_meta meta;
|
|
} __attribute__((aligned(__alignof__(u64))));
|
|
};
|
|
|
|
enum {
|
|
NFT_INNER_EXPR_PAYLOAD,
|
|
NFT_INNER_EXPR_META,
|
|
};
|
|
|
|
struct nft_inner {
|
|
u8 flags;
|
|
u8 hdrsize;
|
|
u8 type;
|
|
u8 expr_type;
|
|
|
|
struct __nft_expr expr;
|
|
};
|
|
|
|
static int nft_inner_parse_l2l3(const struct nft_inner *priv,
|
|
const struct nft_pktinfo *pkt,
|
|
struct nft_inner_tun_ctx *ctx, u32 off)
|
|
{
|
|
__be16 llproto, outer_llproto;
|
|
u32 nhoff, thoff;
|
|
|
|
if (priv->flags & NFT_INNER_LL) {
|
|
struct vlan_ethhdr *veth, _veth;
|
|
struct ethhdr *eth, _eth;
|
|
u32 hdrsize;
|
|
|
|
eth = skb_header_pointer(pkt->skb, off, sizeof(_eth), &_eth);
|
|
if (!eth)
|
|
return -1;
|
|
|
|
switch (eth->h_proto) {
|
|
case htons(ETH_P_IP):
|
|
case htons(ETH_P_IPV6):
|
|
llproto = eth->h_proto;
|
|
hdrsize = sizeof(_eth);
|
|
break;
|
|
case htons(ETH_P_8021Q):
|
|
veth = skb_header_pointer(pkt->skb, off, sizeof(_veth), &_veth);
|
|
if (!veth)
|
|
return -1;
|
|
|
|
outer_llproto = veth->h_vlan_encapsulated_proto;
|
|
llproto = veth->h_vlan_proto;
|
|
hdrsize = sizeof(_veth);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
ctx->inner_lloff = off;
|
|
ctx->flags |= NFT_PAYLOAD_CTX_INNER_LL;
|
|
off += hdrsize;
|
|
} else {
|
|
struct iphdr *iph;
|
|
u32 _version;
|
|
|
|
iph = skb_header_pointer(pkt->skb, off, sizeof(_version), &_version);
|
|
if (!iph)
|
|
return -1;
|
|
|
|
switch (iph->version) {
|
|
case 4:
|
|
llproto = htons(ETH_P_IP);
|
|
break;
|
|
case 6:
|
|
llproto = htons(ETH_P_IPV6);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
ctx->llproto = llproto;
|
|
if (llproto == htons(ETH_P_8021Q))
|
|
llproto = outer_llproto;
|
|
|
|
nhoff = off;
|
|
|
|
switch (llproto) {
|
|
case htons(ETH_P_IP): {
|
|
struct iphdr *iph, _iph;
|
|
|
|
iph = skb_header_pointer(pkt->skb, nhoff, sizeof(_iph), &_iph);
|
|
if (!iph)
|
|
return -1;
|
|
|
|
if (iph->ihl < 5 || iph->version != 4)
|
|
return -1;
|
|
|
|
ctx->inner_nhoff = nhoff;
|
|
ctx->flags |= NFT_PAYLOAD_CTX_INNER_NH;
|
|
|
|
thoff = nhoff + (iph->ihl * 4);
|
|
if ((ntohs(iph->frag_off) & IP_OFFSET) == 0) {
|
|
ctx->flags |= NFT_PAYLOAD_CTX_INNER_TH;
|
|
ctx->inner_thoff = thoff;
|
|
ctx->l4proto = iph->protocol;
|
|
}
|
|
}
|
|
break;
|
|
case htons(ETH_P_IPV6): {
|
|
struct ipv6hdr *ip6h, _ip6h;
|
|
int fh_flags = IP6_FH_F_AUTH;
|
|
unsigned short fragoff;
|
|
int l4proto;
|
|
|
|
ip6h = skb_header_pointer(pkt->skb, nhoff, sizeof(_ip6h), &_ip6h);
|
|
if (!ip6h)
|
|
return -1;
|
|
|
|
if (ip6h->version != 6)
|
|
return -1;
|
|
|
|
ctx->inner_nhoff = nhoff;
|
|
ctx->flags |= NFT_PAYLOAD_CTX_INNER_NH;
|
|
|
|
thoff = nhoff;
|
|
l4proto = ipv6_find_hdr(pkt->skb, &thoff, -1, &fragoff, &fh_flags);
|
|
if (l4proto < 0 || thoff > U16_MAX)
|
|
return -1;
|
|
|
|
if (fragoff == 0) {
|
|
thoff = nhoff + sizeof(_ip6h);
|
|
ctx->flags |= NFT_PAYLOAD_CTX_INNER_TH;
|
|
ctx->inner_thoff = thoff;
|
|
ctx->l4proto = l4proto;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nft_inner_parse_tunhdr(const struct nft_inner *priv,
|
|
const struct nft_pktinfo *pkt,
|
|
struct nft_inner_tun_ctx *ctx, u32 *off)
|
|
{
|
|
if (pkt->tprot == IPPROTO_GRE) {
|
|
ctx->inner_tunoff = pkt->thoff;
|
|
ctx->flags |= NFT_PAYLOAD_CTX_INNER_TUN;
|
|
return 0;
|
|
}
|
|
|
|
if (pkt->tprot != IPPROTO_UDP)
|
|
return -1;
|
|
|
|
ctx->inner_tunoff = *off;
|
|
ctx->flags |= NFT_PAYLOAD_CTX_INNER_TUN;
|
|
*off += priv->hdrsize;
|
|
|
|
switch (priv->type) {
|
|
case NFT_INNER_GENEVE: {
|
|
struct genevehdr *gnvh, _gnvh;
|
|
|
|
gnvh = skb_header_pointer(pkt->skb, pkt->inneroff,
|
|
sizeof(_gnvh), &_gnvh);
|
|
if (!gnvh)
|
|
return -1;
|
|
|
|
*off += gnvh->opt_len * 4;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nft_inner_parse(const struct nft_inner *priv,
|
|
struct nft_pktinfo *pkt,
|
|
struct nft_inner_tun_ctx *tun_ctx)
|
|
{
|
|
struct nft_inner_tun_ctx ctx = {};
|
|
u32 off = pkt->inneroff;
|
|
|
|
if (priv->flags & NFT_INNER_HDRSIZE &&
|
|
nft_inner_parse_tunhdr(priv, pkt, &ctx, &off) < 0)
|
|
return -1;
|
|
|
|
if (priv->flags & (NFT_INNER_LL | NFT_INNER_NH)) {
|
|
if (nft_inner_parse_l2l3(priv, pkt, &ctx, off) < 0)
|
|
return -1;
|
|
} else if (priv->flags & NFT_INNER_TH) {
|
|
ctx.inner_thoff = off;
|
|
ctx.flags |= NFT_PAYLOAD_CTX_INNER_TH;
|
|
}
|
|
|
|
*tun_ctx = ctx;
|
|
tun_ctx->type = priv->type;
|
|
pkt->flags |= NFT_PKTINFO_INNER_FULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool nft_inner_parse_needed(const struct nft_inner *priv,
|
|
const struct nft_pktinfo *pkt,
|
|
const struct nft_inner_tun_ctx *tun_ctx)
|
|
{
|
|
if (!(pkt->flags & NFT_PKTINFO_INNER_FULL))
|
|
return true;
|
|
|
|
if (priv->type != tun_ctx->type)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void nft_inner_eval(const struct nft_expr *expr, struct nft_regs *regs,
|
|
const struct nft_pktinfo *pkt)
|
|
{
|
|
struct nft_inner_tun_ctx *tun_ctx = this_cpu_ptr(&nft_pcpu_tun_ctx);
|
|
const struct nft_inner *priv = nft_expr_priv(expr);
|
|
|
|
if (nft_payload_inner_offset(pkt) < 0)
|
|
goto err;
|
|
|
|
if (nft_inner_parse_needed(priv, pkt, tun_ctx) &&
|
|
nft_inner_parse(priv, (struct nft_pktinfo *)pkt, tun_ctx) < 0)
|
|
goto err;
|
|
|
|
switch (priv->expr_type) {
|
|
case NFT_INNER_EXPR_PAYLOAD:
|
|
nft_payload_inner_eval((struct nft_expr *)&priv->expr, regs, pkt, tun_ctx);
|
|
break;
|
|
case NFT_INNER_EXPR_META:
|
|
nft_meta_inner_eval((struct nft_expr *)&priv->expr, regs, pkt, tun_ctx);
|
|
break;
|
|
default:
|
|
WARN_ON_ONCE(1);
|
|
goto err;
|
|
}
|
|
return;
|
|
err:
|
|
regs->verdict.code = NFT_BREAK;
|
|
}
|
|
|
|
static const struct nla_policy nft_inner_policy[NFTA_INNER_MAX + 1] = {
|
|
[NFTA_INNER_NUM] = { .type = NLA_U32 },
|
|
[NFTA_INNER_FLAGS] = { .type = NLA_U32 },
|
|
[NFTA_INNER_HDRSIZE] = { .type = NLA_U32 },
|
|
[NFTA_INNER_TYPE] = { .type = NLA_U32 },
|
|
[NFTA_INNER_EXPR] = { .type = NLA_NESTED },
|
|
};
|
|
|
|
struct nft_expr_info {
|
|
const struct nft_expr_ops *ops;
|
|
const struct nlattr *attr;
|
|
struct nlattr *tb[NFT_EXPR_MAXATTR + 1];
|
|
};
|
|
|
|
static int nft_inner_init(const struct nft_ctx *ctx,
|
|
const struct nft_expr *expr,
|
|
const struct nlattr * const tb[])
|
|
{
|
|
struct nft_inner *priv = nft_expr_priv(expr);
|
|
u32 flags, hdrsize, type, num;
|
|
struct nft_expr_info expr_info;
|
|
int err;
|
|
|
|
if (!tb[NFTA_INNER_FLAGS] ||
|
|
!tb[NFTA_INNER_HDRSIZE] ||
|
|
!tb[NFTA_INNER_TYPE] ||
|
|
!tb[NFTA_INNER_EXPR])
|
|
return -EINVAL;
|
|
|
|
flags = ntohl(nla_get_be32(tb[NFTA_INNER_FLAGS]));
|
|
if (flags & ~NFT_INNER_MASK)
|
|
return -EOPNOTSUPP;
|
|
|
|
num = ntohl(nla_get_be32(tb[NFTA_INNER_NUM]));
|
|
if (num != 0)
|
|
return -EOPNOTSUPP;
|
|
|
|
hdrsize = ntohl(nla_get_be32(tb[NFTA_INNER_HDRSIZE]));
|
|
type = ntohl(nla_get_be32(tb[NFTA_INNER_TYPE]));
|
|
|
|
if (type > U8_MAX)
|
|
return -EINVAL;
|
|
|
|
if (flags & NFT_INNER_HDRSIZE) {
|
|
if (hdrsize == 0 || hdrsize > 64)
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
priv->flags = flags;
|
|
priv->hdrsize = hdrsize;
|
|
priv->type = type;
|
|
|
|
err = nft_expr_inner_parse(ctx, tb[NFTA_INNER_EXPR], &expr_info);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
priv->expr.ops = expr_info.ops;
|
|
|
|
if (!strcmp(expr_info.ops->type->name, "payload"))
|
|
priv->expr_type = NFT_INNER_EXPR_PAYLOAD;
|
|
else if (!strcmp(expr_info.ops->type->name, "meta"))
|
|
priv->expr_type = NFT_INNER_EXPR_META;
|
|
else
|
|
return -EINVAL;
|
|
|
|
err = expr_info.ops->init(ctx, (struct nft_expr *)&priv->expr,
|
|
(const struct nlattr * const*)expr_info.tb);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nft_inner_dump(struct sk_buff *skb,
|
|
const struct nft_expr *expr, bool reset)
|
|
{
|
|
const struct nft_inner *priv = nft_expr_priv(expr);
|
|
|
|
if (nla_put_be32(skb, NFTA_INNER_NUM, htonl(0)) ||
|
|
nla_put_be32(skb, NFTA_INNER_TYPE, htonl(priv->type)) ||
|
|
nla_put_be32(skb, NFTA_INNER_FLAGS, htonl(priv->flags)) ||
|
|
nla_put_be32(skb, NFTA_INNER_HDRSIZE, htonl(priv->hdrsize)))
|
|
goto nla_put_failure;
|
|
|
|
if (nft_expr_dump(skb, NFTA_INNER_EXPR,
|
|
(struct nft_expr *)&priv->expr, reset) < 0)
|
|
goto nla_put_failure;
|
|
|
|
return 0;
|
|
|
|
nla_put_failure:
|
|
return -1;
|
|
}
|
|
|
|
static const struct nft_expr_ops nft_inner_ops = {
|
|
.type = &nft_inner_type,
|
|
.size = NFT_EXPR_SIZE(sizeof(struct nft_inner)),
|
|
.eval = nft_inner_eval,
|
|
.init = nft_inner_init,
|
|
.dump = nft_inner_dump,
|
|
};
|
|
|
|
struct nft_expr_type nft_inner_type __read_mostly = {
|
|
.name = "inner",
|
|
.ops = &nft_inner_ops,
|
|
.policy = nft_inner_policy,
|
|
.maxattr = NFTA_INNER_MAX,
|
|
.owner = THIS_MODULE,
|
|
};
|