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