// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2008 Patrick McHardy <kaber@trash.net>
*
* Development of this code funded by Astaro AG (http://www.astaro.com/)
*/
#include <linux/unaligned.h>
#include <linux/kernel.h>
#include <linux/netlink.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/dccp.h>
#include <linux/sctp.h>
#include <net/netfilter/nf_tables_core.h>
#include <net/netfilter/nf_tables.h>
#include <net/tcp.h>
struct nft_exthdr {
u8 type;
u8 offset;
u8 len;
u8 op;
u8 dreg;
u8 sreg;
u8 flags;
};
static unsigned int optlen(const u8 *opt, unsigned int offset)
{
/* Beware zero-length options: make finite progress */
if (opt[offset] <= TCPOPT_NOP || opt[offset + 1] == 0)
return 1;
else
return opt[offset + 1];
}
static int nft_skb_copy_to_reg(const struct sk_buff *skb, int offset, u32 *dest, unsigned int len)
{
if (len % NFT_REG32_SIZE)
dest[len / NFT_REG32_SIZE] = 0;
return skb_copy_bits(skb, offset, dest, len);
}
static void nft_exthdr_ipv6_eval(const struct nft_expr *expr,
struct nft_regs *regs,
const struct nft_pktinfo *pkt)
{
struct nft_exthdr *priv = nft_expr_priv(expr);
u32 *dest = ®s->data[priv->dreg];
unsigned int offset = 0;
int err;
if (pkt->skb->protocol != htons(ETH_P_IPV6))
goto err;
err = ipv6_find_hdr(pkt->skb, &offset, priv->type, NULL, NULL);
if (priv->flags & NFT_EXTHDR_F_PRESENT) {
nft_reg_store8(dest, err >= 0);
return;
} else if (err < 0) {
goto err;
}
offset += priv->offset;
if (nft_skb_copy_to_reg(pkt->skb, offset, dest, priv->len) < 0)
goto err;
return;
err:
regs->verdict.code = NFT_BREAK;
}
/* find the offset to specified option.
*
* If target header is found, its offset is set in *offset and return option
* number. Otherwise, return negative error.
*
* If the first fragment doesn't contain the End of Options it is considered
* invalid.
*/
static int ipv4_find_option(struct net *net, struct sk_buff *skb,
unsigned int *offset, int target)
{
unsigned char optbuf[sizeof(struct ip_options) + 40];
struct ip_options *opt = (struct ip_options *)optbuf;
struct iphdr *iph, _iph;
bool found = false;
__be32 info;
int optlen;
iph = skb_header_pointer(skb, 0, sizeof(_iph), &_iph);
if (!iph)
return -EBADMSG;
optlen = iph->ihl * 4 - (int)sizeof(struct iphdr);
if (optlen <= 0)
return -ENOENT;
memset(opt, 0, sizeof(struct ip_options));
/* Copy the options since __ip_options_compile() modifies
* the options.
*/
if (skb_copy_bits(skb, sizeof(struct iphdr), opt->__data, optlen))
return -EBADMSG;
opt->optlen = optlen;
if (__ip_options_compile(net, opt, NULL, &info))
return -EBADMSG;
switch (target) {
case IPOPT_SSRR:
case IPOPT_LSRR:
if (!opt->srr)
break;
found = target == IPOPT_SSRR ? opt->is_strictroute :
!opt->is_strictroute;
if (found)
*offset = opt->srr;
break;
case IPOPT_RR:
if (!opt->rr)
break;
*offset = opt->rr;
found = true;
break;
case IPOPT_RA:
if (!opt->router_alert)
break;
*offset = opt->router_alert;
found = true;
break;
default:
return -EOPNOTSUPP;
}
return found ? target : -ENOENT;
}
static void nft_exthdr_ipv4_eval(const struct nft_expr *expr,
struct nft_regs *regs,
const struct nft_pktinfo *pkt)
{
struct nft_exthdr *priv = nft_expr_priv(expr);
u32 *dest = ®s->data[priv->dreg];
struct sk_buff *skb = pkt->skb;
unsigned int offset;
int err;
if (skb->protocol != htons(ETH_P_IP))
goto err;
err = ipv4_find_option(nft_net(pkt), skb, &offset, priv->