Linux-libre 5.7.6-gnu
[librecmc/linux-libre.git] / net / ipv6 / netfilter / ip6t_srh.c
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, &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
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
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(srh_mt6_reg, ARRAY_SIZE(srh_mt6_reg));
308 }
309
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>");