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