| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* Kernel module to match Segment Routing Header (SRH) parameters. */ |
| |
| /* Author: |
| * Ahmed Abdelsalam <amsalam20@gmail.com> |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| #include <linux/module.h> |
| #include <linux/skbuff.h> |
| #include <linux/ipv6.h> |
| #include <linux/types.h> |
| #include <net/ipv6.h> |
| #include <net/seg6.h> |
| |
| #include <linux/netfilter/x_tables.h> |
| #include <linux/netfilter_ipv6/ip6t_srh.h> |
| #include <linux/netfilter_ipv6/ip6_tables.h> |
| |
| /* Test a struct->mt_invflags and a boolean for inequality */ |
| #define NF_SRH_INVF(ptr, flag, boolean) \ |
| ((boolean) ^ !!((ptr)->mt_invflags & (flag))) |
| |
| static bool srh_mt6(const struct sk_buff *skb, struct xt_action_param *par) |
| { |
| const struct ip6t_srh *srhinfo = par->matchinfo; |
| struct ipv6_sr_hdr *srh; |
| struct ipv6_sr_hdr _srh; |
| int hdrlen, srhoff = 0; |
| |
| if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) |
| return false; |
| srh = skb_header_pointer(skb, srhoff, sizeof(_srh), &_srh); |
| if (!srh) |
| return false; |
| |
| hdrlen = ipv6_optlen(srh); |
| if (skb->len - srhoff < hdrlen) |
| return false; |
| |
| if (srh->type != IPV6_SRCRT_TYPE_4) |
| return false; |
| |
| if (srh->segments_left > srh->first_segment) |
| return false; |
| |
| /* Next Header matching */ |
| if (srhinfo->mt_flags & IP6T_SRH_NEXTHDR) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_NEXTHDR, |
| !(srh->nexthdr == srhinfo->next_hdr))) |
| return false; |
| |
| /* Header Extension Length matching */ |
| if (srhinfo->mt_flags & IP6T_SRH_LEN_EQ) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LEN_EQ, |
| !(srh->hdrlen == srhinfo->hdr_len))) |
| return false; |
| |
| if (srhinfo->mt_flags & IP6T_SRH_LEN_GT) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LEN_GT, |
| !(srh->hdrlen > srhinfo->hdr_len))) |
| return false; |
| |
| if (srhinfo->mt_flags & IP6T_SRH_LEN_LT) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LEN_LT, |
| !(srh->hdrlen < srhinfo->hdr_len))) |
| return false; |
| |
| /* Segments Left matching */ |
| if (srhinfo->mt_flags & IP6T_SRH_SEGS_EQ) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_SEGS_EQ, |
| !(srh->segments_left == srhinfo->segs_left))) |
| return false; |
| |
| if (srhinfo->mt_flags & IP6T_SRH_SEGS_GT) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_SEGS_GT, |
| !(srh->segments_left > srhinfo->segs_left))) |
| return false; |
| |
| if (srhinfo->mt_flags & IP6T_SRH_SEGS_LT) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_SEGS_LT, |
| !(srh->segments_left < srhinfo->segs_left))) |
| return false; |
| |
| /** |
| * Last Entry matching |
| * Last_Entry field was introduced in revision 6 of the SRH draft. |
| * It was called First_Segment in the previous revision |
| */ |
| if (srhinfo->mt_flags & IP6T_SRH_LAST_EQ) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LAST_EQ, |
| !(srh->first_segment == srhinfo->last_entry))) |
| return false; |
| |
| if (srhinfo->mt_flags & IP6T_SRH_LAST_GT) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LAST_GT, |
| !(srh->first_segment > srhinfo->last_entry))) |
| return false; |
| |
| if (srhinfo->mt_flags & IP6T_SRH_LAST_LT) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LAST_LT, |
| !(srh->first_segment < srhinfo->last_entry))) |
| return false; |
| |
| /** |
| * Tag matchig |
| * Tag field was introduced in revision 6 of the SRH draft. |
| */ |
| if (srhinfo->mt_flags & IP6T_SRH_TAG) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_TAG, |
| !(srh->tag == srhinfo->tag))) |
| return false; |
| return true; |
| } |
| |
| static bool srh1_mt6(const struct sk_buff *skb, struct xt_action_param *par) |
| { |
| int hdrlen, psidoff, nsidoff, lsidoff, srhoff = 0; |
| const struct ip6t_srh1 *srhinfo = par->matchinfo; |
| struct in6_addr *psid, *nsid, *lsid; |
| struct in6_addr _psid, _nsid, _lsid; |
| struct ipv6_sr_hdr *srh; |
| struct ipv6_sr_hdr _srh; |
| |
| if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) |
| return false; |
| srh = skb_header_pointer(skb, srhoff, sizeof(_srh), &_srh); |
| if (!srh) |
| return false; |
| |
| hdrlen = ipv6_optlen(srh); |
| if (skb->len - srhoff < hdrlen) |
| return false; |
| |
| if (srh->type != IPV6_SRCRT_TYPE_4) |
| return false; |
| |
| if (srh->segments_left > srh->first_segment) |
| return false; |
| |
| /* Next Header matching */ |
| if (srhinfo->mt_flags & IP6T_SRH_NEXTHDR) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_NEXTHDR, |
| !(srh->nexthdr == srhinfo->next_hdr))) |
| return false; |
| |
| /* Header Extension Length matching */ |
| if (srhinfo->mt_flags & IP6T_SRH_LEN_EQ) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LEN_EQ, |
| !(srh->hdrlen == srhinfo->hdr_len))) |
| return false; |
| if (srhinfo->mt_flags & IP6T_SRH_LEN_GT) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LEN_GT, |
| !(srh->hdrlen > srhinfo->hdr_len))) |
| return false; |
| if (srhinfo->mt_flags & IP6T_SRH_LEN_LT) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LEN_LT, |
| !(srh->hdrlen < srhinfo->hdr_len))) |
| return false; |
| |
| /* Segments Left matching */ |
| if (srhinfo->mt_flags & IP6T_SRH_SEGS_EQ) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_SEGS_EQ, |
| !(srh->segments_left == srhinfo->segs_left))) |
| return false; |
| if (srhinfo->mt_flags & IP6T_SRH_SEGS_GT) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_SEGS_GT, |
| !(srh->segments_left > srhinfo->segs_left))) |
| return false; |
| if (srhinfo->mt_flags & IP6T_SRH_SEGS_LT) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_SEGS_LT, |
| !(srh->segments_left < srhinfo->segs_left))) |
| return false; |
| |
| /** |
| * Last Entry matching |
| * Last_Entry field was introduced in revision 6 of the SRH draft. |
| * It was called First_Segment in the previous revision |
| */ |
| if (srhinfo->mt_flags & IP6T_SRH_LAST_EQ) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LAST_EQ, |
| !(srh->first_segment == srhinfo->last_entry))) |
| return false; |
| if (srhinfo->mt_flags & IP6T_SRH_LAST_GT) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LAST_GT, |
| !(srh->first_segment > srhinfo->last_entry))) |
| return false; |
| if (srhinfo->mt_flags & IP6T_SRH_LAST_LT) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LAST_LT, |
| !(srh->first_segment < srhinfo->last_entry))) |
| return false; |
| |
| /** |
| * Tag matchig |
| * Tag field was introduced in revision 6 of the SRH draft |
| */ |
| if (srhinfo->mt_flags & IP6T_SRH_TAG) |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_TAG, |
| !(srh->tag == srhinfo->tag))) |
| return false; |
| |
| /* Previous SID matching */ |
| if (srhinfo->mt_flags & IP6T_SRH_PSID) { |
| if (srh->segments_left == srh->first_segment) |
| return false; |
| psidoff = srhoff + sizeof(struct ipv6_sr_hdr) + |
| ((srh->segments_left + 1) * sizeof(struct in6_addr)); |
| psid = skb_header_pointer(skb, psidoff, sizeof(_psid), &_psid); |
| if (!psid) |
| return false; |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_PSID, |
| ipv6_masked_addr_cmp(psid, &srhinfo->psid_msk, |
| &srhinfo->psid_addr))) |
| return false; |
| } |
| |
| /* Next SID matching */ |
| if (srhinfo->mt_flags & IP6T_SRH_NSID) { |
| if (srh->segments_left == 0) |
| return false; |
| nsidoff = srhoff + sizeof(struct ipv6_sr_hdr) + |
| ((srh->segments_left - 1) * sizeof(struct in6_addr)); |
| nsid = skb_header_pointer(skb, nsidoff, sizeof(_nsid), &_nsid); |
| if (!nsid) |
| return false; |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_NSID, |
| ipv6_masked_addr_cmp(nsid, &srhinfo->nsid_msk, |
| &srhinfo->nsid_addr))) |
| return false; |
| } |
| |
| /* Last SID matching */ |
| if (srhinfo->mt_flags & IP6T_SRH_LSID) { |
| lsidoff = srhoff + sizeof(struct ipv6_sr_hdr); |
| lsid = skb_header_pointer(skb, lsidoff, sizeof(_lsid), &_lsid); |
| if (!lsid) |
| return false; |
| if (NF_SRH_INVF(srhinfo, IP6T_SRH_INV_LSID, |
| ipv6_masked_addr_cmp(lsid, &srhinfo->lsid_msk, |
| &srhinfo->lsid_addr))) |
| return false; |
| } |
| return true; |
| } |
| |
| static int srh_mt6_check(const struct xt_mtchk_param *par) |
| { |
| const struct ip6t_srh *srhinfo = par->matchinfo; |
| |
| if (srhinfo->mt_flags & ~IP6T_SRH_MASK) { |
| pr_info_ratelimited("unknown srh match flags %X\n", |
| srhinfo->mt_flags); |
| return -EINVAL; |
| } |
| |
| if (srhinfo->mt_invflags & ~IP6T_SRH_INV_MASK) { |
| pr_info_ratelimited("unknown srh invflags %X\n", |
| srhinfo->mt_invflags); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int srh1_mt6_check(const struct xt_mtchk_param *par) |
| { |
| const struct ip6t_srh1 *srhinfo = par->matchinfo; |
| |
| if (srhinfo->mt_flags & ~IP6T_SRH_MASK) { |
| pr_info_ratelimited("unknown srh match flags %X\n", |
| srhinfo->mt_flags); |
| return -EINVAL; |
| } |
| |
| if (srhinfo->mt_invflags & ~IP6T_SRH_INV_MASK) { |
| pr_info_ratelimited("unknown srh invflags %X\n", |
| srhinfo->mt_invflags); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static struct xt_match srh_mt6_reg[] __read_mostly = { |
| { |
| .name = "srh", |
| .revision = 0, |
| .family = NFPROTO_IPV6, |
| .match = srh_mt6, |
| .matchsize = sizeof(struct ip6t_srh), |
| .checkentry = srh_mt6_check, |
| .me = THIS_MODULE, |
| }, |
| { |
| .name = "srh", |
| .revision = 1, |
| .family = NFPROTO_IPV6, |
| .match = srh1_mt6, |
| .matchsize = sizeof(struct ip6t_srh1), |
| .checkentry = srh1_mt6_check, |
| .me = THIS_MODULE, |
| } |
| }; |
| |
| static int __init srh_mt6_init(void) |
| { |
| return xt_register_matches(srh_mt6_reg, ARRAY_SIZE(srh_mt6_reg)); |
| } |
| |
| static void __exit srh_mt6_exit(void) |
| { |
| xt_unregister_matches(srh_mt6_reg, ARRAY_SIZE(srh_mt6_reg)); |
| } |
| |
| module_init(srh_mt6_init); |
| module_exit(srh_mt6_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("Xtables: IPv6 Segment Routing Header match"); |
| MODULE_AUTHOR("Ahmed Abdelsalam <amsalam20@gmail.com>"); |