xref: /linux/net/ipv6/netfilter/ip6t_hbh.c (revision 49bda4826843be0ef97a162009a29ea3a63f3935)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Kernel module to match Hop-by-Hop and Destination parameters. */
3 
4 /* (C) 2001-2002 Andras Kis-Szabo <kisza@sch.bme.hu>
5  */
6 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
7 #include <linux/module.h>
8 #include <linux/skbuff.h>
9 #include <linux/ipv6.h>
10 #include <linux/types.h>
11 #include <net/checksum.h>
12 #include <net/ipv6.h>
13 
14 #include <asm/byteorder.h>
15 
16 #include <linux/netfilter/x_tables.h>
17 #include <linux/netfilter_ipv6/ip6_tables.h>
18 #include <linux/netfilter_ipv6/ip6t_opts.h>
19 
20 MODULE_LICENSE("GPL");
21 MODULE_DESCRIPTION("Xtables: IPv6 Hop-By-Hop and Destination Header match");
22 MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
23 MODULE_ALIAS("ip6t_dst");
24 
25 /*
26  *  (Type & 0xC0) >> 6
27  *	0	-> ignorable
28  *	1	-> must drop the packet
29  *	2	-> send ICMP PARM PROB regardless and drop packet
30  *	3	-> Send ICMP if not a multicast address and drop packet
31  *  (Type & 0x20) >> 5
32  *	0	-> invariant
33  *	1	-> can change the routing
34  *  (Type & 0x1F) Type
35  *	0	-> Pad1 (only 1 byte!)
36  *	1	-> PadN LENGTH info (total length = length + 2)
37  *	C0 | 2	-> JUMBO 4 x x x x ( xxxx > 64k )
38  *	5	-> RTALERT 2 x x
39  */
40 
41 static struct xt_match hbh_mt6_reg[] __read_mostly;
42 
43 static bool
hbh_mt6(const struct sk_buff * skb,struct xt_action_param * par)44 hbh_mt6(const struct sk_buff *skb, struct xt_action_param *par)
45 {
46 	struct ipv6_opt_hdr _optsh;
47 	const struct ipv6_opt_hdr *oh;
48 	const struct ip6t_opts *optinfo = par->matchinfo;
49 	unsigned int temp;
50 	unsigned int ptr = 0;
51 	unsigned int hdrlen = 0;
52 	bool ret = false;
53 	u8 _opttype;
54 	u8 _optlen;
55 	const u_int8_t *tp = NULL;
56 	const u_int8_t *lp = NULL;
57 	unsigned int optlen;
58 	int err;
59 
60 	err = ipv6_find_hdr(skb, &ptr,
61 			    (par->match == &hbh_mt6_reg[0]) ?
62 			    NEXTHDR_HOP : NEXTHDR_DEST, NULL, NULL);
63 	if (err < 0) {
64 		if (err != -ENOENT)
65 			par->hotdrop = true;
66 		return false;
67 	}
68 
69 	oh = skb_header_pointer(skb, ptr, sizeof(_optsh), &_optsh);
70 	if (oh == NULL) {
71 		par->hotdrop = true;
72 		return false;
73 	}
74 
75 	hdrlen = ipv6_optlen(oh);
76 	if (skb->len - ptr < hdrlen) {
77 		/* Packet smaller than it's length field */
78 		par->hotdrop = true;
79 		return false;
80 	}
81 
82 	ret = (!(optinfo->flags & IP6T_OPTS_LEN) ||
83 	       ((optinfo->hdrlen == hdrlen) ^
84 		!!(optinfo->invflags & IP6T_OPTS_INV_LEN)));
85 
86 	ptr += 2;
87 	hdrlen -= 2;
88 	if (!(optinfo->flags & IP6T_OPTS_OPTS)) {
89 		return ret;
90 	} else {
91 		for (temp = 0; temp < optinfo->optsnr; temp++) {
92 			/* type field exists ? */
93 			if (hdrlen < 1)
94 				break;
95 			tp = skb_header_pointer(skb, ptr, sizeof(_opttype),
96 						&_opttype);
97 			if (tp == NULL)
98 				break;
99 
100 			/* Type check */
101 			if (*tp != (optinfo->opts[temp] & 0xFF00) >> 8)
102 				return false;
103 
104 			/* Length check */
105 			if (*tp) {
106 				u16 spec_len;
107 
108 				/* length field exists ? */
109 				if (hdrlen < 2)
110 					break;
111 				lp = skb_header_pointer(skb, ptr + 1,
112 							sizeof(_optlen),
113 							&_optlen);
114 				if (lp == NULL)
115 					break;
116 				spec_len = optinfo->opts[temp] & 0x00FF;
117 
118 				if (spec_len != 0x00FF && spec_len != *lp)
119 					return false;
120 
121 				optlen = *lp + 2;
122 			} else {
123 				optlen = 1;
124 			}
125 
126 			if ((ptr > skb->len - optlen || hdrlen < optlen) &&
127 			    temp < optinfo->optsnr - 1)
128 				break;
129 
130 			ptr += optlen;
131 			hdrlen -= optlen;
132 		}
133 		if (temp == optinfo->optsnr)
134 			return ret;
135 		else
136 			return false;
137 	}
138 
139 	return false;
140 }
141 
hbh_mt6_check(const struct xt_mtchk_param * par)142 static int hbh_mt6_check(const struct xt_mtchk_param *par)
143 {
144 	const struct ip6t_opts *optsinfo = par->matchinfo;
145 
146 	if (optsinfo->invflags & ~IP6T_OPTS_INV_MASK) {
147 		pr_info_ratelimited("unknown flags %X\n", optsinfo->invflags);
148 		return -EINVAL;
149 	}
150 	if (optsinfo->optsnr > IP6T_OPTS_OPTSNR) {
151 		pr_info_ratelimited("too many supported opts specified\n");
152 		return -EINVAL;
153 	}
154 
155 	if (optsinfo->flags & IP6T_OPTS_NSTRICT) {
156 		pr_info_ratelimited("Not strict - not implemented\n");
157 		return -EINVAL;
158 	}
159 
160 	return 0;
161 }
162 
163 static struct xt_match hbh_mt6_reg[] __read_mostly = {
164 	{
165 		/* Note, hbh_mt6 relies on the order of hbh_mt6_reg */
166 		.name		= "hbh",
167 		.family		= NFPROTO_IPV6,
168 		.match		= hbh_mt6,
169 		.matchsize	= sizeof(struct ip6t_opts),
170 		.checkentry	= hbh_mt6_check,
171 		.me		= THIS_MODULE,
172 	},
173 	{
174 		.name		= "dst",
175 		.family		= NFPROTO_IPV6,
176 		.match		= hbh_mt6,
177 		.matchsize	= sizeof(struct ip6t_opts),
178 		.checkentry	= hbh_mt6_check,
179 		.me		= THIS_MODULE,
180 	},
181 };
182 
hbh_mt6_init(void)183 static int __init hbh_mt6_init(void)
184 {
185 	return xt_register_matches(hbh_mt6_reg, ARRAY_SIZE(hbh_mt6_reg));
186 }
187 
hbh_mt6_exit(void)188 static void __exit hbh_mt6_exit(void)
189 {
190 	xt_unregister_matches(hbh_mt6_reg, ARRAY_SIZE(hbh_mt6_reg));
191 }
192 
193 module_init(hbh_mt6_init);
194 module_exit(hbh_mt6_exit);
195