1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /* Kernel module to match Segment Routing Header (SRH) parameters. */
3 
4 /* Author:
5  * Ahmed Abdelsalam <amsalam20@gmail.com>
6  */
7 
8 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9 #include <linux/module.h>
10 #include <linux/skbuff.h>
11 #include <linux/ipv6.h>
12 #include <linux/types.h>
13 #include <net/ipv6.h>
14 #include <net/seg6.h>
15 
16 #include <linux/netfilter/x_tables.h>
17 #include <linux/netfilter_ipv6/ip6t_srh.h>
18 #include <linux/netfilter_ipv6/ip6_tables.h>
19 
20 /* Test a struct->mt_invflags and a boolean for inequality */
21 #define NF_SRH_INVF(ptr, flag, boolean)	\
22 	((boolean) ^ !!((ptr)->mt_invflags & (flag)))
23 
srh_mt6(const struct sk_buff * skb,struct xt_action_param * par)24 static bool srh_mt6(const struct sk_buff *skb, struct xt_action_param *par)
25 {
26 	const struct ip6t_srh *srhinfo = par->matchinfo;
27 	struct ipv6_sr_hdr *srh;
28 	struct ipv6_sr_hdr _srh;
29 	int hdrlen, srhoff = 0;
30 
31 	if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0)
32 		return false;
33 	srh = skb_header_pointer(skb, srhoff, sizeof(_srh), &_srh);
34 	if (!srh)
35 		return false;
36 
37 	hdrlen = ipv6_optlen(srh);
38 	if (skb->len - srhoff < hdrlen)
39 		return false;
40 
41 	if (srh->type != IPV6_SRCRT_TYPE_4)
42 		return false;
43 
44 	if (srh->segments_left > srh->first_segment)
45 		return false;
46 
47 	/* Next Header matching */
48 	if (srhinfo->mt_flags & IP6T_SRH_NEXTHDR)
49 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_NEXTHDR,
50 				!(srh->nexthdr == srhinfo->next_hdr)))
51 			return false;
52 
53 	/* Header Extension Length matching */
54 	if (srhinfo->mt_flags & IP6T_SRH_LEN_EQ)
55 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LEN_EQ,
56 				!(srh->hdrlen == srhinfo->hdr_len)))
57 			return false;
58 
59 	if (srhinfo->mt_flags & IP6T_SRH_LEN_GT)
60 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LEN_GT,
61 				!(srh->hdrlen > srhinfo->hdr_len)))
62 			return false;
63 
64 	if (srhinfo->mt_flags & IP6T_SRH_LEN_LT)
65 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LEN_LT,
66 				!(srh->hdrlen < srhinfo->hdr_len)))
67 			return false;
68 
69 	/* Segments Left matching */
70 	if (srhinfo->mt_flags & IP6T_SRH_SEGS_EQ)
71 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_SEGS_EQ,
72 				!(srh->segments_left == srhinfo->segs_left)))
73 			return false;
74 
75 	if (srhinfo->mt_flags & IP6T_SRH_SEGS_GT)
76 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_SEGS_GT,
77 				!(srh->segments_left > srhinfo->segs_left)))
78 			return false;
79 
80 	if (srhinfo->mt_flags & IP6T_SRH_SEGS_LT)
81 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_SEGS_LT,
82 				!(srh->segments_left < srhinfo->segs_left)))
83 			return false;
84 
85 	/**
86 	 * Last Entry matching
87 	 * Last_Entry field was introduced in revision 6 of the SRH draft.
88 	 * It was called First_Segment in the previous revision
89 	 */
90 	if (srhinfo->mt_flags & IP6T_SRH_LAST_EQ)
91 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LAST_EQ,
92 				!(srh->first_segment == srhinfo->last_entry)))
93 			return false;
94 
95 	if (srhinfo->mt_flags & IP6T_SRH_LAST_GT)
96 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LAST_GT,
97 				!(srh->first_segment > srhinfo->last_entry)))
98 			return false;
99 
100 	if (srhinfo->mt_flags & IP6T_SRH_LAST_LT)
101 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LAST_LT,
102 				!(srh->first_segment < srhinfo->last_entry)))
103 			return false;
104 
105 	/**
106 	 * Tag matchig
107 	 * Tag field was introduced in revision 6 of the SRH draft.
108 	 */
109 	if (srhinfo->mt_flags & IP6T_SRH_TAG)
110 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_TAG,
111 				!(srh->tag == srhinfo->tag)))
112 			return false;
113 	return true;
114 }
115 
srh1_mt6(const struct sk_buff * skb,struct xt_action_param * par)116 static bool srh1_mt6(const struct sk_buff *skb, struct xt_action_param *par)
117 {
118 	int hdrlen, psidoff, nsidoff, lsidoff, srhoff = 0;
119 	const struct ip6t_srh1 *srhinfo = par->matchinfo;
120 	struct in6_addr *psid, *nsid, *lsid;
121 	struct in6_addr _psid, _nsid, _lsid;
122 	struct ipv6_sr_hdr *srh;
123 	struct ipv6_sr_hdr _srh;
124 
125 	if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0)
126 		return false;
127 	srh = skb_header_pointer(skb, srhoff, sizeof(_srh), &_srh);
128 	if (!srh)
129 		return false;
130 
131 	hdrlen = ipv6_optlen(srh);
132 	if (skb->len - srhoff < hdrlen)
133 		return false;
134 
135 	if (srh->type != IPV6_SRCRT_TYPE_4)
136 		return false;
137 
138 	if (srh->segments_left > srh->first_segment)
139 		return false;
140 
141 	/* Next Header matching */
142 	if (srhinfo->mt_flags & IP6T_SRH_NEXTHDR)
143 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_NEXTHDR,
144 				!(srh->nexthdr == srhinfo->next_hdr)))
145 			return false;
146 
147 	/* Header Extension Length matching */
148 	if (srhinfo->mt_flags & IP6T_SRH_LEN_EQ)
149 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LEN_EQ,
150 				!(srh->hdrlen == srhinfo->hdr_len)))
151 			return false;
152 	if (srhinfo->mt_flags & IP6T_SRH_LEN_GT)
153 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LEN_GT,
154 				!(srh->hdrlen > srhinfo->hdr_len)))
155 			return false;
156 	if (srhinfo->mt_flags & IP6T_SRH_LEN_LT)
157 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LEN_LT,
158 				!(srh->hdrlen < srhinfo->hdr_len)))
159 			return false;
160 
161 	/* Segments Left matching */
162 	if (srhinfo->mt_flags & IP6T_SRH_SEGS_EQ)
163 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_SEGS_EQ,
164 				!(srh->segments_left == srhinfo->segs_left)))
165 			return false;
166 	if (srhinfo->mt_flags & IP6T_SRH_SEGS_GT)
167 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_SEGS_GT,
168 				!(srh->segments_left > srhinfo->segs_left)))
169 			return false;
170 	if (srhinfo->mt_flags & IP6T_SRH_SEGS_LT)
171 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_SEGS_LT,
172 				!(srh->segments_left < srhinfo->segs_left)))
173 			return false;
174 
175 	/**
176 	 * Last Entry matching
177 	 * Last_Entry field was introduced in revision 6 of the SRH draft.
178 	 * It was called First_Segment in the previous revision
179 	 */
180 	if (srhinfo->mt_flags & IP6T_SRH_LAST_EQ)
181 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LAST_EQ,
182 				!(srh->first_segment == srhinfo->last_entry)))
183 			return false;
184 	if (srhinfo->mt_flags & IP6T_SRH_LAST_GT)
185 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LAST_GT,
186 				!(srh->first_segment > srhinfo->last_entry)))
187 			return false;
188 	if (srhinfo->mt_flags & IP6T_SRH_LAST_LT)
189 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LAST_LT,
190 				!(srh->first_segment < srhinfo->last_entry)))
191 			return false;
192 
193 	/**
194 	 * Tag matchig
195 	 * Tag field was introduced in revision 6 of the SRH draft
196 	 */
197 	if (srhinfo->mt_flags & IP6T_SRH_TAG)
198 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_TAG,
199 				!(srh->tag == srhinfo->tag)))
200 			return false;
201 
202 	/* Previous SID matching */
203 	if (srhinfo->mt_flags & IP6T_SRH_PSID) {
204 		if (srh->segments_left == srh->first_segment)
205 			return false;
206 		psidoff = srhoff + sizeof(struct ipv6_sr_hdr) +
207 			  ((srh->segments_left + 1) * sizeof(struct in6_addr));
208 		psid = skb_header_pointer(skb, psidoff, sizeof(_psid), &_psid);
209 		if (!psid)
210 			return false;
211 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_PSID,
212 				ipv6_masked_addr_cmp(psid, &srhinfo->psid_msk,
213 						     &srhinfo->psid_addr)))
214 			return false;
215 	}
216 
217 	/* Next SID matching */
218 	if (srhinfo->mt_flags & IP6T_SRH_NSID) {
219 		if (srh->segments_left == 0)
220 			return false;
221 		nsidoff = srhoff + sizeof(struct ipv6_sr_hdr) +
222 			  ((srh->segments_left - 1) * sizeof(struct in6_addr));
223 		nsid = skb_header_pointer(skb, nsidoff, sizeof(_nsid), &_nsid);
224 		if (!nsid)
225 			return false;
226 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_NSID,
227 				ipv6_masked_addr_cmp(nsid, &srhinfo->nsid_msk,
228 						     &srhinfo->nsid_addr)))
229 			return false;
230 	}
231 
232 	/* Last SID matching */
233 	if (srhinfo->mt_flags & IP6T_SRH_LSID) {
234 		lsidoff = srhoff + sizeof(struct ipv6_sr_hdr);
235 		lsid = skb_header_pointer(skb, lsidoff, sizeof(_lsid), &_lsid);
236 		if (!lsid)
237 			return false;
238 		if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LSID,
239 				ipv6_masked_addr_cmp(lsid, &srhinfo->lsid_msk,
240 						     &srhinfo->lsid_addr)))
241 			return false;
242 	}
243 	return true;
244 }
245 
srh_mt6_check(const struct xt_mtchk_param * par)246 static int srh_mt6_check(const struct xt_mtchk_param *par)
247 {
248 	const struct ip6t_srh *srhinfo = par->matchinfo;
249 
250 	if (srhinfo->mt_flags & ~IP6T_SRH_MASK) {
251 		pr_info_ratelimited("unknown srh match flags  %X\n",
252 				    srhinfo->mt_flags);
253 		return -EINVAL;
254 	}
255 
256 	if (srhinfo->mt_invflags & ~IP6T_SRH_INV_MASK) {
257 		pr_info_ratelimited("unknown srh invflags %X\n",
258 				    srhinfo->mt_invflags);
259 		return -EINVAL;
260 	}
261 
262 	return 0;
263 }
264 
srh1_mt6_check(const struct xt_mtchk_param * par)265 static int srh1_mt6_check(const struct xt_mtchk_param *par)
266 {
267 	const struct ip6t_srh1 *srhinfo = par->matchinfo;
268 
269 	if (srhinfo->mt_flags & ~IP6T_SRH_MASK) {
270 		pr_info_ratelimited("unknown srh match flags  %X\n",
271 				    srhinfo->mt_flags);
272 		return -EINVAL;
273 	}
274 
275 	if (srhinfo->mt_invflags & ~IP6T_SRH_INV_MASK) {
276 		pr_info_ratelimited("unknown srh invflags %X\n",
277 				    srhinfo->mt_invflags);
278 		return -EINVAL;
279 	}
280 
281 	return 0;
282 }
283 
284 static struct xt_match srh_mt6_reg[] __read_mostly = {
285 	{
286 		.name		= "srh",
287 		.revision	= 0,
288 		.family		= NFPROTO_IPV6,
289 		.match		= srh_mt6,
290 		.matchsize	= sizeof(struct ip6t_srh),
291 		.checkentry	= srh_mt6_check,
292 		.me		= THIS_MODULE,
293 	},
294 	{
295 		.name           = "srh",
296 		.revision       = 1,
297 		.family         = NFPROTO_IPV6,
298 		.match          = srh1_mt6,
299 		.matchsize      = sizeof(struct ip6t_srh1),
300 		.checkentry     = srh1_mt6_check,
301 		.me             = THIS_MODULE,
302 	}
303 };
304 
srh_mt6_init(void)305 static int __init srh_mt6_init(void)
306 {
307 	return xt_register_matches(srh_mt6_reg, ARRAY_SIZE(srh_mt6_reg));
308 }
309 
srh_mt6_exit(void)310 static void __exit srh_mt6_exit(void)
311 {
312 	xt_unregister_matches(srh_mt6_reg, ARRAY_SIZE(srh_mt6_reg));
313 }
314 
315 module_init(srh_mt6_init);
316 module_exit(srh_mt6_exit);
317 
318 MODULE_LICENSE("GPL");
319 MODULE_DESCRIPTION("Xtables: IPv6 Segment Routing Header match");
320 MODULE_AUTHOR("Ahmed Abdelsalam <amsalam20@gmail.com>");
321