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 | |
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, offset: &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) |
32 | return false; |
33 | srh = skb_header_pointer(skb, offset: srhoff, len: sizeof(_srh), buffer: &_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 | |
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, offset: &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) |
126 | return false; |
127 | srh = skb_header_pointer(skb, offset: srhoff, len: sizeof(_srh), buffer: &_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, offset: psidoff, len: sizeof(_psid), buffer: &_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, offset: nsidoff, len: sizeof(_nsid), buffer: &_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, offset: lsidoff, len: sizeof(_lsid), buffer: &_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 | |
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 | |
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 | |
305 | static int __init srh_mt6_init(void) |
306 | { |
307 | return xt_register_matches(match: srh_mt6_reg, ARRAY_SIZE(srh_mt6_reg)); |
308 | } |
309 | |
310 | static void __exit srh_mt6_exit(void) |
311 | { |
312 | xt_unregister_matches(match: 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 | |