Signed-off-by: Linus Lüssing linus.luessing@c0d3.blue --- Makefile | 4 + compat-include/linux/igmp.h | 13 ++ compat-include/linux/skbuff.h | 14 +++ compat-include/net/addrconf.h | 13 ++ compat-include/net/ip6_checksum.h | 18 +++ compat-include/net/ipv6.h | 17 +++ compat-include/net/mld.h | 52 ++++++++ compat-sources/Makefile | 3 + compat-sources/net/core/skbuff.c | 136 +++++++++++++++++++++ compat-sources/net/ipv4/igmp.c | 169 ++++++++++++++++++++++++++ compat-sources/net/ipv6/mcast_snoop.c | 216 +++++++++++++++++++++++++++++++++ 11 files changed, 655 insertions(+) create mode 100644 compat-include/linux/igmp.h create mode 100644 compat-include/net/addrconf.h create mode 100644 compat-include/net/ip6_checksum.h create mode 100644 compat-include/net/ipv6.h create mode 100644 compat-include/net/mld.h create mode 100644 compat-sources/Makefile create mode 100644 compat-sources/net/core/skbuff.c create mode 100644 compat-sources/net/ipv4/igmp.c create mode 100644 compat-sources/net/ipv6/mcast_snoop.c
diff --git a/Makefile b/Makefile index ee3be1d..69056d7 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,10 @@ ifneq ($(REVISION),) NOSTDINC_FLAGS += -DBATADV_SOURCE_VERSION="$(REVISION)" endif
+include $(PWD)/compat-sources/Makefile + +export batman-adv-y + BUILD_FLAGS := \ M=$(PWD)/net/batman-adv \ CONFIG_BATMAN_ADV=m \ diff --git a/compat-include/linux/igmp.h b/compat-include/linux/igmp.h new file mode 100644 index 0000000..f61ab79 --- /dev/null +++ b/compat-include/linux/igmp.h @@ -0,0 +1,13 @@ +#ifndef _NET_BATMAN_ADV_COMPAT_LINUX_IGMP_H_ +#define _NET_BATMAN_ADV_COMPAT_LINUX_IGMP_H_ + +#include <linux/version.h> +#include_next <linux/igmp.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) + +int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed); + +#endif /* < KERNEL_VERSION(4, 2, 0) */ + +#endif /* _NET_BATMAN_ADV_COMPAT_LINUX_IGMP_H_ */ diff --git a/compat-include/linux/skbuff.h b/compat-include/linux/skbuff.h index d363cc0..1d4f569 100644 --- a/compat-include/linux/skbuff.h +++ b/compat-include/linux/skbuff.h @@ -89,6 +89,20 @@ static inline void skb_reset_mac_len(struct sk_buff *skb)
#define pskb_copy_for_clone pskb_copy
+__sum16 skb_checksum_simple_validate(struct sk_buff *skb); + +__sum16 +skb_checksum_validate(struct sk_buff *skb, int proto, + __wsum (*compute_pseudo)(struct sk_buff *skb, int proto)); + #endif /* < KERNEL_VERSION(3, 16, 0) */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) + +struct sk_buff *skb_checksum_trimmed(struct sk_buff *skb, + unsigned int transport_len, + __sum16(*skb_chkf)(struct sk_buff *skb)); + +#endif /* < KERNEL_VERSION(4, 2, 0) */ + #endif /* _NET_BATMAN_ADV_COMPAT_LINUX_SKBUFF_H_ */ diff --git a/compat-include/net/addrconf.h b/compat-include/net/addrconf.h new file mode 100644 index 0000000..69c45d0 --- /dev/null +++ b/compat-include/net/addrconf.h @@ -0,0 +1,13 @@ +#ifndef _NET_BATMAN_ADV_COMPAT_NET_ADDRCONF_H_ +#define _NET_BATMAN_ADV_COMPAT_NET_ADDRCONF_H_ + +#include <linux/version.h> +#include_next <net/addrconf.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) + +int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed); + +#endif /* < KERNEL_VERSION(4, 2, 0) */ + +#endif /* _NET_BATMAN_ADV_COMPAT_NET_ADDRCONF_H_ */ diff --git a/compat-include/net/ip6_checksum.h b/compat-include/net/ip6_checksum.h new file mode 100644 index 0000000..fda0c07 --- /dev/null +++ b/compat-include/net/ip6_checksum.h @@ -0,0 +1,18 @@ +#ifndef _NET_BATMAN_ADV_COMPAT_NET_IP6_CHECKSUM_H_ +#define _NET_BATMAN_ADV_COMPAT_NET_IP6_CHECKSUM_H_ + +#include <linux/version.h> +#include_next <net/ip6_checksum.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0) + +static inline __wsum ip6_compute_pseudo(struct sk_buff *skb, int proto) +{ + return ~csum_unfold(csum_ipv6_magic(&ipv6_hdr(skb)->saddr, + &ipv6_hdr(skb)->daddr, + skb->len, proto, 0)); +} + +#endif /* < KERNEL_VERSION(3, 16, 0) */ + +#endif /* _NET_BATMAN_ADV_COMPAT_NET_IP6_CHECKSUM_H_ */ diff --git a/compat-include/net/ipv6.h b/compat-include/net/ipv6.h new file mode 100644 index 0000000..1e190d8 --- /dev/null +++ b/compat-include/net/ipv6.h @@ -0,0 +1,17 @@ +#ifndef _NET_BATMAN_ADV_COMPAT_NET_IPV6_H_ +#define _NET_BATMAN_ADV_COMPAT_NET_IPV6_H_ + +#include <linux/version.h> +#include_next <net/ipv6.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) + +#define ipv6_skip_exthdr(skb, start, nexthdrp, frag_offp) \ + ({ \ + (void)frag_offp; \ + ipv6_skip_exthdr(skb, start, nexthdrp); \ + }) + +#endif /* < KERNEL_VERSION(3, 3, 0) */ + +#endif /* _NET_BATMAN_ADV_COMPAT_NET_IPV6_H_ */ diff --git a/compat-include/net/mld.h b/compat-include/net/mld.h new file mode 100644 index 0000000..e041eb6 --- /dev/null +++ b/compat-include/net/mld.h @@ -0,0 +1,52 @@ +#ifndef _NET_BATMAN_ADV_COMPAT_NET_MLD_H_ +#define _NET_BATMAN_ADV_COMPAT_NET_MLD_H_ + +#include <linux/version.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35) +#include_next <net/mld.h> +#endif /* >= KERNEL_VERSION(2, 6, 35) */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +struct mld_msg { + struct icmp6hdr mld_hdr; + struct in6_addr mld_mca; +}; + +#define mld_type mld_hdr.icmp6_type + +struct mld2_grec { + __u8 grec_type; + __u8 grec_auxwords; + __be16 grec_nsrcs; + struct in6_addr grec_mca; + struct in6_addr grec_src[0]; +}; + +struct mld2_report { + struct icmp6hdr mld2r_hdr; + struct mld2_grec mld2r_grec[0]; +}; + +struct mld2_query { + struct icmp6hdr mld2q_hdr; + struct in6_addr mld2q_mca; +#if defined(__LITTLE_ENDIAN_BITFIELD) + __u8 mld2q_qrv:3, + mld2q_suppress:1, + mld2q_resv2:4; +#elif defined(__BIG_ENDIAN_BITFIELD) + __u8 mld2q_resv2:4, + mld2q_suppress:1, + mld2q_qrv:3; +#else +#error "Please fix <asm/byteorder.h>" +#endif + __u8 mld2q_qqic; + __be16 mld2q_nsrcs; + struct in6_addr mld2q_srcs[0]; +}; + +#endif /* < KERNEL_VERSION(2, 6, 35) */ + +#endif /* _NET_BATMAN_ADV_COMPAT_NET_MLD_H_ */ diff --git a/compat-sources/Makefile b/compat-sources/Makefile new file mode 100644 index 0000000..c364ded --- /dev/null +++ b/compat-sources/Makefile @@ -0,0 +1,3 @@ +batman-adv-y += ../../compat-sources/net/core/skbuff.o +batman-adv-y += ../../compat-sources/net/ipv4/igmp.o +batman-adv-y += ../../compat-sources/net/ipv6/mcast_snoop.o diff --git a/compat-sources/net/core/skbuff.c b/compat-sources/net/core/skbuff.c new file mode 100644 index 0000000..eb15adf --- /dev/null +++ b/compat-sources/net/core/skbuff.c @@ -0,0 +1,136 @@ +#include <linux/ipv6.h> +#include <linux/skbuff.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0) + +__sum16 skb_checksum_simple_validate(struct sk_buff *skb) +{ + switch (skb->ip_summed) { + case CHECKSUM_COMPLETE: + if (!csum_fold(skb->csum)) + break; + /* fall through */ + case CHECKSUM_NONE: + skb->csum = 0; + if (skb_checksum_complete(skb)) + return -EINVAL; + } + + return 0; +} + +__sum16 +skb_checksum_validate(struct sk_buff *skb, int proto, + __wsum (*compute_pseudo)(struct sk_buff *skb, int proto)) +{ + const struct ipv6hdr *ip6h = ipv6_hdr(skb); + + switch (skb->ip_summed) { + case CHECKSUM_COMPLETE: + if (!csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, skb->len, + IPPROTO_ICMPV6, skb->csum)) + break; + /*FALLTHROUGH*/ + case CHECKSUM_NONE: + skb->csum = ~csum_unfold(csum_ipv6_magic(&ip6h->saddr, + &ip6h->daddr, + skb->len, + IPPROTO_ICMPV6, 0)); + if (__skb_checksum_complete(skb)) + return -EINVAL; + } + + return 0; +} + +#endif /* < KERNEL_VERSION(3, 16, 0) */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) + +/** + * skb_checksum_maybe_trim - maybe trims the given skb + * @skb: the skb to check + * @transport_len: the data length beyond the network header + * + * Checks whether the given skb has data beyond the given transport length. + * If so, returns a cloned skb trimmed to this transport length. + * Otherwise returns the provided skb. Returns NULL in error cases + * (e.g. transport_len exceeds skb length or out-of-memory). + * + * Caller needs to set the skb transport header and release the returned skb. + * Provided skb is consumed. + */ +static struct sk_buff *skb_checksum_maybe_trim(struct sk_buff *skb, + unsigned int transport_len) +{ + struct sk_buff *skb_chk; + unsigned int len = skb_transport_offset(skb) + transport_len; + int ret; + + if (skb->len < len) { + kfree_skb(skb); + return NULL; + } else if (skb->len == len) { + return skb; + } + + skb_chk = skb_clone(skb, GFP_ATOMIC); + kfree_skb(skb); + + if (!skb_chk) + return NULL; + + ret = pskb_trim_rcsum(skb_chk, len); + if (ret) { + kfree_skb(skb_chk); + return NULL; + } + + return skb_chk; +} + +/** + * skb_checksum_trimmed - validate checksum of an skb + * @skb: the skb to check + * @transport_len: the data length beyond the network header + * @skb_chkf: checksum function to use + * + * Applies the given checksum function skb_chkf to the provided skb. + * Returns a checked and maybe trimmed skb. Returns NULL on error. + * + * If the skb has data beyond the given transport length, then a + * trimmed & cloned skb is checked and returned. + * + * Caller needs to set the skb transport header and release the returned skb. + * Provided skb is consumed. + */ +struct sk_buff *skb_checksum_trimmed(struct sk_buff *skb, + unsigned int transport_len, + __sum16(*skb_chkf)(struct sk_buff *skb)) +{ + struct sk_buff *skb_chk; + unsigned int offset = skb_transport_offset(skb); + __sum16 ret; + + skb_chk = skb_checksum_maybe_trim(skb, transport_len); + if (!skb_chk) + return NULL; + + if (!pskb_may_pull(skb_chk, offset)) { + kfree_skb(skb_chk); + return NULL; + } + + __skb_pull(skb_chk, offset); + ret = skb_chkf(skb_chk); + __skb_push(skb_chk, offset); + + if (ret) { + kfree_skb(skb_chk); + return NULL; + } + + return skb_chk; +} + +#endif /* < KERNEL_VERSION(4, 2, 0) */ diff --git a/compat-sources/net/ipv4/igmp.c b/compat-sources/net/ipv4/igmp.c new file mode 100644 index 0000000..7dd69d7 --- /dev/null +++ b/compat-sources/net/ipv4/igmp.c @@ -0,0 +1,169 @@ +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) + +#include <linux/igmp.h> +#include <linux/ip.h> +#include <linux/skbuff.h> +#include <net/ip.h> + +static int ip_mc_check_iphdr(struct sk_buff *skb) +{ + const struct iphdr *iph; + unsigned int len; + unsigned int offset = skb_network_offset(skb) + sizeof(*iph); + + if (!pskb_may_pull(skb, offset)) + return -EINVAL; + + iph = ip_hdr(skb); + + if (iph->version != 4 || ip_hdrlen(skb) < sizeof(*iph)) + return -EINVAL; + + offset += ip_hdrlen(skb) - sizeof(*iph); + + if (!pskb_may_pull(skb, offset)) + return -EINVAL; + + iph = ip_hdr(skb); + + if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl))) + return -EINVAL; + + len = skb_network_offset(skb) + ntohs(iph->tot_len); + if (skb->len < len || len < offset) + return -EINVAL; + + skb_set_transport_header(skb, offset); + + return 0; +} + +static int ip_mc_check_igmp_reportv3(struct sk_buff *skb) +{ + unsigned int len = skb_transport_offset(skb); + + len += sizeof(struct igmpv3_report); + + return pskb_may_pull(skb, len) ? 0 : -EINVAL; +} + +static int ip_mc_check_igmp_query(struct sk_buff *skb) +{ + unsigned int len = skb_transport_offset(skb); + + len += sizeof(struct igmphdr); + if (skb->len < len) + return -EINVAL; + + /* IGMPv{1,2}? */ + if (skb->len != len) { + /* or IGMPv3? */ + len += sizeof(struct igmpv3_query) - sizeof(struct igmphdr); + if (skb->len < len || !pskb_may_pull(skb, len)) + return -EINVAL; + } + + /* RFC2236+RFC3376 (IGMPv2+IGMPv3) require the multicast link layer + * all-systems destination addresses (224.0.0.1) for general queries + */ + if (!igmp_hdr(skb)->group && + ip_hdr(skb)->daddr != htonl(INADDR_ALLHOSTS_GROUP)) + return -EINVAL; + + return 0; +} + +static int ip_mc_check_igmp_msg(struct sk_buff *skb) +{ + switch (igmp_hdr(skb)->type) { + case IGMP_HOST_LEAVE_MESSAGE: + case IGMP_HOST_MEMBERSHIP_REPORT: + case IGMPV2_HOST_MEMBERSHIP_REPORT: + /* fall through */ + return 0; + case IGMPV3_HOST_MEMBERSHIP_REPORT: + return ip_mc_check_igmp_reportv3(skb); + case IGMP_HOST_MEMBERSHIP_QUERY: + return ip_mc_check_igmp_query(skb); + default: + return -ENOMSG; + } +} + +static inline __sum16 ip_mc_validate_checksum(struct sk_buff *skb) +{ + return skb_checksum_simple_validate(skb); +} + +static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) + +{ + struct sk_buff *skb_chk; + unsigned int transport_len; + unsigned int len = skb_transport_offset(skb) + sizeof(struct igmphdr); + int ret; + + transport_len = ntohs(ip_hdr(skb)->tot_len) - ip_hdrlen(skb); + + skb_get(skb); + skb_chk = skb_checksum_trimmed(skb, transport_len, + ip_mc_validate_checksum); + if (!skb_chk) + return -EINVAL; + + if (!pskb_may_pull(skb_chk, len)) { + kfree_skb(skb_chk); + return -EINVAL; + } + + ret = ip_mc_check_igmp_msg(skb_chk); + if (ret) { + kfree_skb(skb_chk); + return ret; + } + + if (skb_trimmed) + *skb_trimmed = skb_chk; + else + kfree_skb(skb_chk); + + return 0; +} + +/** + * ip_mc_check_igmp - checks whether this is a sane IGMP packet + * @skb: the skb to validate + * @skb_trimmed: to store an skb pointer trimmed to IPv4 packet tail (optional) + * + * Checks whether an IPv4 packet is a valid IGMP packet. If so sets + * skb network and transport headers accordingly and returns zero. + * + * -EINVAL: A broken packet was detected, i.e. it violates some internet + * standard + * -ENOMSG: IP header validation succeeded but it is not an IGMP packet. + * -ENOMEM: A memory allocation failure happened. + * + * Optionally, an skb pointer might be provided via skb_trimmed (or set it + * to NULL): After parsing an IGMP packet successfully it will point to + * an skb which has its tail aligned to the IP packet end. This might + * either be the originally provided skb or a trimmed, cloned version if + * the skb frame had data beyond the IP packet. A cloned skb allows us + * to leave the original skb and its full frame unchanged (which might be + * desirable for layer 2 frame jugglers). + * + * The caller needs to release a reference count from any returned skb_trimmed. + */ +int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) +{ + int ret = ip_mc_check_iphdr(skb); + + if (ret < 0) + return ret; + + if (ip_hdr(skb)->protocol != IPPROTO_IGMP) + return -ENOMSG; + + return __ip_mc_check_igmp(skb, skb_trimmed); +} + +#endif /* < KERNEL_VERSION(4, 2, 0) */ diff --git a/compat-sources/net/ipv6/mcast_snoop.c b/compat-sources/net/ipv6/mcast_snoop.c new file mode 100644 index 0000000..32a57bc --- /dev/null +++ b/compat-sources/net/ipv6/mcast_snoop.c @@ -0,0 +1,216 @@ +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) + +/* Copyright (C) 2010: YOSHIFUJI Hideaki yoshfuji@linux-ipv6.org + * Copyright (C) 2015: Linus Lüssing linus.luessing@c0d3.blue + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses/. + * + * + * Based on the MLD support added to br_multicast.c by YOSHIFUJI Hideaki. + */ + +#include <linux/skbuff.h> +#include <net/ipv6.h> +#include <net/mld.h> +#include <net/addrconf.h> +#include <net/ip6_checksum.h> + +static int ipv6_mc_check_ip6hdr(struct sk_buff *skb) +{ + const struct ipv6hdr *ip6h; + unsigned int len; + unsigned int offset = skb_network_offset(skb) + sizeof(*ip6h); + + if (!pskb_may_pull(skb, offset)) + return -EINVAL; + + ip6h = ipv6_hdr(skb); + + if (ip6h->version != 6) + return -EINVAL; + + len = offset + ntohs(ip6h->payload_len); + if (skb->len < len || len <= offset) + return -EINVAL; + + return 0; +} + +static int ipv6_mc_check_exthdrs(struct sk_buff *skb) +{ + const struct ipv6hdr *ip6h; + int offset; + u8 nexthdr; + __be16 frag_off; + + ip6h = ipv6_hdr(skb); + + if (ip6h->nexthdr != IPPROTO_HOPOPTS) + return -ENOMSG; + + nexthdr = ip6h->nexthdr; + offset = skb_network_offset(skb) + sizeof(*ip6h); + offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off); + + if (offset < 0) + return -EINVAL; + + if (nexthdr != IPPROTO_ICMPV6) + return -ENOMSG; + + skb_set_transport_header(skb, offset); + + return 0; +} + +static int ipv6_mc_check_mld_reportv2(struct sk_buff *skb) +{ + unsigned int len = skb_transport_offset(skb); + + len += sizeof(struct mld2_report); + + return pskb_may_pull(skb, len) ? 0 : -EINVAL; +} + +static int ipv6_mc_check_mld_query(struct sk_buff *skb) +{ + struct mld_msg *mld; + unsigned int len = skb_transport_offset(skb); + + /* RFC2710+RFC3810 (MLDv1+MLDv2) require link-local source addresses */ + if (!(ipv6_addr_type(&ipv6_hdr(skb)->saddr) & IPV6_ADDR_LINKLOCAL)) + return -EINVAL; + + len += sizeof(struct mld_msg); + if (skb->len < len) + return -EINVAL; + + /* MLDv1? */ + if (skb->len != len) { + /* or MLDv2? */ + len += sizeof(struct mld2_query) - sizeof(struct mld_msg); + if (skb->len < len || !pskb_may_pull(skb, len)) + return -EINVAL; + } + + mld = (struct mld_msg *)skb_transport_header(skb); + + /* RFC2710+RFC3810 (MLDv1+MLDv2) require the multicast link layer + * all-nodes destination address (ff02::1) for general queries + */ + if (ipv6_addr_any(&mld->mld_mca) && + !ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr)) + return -EINVAL; + + return 0; +} + +static int ipv6_mc_check_mld_msg(struct sk_buff *skb) +{ + struct mld_msg *mld = (struct mld_msg *)skb_transport_header(skb); + + switch (mld->mld_type) { + case ICMPV6_MGM_REDUCTION: + case ICMPV6_MGM_REPORT: + /* fall through */ + return 0; + case ICMPV6_MLD2_REPORT: + return ipv6_mc_check_mld_reportv2(skb); + case ICMPV6_MGM_QUERY: + return ipv6_mc_check_mld_query(skb); + default: + return -ENOMSG; + } +} + +static inline __sum16 ipv6_mc_validate_checksum(struct sk_buff *skb) +{ + return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo); +} + +static int __ipv6_mc_check_mld(struct sk_buff *skb, + struct sk_buff **skb_trimmed) + +{ + struct sk_buff *skb_chk = NULL; + unsigned int transport_len; + unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg); + int ret; + + transport_len = ntohs(ipv6_hdr(skb)->payload_len); + transport_len -= skb_transport_offset(skb) - sizeof(struct ipv6hdr); + + skb_get(skb); + skb_chk = skb_checksum_trimmed(skb, transport_len, + ipv6_mc_validate_checksum); + if (!skb_chk) + return -EINVAL; + + if (!pskb_may_pull(skb_chk, len)) { + kfree_skb(skb_chk); + return -EINVAL; + } + + ret = ipv6_mc_check_mld_msg(skb_chk); + if (ret) { + kfree_skb(skb_chk); + return ret; + } + + if (skb_trimmed) + *skb_trimmed = skb_chk; + else + kfree_skb(skb_chk); + + return 0; +} + +/** + * ipv6_mc_check_mld - checks whether this is a sane MLD packet + * @skb: the skb to validate + * @skb_trimmed: to store an skb pointer trimmed to IPv6 packet tail (optional) + * + * Checks whether an IPv6 packet is a valid MLD packet. If so sets + * skb network and transport headers accordingly and returns zero. + * + * -EINVAL: A broken packet was detected, i.e. it violates some internet + * standard + * -ENOMSG: IP header validation succeeded but it is not an MLD packet. + * -ENOMEM: A memory allocation failure happened. + * + * Optionally, an skb pointer might be provided via skb_trimmed (or set it + * to NULL): After parsing an MLD packet successfully it will point to + * an skb which has its tail aligned to the IP packet end. This might + * either be the originally provided skb or a trimmed, cloned version if + * the skb frame had data beyond the IP packet. A cloned skb allows us + * to leave the original skb and its full frame unchanged (which might be + * desirable for layer 2 frame jugglers). + * + * The caller needs to release a reference count from any returned skb_trimmed. + */ +int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed) +{ + int ret; + + ret = ipv6_mc_check_ip6hdr(skb); + if (ret < 0) + return ret; + + ret = ipv6_mc_check_exthdrs(skb); + if (ret < 0) + return ret; + + return __ipv6_mc_check_mld(skb, skb_trimmed); +} + +#endif /* < KERNEL_VERSION(4, 2, 0) */