1/* SPDX-License-Identifier: GPL-2.0 */
2#ifndef _NF_TABLES_IPV6_H_
3#define _NF_TABLES_IPV6_H_
4
5#include <linux/netfilter_ipv6/ip6_tables.h>
6#include <net/ipv6.h>
7#include <net/netfilter/nf_tables.h>
8
9static inline void nft_set_pktinfo_ipv6(struct nft_pktinfo *pkt)
10{
11	unsigned int flags = IP6_FH_F_AUTH;
12	int protohdr, thoff = 0;
13	unsigned short frag_off;
14
15	protohdr = ipv6_find_hdr(pkt->skb, &thoff, -1, &frag_off, &flags);
16	if (protohdr < 0 || thoff > U16_MAX) {
17		nft_set_pktinfo_unspec(pkt);
18		return;
19	}
20
21	pkt->flags = NFT_PKTINFO_L4PROTO;
22	pkt->tprot = protohdr;
23	pkt->thoff = thoff;
24	pkt->fragoff = frag_off;
25}
26
27static inline int __nft_set_pktinfo_ipv6_validate(struct nft_pktinfo *pkt)
28{
29#if IS_ENABLED(CONFIG_IPV6)
30	unsigned int flags = IP6_FH_F_AUTH;
31	struct ipv6hdr *ip6h, _ip6h;
32	unsigned int thoff = 0;
33	unsigned short frag_off;
34	int protohdr;
35	u32 pkt_len;
36
37	ip6h = skb_header_pointer(pkt->skb, skb_network_offset(pkt->skb),
38				  sizeof(*ip6h), &_ip6h);
39	if (!ip6h)
40		return -1;
41
42	if (ip6h->version != 6)
43		return -1;
44
45	pkt_len = ntohs(ip6h->payload_len);
46	if (pkt_len + sizeof(*ip6h) > pkt->skb->len)
47		return -1;
48
49	protohdr = ipv6_find_hdr(pkt->skb, &thoff, -1, &frag_off, &flags);
50	if (protohdr < 0 || thoff > U16_MAX)
51		return -1;
52
53	pkt->flags = NFT_PKTINFO_L4PROTO;
54	pkt->tprot = protohdr;
55	pkt->thoff = thoff;
56	pkt->fragoff = frag_off;
57
58	return 0;
59#else
60	return -1;
61#endif
62}
63
64static inline void nft_set_pktinfo_ipv6_validate(struct nft_pktinfo *pkt)
65{
66	if (__nft_set_pktinfo_ipv6_validate(pkt) < 0)
67		nft_set_pktinfo_unspec(pkt);
68}
69
70static inline int nft_set_pktinfo_ipv6_ingress(struct nft_pktinfo *pkt)
71{
72#if IS_ENABLED(CONFIG_IPV6)
73	unsigned int flags = IP6_FH_F_AUTH;
74	unsigned short frag_off;
75	unsigned int thoff = 0;
76	struct inet6_dev *idev;
77	struct ipv6hdr *ip6h;
78	int protohdr;
79	u32 pkt_len;
80
81	if (!pskb_may_pull(pkt->skb, sizeof(*ip6h)))
82		return -1;
83
84	ip6h = ipv6_hdr(pkt->skb);
85	if (ip6h->version != 6)
86		goto inhdr_error;
87
88	pkt_len = ntohs(ip6h->payload_len);
89	if (pkt_len + sizeof(*ip6h) > pkt->skb->len) {
90		idev = __in6_dev_get(nft_in(pkt));
91		__IP6_INC_STATS(nft_net(pkt), idev, IPSTATS_MIB_INTRUNCATEDPKTS);
92		return -1;
93	}
94
95	protohdr = ipv6_find_hdr(pkt->skb, &thoff, -1, &frag_off, &flags);
96	if (protohdr < 0 || thoff > U16_MAX)
97		goto inhdr_error;
98
99	pkt->flags = NFT_PKTINFO_L4PROTO;
100	pkt->tprot = protohdr;
101	pkt->thoff = thoff;
102	pkt->fragoff = frag_off;
103
104	return 0;
105
106inhdr_error:
107	idev = __in6_dev_get(nft_in(pkt));
108	__IP6_INC_STATS(nft_net(pkt), idev, IPSTATS_MIB_INHDRERRORS);
109	return -1;
110#else
111	return -1;
112#endif
113}
114
115#endif
116