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