From ec16e8d53f9af3028a766b22e09d4fb521815f65 Mon Sep 17 00:00:00 2001 From: Marek Lindner Date: Sun, 8 May 2016 22:27:03 +0800 Subject: [PATCH] batman-adv: support multicast optimization in bridged setups Signed-off-by: Marek Lindner --- batman-adv/Makefile | 8 +- batman-adv/files/compat-hacks.h | 11 + ...0-multicast-optimization-for-bridges.patch | 1720 +++++++++++++++++ 3 files changed, 1738 insertions(+), 1 deletion(-) create mode 100644 batman-adv/patches/0010-multicast-optimization-for-bridges.patch diff --git a/batman-adv/Makefile b/batman-adv/Makefile index 8a77692..1378c52 100644 --- a/batman-adv/Makefile +++ b/batman-adv/Makefile @@ -69,8 +69,14 @@ NOSTDINC_FLAGS = \ -include backport/backport.h \ -include $(PKG_BUILD_DIR)/compat-hacks.h +COMPAT_SOURCES = \ + $(if $(CONFIG_KMOD_BATMAN_ADV_MCAST),../../compat-sources/net/core/skbuff.o,) \ + $(if $(CONFIG_KMOD_BATMAN_ADV_MCAST),../../compat-sources/net/ipv4/igmp.o,) \ + $(if $(CONFIG_KMOD_BATMAN_ADV_MCAST),../../compat-sources/net/ipv6/mcast_snoop.o,) \ + define Build/Compile - +$(MAKE) $(PKG_JOBS) -C "$(LINUX_DIR)" \ + +env "batman-adv-y=$(COMPAT_SOURCES)" \ + $(MAKE) $(PKG_JOBS) -C "$(LINUX_DIR)" \ ARCH="$(LINUX_KARCH)" \ CROSS_COMPILE="$(TARGET_CROSS)" \ SUBDIRS="$(PKG_BUILD_DIR)/net/batman-adv" \ diff --git a/batman-adv/files/compat-hacks.h b/batman-adv/files/compat-hacks.h index 5126fc2..b5e2d13 100644 --- a/batman-adv/files/compat-hacks.h +++ b/batman-adv/files/compat-hacks.h @@ -31,6 +31,17 @@ #endif /* < KERNEL_VERSION(4, 5, 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)); + +int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed); + +int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed); + +#endif /* < KERNEL_VERSION(4, 2, 0) */ #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0) diff --git a/batman-adv/patches/0010-multicast-optimization-for-bridges.patch b/batman-adv/patches/0010-multicast-optimization-for-bridges.patch new file mode 100644 index 0000000..2a9bedd --- /dev/null +++ b/batman-adv/patches/0010-multicast-optimization-for-bridges.patch @@ -0,0 +1,1720 @@ +diff --git a/Makefile b/Makefile +index 5d2c058..fd6237c 100644 +--- a/Makefile ++++ b/Makefile +@@ -52,6 +52,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/etherdevice.h b/compat-include/linux/etherdevice.h +index f120202..721945c 100644 +--- a/compat-include/linux/etherdevice.h ++++ b/compat-include/linux/etherdevice.h +@@ -35,6 +35,15 @@ static inline void batadv_eth_hw_addr_random(struct net_device *dev) + + #endif /* < KERNEL_VERSION(3, 4, 0) */ + ++#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0) ++ ++static inline void eth_zero_addr(u8 *addr) ++{ ++ memset(addr, 0x00, ETH_ALEN); ++} ++ ++#endif /* < KERNEL_VERSION(3, 7, 0) */ ++ + #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) + + #define ether_addr_equal_unaligned(_a, _b) (memcmp(_a, _b, ETH_ALEN) == 0) +diff --git a/compat-include/linux/if_bridge.h b/compat-include/linux/if_bridge.h +new file mode 100644 +index 0000000..4d7ab67 +--- /dev/null ++++ b/compat-include/linux/if_bridge.h +@@ -0,0 +1,65 @@ ++#ifndef _NET_BATMAN_ADV_COMPAT_LINUX_IF_BRIDGE_H_ ++#define _NET_BATMAN_ADV_COMPAT_LINUX_IF_BRIDGE_H_ ++ ++#include ++#include_next ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0) ++ ++struct br_ip { ++ union { ++ __be32 ip4; ++#if IS_ENABLED(CONFIG_IPV6) ++ struct in6_addr ip6; ++#endif ++ } u; ++ __be16 proto; ++ __u16 vid; ++}; ++ ++struct br_ip_list { ++ struct list_head list; ++ struct br_ip addr; ++}; ++ ++#endif /* < KERNEL_VERSION(3, 16, 0) */ ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0) || \ ++ LINUX_VERSION_CODE == KERNEL_VERSION(3, 16, 0) && \ ++ (!IS_ENABLED(CONFIG_BRIDGE) || \ ++ !IS_ENABLED(CONFIG_BRIDGE_IGMP_SNOOPING)) ++ ++#define br_multicast_list_adjacent(dev, br_ip_list) \ ++ batadv_br_multicast_list_adjacent(dev, br_ip_list) ++ ++#define br_multicast_has_querier_adjacent(dev, proto) \ ++ batadv_br_multicast_has_querier_adjacent(dev, proto) ++ ++static inline int batadv_br_multicast_list_adjacent(struct net_device *dev, ++ struct list_head *br_ip_list) ++{ ++ return 0; ++} ++ ++static inline bool batadv_br_multicast_has_querier_adjacent(struct net_device *dev, int proto) ++{ ++ return false; ++} ++ ++#endif /* < KERNEL_VERSION(3, 16, 0) || ++ * == KERNEL_VERSION(3, 16, 0) && ++ * (!IS_ENABLED(CONFIG_BRIDGE) || ++ * !IS_ENABLED(CONFIG_BRIDGE_IGMP_SNOOPING)) */ ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) ++ ++static inline bool br_multicast_has_querier_anywhere(struct net_device *dev, int proto) ++{ ++ pr_warn_once("Old kernel detected (< 3.17) - multicast optimizations disabled\n"); ++ ++ return false; ++} ++ ++#endif /* < KERNEL_VERSION(3, 17, 0) */ ++ ++#endif /* _NET_BATMAN_ADV_COMPAT_LINUX_IF_BRIDGE_H_ */ +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 ++#include_next ++ ++#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 96b018c..f4ae619 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 ++#include_next ++ ++#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 ++#include_next ++ ++#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 ++#include_next ++ ++#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-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..801c6cf +--- /dev/null ++++ b/compat-sources/net/core/skbuff.c +@@ -0,0 +1,214 @@ ++/* ++ * Routines having to do with the 'struct sk_buff' memory handlers. ++ * ++ * Authors: Alan Cox ++ * Florian La Roche ++ * ++ * Fixes: ++ * Alan Cox : Fixed the worst of the load ++ * balancer bugs. ++ * Dave Platt : Interrupt stacking fix. ++ * Richard Kooijman : Timestamp fixes. ++ * Alan Cox : Changed buffer format. ++ * Alan Cox : destructor hook for AF_UNIX etc. ++ * Linus Torvalds : Better skb_clone. ++ * Alan Cox : Added skb_copy. ++ * Alan Cox : Added all the changed routines Linus ++ * only put in the headers ++ * Ray VanTassle : Fixed --skb->lock in free ++ * Alan Cox : skb_copy copy arp field ++ * Andi Kleen : slabified it. ++ * Robert Olsson : Removed skb_head_pool ++ * ++ * NOTE: ++ * The __skb_ routines should be called with interrupts ++ * disabled, or you better be *real* sure that the operation is atomic ++ * with respect to whatever list is being frobbed (e.g. via lock_sock() ++ * or via disabling bottom half handlers, etc). ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#include ++#include ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0) ++ ++/* Compare with: ++ * "bridge: multicast: call skb_checksum_{simple_, }validate" ++ */ ++__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; ++} ++ ++/* Watch out: Not as generic as upstream ++ * - redefines this method to only fit with ICMPV6 ++ * ++ * Compare with: ++ * "bridge: multicast: call skb_checksum_{simple_, }validate" ++ */ ++__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) ++ ++static inline void skb_postpush_rcsum(struct sk_buff *skb, ++ const void *start, unsigned int len) ++{ ++ /* For performing the reverse operation to skb_postpull_rcsum(), ++ * we can instead of ... ++ * ++ * skb->csum = csum_add(skb->csum, csum_partial(start, len, 0)); ++ * ++ * ... just use this equivalent version here to save a few ++ * instructions. Feeding csum of 0 in csum_partial() and later ++ * on adding skb->csum is equivalent to feed skb->csum in the ++ * first place. ++ */ ++ if (skb->ip_summed == CHECKSUM_COMPLETE) ++ skb->csum = csum_partial(start, len, skb->csum); ++} ++ ++/** ++ * skb_push_rcsum - push skb and update receive checksum ++ * @skb: buffer to update ++ * @len: length of data pulled ++ * ++ * This function performs an skb_push on the packet and updates ++ * the CHECKSUM_COMPLETE checksum. It should be used on ++ * receive path processing instead of skb_push unless you know ++ * that the checksum difference is zero (e.g., a valid IP header) ++ * or you are setting ip_summed to CHECKSUM_NONE. ++ */ ++static unsigned char *skb_push_rcsum(struct sk_buff *skb, unsigned len) ++{ ++ skb_push(skb, len); ++ skb_postpush_rcsum(skb, skb->data, len); ++ return skb->data; ++} ++ ++/** ++ * 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_rcsum(skb_chk, offset); ++ ret = skb_chkf(skb_chk); ++ skb_push_rcsum(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..457a05e +--- /dev/null ++++ b/compat-sources/net/ipv4/igmp.c +@@ -0,0 +1,241 @@ ++/* ++ * Linux NET3: Internet Group Management Protocol [IGMP] ++ * ++ * This code implements the IGMP protocol as defined in RFC1112. There has ++ * been a further revision of this protocol since which is now supported. ++ * ++ * If you have trouble with this module be careful what gcc you have used, ++ * the older version didn't come out right using gcc 2.5.8, the newer one ++ * seems to fall out with gcc 2.6.2. ++ * ++ * Authors: ++ * Alan Cox ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ * ++ * Fixes: ++ * ++ * Alan Cox : Added lots of __inline__ to optimise ++ * the memory usage of all the tiny little ++ * functions. ++ * Alan Cox : Dumped the header building experiment. ++ * Alan Cox : Minor tweaks ready for multicast routing ++ * and extended IGMP protocol. ++ * Alan Cox : Removed a load of inline directives. Gcc 2.5.8 ++ * writes utterly bogus code otherwise (sigh) ++ * fixed IGMP loopback to behave in the manner ++ * desired by mrouted, fixed the fact it has been ++ * broken since 1.3.6 and cleaned up a few minor ++ * points. ++ * ++ * Chih-Jen Chang : Tried to revise IGMP to Version 2 ++ * Tsu-Sheng Tsao E-mail: chihjenc@scf.usc.edu and tsusheng@scf.usc.edu ++ * The enhancements are mainly based on Steve Deering's ++ * ipmulti-3.5 source code. ++ * Chih-Jen Chang : Added the igmp_get_mrouter_info and ++ * Tsu-Sheng Tsao igmp_set_mrouter_info to keep track of ++ * the mrouted version on that device. ++ * Chih-Jen Chang : Added the max_resp_time parameter to ++ * Tsu-Sheng Tsao igmp_heard_query(). Using this parameter ++ * to identify the multicast router version ++ * and do what the IGMP version 2 specified. ++ * Chih-Jen Chang : Added a timer to revert to IGMP V2 router ++ * Tsu-Sheng Tsao if the specified time expired. ++ * Alan Cox : Stop IGMP from 0.0.0.0 being accepted. ++ * Alan Cox : Use GFP_ATOMIC in the right places. ++ * Christian Daudt : igmp timer wasn't set for local group ++ * memberships but was being deleted, ++ * which caused a "del_timer() called ++ * from %p with timer not initialized\n" ++ * message (960131). ++ * Christian Daudt : removed del_timer from ++ * igmp_timer_expire function (960205). ++ * Christian Daudt : igmp_heard_report now only calls ++ * igmp_timer_expire if tm->running is ++ * true (960216). ++ * Malcolm Beattie : ttl comparison wrong in igmp_rcv made ++ * igmp_heard_query never trigger. Expiry ++ * miscalculation fixed in igmp_heard_query ++ * and random() made to return unsigned to ++ * prevent negative expiry times. ++ * Alexey Kuznetsov: Wrong group leaving behaviour, backport ++ * fix from pending 2.1.x patches. ++ * Alan Cox: Forget to enable FDDI support earlier. ++ * Alexey Kuznetsov: Fixed leaving groups on device down. ++ * Alexey Kuznetsov: Accordance to igmp-v2-06 draft. ++ * David L Stevens: IGMPv3 support, with help from ++ * Vinay Kulkarni ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) ++ ++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..3f46ed3 +--- /dev/null ++++ b/compat-sources/net/ipv6/mcast_snoop.c +@@ -0,0 +1,216 @@ ++/* Copyright (C) 2010: YOSHIFUJI Hideaki ++ * Copyright (C) 2015: Linus Lüssing ++ * ++ * 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 . ++ * ++ * ++ * Based on the MLD support added to br_multicast.c by YOSHIFUJI Hideaki. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) ++ ++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) */ +diff --git a/net/batman-adv/debugfs.c b/net/batman-adv/debugfs.c +index 48253cf..9bf37bf 100644 +--- a/net/batman-adv/debugfs.c ++++ b/net/batman-adv/debugfs.c +@@ -48,6 +48,7 @@ + #include "distributed-arp-table.h" + #include "gateway_client.h" + #include "icmp_socket.h" ++#include "multicast.h" + #include "network-coding.h" + #include "originator.h" + #include "translation-table.h" +@@ -363,6 +364,20 @@ static int batadv_nc_nodes_open(struct inode *inode, struct file *file) + } + #endif + ++#ifdef CONFIG_BATMAN_ADV_MCAST ++/** ++ * batadv_mcast_flags_open - prepare file handler for reads from mcast_flags ++ * @inode: inode which was opened ++ * @file: file handle to be initialized ++ */ ++static int batadv_mcast_flags_open(struct inode *inode, struct file *file) ++{ ++ struct net_device *net_dev = (struct net_device *)inode->i_private; ++ ++ return single_open(file, batadv_mcast_flags_seq_print_text, net_dev); ++} ++#endif ++ + #define BATADV_DEBUGINFO(_name, _mode, _open) \ + struct batadv_debuginfo batadv_debuginfo_##_name = { \ + .attr = { .name = __stringify(_name), \ +@@ -404,6 +419,9 @@ static BATADV_DEBUGINFO(transtable_local, S_IRUGO, + #ifdef CONFIG_BATMAN_ADV_NC + static BATADV_DEBUGINFO(nc_nodes, S_IRUGO, batadv_nc_nodes_open); + #endif ++#ifdef CONFIG_BATMAN_ADV_MCAST ++static BATADV_DEBUGINFO(mcast_flags, S_IRUGO, batadv_mcast_flags_open); ++#endif + + static struct batadv_debuginfo *batadv_mesh_debuginfos[] = { + &batadv_debuginfo_neighbors, +@@ -421,6 +439,9 @@ static struct batadv_debuginfo *batadv_mesh_debuginfos[] = { + #ifdef CONFIG_BATMAN_ADV_NC + &batadv_debuginfo_nc_nodes, + #endif ++#ifdef CONFIG_BATMAN_ADV_MCAST ++ &batadv_debuginfo_mcast_flags, ++#endif + NULL, + }; + +diff --git a/net/batman-adv/main.h b/net/batman-adv/main.h +index db45336..c4572b3 100644 +--- a/net/batman-adv/main.h ++++ b/net/batman-adv/main.h +@@ -227,6 +227,7 @@ __be32 batadv_skb_crc32(struct sk_buff *skb, u8 *payload_ptr); + * @BATADV_DBG_BLA: bridge loop avoidance messages + * @BATADV_DBG_DAT: ARP snooping and DAT related messages + * @BATADV_DBG_NC: network coding related messages ++ * @BATADV_DBG_MCAST: multicast related messages + * @BATADV_DBG_ALL: the union of all the above log levels + */ + enum batadv_dbg_level { +@@ -236,7 +237,8 @@ enum batadv_dbg_level { + BATADV_DBG_BLA = BIT(3), + BATADV_DBG_DAT = BIT(4), + BATADV_DBG_NC = BIT(5), +- BATADV_DBG_ALL = 63, ++ BATADV_DBG_MCAST = BIT(6), ++ BATADV_DBG_ALL = 127, + }; + + #ifdef CONFIG_BATMAN_ADV_DEBUG +diff --git a/net/batman-adv/multicast.c b/net/batman-adv/multicast.c +index 8caa2c7..0d732d7 100644 +--- a/net/batman-adv/multicast.c ++++ b/net/batman-adv/multicast.c +@@ -25,7 +25,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -43,18 +46,55 @@ + #include + #include + #include ++#include + #include ++#include + ++#include "hard-interface.h" ++#include "hash.h" + #include "packet.h" + #include "translation-table.h" + + /** ++ * batadv_mcast_get_bridge - get the bridge on top of the softif if it exists ++ * @soft_iface: netdev struct of the mesh interface ++ * ++ * If the given soft interface has a bridge on top then the refcount ++ * of the according net device is increased. ++ * ++ * Return: NULL if no such bridge exists. Otherwise the net device of the ++ * bridge. ++ */ ++static struct net_device *batadv_mcast_get_bridge(struct net_device *soft_iface) ++{ ++ struct net_device *upper = soft_iface; ++ ++ rcu_read_lock(); ++ do { ++ upper = netdev_master_upper_dev_get_rcu(upper); ++ } while (upper && !(upper->priv_flags & IFF_EBRIDGE)); ++ ++ if (upper) ++ dev_hold(upper); ++ rcu_read_unlock(); ++ ++ return upper; ++} ++ ++/** + * batadv_mcast_mla_softif_get - get softif multicast listeners + * @dev: the device to collect multicast addresses from + * @mcast_list: a list to put found addresses into + * +- * Collect multicast addresses of the local multicast listeners +- * on the given soft interface, dev, in the given mcast_list. ++ * Collects multicast addresses of multicast listeners residing ++ * on this kernel on the given soft interface, dev, in ++ * the given mcast_list. In general, multicast listeners provided by ++ * your multicast receiving applications run directly on this node. ++ * ++ * If there is a bridge interface on top of dev, collects from that one ++ * instead. Just like with IP addresses and routes, multicast listeners ++ * will(/should) register to the bridge interface instead of an ++ * enslaved bat0. + * + * Return: -ENOMEM on memory allocation error or the number of + * items added to the mcast_list otherwise. +@@ -62,12 +102,13 @@ + static int batadv_mcast_mla_softif_get(struct net_device *dev, + struct hlist_head *mcast_list) + { ++ struct net_device *bridge = batadv_mcast_get_bridge(dev); + struct netdev_hw_addr *mc_list_entry; + struct batadv_hw_addr *new; + int ret = 0; + +- netif_addr_lock_bh(dev); +- netdev_for_each_mc_addr(mc_list_entry, dev) { ++ netif_addr_lock_bh(bridge ? bridge : dev); ++ netdev_for_each_mc_addr(mc_list_entry, bridge ? bridge : dev) { + new = kmalloc(sizeof(*new), GFP_ATOMIC); + if (!new) { + ret = -ENOMEM; +@@ -78,7 +119,10 @@ static int batadv_mcast_mla_softif_get(struct net_device *dev, + hlist_add_head(&new->list, mcast_list); + ret++; + } +- netif_addr_unlock_bh(dev); ++ netif_addr_unlock_bh(bridge ? bridge : dev); ++ ++ if (bridge) ++ dev_put(bridge); + + return ret; + } +@@ -104,6 +148,83 @@ static bool batadv_mcast_mla_is_duplicate(u8 *mcast_addr, + } + + /** ++ * batadv_mcast_mla_br_addr_cpy - copy a bridge multicast address ++ * @dst: destination to write to - a multicast MAC address ++ * @src: source to read from - a multicast IP address ++ * ++ * Converts a given multicast IPv4/IPv6 address from a bridge ++ * to its matching multicast MAC address and copies it into the given ++ * destination buffer. ++ * ++ * Caller needs to make sure the destination buffer can hold ++ * at least ETH_ALEN bytes. ++ */ ++static void batadv_mcast_mla_br_addr_cpy(char *dst, const struct br_ip *src) ++{ ++ if (src->proto == htons(ETH_P_IP)) ++ ip_eth_mc_map(src->u.ip4, dst); ++#if IS_ENABLED(CONFIG_IPV6) ++ else if (src->proto == htons(ETH_P_IPV6)) ++ ipv6_eth_mc_map(&src->u.ip6, dst); ++#endif ++ else ++ eth_zero_addr(dst); ++} ++ ++/** ++ * batadv_mcast_mla_bridge_get - get bridged-in multicast listeners ++ * @dev: a bridge slave whose bridge to collect multicast addresses from ++ * @mcast_list: a list to put found addresses into ++ * ++ * Collects multicast addresses of multicast listeners residing ++ * on foreign, non-mesh devices which we gave access to our mesh via ++ * a bridge on top of the given soft interface, dev, in the given ++ * mcast_list. ++ * ++ * Return: -ENOMEM on memory allocation error or the number of ++ * items added to the mcast_list otherwise. ++ */ ++static int batadv_mcast_mla_bridge_get(struct net_device *dev, ++ struct hlist_head *mcast_list) ++{ ++ struct list_head bridge_mcast_list = LIST_HEAD_INIT(bridge_mcast_list); ++ struct br_ip_list *br_ip_entry, *tmp; ++ struct batadv_hw_addr *new; ++ u8 mcast_addr[ETH_ALEN]; ++ int ret; ++ ++ /* we don't need to detect these devices/listeners, the IGMP/MLD ++ * snooping code of the Linux bridge already does that for us ++ */ ++ ret = br_multicast_list_adjacent(dev, &bridge_mcast_list); ++ if (ret < 0) ++ goto out; ++ ++ list_for_each_entry(br_ip_entry, &bridge_mcast_list, list) { ++ batadv_mcast_mla_br_addr_cpy(mcast_addr, &br_ip_entry->addr); ++ if (batadv_mcast_mla_is_duplicate(mcast_addr, mcast_list)) ++ continue; ++ ++ new = kmalloc(sizeof(*new), GFP_ATOMIC); ++ if (!new) { ++ ret = -ENOMEM; ++ break; ++ } ++ ++ ether_addr_copy(new->addr, mcast_addr); ++ hlist_add_head(&new->list, mcast_list); ++ } ++ ++out: ++ list_for_each_entry_safe(br_ip_entry, tmp, &bridge_mcast_list, list) { ++ list_del(&br_ip_entry->list); ++ kfree(br_ip_entry); ++ } ++ ++ return ret; ++} ++ ++/** + * batadv_mcast_mla_list_free - free a list of multicast addresses + * @bat_priv: the bat priv with all the soft interface information + * @mcast_list: the list to free +@@ -214,44 +335,195 @@ static bool batadv_mcast_has_bridge(struct batadv_priv *bat_priv) + } + + /** ++ * batadv_mcast_querier_log - debug output regarding the querier status on link ++ * @bat_priv: the bat priv with all the soft interface information ++ * @str_proto: a string for the querier protocol (e.g. "IGMP" or "MLD") ++ * @old_state: the previous querier state on our link ++ * @new_state: the new querier state on our link ++ * ++ * Outputs debug messages to the logging facility with log level 'mcast' ++ * regarding changes to the querier status on the link which are relevant ++ * to our multicast optimizations. ++ * ++ * Usually this is about whether a querier appeared or vanished in ++ * our mesh or whether the querier is in the suboptimal position of being ++ * behind our local bridge segment: Snooping switches will directly ++ * forward listener reports to the querier, therefore batman-adv and ++ * the bridge will potentially not see these listeners - the querier is ++ * potentially shadowing listeners from us then. ++ * ++ * This is only interesting for nodes with a bridge on top of their ++ * soft interface. ++ */ ++static void ++batadv_mcast_querier_log(struct batadv_priv *bat_priv, char *str_proto, ++ struct batadv_mcast_querier_state *old_state, ++ struct batadv_mcast_querier_state *new_state) ++{ ++ if (!old_state->exists && new_state->exists) ++ batadv_info(bat_priv->soft_iface, "%s Querier appeared\n", ++ str_proto); ++ else if (old_state->exists && !new_state->exists) ++ batadv_info(bat_priv->soft_iface, ++ "%s Querier disappeared - multicast optimizations disabled\n", ++ str_proto); ++ else if (!bat_priv->mcast.bridged && !new_state->exists) ++ batadv_info(bat_priv->soft_iface, ++ "No %s Querier present - multicast optimizations disabled\n", ++ str_proto); ++ ++ if (new_state->exists) { ++ if ((!old_state->shadowing && new_state->shadowing) || ++ (!old_state->exists && new_state->shadowing)) ++ batadv_dbg(BATADV_DBG_MCAST, bat_priv, ++ "%s Querier is behind our bridged segment: Might shadow listeners\n", ++ str_proto); ++ else if (old_state->shadowing && !new_state->shadowing) ++ batadv_dbg(BATADV_DBG_MCAST, bat_priv, ++ "%s Querier is not behind our bridged segment\n", ++ str_proto); ++ } ++} ++ ++/** ++ * batadv_mcast_bridge_log - debug output for topology changes in bridged setups ++ * @bat_priv: the bat priv with all the soft interface information ++ * @bridged: a flag about whether the soft interface is currently bridged or not ++ * @querier_ipv4: (maybe) new status of a potential, selected IGMP querier ++ * @querier_ipv6: (maybe) new status of a potential, selected MLD querier ++ * ++ * If no bridges are ever used on this node, then this function does nothing. ++ * ++ * Otherwise this function outputs debug information to the 'mcast' log level ++ * which might be relevant to our multicast optimizations. ++ * ++ * More precisely, it outputs information when a bridge interface is added or ++ * removed from a soft interface. And when a bridge is present, it further ++ * outputs information about the querier state which is relevant for the ++ * multicast flags this node is going to set. ++ */ ++static void ++batadv_mcast_bridge_log(struct batadv_priv *bat_priv, bool bridged, ++ struct batadv_mcast_querier_state *querier_ipv4, ++ struct batadv_mcast_querier_state *querier_ipv6) ++{ ++ if (!bat_priv->mcast.bridged && bridged) ++ batadv_dbg(BATADV_DBG_MCAST, bat_priv, ++ "Bridge added: Setting Unsnoopables(U)-flag\n"); ++ else if (bat_priv->mcast.bridged && !bridged) ++ batadv_dbg(BATADV_DBG_MCAST, bat_priv, ++ "Bridge removed: Unsetting Unsnoopables(U)-flag\n"); ++ ++ if (bridged) { ++ batadv_mcast_querier_log(bat_priv, "IGMP", ++ &bat_priv->mcast.querier_ipv4, ++ querier_ipv4); ++ batadv_mcast_querier_log(bat_priv, "MLD", ++ &bat_priv->mcast.querier_ipv6, ++ querier_ipv6); ++ } ++} ++ ++/** ++ * batadv_mcast_flags_logs - output debug information about mcast flag changes ++ * @bat_priv: the bat priv with all the soft interface information ++ * @mcast_flags: flags indicating the new multicast state ++ * ++ * Whenever the multicast flags this nodes announces changes (@mcast_flags vs. ++ * bat_priv->mcast.flags), this notifies userspace via the 'mcast' log level. ++ */ ++static void batadv_mcast_flags_log(struct batadv_priv *bat_priv, u8 flags) ++{ ++ u8 old_flags = bat_priv->mcast.flags; ++ char str_old_flags[] = "[...]"; ++ ++ sprintf(str_old_flags, "[%c%c%c]", ++ old_flags & BATADV_MCAST_WANT_ALL_UNSNOOPABLES ? 'U' : '.', ++ old_flags & BATADV_MCAST_WANT_ALL_IPV4 ? '4' : '.', ++ old_flags & BATADV_MCAST_WANT_ALL_IPV6 ? '6' : '.'); ++ ++ batadv_dbg(BATADV_DBG_MCAST, bat_priv, ++ "Changing multicast flags from '%s' to '[%c%c%c]'\n", ++ bat_priv->mcast.enabled ? str_old_flags : "", ++ flags & BATADV_MCAST_WANT_ALL_UNSNOOPABLES ? 'U' : '.', ++ flags & BATADV_MCAST_WANT_ALL_IPV4 ? '4' : '.', ++ flags & BATADV_MCAST_WANT_ALL_IPV6 ? '6' : '.'); ++} ++ ++/** + * batadv_mcast_mla_tvlv_update - update multicast tvlv + * @bat_priv: the bat priv with all the soft interface information + * + * Updates the own multicast tvlv with our current multicast related settings, + * capabilities and inabilities. + * +- * Return: true if the tvlv container is registered afterwards. Otherwise +- * returns false. ++ * Return: false if we want all IPv4 && IPv6 multicast traffic and true ++ * otherwise. + */ + static bool batadv_mcast_mla_tvlv_update(struct batadv_priv *bat_priv) + { + struct batadv_tvlv_mcast_data mcast_data; ++ struct batadv_mcast_querier_state querier4 = {false, false}; ++ struct batadv_mcast_querier_state querier6 = {false, false}; ++ struct net_device *dev = bat_priv->soft_iface; ++ bool bridged; + + mcast_data.flags = BATADV_NO_FLAGS; + memset(mcast_data.reserved, 0, sizeof(mcast_data.reserved)); + +- /* Avoid attaching MLAs, if there is a bridge on top of our soft +- * interface, we don't support that yet (TODO) ++ bridged = batadv_mcast_has_bridge(bat_priv); ++ if (!bridged) ++ goto update; ++ ++#if !IS_ENABLED(CONFIG_BRIDGE_IGMP_SNOOPING) ++ pr_warn_once("No bridge IGMP snooping compiled - multicast optimizations disabled\n"); ++#endif ++ ++ querier4.exists = br_multicast_has_querier_anywhere(dev, ETH_P_IP); ++ querier4.shadowing = br_multicast_has_querier_adjacent(dev, ETH_P_IP); ++ ++ querier6.exists = br_multicast_has_querier_anywhere(dev, ETH_P_IPV6); ++ querier6.shadowing = br_multicast_has_querier_adjacent(dev, ETH_P_IPV6); ++ ++ mcast_data.flags |= BATADV_MCAST_WANT_ALL_UNSNOOPABLES; ++ ++ /* 1) If no querier exists at all, then multicast listeners on ++ * our local TT clients behind the bridge will keep silent. ++ * 2) If the selected querier is on one of our local TT clients, ++ * behind the bridge, then this querier might shadow multicast ++ * listeners on our local TT clients, behind this bridge. ++ * ++ * In both cases, we will signalize other batman nodes that ++ * we need all multicast traffic of the according protocol. + */ +- if (batadv_mcast_has_bridge(bat_priv)) { +- if (bat_priv->mcast.enabled) { +- batadv_tvlv_container_unregister(bat_priv, +- BATADV_TVLV_MCAST, 1); +- bat_priv->mcast.enabled = false; +- } ++ if (!querier4.exists || querier4.shadowing) ++ mcast_data.flags |= BATADV_MCAST_WANT_ALL_IPV4; + +- return false; +- } ++ if (!querier6.exists || querier6.shadowing) ++ mcast_data.flags |= BATADV_MCAST_WANT_ALL_IPV6; ++ ++update: ++ batadv_mcast_bridge_log(bat_priv, bridged, &querier4, &querier6); ++ ++ bat_priv->mcast.querier_ipv4.exists = querier4.exists; ++ bat_priv->mcast.querier_ipv4.shadowing = querier4.shadowing; ++ ++ bat_priv->mcast.querier_ipv6.exists = querier6.exists; ++ bat_priv->mcast.querier_ipv6.shadowing = querier6.shadowing; ++ ++ bat_priv->mcast.bridged = bridged; + + if (!bat_priv->mcast.enabled || + mcast_data.flags != bat_priv->mcast.flags) { +- batadv_tvlv_container_register(bat_priv, BATADV_TVLV_MCAST, 1, ++ batadv_mcast_flags_log(bat_priv, mcast_data.flags); ++ batadv_tvlv_container_register(bat_priv, BATADV_TVLV_MCAST, 2, + &mcast_data, sizeof(mcast_data)); + bat_priv->mcast.flags = mcast_data.flags; + bat_priv->mcast.enabled = true; + } + +- return true; ++ return !(mcast_data.flags & ++ (BATADV_MCAST_WANT_ALL_IPV4 + BATADV_MCAST_WANT_ALL_IPV6)); + } + + /** +@@ -274,6 +546,10 @@ void batadv_mcast_mla_update(struct batadv_priv *bat_priv) + if (ret < 0) + goto out; + ++ ret = batadv_mcast_mla_bridge_get(soft_iface, &mcast_list); ++ if (ret < 0) ++ goto out; ++ + update: + batadv_mcast_mla_tt_retract(bat_priv, &mcast_list); + batadv_mcast_mla_tt_add(bat_priv, &mcast_list); +@@ -283,6 +559,31 @@ out: + } + + /** ++ * batadv_mcast_is_report_ipv4 - check for IGMP reports ++ * @skb: the ethernet frame destined for the mesh ++ * ++ * This call might reallocate skb data. ++ * ++ * Checks whether the given frame is a valid IGMP report. ++ * ++ * Return: If so then true, otherwise false. ++ */ ++static bool batadv_mcast_is_report_ipv4(struct sk_buff *skb) ++{ ++ if (ip_mc_check_igmp(skb, NULL) < 0) ++ return false; ++ ++ switch (igmp_hdr(skb)->type) { ++ case IGMP_HOST_MEMBERSHIP_REPORT: ++ case IGMPV2_HOST_MEMBERSHIP_REPORT: ++ case IGMPV3_HOST_MEMBERSHIP_REPORT: ++ return true; ++ } ++ ++ return false; ++} ++ ++/** + * batadv_mcast_forw_mode_check_ipv4 - check for optimized forwarding potential + * @bat_priv: the bat priv with all the soft interface information + * @skb: the IPv4 packet to check +@@ -304,6 +605,9 @@ static int batadv_mcast_forw_mode_check_ipv4(struct batadv_priv *bat_priv, + if (!pskb_may_pull(skb, sizeof(struct ethhdr) + sizeof(*iphdr))) + return -ENOMEM; + ++ if (batadv_mcast_is_report_ipv4(skb)) ++ return -EINVAL; ++ + iphdr = ip_hdr(skb); + + /* TODO: Implement Multicast Router Discovery (RFC4286), +@@ -320,6 +624,31 @@ static int batadv_mcast_forw_mode_check_ipv4(struct batadv_priv *bat_priv, + return 0; + } + ++#if IS_ENABLED(CONFIG_IPV6) ++/** ++ * batadv_mcast_is_report_ipv6 - check for MLD reports ++ * @skb: the ethernet frame destined for the mesh ++ * ++ * This call might reallocate skb data. ++ * ++ * Checks whether the given frame is a valid MLD report. ++ * ++ * Return: If so then true, otherwise false. ++ */ ++static bool batadv_mcast_is_report_ipv6(struct sk_buff *skb) ++{ ++ if (ipv6_mc_check_mld(skb, NULL) < 0) ++ return false; ++ ++ switch (icmp6_hdr(skb)->icmp6_type) { ++ case ICMPV6_MGM_REPORT: ++ case ICMPV6_MLD2_REPORT: ++ return true; ++ } ++ ++ return false; ++} ++ + /** + * batadv_mcast_forw_mode_check_ipv6 - check for optimized forwarding potential + * @bat_priv: the bat priv with all the soft interface information +@@ -341,6 +670,9 @@ static int batadv_mcast_forw_mode_check_ipv6(struct batadv_priv *bat_priv, + if (!pskb_may_pull(skb, sizeof(struct ethhdr) + sizeof(*ip6hdr))) + return -ENOMEM; + ++ if (batadv_mcast_is_report_ipv6(skb)) ++ return -EINVAL; ++ + ip6hdr = ipv6_hdr(skb); + + /* TODO: Implement Multicast Router Discovery (RFC4286), +@@ -357,6 +689,7 @@ static int batadv_mcast_forw_mode_check_ipv6(struct batadv_priv *bat_priv, + + return 0; + } ++#endif + + /** + * batadv_mcast_forw_mode_check - check for optimized forwarding potential +@@ -385,9 +718,11 @@ static int batadv_mcast_forw_mode_check(struct batadv_priv *bat_priv, + case ETH_P_IP: + return batadv_mcast_forw_mode_check_ipv4(bat_priv, skb, + is_unsnoopable); ++#if IS_ENABLED(CONFIG_IPV6) + case ETH_P_IPV6: + return batadv_mcast_forw_mode_check_ipv6(bat_priv, skb, + is_unsnoopable); ++#endif + default: + return -EINVAL; + } +@@ -727,18 +1062,18 @@ static void batadv_mcast_want_ipv6_update(struct batadv_priv *bat_priv, + } + + /** +- * batadv_mcast_tvlv_ogm_handler_v1 - process incoming multicast tvlv container ++ * batadv_mcast_tvlv_ogm_handler - process incoming multicast tvlv container + * @bat_priv: the bat priv with all the soft interface information + * @orig: the orig_node of the ogm + * @flags: flags indicating the tvlv state (see batadv_tvlv_handler_flags) + * @tvlv_value: tvlv buffer containing the multicast data + * @tvlv_value_len: tvlv buffer length + */ +-static void batadv_mcast_tvlv_ogm_handler_v1(struct batadv_priv *bat_priv, +- struct batadv_orig_node *orig, +- u8 flags, +- void *tvlv_value, +- u16 tvlv_value_len) ++static void batadv_mcast_tvlv_ogm_handler(struct batadv_priv *bat_priv, ++ struct batadv_orig_node *orig, ++ u8 flags, ++ void *tvlv_value, ++ u16 tvlv_value_len) + { + bool orig_mcast_enabled = !(flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND); + u8 mcast_flags = BATADV_NO_FLAGS; +@@ -788,19 +1123,118 @@ static void batadv_mcast_tvlv_ogm_handler_v1(struct batadv_priv *bat_priv, + */ + void batadv_mcast_init(struct batadv_priv *bat_priv) + { +- batadv_tvlv_handler_register(bat_priv, batadv_mcast_tvlv_ogm_handler_v1, +- NULL, BATADV_TVLV_MCAST, 1, ++ batadv_tvlv_handler_register(bat_priv, batadv_mcast_tvlv_ogm_handler, ++ NULL, BATADV_TVLV_MCAST, 2, + BATADV_TVLV_HANDLER_OGM_CIFNOTFND); + } + + /** ++ * batadv_mcast_flags_print_header - print own mcast flags to debugfs table ++ * @bat_priv: the bat priv with all the soft interface information ++ * @seq: debugfs table seq_file struct ++ * ++ * Prints our own multicast flags including a more specific reason why ++ * they are set, that is prints the bridge and querier state too, to ++ * the debugfs table specified via @seq. ++ */ ++static void batadv_mcast_flags_print_header(struct batadv_priv *bat_priv, ++ struct seq_file *seq) ++{ ++ u8 flags = bat_priv->mcast.flags; ++ char querier4, querier6, shadowing4, shadowing6; ++ bool bridged = bat_priv->mcast.bridged; ++ ++ if (bridged) { ++ querier4 = bat_priv->mcast.querier_ipv4.exists ? '.' : '4'; ++ querier6 = bat_priv->mcast.querier_ipv6.exists ? '.' : '6'; ++ shadowing4 = bat_priv->mcast.querier_ipv4.shadowing ? '4' : '.'; ++ shadowing6 = bat_priv->mcast.querier_ipv6.shadowing ? '6' : '.'; ++ } else { ++ querier4 = '?'; ++ querier6 = '?'; ++ shadowing4 = '?'; ++ shadowing6 = '?'; ++ } ++ ++ seq_printf(seq, "Multicast flags (own flags: [%c%c%c])\n", ++ flags & BATADV_MCAST_WANT_ALL_UNSNOOPABLES ? 'U' : '.', ++ flags & BATADV_MCAST_WANT_ALL_IPV4 ? '4' : '.', ++ flags & BATADV_MCAST_WANT_ALL_IPV6 ? '6' : '.'); ++ seq_printf(seq, "* Bridged [U]\t\t\t\t%c\n", bridged ? 'U' : '.'); ++ seq_printf(seq, "* No IGMP/MLD Querier [4/6]:\t\t%c/%c\n", ++ querier4, querier6); ++ seq_printf(seq, "* Shadowing IGMP/MLD Querier [4/6]:\t%c/%c\n", ++ shadowing4, shadowing6); ++ seq_puts(seq, "-------------------------------------------\n"); ++ seq_printf(seq, " %-10s %s\n", "Originator", "Flags"); ++} ++ ++/** ++ * batadv_mcast_flags_seq_print_text - print the mcast flags of other nodes ++ * @seq: seq file to print on ++ * @offset: not used ++ * ++ * This prints a table of (primary) originators and their according ++ * multicast flags, including (in the header) our own. ++ */ ++int batadv_mcast_flags_seq_print_text(struct seq_file *seq, void *offset) ++{ ++ struct net_device *net_dev = (struct net_device *)seq->private; ++ struct batadv_priv *bat_priv = netdev_priv(net_dev); ++ struct batadv_hard_iface *primary_if; ++ struct batadv_hashtable *hash = bat_priv->orig_hash; ++ struct batadv_orig_node *orig_node; ++ struct hlist_head *head; ++ u8 flags; ++ u32 i; ++ ++ primary_if = batadv_seq_print_text_primary_if_get(seq); ++ if (!primary_if) ++ return 0; ++ ++ batadv_mcast_flags_print_header(bat_priv, seq); ++ ++ for (i = 0; i < hash->size; i++) { ++ head = &hash->table[i]; ++ ++ rcu_read_lock(); ++ hlist_for_each_entry_rcu(orig_node, head, hash_entry) { ++ if (!(orig_node->capa_initialized & ++ BATADV_ORIG_CAPA_HAS_MCAST)) ++ continue; ++ ++ flags = orig_node->mcast_flags; ++ ++ if (!(orig_node->capabilities & ++ BATADV_ORIG_CAPA_HAS_MCAST)) { ++ seq_printf(seq, "%pM -\n", orig_node->orig); ++ continue; ++ } ++ ++ seq_printf(seq, "%pM [%c%c%c]\n", orig_node->orig, ++ (flags & BATADV_MCAST_WANT_ALL_UNSNOOPABLES ? ++ 'U' : '.'), ++ (flags & BATADV_MCAST_WANT_ALL_IPV4 ? ++ '4' : '.'), ++ (flags & BATADV_MCAST_WANT_ALL_IPV6 ? ++ '6' : '.')); ++ } ++ rcu_read_unlock(); ++ } ++ ++ batadv_hardif_put(primary_if); ++ ++ return 0; ++} ++ ++/** + * batadv_mcast_free - free the multicast optimizations structures + * @bat_priv: the bat priv with all the soft interface information + */ + void batadv_mcast_free(struct batadv_priv *bat_priv) + { +- batadv_tvlv_container_unregister(bat_priv, BATADV_TVLV_MCAST, 1); +- batadv_tvlv_handler_unregister(bat_priv, BATADV_TVLV_MCAST, 1); ++ batadv_tvlv_container_unregister(bat_priv, BATADV_TVLV_MCAST, 2); ++ batadv_tvlv_handler_unregister(bat_priv, BATADV_TVLV_MCAST, 2); + + spin_lock_bh(&bat_priv->tt.commit_lock); + batadv_mcast_mla_tt_retract(bat_priv, NULL); +diff --git a/net/batman-adv/multicast.h b/net/batman-adv/multicast.h +index 80bceec..df744c6 100644 +--- a/net/batman-adv/multicast.h ++++ b/net/batman-adv/multicast.h +@@ -46,6 +46,8 @@ batadv_mcast_forw_mode(struct batadv_priv *bat_priv, struct sk_buff *skb, + + void batadv_mcast_init(struct batadv_priv *bat_priv); + ++int batadv_mcast_flags_seq_print_text(struct seq_file *seq, void *offset); ++ + void batadv_mcast_free(struct batadv_priv *bat_priv); + + void batadv_mcast_purge_orig(struct batadv_orig_node *orig_node); +diff --git a/net/batman-adv/soft-interface.c b/net/batman-adv/soft-interface.c +index 8a136b6..1ab46f3 100644 +--- a/net/batman-adv/soft-interface.c ++++ b/net/batman-adv/soft-interface.c +@@ -795,6 +795,10 @@ static int batadv_softif_init_late(struct net_device *dev) + atomic_set(&bat_priv->distributed_arp_table, 1); + #endif + #ifdef CONFIG_BATMAN_ADV_MCAST ++ bat_priv->mcast.querier_ipv4.exists = false; ++ bat_priv->mcast.querier_ipv4.shadowing = false; ++ bat_priv->mcast.querier_ipv6.exists = false; ++ bat_priv->mcast.querier_ipv6.shadowing = false; + bat_priv->mcast.flags = BATADV_NO_FLAGS; + atomic_set(&bat_priv->multicast_mode, 1); + atomic_set(&bat_priv->mcast.num_disabled, 0); +diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h +index 1e47fbe..0ed0bee 100644 +--- a/net/batman-adv/types.h ++++ b/net/batman-adv/types.h +@@ -745,14 +745,28 @@ struct batadv_priv_dat { + + #ifdef CONFIG_BATMAN_ADV_MCAST + /** ++ * struct batadv_mcast_querier_state - IGMP/MLD querier state when bridged ++ * @exists: whether a querier exists in the mesh ++ * @shadowing: if a querier exists, whether it is potentially shadowing ++ * multicast listeners (i.e. querier is behind our own bridge segment) ++ */ ++struct batadv_mcast_querier_state { ++ bool exists; ++ bool shadowing; ++}; ++ ++/** + * struct batadv_priv_mcast - per mesh interface mcast data + * @mla_list: list of multicast addresses we are currently announcing via TT + * @want_all_unsnoopables_list: a list of orig_nodes wanting all unsnoopable + * multicast traffic + * @want_all_ipv4_list: a list of orig_nodes wanting all IPv4 multicast traffic + * @want_all_ipv6_list: a list of orig_nodes wanting all IPv6 multicast traffic ++ * @querier_ipv4: the current state of an IGMP querier in the mesh ++ * @querier_ipv6: the current state of an MLD querier in the mesh + * @flags: the flags we have last sent in our mcast tvlv + * @enabled: whether the multicast tvlv is currently enabled ++ * @bridged: whether the soft interface has a bridge on top + * @num_disabled: number of nodes that have no mcast tvlv + * @num_want_all_unsnoopables: number of nodes wanting unsnoopable IP traffic + * @num_want_all_ipv4: counter for items in want_all_ipv4_list +@@ -765,8 +779,11 @@ struct batadv_priv_mcast { + struct hlist_head want_all_unsnoopables_list; + struct hlist_head want_all_ipv4_list; + struct hlist_head want_all_ipv6_list; ++ struct batadv_mcast_querier_state querier_ipv4; ++ struct batadv_mcast_querier_state querier_ipv6; + u8 flags; + bool enabled; ++ bool bridged; + atomic_t num_disabled; + atomic_t num_want_all_unsnoopables; + atomic_t num_want_all_ipv4;