Linux Audio

Check our new training course

Linux debugging, profiling, tracing and performance analysis training

Mar 24-27, 2025, special US time zones
Register
Loading...
Note: File does not exist in v5.14.15.
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Copyright (c) 2022 Pablo Neira Ayuso <pablo@netfilter.org>
  4 */
  5
  6#include <linux/kernel.h>
  7#include <linux/if_vlan.h>
  8#include <linux/init.h>
  9#include <linux/module.h>
 10#include <linux/netlink.h>
 11#include <linux/netfilter.h>
 12#include <linux/netfilter/nf_tables.h>
 13#include <net/netfilter/nf_tables_core.h>
 14#include <net/netfilter/nf_tables.h>
 15#include <net/netfilter/nft_meta.h>
 16#include <net/netfilter/nf_tables_offload.h>
 17#include <linux/tcp.h>
 18#include <linux/udp.h>
 19#include <net/gre.h>
 20#include <net/geneve.h>
 21#include <net/ip.h>
 22#include <linux/icmpv6.h>
 23#include <linux/ip.h>
 24#include <linux/ipv6.h>
 25
 26static DEFINE_PER_CPU(struct nft_inner_tun_ctx, nft_pcpu_tun_ctx);
 27
 28/* Same layout as nft_expr but it embeds the private expression data area. */
 29struct __nft_expr {
 30	const struct nft_expr_ops	*ops;
 31	union {
 32		struct nft_payload	payload;
 33		struct nft_meta		meta;
 34	} __attribute__((aligned(__alignof__(u64))));
 35};
 36
 37enum {
 38	NFT_INNER_EXPR_PAYLOAD,
 39	NFT_INNER_EXPR_META,
 40};
 41
 42struct nft_inner {
 43	u8			flags;
 44	u8			hdrsize;
 45	u8			type;
 46	u8			expr_type;
 47
 48	struct __nft_expr	expr;
 49};
 50
 51static int nft_inner_parse_l2l3(const struct nft_inner *priv,
 52				const struct nft_pktinfo *pkt,
 53				struct nft_inner_tun_ctx *ctx, u32 off)
 54{
 55	__be16 llproto, outer_llproto;
 56	u32 nhoff, thoff;
 57
 58	if (priv->flags & NFT_INNER_LL) {
 59		struct vlan_ethhdr *veth, _veth;
 60		struct ethhdr *eth, _eth;
 61		u32 hdrsize;
 62
 63		eth = skb_header_pointer(pkt->skb, off, sizeof(_eth), &_eth);
 64		if (!eth)
 65			return -1;
 66
 67		switch (eth->h_proto) {
 68		case htons(ETH_P_IP):
 69		case htons(ETH_P_IPV6):
 70			llproto = eth->h_proto;
 71			hdrsize = sizeof(_eth);
 72			break;
 73		case htons(ETH_P_8021Q):
 74			veth = skb_header_pointer(pkt->skb, off, sizeof(_veth), &_veth);
 75			if (!veth)
 76				return -1;
 77
 78			outer_llproto = veth->h_vlan_encapsulated_proto;
 79			llproto = veth->h_vlan_proto;
 80			hdrsize = sizeof(_veth);
 81			break;
 82		default:
 83			return -1;
 84		}
 85
 86		ctx->inner_lloff = off;
 87		ctx->flags |= NFT_PAYLOAD_CTX_INNER_LL;
 88		off += hdrsize;
 89	} else {
 90		struct iphdr *iph;
 91		u32 _version;
 92
 93		iph = skb_header_pointer(pkt->skb, off, sizeof(_version), &_version);
 94		if (!iph)
 95			return -1;
 96
 97		switch (iph->version) {
 98		case 4:
 99			llproto = htons(ETH_P_IP);
100			break;
101		case 6:
102			llproto = htons(ETH_P_IPV6);
103			break;
104		default:
105			return -1;
106		}
107	}
108
109	ctx->llproto = llproto;
110	if (llproto == htons(ETH_P_8021Q))
111		llproto = outer_llproto;
112
113	nhoff = off;
114
115	switch (llproto) {
116	case htons(ETH_P_IP): {
117		struct iphdr *iph, _iph;
118
119		iph = skb_header_pointer(pkt->skb, nhoff, sizeof(_iph), &_iph);
120		if (!iph)
121			return -1;
122
123		if (iph->ihl < 5 || iph->version != 4)
124			return -1;
125
126		ctx->inner_nhoff = nhoff;
127		ctx->flags |= NFT_PAYLOAD_CTX_INNER_NH;
128
129		thoff = nhoff + (iph->ihl * 4);
130		if ((ntohs(iph->frag_off) & IP_OFFSET) == 0) {
131			ctx->flags |= NFT_PAYLOAD_CTX_INNER_TH;
132			ctx->inner_thoff = thoff;
133			ctx->l4proto = iph->protocol;
134		}
135		}
136		break;
137	case htons(ETH_P_IPV6): {
138		struct ipv6hdr *ip6h, _ip6h;
139		int fh_flags = IP6_FH_F_AUTH;
140		unsigned short fragoff;
141		int l4proto;
142
143		ip6h = skb_header_pointer(pkt->skb, nhoff, sizeof(_ip6h), &_ip6h);
144		if (!ip6h)
145			return -1;
146
147		if (ip6h->version != 6)
148			return -1;
149
150		ctx->inner_nhoff = nhoff;
151		ctx->flags |= NFT_PAYLOAD_CTX_INNER_NH;
152
153		thoff = nhoff;
154		l4proto = ipv6_find_hdr(pkt->skb, &thoff, -1, &fragoff, &fh_flags);
155		if (l4proto < 0 || thoff > U16_MAX)
156			return -1;
157
158		if (fragoff == 0) {
159			thoff = nhoff + sizeof(_ip6h);
160			ctx->flags |= NFT_PAYLOAD_CTX_INNER_TH;
161			ctx->inner_thoff = thoff;
162			ctx->l4proto = l4proto;
163		}
164		}
165		break;
166	default:
167		return -1;
168	}
169
170	return 0;
171}
172
173static int nft_inner_parse_tunhdr(const struct nft_inner *priv,
174				  const struct nft_pktinfo *pkt,
175				  struct nft_inner_tun_ctx *ctx, u32 *off)
176{
177	if (pkt->tprot == IPPROTO_GRE) {
178		ctx->inner_tunoff = pkt->thoff;
179		ctx->flags |= NFT_PAYLOAD_CTX_INNER_TUN;
180		return 0;
181	}
182
183	if (pkt->tprot != IPPROTO_UDP)
184		return -1;
185
186	ctx->inner_tunoff = *off;
187	ctx->flags |= NFT_PAYLOAD_CTX_INNER_TUN;
188	*off += priv->hdrsize;
189
190	switch (priv->type) {
191	case NFT_INNER_GENEVE: {
192		struct genevehdr *gnvh, _gnvh;
193
194		gnvh = skb_header_pointer(pkt->skb, pkt->inneroff,
195					  sizeof(_gnvh), &_gnvh);
196		if (!gnvh)
197			return -1;
198
199		*off += gnvh->opt_len * 4;
200		}
201		break;
202	default:
203		break;
204	}
205
206	return 0;
207}
208
209static int nft_inner_parse(const struct nft_inner *priv,
210			   struct nft_pktinfo *pkt,
211			   struct nft_inner_tun_ctx *tun_ctx)
212{
213	u32 off = pkt->inneroff;
214
215	if (priv->flags & NFT_INNER_HDRSIZE &&
216	    nft_inner_parse_tunhdr(priv, pkt, tun_ctx, &off) < 0)
217		return -1;
218
219	if (priv->flags & (NFT_INNER_LL | NFT_INNER_NH)) {
220		if (nft_inner_parse_l2l3(priv, pkt, tun_ctx, off) < 0)
221			return -1;
222	} else if (priv->flags & NFT_INNER_TH) {
223		tun_ctx->inner_thoff = off;
224		tun_ctx->flags |= NFT_PAYLOAD_CTX_INNER_TH;
225	}
226
227	tun_ctx->type = priv->type;
228	tun_ctx->cookie = (unsigned long)pkt->skb;
229	pkt->flags |= NFT_PKTINFO_INNER_FULL;
230
231	return 0;
232}
233
234static bool nft_inner_restore_tun_ctx(const struct nft_pktinfo *pkt,
235				      struct nft_inner_tun_ctx *tun_ctx)
236{
237	struct nft_inner_tun_ctx *this_cpu_tun_ctx;
238
239	local_bh_disable();
240	this_cpu_tun_ctx = this_cpu_ptr(&nft_pcpu_tun_ctx);
241	if (this_cpu_tun_ctx->cookie != (unsigned long)pkt->skb) {
242		local_bh_enable();
243		return false;
244	}
245	*tun_ctx = *this_cpu_tun_ctx;
246	local_bh_enable();
247
248	return true;
249}
250
251static void nft_inner_save_tun_ctx(const struct nft_pktinfo *pkt,
252				   const struct nft_inner_tun_ctx *tun_ctx)
253{
254	struct nft_inner_tun_ctx *this_cpu_tun_ctx;
255
256	local_bh_disable();
257	this_cpu_tun_ctx = this_cpu_ptr(&nft_pcpu_tun_ctx);
258	if (this_cpu_tun_ctx->cookie != tun_ctx->cookie)
259		*this_cpu_tun_ctx = *tun_ctx;
260	local_bh_enable();
261}
262
263static bool nft_inner_parse_needed(const struct nft_inner *priv,
264				   const struct nft_pktinfo *pkt,
265				   struct nft_inner_tun_ctx *tun_ctx)
266{
267	if (!(pkt->flags & NFT_PKTINFO_INNER_FULL))
268		return true;
269
270	if (!nft_inner_restore_tun_ctx(pkt, tun_ctx))
271		return true;
272
273	if (priv->type != tun_ctx->type)
274		return true;
275
276	return false;
277}
278
279static void nft_inner_eval(const struct nft_expr *expr, struct nft_regs *regs,
280			   const struct nft_pktinfo *pkt)
281{
282	const struct nft_inner *priv = nft_expr_priv(expr);
283	struct nft_inner_tun_ctx tun_ctx = {};
284
285	if (nft_payload_inner_offset(pkt) < 0)
286		goto err;
287
288	if (nft_inner_parse_needed(priv, pkt, &tun_ctx) &&
289	    nft_inner_parse(priv, (struct nft_pktinfo *)pkt, &tun_ctx) < 0)
290		goto err;
291
292	switch (priv->expr_type) {
293	case NFT_INNER_EXPR_PAYLOAD:
294		nft_payload_inner_eval((struct nft_expr *)&priv->expr, regs, pkt, &tun_ctx);
295		break;
296	case NFT_INNER_EXPR_META:
297		nft_meta_inner_eval((struct nft_expr *)&priv->expr, regs, pkt, &tun_ctx);
298		break;
299	default:
300		WARN_ON_ONCE(1);
301		goto err;
302	}
303	nft_inner_save_tun_ctx(pkt, &tun_ctx);
304
305	return;
306err:
307	regs->verdict.code = NFT_BREAK;
308}
309
310static const struct nla_policy nft_inner_policy[NFTA_INNER_MAX + 1] = {
311	[NFTA_INNER_NUM]	= { .type = NLA_U32 },
312	[NFTA_INNER_FLAGS]	= { .type = NLA_U32 },
313	[NFTA_INNER_HDRSIZE]	= { .type = NLA_U32 },
314	[NFTA_INNER_TYPE]	= { .type = NLA_U32 },
315	[NFTA_INNER_EXPR]	= { .type = NLA_NESTED },
316};
317
318struct nft_expr_info {
319	const struct nft_expr_ops	*ops;
320	const struct nlattr		*attr;
321	struct nlattr			*tb[NFT_EXPR_MAXATTR + 1];
322};
323
324static int nft_inner_init(const struct nft_ctx *ctx,
325			  const struct nft_expr *expr,
326			  const struct nlattr * const tb[])
327{
328	struct nft_inner *priv = nft_expr_priv(expr);
329	u32 flags, hdrsize, type, num;
330	struct nft_expr_info expr_info;
331	int err;
332
333	if (!tb[NFTA_INNER_FLAGS] ||
334	    !tb[NFTA_INNER_NUM] ||
335	    !tb[NFTA_INNER_HDRSIZE] ||
336	    !tb[NFTA_INNER_TYPE] ||
337	    !tb[NFTA_INNER_EXPR])
338		return -EINVAL;
339
340	flags = ntohl(nla_get_be32(tb[NFTA_INNER_FLAGS]));
341	if (flags & ~NFT_INNER_MASK)
342		return -EOPNOTSUPP;
343
344	num = ntohl(nla_get_be32(tb[NFTA_INNER_NUM]));
345	if (num != 0)
346		return -EOPNOTSUPP;
347
348	hdrsize = ntohl(nla_get_be32(tb[NFTA_INNER_HDRSIZE]));
349	type = ntohl(nla_get_be32(tb[NFTA_INNER_TYPE]));
350
351	if (type > U8_MAX)
352		return -EINVAL;
353
354	if (flags & NFT_INNER_HDRSIZE) {
355		if (hdrsize == 0 || hdrsize > 64)
356			return -EOPNOTSUPP;
357	}
358
359	priv->flags = flags;
360	priv->hdrsize = hdrsize;
361	priv->type = type;
362
363	err = nft_expr_inner_parse(ctx, tb[NFTA_INNER_EXPR], &expr_info);
364	if (err < 0)
365		return err;
366
367	priv->expr.ops = expr_info.ops;
368
369	if (!strcmp(expr_info.ops->type->name, "payload"))
370		priv->expr_type = NFT_INNER_EXPR_PAYLOAD;
371	else if (!strcmp(expr_info.ops->type->name, "meta"))
372		priv->expr_type = NFT_INNER_EXPR_META;
373	else
374		return -EINVAL;
375
376	err = expr_info.ops->init(ctx, (struct nft_expr *)&priv->expr,
377				  (const struct nlattr * const*)expr_info.tb);
378	if (err < 0)
379		return err;
380
381	return 0;
382}
383
384static int nft_inner_dump(struct sk_buff *skb,
385			  const struct nft_expr *expr, bool reset)
386{
387	const struct nft_inner *priv = nft_expr_priv(expr);
388
389	if (nla_put_be32(skb, NFTA_INNER_NUM, htonl(0)) ||
390	    nla_put_be32(skb, NFTA_INNER_TYPE, htonl(priv->type)) ||
391	    nla_put_be32(skb, NFTA_INNER_FLAGS, htonl(priv->flags)) ||
392	    nla_put_be32(skb, NFTA_INNER_HDRSIZE, htonl(priv->hdrsize)))
393		goto nla_put_failure;
394
395	if (nft_expr_dump(skb, NFTA_INNER_EXPR,
396			  (struct nft_expr *)&priv->expr, reset) < 0)
397		goto nla_put_failure;
398
399	return 0;
400
401nla_put_failure:
402	return -1;
403}
404
405static const struct nft_expr_ops nft_inner_ops = {
406	.type		= &nft_inner_type,
407	.size		= NFT_EXPR_SIZE(sizeof(struct nft_inner)),
408	.eval		= nft_inner_eval,
409	.init		= nft_inner_init,
410	.dump		= nft_inner_dump,
411};
412
413struct nft_expr_type nft_inner_type __read_mostly = {
414	.name		= "inner",
415	.ops		= &nft_inner_ops,
416	.policy		= nft_inner_policy,
417	.maxattr	= NFTA_INNER_MAX,
418	.owner		= THIS_MODULE,
419};