From: Linus Lüssing <linus.luessing@c0d3.blue>
Date: Tue, 30 Jun 2020 18:01:56 +0200
Subject: kernel: bridge: Implement MLD Querier wake-up calls / Android bug workaround

Implement a configurable MLD Querier wake-up calls "feature" which
works around a widely spread Android bug in connection with IGMP/MLD
snooping.

Currently there are mobile devices (e.g. Android) which are not able
to receive and respond to MLD Queries reliably because the Wifi driver
filters a lot of ICMPv6 when the device is asleep - including
MLD. This in turn breaks IPv6 communication when MLD Snooping is
enabled. However there is one ICMPv6 type which is allowed to pass and
which can be used to wake up the mobile device: ICMPv6 Echo Requests.

If this bridge is the selected MLD Querier then setting
"multicast_wakeupcall" to a number n greater than 0 will send n
ICMPv6 Echo Requests to each host behind this port to wake
them up with each MLD Query. Upon receiving a matching ICMPv6 Echo
Reply an MLD Query with a unicast ethernet destination will be sent
to the specific host(s).

Link: https://issuetracker.google.com/issues/149630944
Link: https://github.com/freifunk-gluon/gluon/issues/1832

Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>

diff --git a/package/network/config/netifd/patches/0001-bridge-Add-multicast_wakeupcall-option.patch b/package/network/config/netifd/patches/0001-bridge-Add-multicast_wakeupcall-option.patch
new file mode 100644
index 0000000000000000000000000000000000000000..7ae2960d51d78b03be368bbfd17aff9219da524d
--- /dev/null
+++ b/package/network/config/netifd/patches/0001-bridge-Add-multicast_wakeupcall-option.patch
@@ -0,0 +1,169 @@
+From 026c823dc34c34393498f10a489ea8773866e9e4 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Linus=20L=C3=BCssing?= <linus.luessing@c0d3.blue>
+Date: Sun, 5 Jul 2020 23:33:51 +0200
+Subject: [PATCH] bridge: Add multicast_wakeupcall option
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This makes the new per bridge port multicast_wakeupcall feature
+for the Linux bridge configurable for wireless interfaces and enables it
+by default for an AP interface.
+
+The MLD Querier wake-up calls "feature" works around a widely spread Android
+bug in connection with IGMP/MLD snooping.
+
+Currently there are mobile devices (e.g. Android) which are not able
+to receive and respond to MLD Queries reliably because the Wifi driver
+filters a lot of ICMPv6 when the device is asleep - including
+MLD. This in turn breaks IPv6 communication when MLD Snooping is
+enabled. However there is one ICMPv6 type which is allowed to pass and
+which can be used to wake up the mobile device: ICMPv6 Echo Requests.
+
+If this bridge is the selected MLD Querier then setting
+"multicast_wakeupcall" to a number n greater than 0 will send n
+ICMPv6 Echo Requests to each host behind this port to wake
+them up with each MLD Query. Upon receiving a matching ICMPv6 Echo
+Reply an MLD Query with a unicast ethernet destination will be sent
+to the specific host(s).
+
+Link: https://issuetracker.google.com/issues/149630944
+Link: https://github.com/freifunk-gluon/gluon/issues/1832
+
+Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
+---
+ device.c       |  9 +++++++++
+ device.h       | 21 ++++++++++++---------
+ system-linux.c | 13 +++++++++++++
+ 3 files changed, 34 insertions(+), 9 deletions(-)
+
+diff --git a/device.c b/device.c
+index 128151a..2e8c24c 100644
+--- a/device.c
++++ b/device.c
+@@ -50,6 +50,7 @@ static const struct blobmsg_policy dev_attrs[__DEV_ATTR_MAX] = {
+ 	[DEV_ATTR_NEIGHGCSTALETIME] = { .name = "neighgcstaletime", .type = BLOBMSG_TYPE_INT32 },
+ 	[DEV_ATTR_DADTRANSMITS] = { .name = "dadtransmits", .type = BLOBMSG_TYPE_INT32 },
+ 	[DEV_ATTR_MULTICAST_TO_UNICAST] = { .name = "multicast_to_unicast", .type = BLOBMSG_TYPE_BOOL },
++	[DEV_ATTR_MULTICAST_WAKEUPCALL] = { .name = "multicast_wakeupcall", .type = BLOBMSG_TYPE_INT32 },
+ 	[DEV_ATTR_MULTICAST_ROUTER] = { .name = "multicast_router", .type = BLOBMSG_TYPE_INT32 },
+ 	[DEV_ATTR_MULTICAST_FAST_LEAVE] = { .name = "multicast_fast_leave", . type = BLOBMSG_TYPE_BOOL },
+ 	[DEV_ATTR_MULTICAST] = { .name ="multicast", .type = BLOBMSG_TYPE_BOOL },
+@@ -223,6 +224,7 @@ device_merge_settings(struct device *dev, struct device_settings *n)
+ 	n->multicast = s->flags & DEV_OPT_MULTICAST ?
+ 		s->multicast : os->multicast;
+ 	n->multicast_to_unicast = s->multicast_to_unicast;
++	n->multicast_wakeupcall = s->multicast_wakeupcall;
+ 	n->multicast_router = s->multicast_router;
+ 	n->multicast_fast_leave = s->multicast_fast_leave;
+ 	n->learning = s->learning;
+@@ -330,6 +332,11 @@ device_init_settings(struct device *dev, struct blob_attr **tb)
+ 		s->flags |= DEV_OPT_MULTICAST_TO_UNICAST;
+ 	}
+ 
++	if ((cur = tb[DEV_ATTR_MULTICAST_WAKEUPCALL])) {
++		s->multicast_wakeupcall = blobmsg_get_u32(cur);
++		s->flags |= DEV_OPT_MULTICAST_WAKEUPCALL;
++	}
++
+ 	if ((cur = tb[DEV_ATTR_MULTICAST_ROUTER])) {
+ 		s->multicast_router = blobmsg_get_u32(cur);
+ 		if (s->multicast_router <= 2)
+@@ -1028,6 +1035,8 @@ device_dump_status(struct blob_buf *b, struct device *dev)
+ 			blobmsg_add_u32(b, "dadtransmits", st.dadtransmits);
+ 		if (st.flags & DEV_OPT_MULTICAST_TO_UNICAST)
+ 			blobmsg_add_u8(b, "multicast_to_unicast", st.multicast_to_unicast);
++		if (st.flags & DEV_OPT_MULTICAST_WAKEUPCALL)
++			blobmsg_add_u32(b, "multicast_wakeupcall", st.multicast_wakeupcall);
+ 		if (st.flags & DEV_OPT_MULTICAST_ROUTER)
+ 			blobmsg_add_u32(b, "multicast_router", st.multicast_router);
+ 		if (st.flags & DEV_OPT_MULTICAST_FAST_LEAVE)
+diff --git a/device.h b/device.h
+index 5f3fae2..4935db0 100644
+--- a/device.h
++++ b/device.h
+@@ -42,6 +42,7 @@ enum {
+ 	DEV_ATTR_NEIGHREACHABLETIME,
+ 	DEV_ATTR_DADTRANSMITS,
+ 	DEV_ATTR_MULTICAST_TO_UNICAST,
++	DEV_ATTR_MULTICAST_WAKEUPCALL,
+ 	DEV_ATTR_MULTICAST_ROUTER,
+ 	DEV_ATTR_MULTICAST_FAST_LEAVE,
+ 	DEV_ATTR_MULTICAST,
+@@ -95,15 +96,16 @@ enum {
+ 	DEV_OPT_MTU6			= (1 << 12),
+ 	DEV_OPT_DADTRANSMITS		= (1 << 13),
+ 	DEV_OPT_MULTICAST_TO_UNICAST	= (1 << 14),
+-	DEV_OPT_MULTICAST_ROUTER	= (1 << 15),
+-	DEV_OPT_MULTICAST		= (1 << 16),
+-	DEV_OPT_LEARNING		= (1 << 17),
+-	DEV_OPT_UNICAST_FLOOD		= (1 << 18),
+-	DEV_OPT_NEIGHGCSTALETIME	= (1 << 19),
+-	DEV_OPT_MULTICAST_FAST_LEAVE	= (1 << 20),
+-	DEV_OPT_SENDREDIRECTS		= (1 << 21),
+-	DEV_OPT_NEIGHLOCKTIME		= (1 << 22),
+-	DEV_OPT_ISOLATE			= (1 << 23),
++	DEV_OPT_MULTICAST_WAKEUPCALL	= (1 << 15),
++	DEV_OPT_MULTICAST_ROUTER	= (1 << 16),
++	DEV_OPT_MULTICAST		= (1 << 17),
++	DEV_OPT_LEARNING		= (1 << 18),
++	DEV_OPT_UNICAST_FLOOD		= (1 << 19),
++	DEV_OPT_NEIGHGCSTALETIME	= (1 << 20),
++	DEV_OPT_MULTICAST_FAST_LEAVE	= (1 << 21),
++	DEV_OPT_SENDREDIRECTS		= (1 << 22),
++	DEV_OPT_NEIGHLOCKTIME		= (1 << 23),
++	DEV_OPT_ISOLATE			= (1 << 24),
+ };
+ 
+ /* events broadcasted to all users of a device */
+@@ -164,6 +166,7 @@ struct device_settings {
+ 	int neigh4locktime;
+ 	unsigned int dadtransmits;
+ 	bool multicast_to_unicast;
++	unsigned int multicast_wakeupcall;
+ 	unsigned int multicast_router;
+ 	bool multicast_fast_leave;
+ 	bool multicast;
+diff --git a/system-linux.c b/system-linux.c
+index acfd40e..f1abccf 100644
+--- a/system-linux.c
++++ b/system-linux.c
+@@ -355,6 +355,11 @@ static void system_bridge_set_multicast_to_unicast(struct device *dev, const cha
+ 	system_set_dev_sysctl("/sys/class/net/%s/brport/multicast_to_unicast", dev->ifname, val);
+ }
+ 
++static void system_bridge_set_multicast_wakeupcall(struct device *dev, const char *val)
++{
++	system_set_dev_sysctl("/sys/class/net/%s/brport/multicast_wakeupcall", dev->ifname, val);
++}
++
+ static void system_bridge_set_multicast_fast_leave(struct device *dev, const char *val)
+ {
+ 	system_set_dev_sysctl("/sys/class/net/%s/brport/multicast_fast_leave", dev->ifname, val);
+@@ -784,8 +789,10 @@ static char *system_get_bridge(const char *name, char *buf, int buflen)
+ static void
+ system_bridge_set_wireless(struct device *bridge, struct device *dev)
+ {
++	unsigned int mcast_wakeupcall = dev->wireless_ap ? 2 : 0;
+ 	bool mcast_to_ucast = dev->wireless_ap;
+ 	bool hairpin = true;
++	char buf[64];
+ 
+ 	if (bridge->settings.flags & DEV_OPT_MULTICAST_TO_UNICAST &&
+ 	    !bridge->settings.multicast_to_unicast)
+@@ -796,6 +803,12 @@ system_bridge_set_wireless(struct device *bridge, struct device *dev)
+ 
+ 	system_bridge_set_multicast_to_unicast(dev, mcast_to_ucast ? "1" : "0");
+ 	system_bridge_set_hairpin_mode(dev, hairpin ? "1" : "0");
++
++	if (bridge->settings.flags & DEV_OPT_MULTICAST_WAKEUPCALL)
++		mcast_wakeupcall = dev->settings.multicast_wakeupcall;
++
++	snprintf(buf, sizeof(buf), "%u", mcast_wakeupcall);
++	system_bridge_set_multicast_wakeupcall(dev, buf);
+ }
+ 
+ int system_bridge_addif(struct device *bridge, struct device *dev)
+-- 
+2.27.0
+
diff --git a/target/linux/generic/backport-4.14/111-bridge-simplify-ip_mc_check_igmp-and-ipv6_mc_check_m.patch b/target/linux/generic/backport-4.14/111-bridge-simplify-ip_mc_check_igmp-and-ipv6_mc_check_m.patch
new file mode 100644
index 0000000000000000000000000000000000000000..e8f60cc4cc267d3bb1c38770b7e1447765514f2b
--- /dev/null
+++ b/target/linux/generic/backport-4.14/111-bridge-simplify-ip_mc_check_igmp-and-ipv6_mc_check_m.patch
@@ -0,0 +1,506 @@
+From 2a0f1172f505121c75313f6cce2874476b82ce45 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Linus=20L=C3=BCssing?= <linus.luessing@c0d3.blue>
+Date: Mon, 21 Jan 2019 07:26:25 +0100
+Subject: [PATCH] bridge: simplify ip_mc_check_igmp() and
+ ipv6_mc_check_mld() calls
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This patch refactors ip_mc_check_igmp(), ipv6_mc_check_mld() and
+their callers (more precisely, the Linux bridge) to not rely on
+the skb_trimmed parameter anymore.
+
+An skb with its tail trimmed to the IP packet length was initially
+introduced for the following three reasons:
+
+1) To be able to verify the ICMPv6 checksum.
+2) To be able to distinguish the version of an IGMP or MLD query.
+   They are distinguishable only by their size.
+3) To avoid parsing data for an IGMPv3 or MLDv2 report that is
+   beyond the IP packet but still within the skb.
+
+The first case still uses a cloned and potentially trimmed skb to
+verfiy. However, there is no need to propagate it to the caller.
+For the second and third case explicit IP packet length checks were
+added.
+
+This hopefully makes ip_mc_check_igmp() and ipv6_mc_check_mld() easier
+to read and verfiy, as well as easier to use.
+
+Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/igmp.h       | 11 +++++++-
+ include/linux/ip.h         |  5 ++++
+ include/linux/ipv6.h       |  6 ++++
+ include/net/addrconf.h     | 12 +++++++-
+ net/batman-adv/multicast.c |  4 +--
+ net/bridge/br_multicast.c  | 56 ++++++++++++++++++--------------------
+ net/ipv4/igmp.c            | 23 +++-------------
+ net/ipv6/mcast_snoop.c     | 24 +++-------------
+ 8 files changed, 69 insertions(+), 72 deletions(-)
+
+diff --git a/include/linux/igmp.h b/include/linux/igmp.h
+index f8231854b5d6..858143489a2a 100644
+--- a/include/linux/igmp.h
++++ b/include/linux/igmp.h
+@@ -18,6 +18,7 @@
+ #include <linux/skbuff.h>
+ #include <linux/timer.h>
+ #include <linux/in.h>
++#include <linux/ip.h>
+ #include <linux/refcount.h>
+ #include <uapi/linux/igmp.h>
+ 
+@@ -106,6 +107,14 @@ struct ip_mc_list {
+ #define IGMPV3_QQIC(value) IGMPV3_EXP(0x80, 4, 3, value)
+ #define IGMPV3_MRC(value) IGMPV3_EXP(0x80, 4, 3, value)
+ 
++static inline int ip_mc_may_pull(struct sk_buff *skb, unsigned int len)
++{
++	if (skb_transport_offset(skb) + ip_transport_len(skb) < len)
++		return -EINVAL;
++
++	return pskb_may_pull(skb, len);
++}
++
+ extern int ip_check_mc_rcu(struct in_device *dev, __be32 mc_addr, __be32 src_addr, u8 proto);
+ extern int igmp_rcv(struct sk_buff *);
+ extern int ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr);
+@@ -128,6 +137,6 @@ extern void ip_mc_unmap(struct in_device *);
+ extern void ip_mc_remap(struct in_device *);
+ extern void ip_mc_dec_group(struct in_device *in_dev, __be32 addr);
+ extern void ip_mc_inc_group(struct in_device *in_dev, __be32 addr);
+-int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed);
++int ip_mc_check_igmp(struct sk_buff *skb);
+ 
+ #endif
+diff --git a/include/linux/ip.h b/include/linux/ip.h
+index 492bc6513533..482b7b7c9f30 100644
+--- a/include/linux/ip.h
++++ b/include/linux/ip.h
+@@ -34,4 +34,9 @@ static inline struct iphdr *ipip_hdr(const struct sk_buff *skb)
+ {
+ 	return (struct iphdr *)skb_transport_header(skb);
+ }
++
++static inline unsigned int ip_transport_len(const struct sk_buff *skb)
++{
++	return ntohs(ip_hdr(skb)->tot_len) - skb_network_header_len(skb);
++}
+ #endif	/* _LINUX_IP_H */
+diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
+index 067a6fa675ed..f2128d94a680 100644
+--- a/include/linux/ipv6.h
++++ b/include/linux/ipv6.h
+@@ -103,6 +103,12 @@ static inline struct ipv6hdr *ipipv6_hdr(const struct sk_buff *skb)
+ 	return (struct ipv6hdr *)skb_transport_header(skb);
+ }
+ 
++static inline unsigned int ipv6_transport_len(const struct sk_buff *skb)
++{
++	return ntohs(ipv6_hdr(skb)->payload_len) + sizeof(struct ipv6hdr) -
++	       skb_network_header_len(skb);
++}
++
+ /* 
+    This structure contains results of exthdrs parsing
+    as offsets from skb->nh.
+diff --git a/include/net/addrconf.h b/include/net/addrconf.h
+index f30ee99a1d72..0eb1e1f6ea9a 100644
+--- a/include/net/addrconf.h
++++ b/include/net/addrconf.h
+@@ -49,6 +49,7 @@ struct prefix_info {
+ 	struct in6_addr		prefix;
+ };
+ 
++#include <linux/ipv6.h>
+ #include <linux/netdevice.h>
+ #include <net/if_inet6.h>
+ #include <net/ipv6.h>
+@@ -189,6 +190,15 @@ u32 ipv6_addr_label(struct net *net, const struct in6_addr *addr,
+ /*
+  *	multicast prototypes (mcast.c)
+  */
++static inline int ipv6_mc_may_pull(struct sk_buff *skb,
++				   unsigned int len)
++{
++	if (skb_transport_offset(skb) + ipv6_transport_len(skb) < len)
++		return -EINVAL;
++
++	return pskb_may_pull(skb, len);
++}
++
+ int ipv6_sock_mc_join(struct sock *sk, int ifindex,
+ 		      const struct in6_addr *addr);
+ int ipv6_sock_mc_drop(struct sock *sk, int ifindex,
+@@ -207,7 +217,7 @@ void ipv6_mc_unmap(struct inet6_dev *idev);
+ void ipv6_mc_remap(struct inet6_dev *idev);
+ void ipv6_mc_init_dev(struct inet6_dev *idev);
+ void ipv6_mc_destroy_dev(struct inet6_dev *idev);
+-int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed);
++int ipv6_mc_check_mld(struct sk_buff *skb);
+ void addrconf_dad_failure(struct inet6_ifaddr *ifp);
+ 
+ bool ipv6_chk_mcast_addr(struct net_device *dev, const struct in6_addr *group,
+diff --git a/net/batman-adv/multicast.c b/net/batman-adv/multicast.c
+index d47865e0e697..9b8f118c69f9 100644
+--- a/net/batman-adv/multicast.c
++++ b/net/batman-adv/multicast.c
+@@ -611,7 +611,7 @@ static void batadv_mcast_mla_update(struct work_struct *work)
+  */
+ static bool batadv_mcast_is_report_ipv4(struct sk_buff *skb)
+ {
+-	if (ip_mc_check_igmp(skb, NULL) < 0)
++	if (ip_mc_check_igmp(skb) < 0)
+ 		return false;
+ 
+ 	switch (igmp_hdr(skb)->type) {
+@@ -677,7 +677,7 @@ static int batadv_mcast_forw_mode_check_ipv4(struct batadv_priv *bat_priv,
+  */
+ static bool batadv_mcast_is_report_ipv6(struct sk_buff *skb)
+ {
+-	if (ipv6_mc_check_mld(skb, NULL) < 0)
++	if (ipv6_mc_check_mld(skb) < 0)
+ 		return false;
+ 
+ 	switch (icmp6_hdr(skb)->icmp6_type) {
+diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
+index b24782d53474..5224e9b3c46d 100644
+--- a/net/bridge/br_multicast.c
++++ b/net/bridge/br_multicast.c
+@@ -1128,7 +1128,7 @@ static int br_ip4_multicast_igmp3_report(struct net_bridge *br,
+ 
+ 	for (i = 0; i < num; i++) {
+ 		len += sizeof(*grec);
+-		if (!pskb_may_pull(skb, len))
++		if (!ip_mc_may_pull(skb, len))
+ 			return -EINVAL;
+ 
+ 		grec = (void *)(skb->data + len - sizeof(*grec));
+@@ -1137,7 +1137,7 @@ static int br_ip4_multicast_igmp3_report(struct net_bridge *br,
+ 		nsrcs = ntohs(grec->grec_nsrcs);
+ 
+ 		len += nsrcs * 4;
+-		if (!pskb_may_pull(skb, len))
++		if (!ip_mc_may_pull(skb, len))
+ 			return -EINVAL;
+ 
+ 		/* We treat this as an IGMPv2 report for now. */
+@@ -1176,15 +1176,17 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
+ 					struct sk_buff *skb,
+ 					u16 vid)
+ {
++	unsigned int nsrcs_offset;
+ 	const unsigned char *src;
+ 	struct icmp6hdr *icmp6h;
+ 	struct mld2_grec *grec;
++	unsigned int grec_len;
+ 	int i;
+ 	int len;
+ 	int num;
+ 	int err = 0;
+ 
+-	if (!pskb_may_pull(skb, sizeof(*icmp6h)))
++	if (!ipv6_mc_may_pull(skb, sizeof(*icmp6h)))
+ 		return -EINVAL;
+ 
+ 	icmp6h = icmp6_hdr(skb);
+@@ -1195,23 +1197,26 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
+ 		__be16 *_nsrcs, __nsrcs;
+ 		u16 nsrcs;
+ 
+-		_nsrcs = skb_header_pointer(skb,
+-					    len + offsetof(struct mld2_grec,
+-							   grec_nsrcs),
++		nsrcs_offset = len + offsetof(struct mld2_grec, grec_nsrcs);
++
++		if (skb_transport_offset(skb) + ipv6_transport_len(skb) <
++		    nsrcs_offset + sizeof(_nsrcs))
++			return -EINVAL;
++
++		_nsrcs = skb_header_pointer(skb, nsrcs_offset,
+ 					    sizeof(__nsrcs), &__nsrcs);
+ 		if (!_nsrcs)
+ 			return -EINVAL;
+ 
+ 		nsrcs = ntohs(*_nsrcs);
++		grec_len = sizeof(*grec) +
++			   sizeof(struct in6_addr) * nsrcs;
+ 
+-		if (!pskb_may_pull(skb,
+-				   len + sizeof(*grec) +
+-				   sizeof(struct in6_addr) * nsrcs))
++		if (!ipv6_mc_may_pull(skb, len + grec_len))
+ 			return -EINVAL;
+ 
+ 		grec = (struct mld2_grec *)(skb->data + len);
+-		len += sizeof(*grec) +
+-		       sizeof(struct in6_addr) * nsrcs;
++		len += grec_len;
+ 
+ 		/* We treat these as MLDv1 reports for now. */
+ 		switch (grec->grec_type) {
+@@ -1403,6 +1408,7 @@ static int br_ip4_multicast_query(struct net_bridge *br,
+ 				  struct sk_buff *skb,
+ 				  u16 vid)
+ {
++	unsigned int transport_len = ip_transport_len(skb);
+ 	const struct iphdr *iph = ip_hdr(skb);
+ 	struct igmphdr *ih = igmp_hdr(skb);
+ 	struct net_bridge_mdb_entry *mp;
+@@ -1412,7 +1418,6 @@ static int br_ip4_multicast_query(struct net_bridge *br,
+ 	struct br_ip saddr;
+ 	unsigned long max_delay;
+ 	unsigned long now = jiffies;
+-	unsigned int offset = skb_transport_offset(skb);
+ 	__be32 group;
+ 	int err = 0;
+ 
+@@ -1423,14 +1428,14 @@ static int br_ip4_multicast_query(struct net_bridge *br,
+ 
+ 	group = ih->group;
+ 
+-	if (skb->len == offset + sizeof(*ih)) {
++	if (transport_len == sizeof(*ih)) {
+ 		max_delay = ih->code * (HZ / IGMP_TIMER_SCALE);
+ 
+ 		if (!max_delay) {
+ 			max_delay = 10 * HZ;
+ 			group = 0;
+ 		}
+-	} else if (skb->len >= offset + sizeof(*ih3)) {
++	} else if (transport_len >= sizeof(*ih3)) {
+ 		ih3 = igmpv3_query_hdr(skb);
+ 		if (ih3->nsrcs)
+ 			goto out;
+@@ -1482,6 +1487,7 @@ static int br_ip6_multicast_query(struct net_bridge *br,
+ 				  struct sk_buff *skb,
+ 				  u16 vid)
+ {
++	unsigned int transport_len = ipv6_transport_len(skb);
+ 	struct mld_msg *mld;
+ 	struct net_bridge_mdb_entry *mp;
+ 	struct mld2_query *mld2q;
+@@ -1500,7 +1506,7 @@ static int br_ip6_multicast_query(struct net_bridge *br,
+ 	    (port && port->state == BR_STATE_DISABLED))
+ 		goto out;
+ 
+-	if (skb->len == offset + sizeof(*mld)) {
++	if (transport_len == sizeof(*mld)) {
+ 		if (!pskb_may_pull(skb, offset + sizeof(*mld))) {
+ 			err = -EINVAL;
+ 			goto out;
+@@ -1771,12 +1777,11 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br,
+ 				 struct sk_buff *skb,
+ 				 u16 vid)
+ {
+-	struct sk_buff *skb_trimmed = NULL;
+ 	const unsigned char *src;
+ 	struct igmphdr *ih;
+ 	int err;
+ 
+-	err = ip_mc_check_igmp(skb, &skb_trimmed);
++	err = ip_mc_check_igmp(skb);
+ 
+ 	if (err == -ENOMSG) {
+ 		if (!ipv4_is_local_multicast(ip_hdr(skb)->daddr)) {
+@@ -1802,19 +1807,16 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br,
+ 		err = br_ip4_multicast_add_group(br, port, ih->group, vid, src);
+ 		break;
+ 	case IGMPV3_HOST_MEMBERSHIP_REPORT:
+-		err = br_ip4_multicast_igmp3_report(br, port, skb_trimmed, vid);
++		err = br_ip4_multicast_igmp3_report(br, port, skb, vid);
+ 		break;
+ 	case IGMP_HOST_MEMBERSHIP_QUERY:
+-		err = br_ip4_multicast_query(br, port, skb_trimmed, vid);
++		err = br_ip4_multicast_query(br, port, skb, vid);
+ 		break;
+ 	case IGMP_HOST_LEAVE_MESSAGE:
+ 		br_ip4_multicast_leave_group(br, port, ih->group, vid, src);
+ 		break;
+ 	}
+ 
+-	if (skb_trimmed && skb_trimmed != skb)
+-		kfree_skb(skb_trimmed);
+-
+ 	br_multicast_count(br, port, skb, BR_INPUT_SKB_CB(skb)->igmp,
+ 			   BR_MCAST_DIR_RX);
+ 
+@@ -1827,12 +1829,11 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
+ 				 struct sk_buff *skb,
+ 				 u16 vid)
+ {
+-	struct sk_buff *skb_trimmed = NULL;
+ 	const unsigned char *src;
+ 	struct mld_msg *mld;
+ 	int err;
+ 
+-	err = ipv6_mc_check_mld(skb, &skb_trimmed);
++	err = ipv6_mc_check_mld(skb);
+ 
+ 	if (err == -ENOMSG) {
+ 		if (!ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr))
+@@ -1854,10 +1855,10 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
+ 						 src);
+ 		break;
+ 	case ICMPV6_MLD2_REPORT:
+-		err = br_ip6_multicast_mld2_report(br, port, skb_trimmed, vid);
++		err = br_ip6_multicast_mld2_report(br, port, skb, vid);
+ 		break;
+ 	case ICMPV6_MGM_QUERY:
+-		err = br_ip6_multicast_query(br, port, skb_trimmed, vid);
++		err = br_ip6_multicast_query(br, port, skb, vid);
+ 		break;
+ 	case ICMPV6_MGM_REDUCTION:
+ 		src = eth_hdr(skb)->h_source;
+@@ -1865,9 +1866,6 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
+ 		break;
+ 	}
+ 
+-	if (skb_trimmed && skb_trimmed != skb)
+-		kfree_skb(skb_trimmed);
+-
+ 	br_multicast_count(br, port, skb, BR_INPUT_SKB_CB(skb)->igmp,
+ 			   BR_MCAST_DIR_RX);
+ 
+diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
+index b6f0ee01f2e0..d6a222032be3 100644
+--- a/net/ipv4/igmp.c
++++ b/net/ipv4/igmp.c
+@@ -1538,7 +1538,7 @@ 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)
++static int __ip_mc_check_igmp(struct sk_buff *skb)
+ 
+ {
+ 	struct sk_buff *skb_chk;
+@@ -1560,16 +1560,10 @@ static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed)
+ 	if (ret)
+ 		goto err;
+ 
+-	if (skb_trimmed)
+-		*skb_trimmed = skb_chk;
+-	/* free now unneeded clone */
+-	else if (skb_chk != skb)
+-		kfree_skb(skb_chk);
+-
+ 	ret = 0;
+ 
+ err:
+-	if (ret && skb_chk && skb_chk != skb)
++	if (skb_chk && skb_chk != skb)
+ 		kfree_skb(skb_chk);
+ 
+ 	return ret;
+@@ -1578,7 +1572,6 @@ static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed)
+ /**
+  * 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 transport header accordingly and returns zero.
+@@ -1588,18 +1581,10 @@ static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed)
+  * -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).
+- *
+  * Caller needs to set the skb network header and free any returned skb if it
+  * differs from the provided skb.
+  */
+-int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed)
++int ip_mc_check_igmp(struct sk_buff *skb)
+ {
+ 	int ret = ip_mc_check_iphdr(skb);
+ 
+@@ -1609,7 +1594,7 @@ int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed)
+ 	if (ip_hdr(skb)->protocol != IPPROTO_IGMP)
+ 		return -ENOMSG;
+ 
+-	return __ip_mc_check_igmp(skb, skb_trimmed);
++	return __ip_mc_check_igmp(skb);
+ }
+ EXPORT_SYMBOL(ip_mc_check_igmp);
+ 
+diff --git a/net/ipv6/mcast_snoop.c b/net/ipv6/mcast_snoop.c
+index 9405b04eecc6..1a917dc80d5e 100644
+--- a/net/ipv6/mcast_snoop.c
++++ b/net/ipv6/mcast_snoop.c
+@@ -136,8 +136,7 @@ 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)
++static int __ipv6_mc_check_mld(struct sk_buff *skb)
+ 
+ {
+ 	struct sk_buff *skb_chk = NULL;
+@@ -160,16 +159,10 @@ static int __ipv6_mc_check_mld(struct sk_buff *skb,
+ 	if (ret)
+ 		goto err;
+ 
+-	if (skb_trimmed)
+-		*skb_trimmed = skb_chk;
+-	/* free now unneeded clone */
+-	else if (skb_chk != skb)
+-		kfree_skb(skb_chk);
+-
+ 	ret = 0;
+ 
+ err:
+-	if (ret && skb_chk && skb_chk != skb)
++	if (skb_chk && skb_chk != skb)
+ 		kfree_skb(skb_chk);
+ 
+ 	return ret;
+@@ -178,7 +171,6 @@ static int __ipv6_mc_check_mld(struct sk_buff *skb,
+ /**
+  * 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 transport header accordingly and returns zero.
+@@ -188,18 +180,10 @@ static int __ipv6_mc_check_mld(struct sk_buff *skb,
+  * -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).
+- *
+  * Caller needs to set the skb network header and free any returned skb if it
+  * differs from the provided skb.
+  */
+-int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed)
++int ipv6_mc_check_mld(struct sk_buff *skb)
+ {
+ 	int ret;
+ 
+@@ -211,6 +195,6 @@ int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed)
+ 	if (ret < 0)
+ 		return ret;
+ 
+-	return __ipv6_mc_check_mld(skb, skb_trimmed);
++	return __ipv6_mc_check_mld(skb);
+ }
+ EXPORT_SYMBOL(ipv6_mc_check_mld);
+-- 
+2.27.0
+
diff --git a/target/linux/generic/backport-4.14/112-bridge-simplify-ip_mc_check_igmp-and-ipv6_mc_check_m.patch b/target/linux/generic/backport-4.14/112-bridge-simplify-ip_mc_check_igmp-and-ipv6_mc_check_m.patch
new file mode 100644
index 0000000000000000000000000000000000000000..f6478bd664c8e2c366ba11c1fb9690ff77d22223
--- /dev/null
+++ b/target/linux/generic/backport-4.14/112-bridge-simplify-ip_mc_check_igmp-and-ipv6_mc_check_m.patch
@@ -0,0 +1,234 @@
+From 96d914ab71c2540e8c492cbe6877854d17a5ec4c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Linus=20L=C3=BCssing?= <linus.luessing@c0d3.blue>
+Date: Mon, 21 Jan 2019 07:26:26 +0100
+Subject: [PATCH] bridge: simplify ip_mc_check_igmp() and
+ ipv6_mc_check_mld() internals
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+With this patch the internal use of the skb_trimmed is reduced to
+the ICMPv6/IGMP checksum verification. And for the length checks
+the newly introduced helper functions are used instead of calculating
+and checking with skb->len directly.
+
+These changes should hopefully make it easier to verify that length
+checks are performed properly.
+
+Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ net/ipv4/igmp.c        | 51 +++++++++++++++-------------------
+ net/ipv6/mcast_snoop.c | 62 ++++++++++++++++++++----------------------
+ 2 files changed, 52 insertions(+), 61 deletions(-)
+
+diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
+index d6a222032be3..9033c8cb0e95 100644
+--- a/net/ipv4/igmp.c
++++ b/net/ipv4/igmp.c
+@@ -1487,22 +1487,22 @@ static int ip_mc_check_igmp_reportv3(struct sk_buff *skb)
+ 
+ 	len += sizeof(struct igmpv3_report);
+ 
+-	return pskb_may_pull(skb, len) ? 0 : -EINVAL;
++	return ip_mc_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;
++	unsigned int transport_len = ip_transport_len(skb);
++	unsigned int len;
+ 
+ 	/* IGMPv{1,2}? */
+-	if (skb->len != len) {
++	if (transport_len != sizeof(struct igmphdr)) {
+ 		/* or IGMPv3? */
+-		len += sizeof(struct igmpv3_query) - sizeof(struct igmphdr);
+-		if (skb->len < len || !pskb_may_pull(skb, len))
++		if (transport_len < sizeof(struct igmpv3_query))
++			return -EINVAL;
++
++		len = skb_transport_offset(skb) + sizeof(struct igmpv3_query);
++		if (!ip_mc_may_pull(skb, len))
+ 			return -EINVAL;
+ 	}
+ 
+@@ -1538,35 +1538,24 @@ 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)
+-
++static int ip_mc_check_igmp_csum(struct sk_buff *skb)
+ {
+-	struct sk_buff *skb_chk;
+-	unsigned int transport_len;
+ 	unsigned int len = skb_transport_offset(skb) + sizeof(struct igmphdr);
+-	int ret = -EINVAL;
++	unsigned int transport_len = ip_transport_len(skb);
++	struct sk_buff *skb_chk;
+ 
+-	transport_len = ntohs(ip_hdr(skb)->tot_len) - ip_hdrlen(skb);
++	if (!ip_mc_may_pull(skb, len))
++		return -EINVAL;
+ 
+ 	skb_chk = skb_checksum_trimmed(skb, transport_len,
+ 				       ip_mc_validate_checksum);
+ 	if (!skb_chk)
+-		goto err;
++		return -EINVAL;
+ 
+-	if (!pskb_may_pull(skb_chk, len))
+-		goto err;
+-
+-	ret = ip_mc_check_igmp_msg(skb_chk);
+-	if (ret)
+-		goto err;
+-
+-	ret = 0;
+-
+-err:
+-	if (skb_chk && skb_chk != skb)
++	if (skb_chk != skb)
+ 		kfree_skb(skb_chk);
+ 
+-	return ret;
++	return 0;
+ }
+ 
+ /**
+@@ -1594,7 +1583,11 @@ int ip_mc_check_igmp(struct sk_buff *skb)
+ 	if (ip_hdr(skb)->protocol != IPPROTO_IGMP)
+ 		return -ENOMSG;
+ 
+-	return __ip_mc_check_igmp(skb);
++	ret = ip_mc_check_igmp_csum(skb);
++	if (ret < 0)
++		return ret;
++
++	return ip_mc_check_igmp_msg(skb);
+ }
+ EXPORT_SYMBOL(ip_mc_check_igmp);
+ 
+diff --git a/net/ipv6/mcast_snoop.c b/net/ipv6/mcast_snoop.c
+index 1a917dc80d5e..a72ddfc40eb3 100644
+--- a/net/ipv6/mcast_snoop.c
++++ b/net/ipv6/mcast_snoop.c
+@@ -77,27 +77,27 @@ static int ipv6_mc_check_mld_reportv2(struct sk_buff *skb)
+ 
+ 	len += sizeof(struct mld2_report);
+ 
+-	return pskb_may_pull(skb, len) ? 0 : -EINVAL;
++	return ipv6_mc_may_pull(skb, len) ? 0 : -EINVAL;
+ }
+ 
+ static int ipv6_mc_check_mld_query(struct sk_buff *skb)
+ {
++	unsigned int transport_len = ipv6_transport_len(skb);
+ 	struct mld_msg *mld;
+-	unsigned int len = skb_transport_offset(skb);
++	unsigned int len;
+ 
+ 	/* 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) {
++	if (transport_len != sizeof(struct mld_msg)) {
+ 		/* or MLDv2? */
+-		len += sizeof(struct mld2_query) - sizeof(struct mld_msg);
+-		if (skb->len < len || !pskb_may_pull(skb, len))
++		if (transport_len < sizeof(struct mld2_query))
++			return -EINVAL;
++
++		len = skb_transport_offset(skb) + sizeof(struct mld2_query);
++		if (!ipv6_mc_may_pull(skb, len))
+ 			return -EINVAL;
+ 	}
+ 
+@@ -115,7 +115,13 @@ static int ipv6_mc_check_mld_query(struct sk_buff *skb)
+ 
+ static int ipv6_mc_check_mld_msg(struct sk_buff *skb)
+ {
+-	struct mld_msg *mld = (struct mld_msg *)skb_transport_header(skb);
++	unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg);
++	struct mld_msg *mld;
++
++	if (!ipv6_mc_may_pull(skb, len))
++		return -EINVAL;
++
++	mld = (struct mld_msg *)skb_transport_header(skb);
+ 
+ 	switch (mld->mld_type) {
+ 	case ICMPV6_MGM_REDUCTION:
+@@ -136,36 +142,24 @@ 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)
+-
++static int ipv6_mc_check_icmpv6(struct sk_buff *skb)
+ {
+-	struct sk_buff *skb_chk = NULL;
+-	unsigned int transport_len;
+-	unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg);
+-	int ret = -EINVAL;
++	unsigned int len = skb_transport_offset(skb) + sizeof(struct icmp6hdr);
++	unsigned int transport_len = ipv6_transport_len(skb);
++	struct sk_buff *skb_chk;
+ 
+-	transport_len = ntohs(ipv6_hdr(skb)->payload_len);
+-	transport_len -= skb_transport_offset(skb) - sizeof(struct ipv6hdr);
++	if (!ipv6_mc_may_pull(skb, len))
++		return -EINVAL;
+ 
+ 	skb_chk = skb_checksum_trimmed(skb, transport_len,
+ 				       ipv6_mc_validate_checksum);
+ 	if (!skb_chk)
+-		goto err;
++		return -EINVAL;
+ 
+-	if (!pskb_may_pull(skb_chk, len))
+-		goto err;
+-
+-	ret = ipv6_mc_check_mld_msg(skb_chk);
+-	if (ret)
+-		goto err;
+-
+-	ret = 0;
+-
+-err:
+-	if (skb_chk && skb_chk != skb)
++	if (skb_chk != skb)
+ 		kfree_skb(skb_chk);
+ 
+-	return ret;
++	return 0;
+ }
+ 
+ /**
+@@ -195,6 +189,10 @@ int ipv6_mc_check_mld(struct sk_buff *skb)
+ 	if (ret < 0)
+ 		return ret;
+ 
+-	return __ipv6_mc_check_mld(skb);
++	ret = ipv6_mc_check_icmpv6(skb);
++	if (ret < 0)
++		return ret;
++
++	return ipv6_mc_check_mld_msg(skb);
+ }
+ EXPORT_SYMBOL(ipv6_mc_check_mld);
+-- 
+2.27.0
+
diff --git a/target/linux/generic/backport-4.14/113-bridge-join-all-snoopers-multicast-address.patch b/target/linux/generic/backport-4.14/113-bridge-join-all-snoopers-multicast-address.patch
new file mode 100644
index 0000000000000000000000000000000000000000..d98a80ffd5c28ab03deb17acd025a8c323898922
--- /dev/null
+++ b/target/linux/generic/backport-4.14/113-bridge-join-all-snoopers-multicast-address.patch
@@ -0,0 +1,174 @@
+From 8fec90b6958b01ea8487cf9c821c39067644756a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Linus=20L=C3=BCssing?= <linus.luessing@c0d3.blue>
+Date: Mon, 21 Jan 2019 07:26:27 +0100
+Subject: [PATCH] bridge: join all-snoopers multicast address
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Next to snooping IGMP/MLD queries RFC4541, section 2.1.1.a) recommends
+to snoop multicast router advertisements to detect multicast routers.
+
+Multicast router advertisements are sent to an "all-snoopers"
+multicast address. To be able to receive them reliably, we need to
+join this group.
+
+Otherwise other snooping switches might refrain from forwarding these
+advertisements to us.
+
+Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/uapi/linux/in.h   |  9 ++---
+ net/bridge/br_multicast.c | 72 ++++++++++++++++++++++++++++++++++++++-
+ net/ipv6/mcast.c          |  2 ++
+ 3 files changed, 78 insertions(+), 5 deletions(-)
+
+diff --git a/include/uapi/linux/in.h b/include/uapi/linux/in.h
+index 48e8a225b985..478443169386 100644
+--- a/include/uapi/linux/in.h
++++ b/include/uapi/linux/in.h
+@@ -288,10 +288,11 @@ struct sockaddr_in {
+ #define	IN_LOOPBACK(a)		((((long int) (a)) & 0xff000000) == 0x7f000000)
+ 
+ /* Defines for Multicast INADDR */
+-#define INADDR_UNSPEC_GROUP   	0xe0000000U	/* 224.0.0.0   */
+-#define INADDR_ALLHOSTS_GROUP 	0xe0000001U	/* 224.0.0.1   */
+-#define INADDR_ALLRTRS_GROUP    0xe0000002U	/* 224.0.0.2 */
+-#define INADDR_MAX_LOCAL_GROUP  0xe00000ffU	/* 224.0.0.255 */
++#define INADDR_UNSPEC_GROUP		0xe0000000U	/* 224.0.0.0   */
++#define INADDR_ALLHOSTS_GROUP		0xe0000001U	/* 224.0.0.1   */
++#define INADDR_ALLRTRS_GROUP		0xe0000002U	/* 224.0.0.2 */
++#define INADDR_ALLSNOOPERS_GROUP	0xe000006aU	/* 224.0.0.106 */
++#define INADDR_MAX_LOCAL_GROUP		0xe00000ffU	/* 224.0.0.255 */
+ #endif
+ 
+ /* <asm/byteorder.h> contains the htonl type stuff.. */
+diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
+index 5224e9b3c46d..917cc9d13ea9 100644
+--- a/net/bridge/br_multicast.c
++++ b/net/bridge/br_multicast.c
+@@ -1970,6 +1970,68 @@ void br_multicast_init(struct net_bridge *br)
+ #endif
+ }
+ 
++static void br_ip4_multicast_join_snoopers(struct net_bridge *br)
++{
++	struct in_device *in_dev = in_dev_get(br->dev);
++
++	if (!in_dev)
++		return;
++
++	ip_mc_inc_group(in_dev, htonl(INADDR_ALLSNOOPERS_GROUP));
++	in_dev_put(in_dev);
++}
++
++#if IS_ENABLED(CONFIG_IPV6)
++static void br_ip6_multicast_join_snoopers(struct net_bridge *br)
++{
++	struct in6_addr addr;
++
++	ipv6_addr_set(&addr, htonl(0xff020000), 0, 0, htonl(0x6a));
++	ipv6_dev_mc_inc(br->dev, &addr);
++}
++#else
++static inline void br_ip6_multicast_join_snoopers(struct net_bridge *br)
++{
++}
++#endif
++
++static void br_multicast_join_snoopers(struct net_bridge *br)
++{
++	br_ip4_multicast_join_snoopers(br);
++	br_ip6_multicast_join_snoopers(br);
++}
++
++static void br_ip4_multicast_leave_snoopers(struct net_bridge *br)
++{
++	struct in_device *in_dev = in_dev_get(br->dev);
++
++	if (WARN_ON(!in_dev))
++		return;
++
++	ip_mc_dec_group(in_dev, htonl(INADDR_ALLSNOOPERS_GROUP));
++	in_dev_put(in_dev);
++}
++
++#if IS_ENABLED(CONFIG_IPV6)
++static void br_ip6_multicast_leave_snoopers(struct net_bridge *br)
++{
++	struct in6_addr addr;
++
++	ipv6_addr_set(&addr, htonl(0xff020000), 0, 0, htonl(0x6a));
++	ipv6_dev_mc_dec(br->dev, &addr);
++}
++#else
++static inline void br_ip6_multicast_leave_snoopers(struct net_bridge *br)
++{
++}
++#endif
++
++static void br_multicast_leave_snoopers(struct net_bridge *br)
++{
++	br_ip4_multicast_leave_snoopers(br);
++	br_ip6_multicast_leave_snoopers(br);
++}
++
+ static void __br_multicast_open(struct net_bridge *br,
+ 				struct bridge_mcast_own_query *query)
+ {
+@@ -1983,6 +2045,9 @@ static void __br_multicast_open(struct net_bridge *br,
+ 
+ void br_multicast_open(struct net_bridge *br)
+ {
++	if (!br->multicast_disabled)
++		br_multicast_join_snoopers(br);
++
+ 	__br_multicast_open(br, &br->ip4_own_query);
+ #if IS_ENABLED(CONFIG_IPV6)
+ 	__br_multicast_open(br, &br->ip6_own_query);
+@@ -1998,6 +2063,9 @@ void br_multicast_stop(struct net_bridge *br)
+ 	del_timer_sync(&br->ip6_other_query.timer);
+ 	del_timer_sync(&br->ip6_own_query.timer);
+ #endif
++
++	if (!br->multicast_disabled)
++		br_multicast_leave_snoopers(br);
+ }
+ 
+ void br_multicast_dev_del(struct net_bridge *br)
+@@ -2152,8 +2220,10 @@ int br_multicast_toggle(struct net_bridge *br, unsigned long val)
+ 
+ 	br_mc_disabled_update(br->dev, !val);
+ 	br->multicast_disabled = !val;
+-	if (br->multicast_disabled)
++	if (br->multicast_disabled) {
++		br_multicast_leave_snoopers(br);
+ 		goto unlock;
++	}
+ 
+ 	if (!netif_running(br->dev))
+ 		goto unlock;
+diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
+index 611dc5d55fa0..d907c938ec1a 100644
+--- a/net/ipv6/mcast.c
++++ b/net/ipv6/mcast.c
+@@ -915,6 +915,7 @@ int ipv6_dev_mc_inc(struct net_device *dev, const struct in6_addr *addr)
+ 	ma_put(mc);
+ 	return 0;
+ }
++EXPORT_SYMBOL(ipv6_dev_mc_inc);
+ 
+ /*
+  *	device multicast group del
+@@ -962,6 +963,7 @@ int ipv6_dev_mc_dec(struct net_device *dev, const struct in6_addr *addr)
+ 
+ 	return err;
+ }
++EXPORT_SYMBOL(ipv6_dev_mc_dec);
+ 
+ /*
+  *	check if the interface/address pair is valid
+-- 
+2.27.0
+
diff --git a/target/linux/generic/backport-4.14/114-bridge-Snoop-Multicast-Router-Advertisements.patch b/target/linux/generic/backport-4.14/114-bridge-Snoop-Multicast-Router-Advertisements.patch
new file mode 100644
index 0000000000000000000000000000000000000000..92279ce9ee866dfd006e5937b718ac7bcb7e7c6a
--- /dev/null
+++ b/target/linux/generic/backport-4.14/114-bridge-Snoop-Multicast-Router-Advertisements.patch
@@ -0,0 +1,246 @@
+From 690f6ed25b5fac9408fdebb7b4e8131f5b0a5c86 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Linus=20L=C3=BCssing?= <linus.luessing@c0d3.blue>
+Date: Mon, 21 Jan 2019 07:26:28 +0100
+Subject: [PATCH] bridge: Snoop Multicast Router Advertisements
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+When multiple multicast routers are present in a broadcast domain then
+only one of them will be detectable via IGMP/MLD query snooping. The
+multicast router with the lowest IP address will become the selected and
+active querier while all other multicast routers will then refrain from
+sending queries.
+
+To detect such rather silent multicast routers, too, RFC4286
+("Multicast Router Discovery") provides a standardized protocol to
+detect multicast routers for multicast snooping switches.
+
+This patch implements the necessary MRD Advertisement message parsing
+and after successful processing adds such routers to the internal
+multicast router list.
+
+Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/in.h          |  5 ++++
+ include/net/addrconf.h      | 15 ++++++++++
+ include/uapi/linux/icmpv6.h |  2 ++
+ include/uapi/linux/igmp.h   |  1 +
+ net/bridge/br_multicast.c   | 55 +++++++++++++++++++++++++++++++++++++
+ net/ipv6/mcast_snoop.c      |  5 +++-
+ 6 files changed, 82 insertions(+), 1 deletion(-)
+
+diff --git a/include/linux/in.h b/include/linux/in.h
+index 31b493734763..435e7f2a513a 100644
+--- a/include/linux/in.h
++++ b/include/linux/in.h
+@@ -60,6 +60,11 @@ static inline bool ipv4_is_lbcast(__be32 addr)
+ 	return addr == htonl(INADDR_BROADCAST);
+ }
+ 
++static inline bool ipv4_is_all_snoopers(__be32 addr)
++{
++	return addr == htonl(INADDR_ALLSNOOPERS_GROUP);
++}
++
+ static inline bool ipv4_is_zeronet(__be32 addr)
+ {
+ 	return (addr & htonl(0xff000000)) == htonl(0x00000000);
+diff --git a/include/net/addrconf.h b/include/net/addrconf.h
+index 0eb1e1f6ea9a..c93530f9e8e4 100644
+--- a/include/net/addrconf.h
++++ b/include/net/addrconf.h
+@@ -217,6 +217,7 @@ void ipv6_mc_unmap(struct inet6_dev *idev);
+ void ipv6_mc_remap(struct inet6_dev *idev);
+ void ipv6_mc_init_dev(struct inet6_dev *idev);
+ void ipv6_mc_destroy_dev(struct inet6_dev *idev);
++int ipv6_mc_check_icmpv6(struct sk_buff *skb);
+ int ipv6_mc_check_mld(struct sk_buff *skb);
+ void addrconf_dad_failure(struct inet6_ifaddr *ifp);
+ 
+@@ -445,6 +446,20 @@ static inline bool ipv6_addr_is_solict_mult(const struct in6_addr *addr)
+ #endif
+ }
+ 
++static inline bool ipv6_addr_is_all_snoopers(const struct in6_addr *addr)
++{
++#if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) && BITS_PER_LONG == 64
++	__be64 *p = (__be64 *)addr;
++
++	return ((p[0] ^ cpu_to_be64(0xff02000000000000UL)) |
++		(p[1] ^ cpu_to_be64(0x6a))) == 0UL;
++#else
++	return ((addr->s6_addr32[0] ^ htonl(0xff020000)) |
++		addr->s6_addr32[1] | addr->s6_addr32[2] |
++		(addr->s6_addr32[3] ^ htonl(0x0000006a))) == 0;
++#endif
++}
++
+ #ifdef CONFIG_PROC_FS
+ int if6_proc_init(void);
+ void if6_proc_exit(void);
+diff --git a/include/uapi/linux/icmpv6.h b/include/uapi/linux/icmpv6.h
+index caf8dc019250..325395f56bfa 100644
+--- a/include/uapi/linux/icmpv6.h
++++ b/include/uapi/linux/icmpv6.h
+@@ -108,6 +108,8 @@ struct icmp6hdr {
+ #define ICMPV6_MOBILE_PREFIX_SOL	146
+ #define ICMPV6_MOBILE_PREFIX_ADV	147
+ 
++#define ICMPV6_MRDISC_ADV		151
++
+ /*
+  *	Codes for Destination Unreachable
+  */
+diff --git a/include/uapi/linux/igmp.h b/include/uapi/linux/igmp.h
+index 7e44ac02ca18..90c28bc466c6 100644
+--- a/include/uapi/linux/igmp.h
++++ b/include/uapi/linux/igmp.h
+@@ -93,6 +93,7 @@ struct igmpv3_query {
+ #define IGMP_MTRACE_RESP		0x1e
+ #define IGMP_MTRACE			0x1f
+ 
++#define IGMP_MRDISC_ADV			0x30	/* From RFC4286 */
+ 
+ /*
+  *	Use the BSD names for these for compatibility
+diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
+index 917cc9d13ea9..8f0bd1133bbd 100644
+--- a/net/bridge/br_multicast.c
++++ b/net/bridge/br_multicast.c
+@@ -14,6 +14,7 @@
+ #include <linux/export.h>
+ #include <linux/if_ether.h>
+ #include <linux/igmp.h>
++#include <linux/in.h>
+ #include <linux/jhash.h>
+ #include <linux/kernel.h>
+ #include <linux/log2.h>
+@@ -29,10 +30,12 @@
+ #include <net/ip.h>
+ #include <net/switchdev.h>
+ #if IS_ENABLED(CONFIG_IPV6)
++#include <linux/icmpv6.h>
+ #include <net/ipv6.h>
+ #include <net/mld.h>
+ #include <net/ip6_checksum.h>
+ #include <net/addrconf.h>
++#include <net/ipv6.h>
+ #endif
+ 
+ #include "br_private.h"
+@@ -1772,6 +1775,19 @@ static void br_multicast_pim(struct net_bridge *br,
+ 	br_multicast_mark_router(br, port);
+ }
+ 
++static int br_ip4_multicast_mrd_rcv(struct net_bridge *br,
++				    struct net_bridge_port *port,
++				    struct sk_buff *skb)
++{
++	if (ip_hdr(skb)->protocol != IPPROTO_IGMP ||
++	    igmp_hdr(skb)->type != IGMP_MRDISC_ADV)
++		return -ENOMSG;
++
++	br_multicast_mark_router(br, port);
++
++	return 0;
++}
++
+ static int br_multicast_ipv4_rcv(struct net_bridge *br,
+ 				 struct net_bridge_port *port,
+ 				 struct sk_buff *skb,
+@@ -1789,7 +1805,15 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br,
+ 		} else if (pim_ipv4_all_pim_routers(ip_hdr(skb)->daddr)) {
+ 			if (ip_hdr(skb)->protocol == IPPROTO_PIM)
+ 				br_multicast_pim(br, port, skb);
++		} else if (ipv4_is_all_snoopers(ip_hdr(skb)->daddr)) {
++			err = br_ip4_multicast_mrd_rcv(br, port, skb);
++
++			if (err < 0 && err != -ENOMSG) {
++				br_multicast_err_count(br, port, skb->protocol);
++				return err;
++			}
+ 		}
++
+ 		return 0;
+ 	} else if (err < 0) {
+ 		br_multicast_err_count(br, port, skb->protocol);
+@@ -1824,6 +1848,27 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br,
+ }
+ 
+ #if IS_ENABLED(CONFIG_IPV6)
++static int br_ip6_multicast_mrd_rcv(struct net_bridge *br,
++				    struct net_bridge_port *port,
++				    struct sk_buff *skb)
++{
++	int ret;
++
++	if (ipv6_hdr(skb)->nexthdr != IPPROTO_ICMPV6)
++		return -ENOMSG;
++
++	ret = ipv6_mc_check_icmpv6(skb);
++	if (ret < 0)
++		return ret;
++
++	if (icmp6_hdr(skb)->icmp6_type != ICMPV6_MRDISC_ADV)
++		return -ENOMSG;
++
++	br_multicast_mark_router(br, port);
++
++	return 0;
++}
++
+ static int br_multicast_ipv6_rcv(struct net_bridge *br,
+ 				 struct net_bridge_port *port,
+ 				 struct sk_buff *skb,
+@@ -1838,6 +1883,16 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
+ 	if (err == -ENOMSG) {
+ 		if (!ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr))
+ 			BR_INPUT_SKB_CB(skb)->mrouters_only = 1;
++
++		if (ipv6_addr_is_all_snoopers(&ipv6_hdr(skb)->daddr)) {
++			err = br_ip6_multicast_mrd_rcv(br, port, skb);
++
++			if (err < 0 && err != -ENOMSG) {
++				br_multicast_err_count(br, port, skb->protocol);
++				return err;
++			}
++		}
++
+ 		return 0;
+ 	} else if (err < 0) {
+ 		br_multicast_err_count(br, port, skb->protocol);
+diff --git a/net/ipv6/mcast_snoop.c b/net/ipv6/mcast_snoop.c
+index a72ddfc40eb3..55e2ac179f28 100644
+--- a/net/ipv6/mcast_snoop.c
++++ b/net/ipv6/mcast_snoop.c
+@@ -41,6 +41,8 @@ static int ipv6_mc_check_ip6hdr(struct sk_buff *skb)
+ 	if (skb->len < len || len <= offset)
+ 		return -EINVAL;
+ 
++	skb_set_transport_header(skb, offset);
++
+ 	return 0;
+ }
+ 
+@@ -142,7 +144,7 @@ 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_icmpv6(struct sk_buff *skb)
++int ipv6_mc_check_icmpv6(struct sk_buff *skb)
+ {
+ 	unsigned int len = skb_transport_offset(skb) + sizeof(struct icmp6hdr);
+ 	unsigned int transport_len = ipv6_transport_len(skb);
+@@ -161,6 +163,7 @@ static int ipv6_mc_check_icmpv6(struct sk_buff *skb)
+ 
+ 	return 0;
+ }
++EXPORT_SYMBOL(ipv6_mc_check_icmpv6);
+ 
+ /**
+  * ipv6_mc_check_mld - checks whether this is a sane MLD packet
+-- 
+2.27.0
+
diff --git a/target/linux/generic/backport-4.14/115-bridge-remove-duplicated-include-from-br_multicast.c.patch b/target/linux/generic/backport-4.14/115-bridge-remove-duplicated-include-from-br_multicast.c.patch
new file mode 100644
index 0000000000000000000000000000000000000000..ce8b19454986f2c61a9cda24f61a9eef5eaacf56
--- /dev/null
+++ b/target/linux/generic/backport-4.14/115-bridge-remove-duplicated-include-from-br_multicast.c.patch
@@ -0,0 +1,28 @@
+From 995dbd5e30c99deb8f0c91264ae8d75951b832f7 Mon Sep 17 00:00:00 2001
+From: YueHaibing <yuehaibing@huawei.com>
+Date: Fri, 25 Jan 2019 10:59:09 +0800
+Subject: [PATCH] bridge: remove duplicated include from br_multicast.c
+
+Remove duplicated include.
+
+Signed-off-by: YueHaibing <yuehaibing@huawei.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ net/bridge/br_multicast.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
+index 8f0bd1133bbd..4c1b9c0a290b 100644
+--- a/net/bridge/br_multicast.c
++++ b/net/bridge/br_multicast.c
+@@ -35,7 +35,6 @@
+ #include <net/mld.h>
+ #include <net/ip6_checksum.h>
+ #include <net/addrconf.h>
+-#include <net/ipv6.h>
+ #endif
+ 
+ #include "br_private.h"
+-- 
+2.27.0
+
diff --git a/target/linux/generic/backport-4.14/116-net-Fix-ip_mc_-dec-inc-_group-allocation-context.patch b/target/linux/generic/backport-4.14/116-net-Fix-ip_mc_-dec-inc-_group-allocation-context.patch
new file mode 100644
index 0000000000000000000000000000000000000000..cb22c3fb97d232e8664569a5842d8c1bbd7c9596
--- /dev/null
+++ b/target/linux/generic/backport-4.14/116-net-Fix-ip_mc_-dec-inc-_group-allocation-context.patch
@@ -0,0 +1,221 @@
+From cb12869990893ec947c2d97b0e64449174c72d4a Mon Sep 17 00:00:00 2001
+From: Florian Fainelli <f.fainelli@gmail.com>
+Date: Fri, 1 Feb 2019 20:20:52 -0800
+Subject: [PATCH] net: Fix ip_mc_{dec,inc}_group allocation context
+
+After 4effd28c1245 ("bridge: join all-snoopers multicast address"), I
+started seeing the following sleep in atomic warnings:
+
+[   26.763893] BUG: sleeping function called from invalid context at mm/slab.h:421
+[   26.771425] in_atomic(): 1, irqs_disabled(): 0, pid: 1658, name: sh
+[   26.777855] INFO: lockdep is turned off.
+[   26.781916] CPU: 0 PID: 1658 Comm: sh Not tainted 5.0.0-rc4 #20
+[   26.787943] Hardware name: BCM97278SV (DT)
+[   26.792118] Call trace:
+[   26.794645]  dump_backtrace+0x0/0x170
+[   26.798391]  show_stack+0x24/0x30
+[   26.801787]  dump_stack+0xa4/0xe4
+[   26.805182]  ___might_sleep+0x208/0x218
+[   26.809102]  __might_sleep+0x78/0x88
+[   26.812762]  kmem_cache_alloc_trace+0x64/0x28c
+[   26.817301]  igmp_group_dropped+0x150/0x230
+[   26.821573]  ip_mc_dec_group+0x1b0/0x1f8
+[   26.825585]  br_ip4_multicast_leave_snoopers.isra.11+0x174/0x190
+[   26.831704]  br_multicast_toggle+0x78/0xcc
+[   26.835887]  store_bridge_parm+0xc4/0xfc
+[   26.839894]  multicast_snooping_store+0x3c/0x4c
+[   26.844517]  dev_attr_store+0x44/0x5c
+[   26.848262]  sysfs_kf_write+0x50/0x68
+[   26.852006]  kernfs_fop_write+0x14c/0x1b4
+[   26.856102]  __vfs_write+0x60/0x190
+[   26.859668]  vfs_write+0xc8/0x168
+[   26.863059]  ksys_write+0x70/0xc8
+[   26.866449]  __arm64_sys_write+0x24/0x30
+[   26.870458]  el0_svc_common+0xa0/0x11c
+[   26.874291]  el0_svc_handler+0x38/0x70
+[   26.878120]  el0_svc+0x8/0xc
+
+while toggling the bridge's multicast_snooping attribute dynamically.
+
+Pass a gfp_t down to igmpv3_add_delrec(), introduce
+__igmp_group_dropped() and introduce __ip_mc_dec_group() to take a gfp_t
+argument.
+
+Similarly introduce ____ip_mc_inc_group() and __ip_mc_inc_group() to
+allow caller to specify gfp_t.
+
+IPv6 part of the patch appears fine.
+
+Fixes: 4effd28c1245 ("bridge: join all-snoopers multicast address")
+Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/igmp.h      |  8 +++++++-
+ net/bridge/br_multicast.c |  4 ++--
+ net/ipv4/igmp.c           | 33 +++++++++++++++++++++++----------
+ 3 files changed, 32 insertions(+), 13 deletions(-)
+
+diff --git a/include/linux/igmp.h b/include/linux/igmp.h
+index 858143489a2a..a9cccb4fcb29 100644
+--- a/include/linux/igmp.h
++++ b/include/linux/igmp.h
+@@ -135,7 +135,13 @@ extern void ip_mc_up(struct in_device *);
+ extern void ip_mc_down(struct in_device *);
+ extern void ip_mc_unmap(struct in_device *);
+ extern void ip_mc_remap(struct in_device *);
+-extern void ip_mc_dec_group(struct in_device *in_dev, __be32 addr);
++extern void __ip_mc_dec_group(struct in_device *in_dev, __be32 addr, gfp_t gfp);
++static inline void ip_mc_dec_group(struct in_device *in_dev, __be32 addr)
++{
++	return __ip_mc_dec_group(in_dev, addr, GFP_KERNEL);
++}
++extern void __ip_mc_inc_group(struct in_device *in_dev, __be32 addr,
++			      gfp_t gfp);
+ extern void ip_mc_inc_group(struct in_device *in_dev, __be32 addr);
+ int ip_mc_check_igmp(struct sk_buff *skb);
+ 
+diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
+index 4c1b9c0a290b..779af4efea27 100644
+--- a/net/bridge/br_multicast.c
++++ b/net/bridge/br_multicast.c
+@@ -2031,7 +2031,7 @@ static void br_ip4_multicast_join_snoopers(struct net_bridge *br)
+ 	if (!in_dev)
+ 		return;
+ 
+-	ip_mc_inc_group(in_dev, htonl(INADDR_ALLSNOOPERS_GROUP));
++	__ip_mc_inc_group(in_dev, htonl(INADDR_ALLSNOOPERS_GROUP), GFP_ATOMIC);
+ 	in_dev_put(in_dev);
+ }
+ 
+@@ -2062,7 +2062,7 @@ static void br_ip4_multicast_leave_snoopers(struct net_bridge *br)
+ 	if (WARN_ON(!in_dev))
+ 		return;
+ 
+-	ip_mc_dec_group(in_dev, htonl(INADDR_ALLSNOOPERS_GROUP));
++	__ip_mc_dec_group(in_dev, htonl(INADDR_ALLSNOOPERS_GROUP), GFP_ATOMIC);
+ 	in_dev_put(in_dev);
+ }
+ 
+diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
+index 9033c8cb0e95..862f25f19441 100644
+--- a/net/ipv4/igmp.c
++++ b/net/ipv4/igmp.c
+@@ -162,7 +162,8 @@ static int unsolicited_report_interval(struct in_device *in_dev)
+ 	return interval_jiffies;
+ }
+ 
+-static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im);
++static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im,
++			      gfp_t gfp);
+ static void igmpv3_del_delrec(struct in_device *in_dev, struct ip_mc_list *im);
+ static void igmpv3_clear_delrec(struct in_device *in_dev);
+ static int sf_setstate(struct ip_mc_list *pmc);
+@@ -1152,7 +1153,8 @@ static void ip_mc_filter_del(struct in_device *in_dev, __be32 addr)
+ /*
+  * deleted ip_mc_list manipulation
+  */
+-static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im)
++static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im,
++			      gfp_t gfp)
+ {
+ 	struct ip_mc_list *pmc;
+ 	struct net *net = dev_net(in_dev->dev);
+@@ -1163,7 +1165,7 @@ static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im)
+ 	 * for deleted items allows change reports to use common code with
+ 	 * non-deleted or query-response MCA's.
+ 	 */
+-	pmc = kzalloc(sizeof(*pmc), GFP_KERNEL);
++	pmc = kzalloc(sizeof(*pmc), gfp);
+ 	if (!pmc)
+ 		return;
+ 	spin_lock_init(&pmc->lock);
+@@ -1264,7 +1266,7 @@ static void igmpv3_clear_delrec(struct in_device *in_dev)
+ }
+ #endif
+ 
+-static void igmp_group_dropped(struct ip_mc_list *im)
++static void __igmp_group_dropped(struct ip_mc_list *im, gfp_t gfp)
+ {
+ 	struct in_device *in_dev = im->interface;
+ #ifdef CONFIG_IP_MULTICAST
+@@ -1295,13 +1297,18 @@ static void igmp_group_dropped(struct ip_mc_list *im)
+ 			return;
+ 		}
+ 		/* IGMPv3 */
+-		igmpv3_add_delrec(in_dev, im);
++		igmpv3_add_delrec(in_dev, im, gfp);
+ 
+ 		igmp_ifc_event(in_dev);
+ 	}
+ #endif
+ }
+ 
++static void igmp_group_dropped(struct ip_mc_list *im)
++{
++	__igmp_group_dropped(im, GFP_KERNEL);
++}
++
+ static void igmp_group_added(struct ip_mc_list *im)
+ {
+ 	struct in_device *in_dev = im->interface;
+@@ -1396,7 +1403,7 @@ static void ip_mc_hash_remove(struct in_device *in_dev,
+  *	A socket has joined a multicast group on device dev.
+  */
+ 
+-void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
++void __ip_mc_inc_group(struct in_device *in_dev, __be32 addr, gfp_t gfp)
+ {
+ 	struct ip_mc_list *im;
+ #ifdef CONFIG_IP_MULTICAST
+@@ -1413,7 +1420,7 @@ void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
+ 		}
+ 	}
+ 
+-	im = kzalloc(sizeof(*im), GFP_KERNEL);
++	im = kzalloc(sizeof(*im), gfp);
+ 	if (!im)
+ 		goto out;
+ 
+@@ -1446,6 +1453,12 @@ void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
+ out:
+ 	return;
+ }
++EXPORT_SYMBOL(__ip_mc_inc_group);
++
++void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
++{
++	__ip_mc_inc_group(in_dev, addr, GFP_KERNEL);
++}
+ EXPORT_SYMBOL(ip_mc_inc_group);
+ 
+ static int ip_mc_check_iphdr(struct sk_buff *skb)
+@@ -1628,7 +1641,7 @@ static void ip_mc_rejoin_groups(struct in_device *in_dev)
+  *	A socket has left a multicast group on device dev
+  */
+ 
+-void ip_mc_dec_group(struct in_device *in_dev, __be32 addr)
++void __ip_mc_dec_group(struct in_device *in_dev, __be32 addr, gfp_t gfp)
+ {
+ 	struct ip_mc_list *i;
+ 	struct ip_mc_list __rcu **ip;
+@@ -1643,7 +1656,7 @@ void ip_mc_dec_group(struct in_device *in_dev, __be32 addr)
+ 				ip_mc_hash_remove(in_dev, i);
+ 				*ip = i->next_rcu;
+ 				in_dev->mc_count--;
+-				igmp_group_dropped(i);
++				__igmp_group_dropped(i, gfp);
+ 				ip_mc_clear_src(i);
+ 
+ 				if (!in_dev->dead)
+@@ -1656,7 +1669,7 @@ void ip_mc_dec_group(struct in_device *in_dev, __be32 addr)
+ 		}
+ 	}
+ }
+-EXPORT_SYMBOL(ip_mc_dec_group);
++EXPORT_SYMBOL(__ip_mc_dec_group);
+ 
+ /* Device changing type */
+ 
+-- 
+2.27.0
+
diff --git a/target/linux/generic/backport-4.14/117-net-remove-unneeded-switch-fall-through.patch b/target/linux/generic/backport-4.14/117-net-remove-unneeded-switch-fall-through.patch
new file mode 100644
index 0000000000000000000000000000000000000000..0d390f1d2f1b4a3542257e14761a7e1a9d9072c3
--- /dev/null
+++ b/target/linux/generic/backport-4.14/117-net-remove-unneeded-switch-fall-through.patch
@@ -0,0 +1,42 @@
+From 4b3920aedc1b730583dccaeb702ab67722b09b5c Mon Sep 17 00:00:00 2001
+From: Li RongQing <lirongqing@baidu.com>
+Date: Tue, 19 Feb 2019 10:15:56 +0800
+Subject: [PATCH] net: remove unneeded switch fall-through
+
+This case block has been terminated by a return, so not need
+a switch fall-through
+
+Signed-off-by: Li RongQing <lirongqing@baidu.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ net/ipv4/igmp.c        | 1 -
+ net/ipv6/mcast_snoop.c | 1 -
+ 2 files changed, 2 deletions(-)
+
+diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
+index 862f25f19441..9fbf4f9117bb 100644
+--- a/net/ipv4/igmp.c
++++ b/net/ipv4/igmp.c
+@@ -1535,7 +1535,6 @@ static int ip_mc_check_igmp_msg(struct sk_buff *skb)
+ 	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);
+diff --git a/net/ipv6/mcast_snoop.c b/net/ipv6/mcast_snoop.c
+index 55e2ac179f28..dddd75d1be0e 100644
+--- a/net/ipv6/mcast_snoop.c
++++ b/net/ipv6/mcast_snoop.c
+@@ -128,7 +128,6 @@ static int ipv6_mc_check_mld_msg(struct sk_buff *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);
+-- 
+2.27.0
+
diff --git a/target/linux/generic/backport-4.14/118-ipv6-Fix-return-value-of-ipv6_mc_may_pull-for-malfor.patch b/target/linux/generic/backport-4.14/118-ipv6-Fix-return-value-of-ipv6_mc_may_pull-for-malfor.patch
new file mode 100644
index 0000000000000000000000000000000000000000..c17e2b4bd7b499b92a224450be0dca311d62027a
--- /dev/null
+++ b/target/linux/generic/backport-4.14/118-ipv6-Fix-return-value-of-ipv6_mc_may_pull-for-malfor.patch
@@ -0,0 +1,47 @@
+From fc8cb7c0bf06853ea1f6ee14409b7f91b0acbe85 Mon Sep 17 00:00:00 2001
+From: Stefano Brivio <sbrivio@redhat.com>
+Date: Tue, 13 Aug 2019 00:46:01 +0200
+Subject: [PATCH] ipv6: Fix return value of ipv6_mc_may_pull() for
+ malformed packets
+
+Commit ba5ea614622d ("bridge: simplify ip_mc_check_igmp() and
+ipv6_mc_check_mld() calls") replaces direct calls to pskb_may_pull()
+in br_ipv6_multicast_mld2_report() with calls to ipv6_mc_may_pull(),
+that returns -EINVAL on buffers too short to be valid IPv6 packets,
+while maintaining the previous handling of the return code.
+
+This leads to the direct opposite of the intended effect: if the
+packet is malformed, -EINVAL evaluates as true, and we'll happily
+proceed with the processing.
+
+Return 0 if the packet is too short, in the same way as this was
+fixed for IPv4 by commit 083b78a9ed64 ("ip: fix ip_mc_may_pull()
+return value").
+
+I don't have a reproducer for this, unlike the one referred to by
+the IPv4 commit, but this is clearly broken.
+
+Fixes: ba5ea614622d ("bridge: simplify ip_mc_check_igmp() and ipv6_mc_check_mld() calls")
+Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
+Acked-by: Guillaume Nault <gnault@redhat.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/net/addrconf.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/include/net/addrconf.h b/include/net/addrconf.h
+index c93530f9e8e4..ea97f70d66b8 100644
+--- a/include/net/addrconf.h
++++ b/include/net/addrconf.h
+@@ -194,7 +194,7 @@ static inline int ipv6_mc_may_pull(struct sk_buff *skb,
+ 				   unsigned int len)
+ {
+ 	if (skb_transport_offset(skb) + ipv6_transport_len(skb) < len)
+-		return -EINVAL;
++		return 0;
+ 
+ 	return pskb_may_pull(skb, len);
+ }
+-- 
+2.27.0
+
diff --git a/target/linux/generic/backport-4.14/119-timers-Add-a-function-to-start-reduce-a-timer.patch b/target/linux/generic/backport-4.14/119-timers-Add-a-function-to-start-reduce-a-timer.patch
new file mode 100644
index 0000000000000000000000000000000000000000..92bb9275df9d54778ce8f00b1cb6e999eae40606
--- /dev/null
+++ b/target/linux/generic/backport-4.14/119-timers-Add-a-function-to-start-reduce-a-timer.patch
@@ -0,0 +1,201 @@
+From dbc2cc8bcbdb0765516a7d0201f3337e66f4f2fc Mon Sep 17 00:00:00 2001
+From: David Howells <dhowells@redhat.com>
+Date: Thu, 9 Nov 2017 12:35:07 +0000
+Subject: [PATCH] timers: Add a function to start/reduce a timer
+
+Add a function, similar to mod_timer(), that will start a timer if it isn't
+running and will modify it if it is running and has an expiry time longer
+than the new time.  If the timer is running with an expiry time that's the
+same or sooner, no change is made.
+
+The function looks like:
+
+	int timer_reduce(struct timer_list *timer, unsigned long expires);
+
+This can be used by code such as networking code to make it easier to share
+a timer for multiple timeouts.  For instance, in upcoming AF_RXRPC code,
+the rxrpc_call struct will maintain a number of timeouts:
+
+	unsigned long	ack_at;
+	unsigned long	resend_at;
+	unsigned long	ping_at;
+	unsigned long	expect_rx_by;
+	unsigned long	expect_req_by;
+	unsigned long	expect_term_by;
+
+each of which is set independently of the others.  With timer reduction
+available, when the code needs to set one of the timeouts, it only needs to
+look at that timeout and then call timer_reduce() to modify the timer,
+starting it or bringing it forward if necessary.  There is no need to refer
+to the other timeouts to see which is earliest and no need to take any lock
+other than, potentially, the timer lock inside timer_reduce().
+
+Note, that this does not protect against concurrent invocations of any of
+the timer functions.
+
+As an example, the expect_rx_by timeout above, which terminates a call if
+we don't get a packet from the server within a certain time window, would
+be set something like this:
+
+	unsigned long now = jiffies;
+	unsigned long expect_rx_by = now + packet_receive_timeout;
+	WRITE_ONCE(call->expect_rx_by, expect_rx_by);
+	timer_reduce(&call->timer, expect_rx_by);
+
+The timer service code (which might, say, be in a work function) would then
+check all the timeouts to see which, if any, had triggered, deal with
+those:
+
+	t = READ_ONCE(call->ack_at);
+	if (time_after_eq(now, t)) {
+		cmpxchg(&call->ack_at, t, now + MAX_JIFFY_OFFSET);
+		set_bit(RXRPC_CALL_EV_ACK, &call->events);
+	}
+
+and then restart the timer if necessary by finding the soonest timeout that
+hasn't yet passed and then calling timer_reduce().
+
+The disadvantage of doing things this way rather than comparing the timers
+each time and calling mod_timer() is that you *will* take timer events
+unless you can finish what you're doing and delete the timer in time.
+
+The advantage of doing things this way is that you don't need to use a lock
+to work out when the next timer should be set, other than the timer's own
+lock - which you might not have to take.
+
+[ tglx: Fixed weird formatting and adopted it to pending changes ]
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Cc: keyrings@vger.kernel.org
+Cc: linux-afs@lists.infradead.org
+Link: https://lkml.kernel.org/r/151023090769.23050.1801643667223880753.stgit@warthog.procyon.org.uk
+---
+ include/linux/timer.h |  1 +
+ kernel/time/timer.c   | 45 ++++++++++++++++++++++++++++++++++++-------
+ 2 files changed, 39 insertions(+), 7 deletions(-)
+
+diff --git a/include/linux/timer.h b/include/linux/timer.h
+index e0ea1fe87572..db65e00fb7b2 100644
+--- a/include/linux/timer.h
++++ b/include/linux/timer.h
+@@ -202,6 +202,7 @@ extern void add_timer_on(struct timer_list *timer, int cpu);
+ extern int del_timer(struct timer_list * timer);
+ extern int mod_timer(struct timer_list *timer, unsigned long expires);
+ extern int mod_timer_pending(struct timer_list *timer, unsigned long expires);
++extern int timer_reduce(struct timer_list *timer, unsigned long expires);
+ 
+ /*
+  * The jiffies value which is added to now, when there is no timer
+diff --git a/kernel/time/timer.c b/kernel/time/timer.c
+index 9f8e8892e5b0..07892e658cbd 100644
+--- a/kernel/time/timer.c
++++ b/kernel/time/timer.c
+@@ -927,8 +927,11 @@ static struct timer_base *lock_timer_base(struct timer_list *timer,
+ 	}
+ }
+ 
++#define MOD_TIMER_PENDING_ONLY		0x01
++#define MOD_TIMER_REDUCE		0x02
++
+ static inline int
+-__mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)
++__mod_timer(struct timer_list *timer, unsigned long expires, unsigned int options)
+ {
+ 	struct timer_base *base, *new_base;
+ 	unsigned int idx = UINT_MAX;
+@@ -948,7 +951,11 @@ __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)
+ 		 * larger granularity than you would get from adding a new
+ 		 * timer with this expiry.
+ 		 */
+-		if (timer->expires == expires)
++		long diff = timer->expires - expires;
++
++		if (!diff)
++			return 1;
++		if (options & MOD_TIMER_REDUCE && diff <= 0)
+ 			return 1;
+ 
+ 		/*
+@@ -960,6 +967,12 @@ __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)
+ 		base = lock_timer_base(timer, &flags);
+ 		forward_timer_base(base);
+ 
++		if (timer_pending(timer) && (options & MOD_TIMER_REDUCE) &&
++		    time_before_eq(timer->expires, expires)) {
++			ret = 1;
++			goto out_unlock;
++		}
++
+ 		clk = base->clk;
+ 		idx = calc_wheel_index(expires, clk);
+ 
+@@ -969,7 +982,10 @@ __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)
+ 		 * subsequent call will exit in the expires check above.
+ 		 */
+ 		if (idx == timer_get_idx(timer)) {
+-			timer->expires = expires;
++			if (!(options & MOD_TIMER_REDUCE))
++				timer->expires = expires;
++			else if (time_after(timer->expires, expires))
++				timer->expires = expires;
+ 			ret = 1;
+ 			goto out_unlock;
+ 		}
+@@ -979,7 +995,7 @@ __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)
+ 	}
+ 
+ 	ret = detach_if_pending(timer, base, false);
+-	if (!ret && pending_only)
++	if (!ret && (options & MOD_TIMER_PENDING_ONLY))
+ 		goto out_unlock;
+ 
+ 	new_base = get_target_base(base, timer->flags);
+@@ -1040,7 +1056,7 @@ __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)
+  */
+ int mod_timer_pending(struct timer_list *timer, unsigned long expires)
+ {
+-	return __mod_timer(timer, expires, true);
++	return __mod_timer(timer, expires, MOD_TIMER_PENDING_ONLY);
+ }
+ EXPORT_SYMBOL(mod_timer_pending);
+ 
+@@ -1066,10 +1082,25 @@ EXPORT_SYMBOL(mod_timer_pending);
+  */
+ int mod_timer(struct timer_list *timer, unsigned long expires)
+ {
+-	return __mod_timer(timer, expires, false);
++	return __mod_timer(timer, expires, 0);
+ }
+ EXPORT_SYMBOL(mod_timer);
+ 
++/**
++ * timer_reduce - Modify a timer's timeout if it would reduce the timeout
++ * @timer:	The timer to be modified
++ * @expires:	New timeout in jiffies
++ *
++ * timer_reduce() is very similar to mod_timer(), except that it will only
++ * modify a running timer if that would reduce the expiration time (it will
++ * start a timer that isn't running).
++ */
++int timer_reduce(struct timer_list *timer, unsigned long expires)
++{
++	return __mod_timer(timer, expires, MOD_TIMER_REDUCE);
++}
++EXPORT_SYMBOL(timer_reduce);
++
+ /**
+  * add_timer - start a timer
+  * @timer: the timer to be added
+@@ -1742,7 +1773,7 @@ signed long __sched schedule_timeout(signed long timeout)
+ 	expire = timeout + jiffies;
+ 
+ 	setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
+-	__mod_timer(&timer, expire, false);
++	__mod_timer(&timer, expire, 0);
+ 	schedule();
+ 	del_singleshot_timer_sync(&timer);
+ 
+-- 
+2.27.0
+
diff --git a/target/linux/generic/config-4.14 b/target/linux/generic/config-4.14
index d54ede9efda0a3ffd84e9a0c49dc410a01737d82..15b50523bf55d9a77fc1655ec6ba6ffde6d93a3e 100644
--- a/target/linux/generic/config-4.14
+++ b/target/linux/generic/config-4.14
@@ -628,6 +628,7 @@ CONFIG_BRIDGE=y
 # CONFIG_BRIDGE_EBT_T_NAT is not set
 # CONFIG_BRIDGE_EBT_VLAN is not set
 CONFIG_BRIDGE_IGMP_SNOOPING=y
+CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS=y
 # CONFIG_BRIDGE_NETFILTER is not set
 # CONFIG_BRIDGE_NF_EBTABLES is not set
 CONFIG_BRIDGE_VLAN_FILTERING=y
diff --git a/target/linux/generic/pending-4.14/151-bridge-Implement-MLD-Querier-wake-up-calls-Android-b.patch b/target/linux/generic/pending-4.14/151-bridge-Implement-MLD-Querier-wake-up-calls-Android-b.patch
new file mode 100644
index 0000000000000000000000000000000000000000..d02a4bc64deafa7fe2e17e9d17b259cea999c13b
--- /dev/null
+++ b/target/linux/generic/pending-4.14/151-bridge-Implement-MLD-Querier-wake-up-calls-Android-b.patch
@@ -0,0 +1,632 @@
+From 95479b1485191f0e7b95f9b37df0b39c751a5b56 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Linus=20L=C3=BCssing?= <linus.luessing@c0d3.blue>
+Date: Mon, 29 Jun 2020 19:04:05 +0200
+Subject: [PATCH] bridge: Implement MLD Querier wake-up calls / Android bug
+ workaround
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Implement a configurable MLD Querier wake-up calls "feature" which
+works around a widely spread Android bug in connection with IGMP/MLD
+snooping.
+
+Currently there are mobile devices (e.g. Android) which are not able
+to receive and respond to MLD Queries reliably because the Wifi driver
+filters a lot of ICMPv6 when the device is asleep - including
+MLD. This in turn breaks IPv6 communication when MLD Snooping is
+enabled. However there is one ICMPv6 type which is allowed to pass and
+which can be used to wake up the mobile device: ICMPv6 Echo Requests.
+
+If this bridge is the selected MLD Querier then setting
+"multicast_wakeupcall" to a number n greater than 0 will send n
+ICMPv6 Echo Requests to each host behind this port to wake
+them up with each MLD Query. Upon receiving a matching ICMPv6 Echo
+Reply an MLD Query with a unicast ethernet destination will be sent
+to the specific host(s).
+
+Link: https://issuetracker.google.com/issues/149630944
+Link: https://github.com/freifunk-gluon/gluon/issues/1832
+
+Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
+---
+ include/linux/if_bridge.h    |   1 +
+ include/uapi/linux/if_link.h |   1 +
+ net/bridge/Kconfig           |  26 ++++
+ net/bridge/br_fdb.c          |  10 ++
+ net/bridge/br_input.c        |   4 +-
+ net/bridge/br_multicast.c    | 288 ++++++++++++++++++++++++++++++++++-
+ net/bridge/br_netlink.c      |  19 +++
+ net/bridge/br_private.h      |  19 +++
+ net/bridge/br_sysfs_if.c     |  18 +++
+ 9 files changed, 378 insertions(+), 8 deletions(-)
+
+diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
+index 78d2ed50c524..44fa4235c8e8 100644
+--- a/include/linux/if_bridge.h
++++ b/include/linux/if_bridge.h
+@@ -50,6 +50,7 @@ struct br_ip_list {
+ #define BR_VLAN_TUNNEL		BIT(13)
+ #define BR_BCAST_FLOOD		BIT(14)
+ #define BR_ISOLATED		BIT(16)
++#define BR_MULTICAST_WAKEUPCALL	BIT(17)
+ 
+ #define BR_DEFAULT_AGEING_TIME	(300 * HZ)
+ 
+diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
+index 45529dba6554..d019754d6870 100644
+--- a/include/uapi/linux/if_link.h
++++ b/include/uapi/linux/if_link.h
+@@ -328,6 +328,7 @@ enum {
+ 	IFLA_BRPORT_BCAST_FLOOD,
+ 	IFLA_BRPORT_NEIGH_SUPPRESS,
+ 	IFLA_BRPORT_ISOLATED,
++	IFLA_BRPORT_MCAST_WAKEUPCALL,
+ 	__IFLA_BRPORT_MAX
+ };
+ #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1)
+diff --git a/net/bridge/Kconfig b/net/bridge/Kconfig
+index aa0d3b2f1bb7..66495bc07dfc 100644
+--- a/net/bridge/Kconfig
++++ b/net/bridge/Kconfig
+@@ -47,6 +47,32 @@ config BRIDGE_IGMP_SNOOPING
+ 
+ 	  If unsure, say Y.
+ 
++config BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
++	bool "MLD Querier wake-up calls"
++	depends on BRIDGE_IGMP_SNOOPING
++	depends on IPV6
++	help
++	  If you say Y here, then the MLD Snooping Querier will be built
++	  with a per bridge port wake-up call "feature"/workaround.
++
++	  Currently there are mobile devices (e.g. Android) which are not able
++	  to receive and respond to MLD Queries reliably because the Wifi driver
++	  filters a lot of ICMPv6 when the device is asleep - including MLD.
++	  This in turn breaks IPv6 communication when MLD Snooping is enabled.
++	  However there is one ICMPv6 type which is allowed to pass and
++	  which can be used to wake up the mobile device: ICMPv6 Echo Requests.
++
++	  If this bridge is the selected MLD Querier then setting
++	  "multicast_wakeupcall" to a number n greater than 0 will send n
++	  ICMPv6 Echo Requests to each host behind this port to wake them up
++	  with each MLD Query. Upon receiving a matching ICMPv6 Echo Reply
++	  an MLD Query with a unicast ethernet destination will be sent to the
++	  specific host(s).
++
++	  Say N to exclude this support and reduce the binary size.
++
++	  If unsure, say N.
++
+ config BRIDGE_VLAN_FILTERING
+ 	bool "VLAN filtering"
+ 	depends on BRIDGE
+diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
+index 4ea5c8bbe286..180555d2f21b 100644
+--- a/net/bridge/br_fdb.c
++++ b/net/bridge/br_fdb.c
+@@ -81,6 +81,10 @@ static void fdb_rcu_free(struct rcu_head *head)
+ {
+ 	struct net_bridge_fdb_entry *ent
+ 		= container_of(head, struct net_bridge_fdb_entry, rcu);
++
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
++	del_timer_sync(&ent->wakeupcall_timer);
++#endif
+ 	kmem_cache_free(br_fdb_cache, ent);
+ }
+ 
+@@ -498,6 +502,12 @@ static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head,
+ 		fdb->added_by_external_learn = 0;
+ 		fdb->offloaded = 0;
+ 		fdb->updated = fdb->used = jiffies;
++
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
++		timer_setup(&fdb->wakeupcall_timer,
++			    br_multicast_send_wakeupcall, 0);
++#endif
++
+ 		hlist_add_head_rcu(&fdb->hlist, head);
+ 	}
+ 	return fdb;
+diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
+index 66175565efa6..cc8f04408981 100644
+--- a/net/bridge/br_input.c
++++ b/net/bridge/br_input.c
+@@ -200,8 +200,10 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
+ 	if (dst) {
+ 		unsigned long now = jiffies;
+ 
+-		if (dst->is_local)
++		if (dst->is_local) {
++			br_multicast_wakeupcall_rcv(br, p, skb, vid);
+ 			return br_pass_frame_up(skb);
++		}
+ 
+ 		if (now != dst->used)
+ 			dst->used = now;
+diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
+index 779af4efea27..6d8d1cf2ddc6 100644
+--- a/net/bridge/br_multicast.c
++++ b/net/bridge/br_multicast.c
+@@ -463,10 +463,11 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
+ #if IS_ENABLED(CONFIG_IPV6)
+ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br,
+ 						    const struct in6_addr *grp,
+-						    u8 *igmp_type)
++						    u8 *igmp_type,
++						    bool delay)
+ {
++	unsigned long interval = 0;
+ 	struct mld2_query *mld2q;
+-	unsigned long interval;
+ 	struct ipv6hdr *ip6h;
+ 	struct mld_msg *mldq;
+ 	size_t mld_hdr_size;
+@@ -525,9 +526,13 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br,
+ 
+ 	/* ICMPv6 */
+ 	skb_set_transport_header(skb, skb->len);
+-	interval = ipv6_addr_any(grp) ?
+-			br->multicast_query_response_interval :
+-			br->multicast_last_member_interval;
++	if (delay) {
++		interval = ipv6_addr_any(grp) ?
++				br->multicast_query_response_interval :
++				br->multicast_last_member_interval;
++		interval = jiffies_to_msecs(interval);
++	}
++
+ 	*igmp_type = ICMPV6_MGM_QUERY;
+ 	switch (br->multicast_mld_version) {
+ 	case 1:
+@@ -535,7 +540,7 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br,
+ 		mldq->mld_type = ICMPV6_MGM_QUERY;
+ 		mldq->mld_code = 0;
+ 		mldq->mld_cksum = 0;
+-		mldq->mld_maxdelay = htons((u16)jiffies_to_msecs(interval));
++		mldq->mld_maxdelay = htons((u16)interval);
+ 		mldq->mld_reserved = 0;
+ 		mldq->mld_mca = *grp;
+ 		mldq->mld_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
+@@ -584,7 +589,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br,
+ #if IS_ENABLED(CONFIG_IPV6)
+ 	case htons(ETH_P_IPV6):
+ 		return br_ip6_multicast_alloc_query(br, &addr->u.ip6,
+-						    igmp_type);
++						    igmp_type, true);
+ #endif
+ 	}
+ 	return NULL;
+@@ -906,6 +911,172 @@ static void br_multicast_select_own_querier(struct net_bridge *br,
+ #endif
+ }
+ 
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
++
++#define BR_MC_WAKEUP_ID htons(0xEC6B) /* random identifier */
++#define BR_MC_ETH_ZERO { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
++#define BR_MC_IN6_ZERO \
++{ \
++	.s6_addr32[0] = 0, .s6_addr32[1] = 0, \
++	.s6_addr32[2] = 0, .s6_addr32[3] = 0, \
++}
++
++#define BR_MC_IN6_FE80 \
++{ \
++	.s6_addr32[0] = htonl(0xfe800000), \
++	.s6_addr32[1] = 0, \
++	.s6_addr32[2] = htonl(0x000000ff), \
++	.s6_addr32[3] = htonl(0xfe000000), \
++}
++
++#define BR_MC_ECHO_LEN sizeof(pkt->echohdr)
++
++static struct sk_buff *br_multicast_alloc_wakeupcall(struct net_bridge *br,
++						     struct net_bridge_port *port,
++						     u8 *eth_dst)
++{
++	struct in6_addr ip6_src, ip6_dst = BR_MC_IN6_FE80;
++	struct sk_buff *skb;
++	__wsum csum_part;
++	__sum16 csum;
++
++	struct wakeupcall_pkt {
++		struct ethhdr ethhdr;
++		struct ipv6hdr ip6hdr;
++		struct icmp6hdr echohdr;
++	} __packed;
++
++	struct wakeupcall_pkt *pkt;
++
++	static const struct wakeupcall_pkt __pkt_template = {
++		.ethhdr = {
++			.h_dest = BR_MC_ETH_ZERO, // update
++			.h_source = BR_MC_ETH_ZERO, // update
++			.h_proto = htons(ETH_P_IPV6),
++		},
++		.ip6hdr = {
++			.priority = 0,
++			.version = 0x6,
++			.flow_lbl = { 0x00, 0x00, 0x00 },
++			.payload_len = htons(BR_MC_ECHO_LEN),
++			.nexthdr = IPPROTO_ICMPV6,
++			.hop_limit = 1,
++			.saddr = BR_MC_IN6_ZERO, // update
++			.daddr = BR_MC_IN6_ZERO, // update
++		},
++		.echohdr = {
++			.icmp6_type = ICMPV6_ECHO_REQUEST,
++			.icmp6_code = 0,
++			.icmp6_cksum = 0, // update
++			.icmp6_dataun.u_echo = {
++				.identifier = BR_MC_WAKEUP_ID,
++				.sequence = 0,
++			},
++		},
++	};
++
++	memcpy(&ip6_dst.s6_addr32[2], &eth_dst[0], ETH_ALEN / 2);
++	memcpy(&ip6_dst.s6_addr[13], &eth_dst[3], ETH_ALEN / 2);
++	ip6_dst.s6_addr[8] ^= 0x02;
++	if (ipv6_dev_get_saddr(dev_net(br->dev), br->dev, &ip6_dst, 0,
++			       &ip6_src))
++		return NULL;
++
++	skb = netdev_alloc_skb_ip_align(br->dev, sizeof(*pkt));
++	if (!skb)
++		return NULL;
++
++	skb->protocol = htons(ETH_P_IPV6);
++	skb->dev = port->dev;
++
++	pkt = (struct wakeupcall_pkt *)skb->data;
++	*pkt = __pkt_template;
++
++	ether_addr_copy(pkt->ethhdr.h_source, br->dev->dev_addr);
++	ether_addr_copy(pkt->ethhdr.h_dest, eth_dst);
++
++	pkt->ip6hdr.saddr = ip6_src;
++	pkt->ip6hdr.daddr = ip6_dst;
++
++	csum_part = csum_partial(&pkt->echohdr, sizeof(pkt->echohdr), 0);
++	csum = csum_ipv6_magic(&ip6_src, &ip6_dst, sizeof(pkt->echohdr),
++			       IPPROTO_ICMPV6, csum_part);
++	pkt->echohdr.icmp6_cksum = csum;
++
++	skb_reset_mac_header(skb);
++	skb_set_network_header(skb, offsetof(struct wakeupcall_pkt, ip6hdr));
++	skb_set_transport_header(skb, offsetof(struct wakeupcall_pkt, echohdr));
++	skb_put(skb, sizeof(*pkt));
++	__skb_pull(skb, sizeof(pkt->ethhdr));
++
++	return skb;
++}
++
++void br_multicast_send_wakeupcall(struct timer_list *t)
++{
++	struct net_bridge_fdb_entry *fdb = from_timer(fdb, t, wakeupcall_timer);
++	struct net_bridge_port *port = fdb->dst;
++	struct net_bridge *br = port->br;
++	struct sk_buff *skb, *skb0;
++	int i;
++
++	skb0 = br_multicast_alloc_wakeupcall(br, port, fdb->addr.addr);
++	if (!skb0)
++		return;
++
++	for (i = port->wakeupcall_num_rings; i > 0; i--) {
++		if (i > 1) {
++			skb = skb_clone(skb0, GFP_ATOMIC);
++			if (!skb) {
++				kfree_skb(skb0);
++				break;
++			}
++		} else {
++			skb = skb0;
++		}
++
++		NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT,
++			dev_net(port->dev), NULL, skb, NULL, skb->dev,
++			br_dev_queue_push_xmit);
++	}
++}
++
++static void br_multicast_schedule_wakeupcalls(struct net_bridge *br,
++					      struct net_bridge_port *port,
++					      const struct in6_addr *group)
++{
++	struct net_bridge_fdb_entry *fdb;
++	unsigned long delay;
++	int i;
++
++	rcu_read_lock();
++	for (i = 0; i < BR_HASH_SIZE; i++) {
++		hlist_for_each_entry_rcu(fdb, &br->hash[i], hlist) {
++			if (!fdb->dst || fdb->dst->dev != port->dev)
++				continue;
++
++			/* Wake-up calls to VLANs unsupported for now */
++			if (fdb->vlan_id)
++				continue;
++
++			/* Spread the ICMPv6 Echo Requests to avoid congestion.
++			 * We then won't use a max response delay for the
++			 * queries later, as that would be redundant. Spread
++			 * randomly by a little less than max response delay to
++			 * anticipate the extra round trip.
++			 */
++			delay =	ipv6_addr_any(group) ?
++					br->multicast_query_response_interval :
++					br->multicast_last_member_interval;
++			delay = prandom_u32() % (3 * delay / 4);
++
++			timer_reduce(&fdb->wakeupcall_timer, jiffies + delay);
++		}
++	}
++	rcu_read_unlock();
++}
++#endif /* CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS */
++
+ static void __br_multicast_send_query(struct net_bridge *br,
+ 				      struct net_bridge_port *port,
+ 				      struct br_ip *ip)
+@@ -924,6 +1095,13 @@ static void __br_multicast_send_query(struct net_bridge *br,
+ 		NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT,
+ 			dev_net(port->dev), NULL, skb, NULL, skb->dev,
+ 			br_dev_queue_push_xmit);
++
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
++		if (port->wakeupcall_num_rings &&
++		    ip->proto == htons(ETH_P_IPV6))
++			br_multicast_schedule_wakeupcalls(br, port,
++							  &ip->u.ip6);
++#endif
+ 	} else {
+ 		br_multicast_select_own_querier(br, ip, skb);
+ 		br_multicast_count(br, port, skb, igmp_type,
+@@ -1952,6 +2130,93 @@ int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port,
+ 	return ret;
+ }
+ 
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
++
++static bool br_multicast_wakeupcall_check(struct net_bridge *br,
++					  struct net_bridge_port *port,
++					  struct sk_buff *skb, u16 vid)
++{
++	struct ethhdr *eth = eth_hdr(skb);
++	const struct ipv6hdr *ip6h;
++	unsigned int offset, len;
++	struct icmp6hdr *icmp6h;
++
++	/* Wake-up calls to VLANs unsupported for now */
++	if (!port->wakeupcall_num_rings || vid ||
++	    eth->h_proto != htons(ETH_P_IPV6))
++		return false;
++
++	if (!ether_addr_equal(eth->h_dest, br->dev->dev_addr) ||
++	    is_multicast_ether_addr(eth->h_source) ||
++	    is_zero_ether_addr(eth->h_source))
++		return false;
++
++	offset = skb_network_offset(skb) + sizeof(*ip6h);
++	if (!pskb_may_pull(skb, offset))
++		return false;
++
++	ip6h = ipv6_hdr(skb);
++
++	if (ip6h->version != 6)
++		return false;
++
++	len = offset + ntohs(ip6h->payload_len);
++	if (skb->len < len || len <= offset)
++		return false;
++
++	if (ip6h->nexthdr != IPPROTO_ICMPV6)
++		return false;
++
++	skb_set_transport_header(skb, offset);
++
++	if (ipv6_mc_check_icmpv6(skb) < 0)
++		return false;
++
++	icmp6h = (struct icmp6hdr *)skb_transport_header(skb);
++	if (icmp6h->icmp6_type != ICMPV6_ECHO_REPLY ||
++	    icmp6h->icmp6_dataun.u_echo.identifier != BR_MC_WAKEUP_ID)
++		return false;
++
++	return true;
++}
++
++static void br_multicast_wakeupcall_send_mldq(struct net_bridge *br,
++					      struct net_bridge_port *port,
++					      const u8 *eth_dst)
++{
++	const struct in6_addr grp = BR_MC_IN6_ZERO;
++	struct sk_buff *skb;
++	u8 igmp_type;
++
++	/* we might have been triggered by multicast-address-specific query
++	 * but reply with a general MLD query for now to keep things simple
++	 */
++	skb = br_ip6_multicast_alloc_query(br, &grp, &igmp_type, false);
++	if (!skb)
++		return;
++
++	skb->dev = port->dev;
++	ether_addr_copy(eth_hdr(skb)->h_dest, eth_dst);
++
++	br_multicast_count(br, port, skb, igmp_type,
++			   BR_MCAST_DIR_TX);
++	NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT,
++		dev_net(port->dev), NULL, skb, NULL, skb->dev,
++		br_dev_queue_push_xmit);
++}
++
++void br_multicast_wakeupcall_rcv(struct net_bridge *br,
++				 struct net_bridge_port *port,
++				 struct sk_buff *skb, u16 vid)
++{
++	if (!br_multicast_wakeupcall_check(br, port, skb, vid))
++		return;
++
++	br_multicast_wakeupcall_send_mldq(br, port, eth_hdr(skb)->h_source);
++}
++
++#endif /* CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS */
++
+ static void br_multicast_query_expired(struct net_bridge *br,
+ 				       struct bridge_mcast_own_query *query,
+ 				       struct bridge_mcast_querier *querier)
+@@ -2239,6 +2504,15 @@ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val)
+ 	return err;
+ }
+ 
++int br_multicast_set_wakeupcall(struct net_bridge_port *p, unsigned long val)
++{
++	if (val > U8_MAX)
++		return -EINVAL;
++
++	p->wakeupcall_num_rings = val;
++	return 0;
++}
++
+ static void br_multicast_start_querier(struct net_bridge *br,
+ 				       struct bridge_mcast_own_query *query)
+ {
+diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
+index 2bf371d9c6d9..85f44a15a97a 100644
+--- a/net/bridge/br_netlink.c
++++ b/net/bridge/br_netlink.c
+@@ -152,6 +152,9 @@ static inline size_t br_port_info_size(void)
+ 		+ nla_total_size_64bit(sizeof(u64)) /* IFLA_BRPORT_HOLD_TIMER */
+ #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
+ 		+ nla_total_size(sizeof(u8))	/* IFLA_BRPORT_MULTICAST_ROUTER */
++#endif
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
++		+ nla_total_size(sizeof(u8))	/* IFLA_BRPORT_MCAST_WAKEUPCALL */
+ #endif
+ 		+ 0;
+ }
+@@ -231,6 +234,11 @@ static int br_port_fill_attrs(struct sk_buff *skb,
+ 		       p->multicast_router))
+ 		return -EMSGSIZE;
+ #endif
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
++	if (nla_put_u8(skb, IFLA_BRPORT_MCAST_WAKEUPCALL,
++		       p->wakeupcall_num_rings))
++		return -EMSGSIZE;
++#endif
+ 
+ 	return 0;
+ }
+@@ -637,6 +645,7 @@ static const struct nla_policy br_port_policy[IFLA_BRPORT_MAX + 1] = {
+ 	[IFLA_BRPORT_PROXYARP_WIFI] = { .type = NLA_U8 },
+ 	[IFLA_BRPORT_MULTICAST_ROUTER] = { .type = NLA_U8 },
+ 	[IFLA_BRPORT_MCAST_TO_UCAST] = { .type = NLA_U8 },
++	[IFLA_BRPORT_MCAST_WAKEUPCALL] = { .type = NLA_U8 },
+ 	[IFLA_BRPORT_MCAST_FLOOD] = { .type = NLA_U8 },
+ 	[IFLA_BRPORT_BCAST_FLOOD] = { .type = NLA_U8 },
+ 	[IFLA_BRPORT_ISOLATED]	= { .type = NLA_U8 },
+@@ -781,6 +790,16 @@ static int br_setport(struct net_bridge_port *p, struct nlattr *tb[])
+ 	if (err)
+ 		return err;
+ 
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
++	if (tb[IFLA_BRPORT_MCAST_WAKEUPCALL]) {
++		u8 wakeupcall = nla_get_u8(tb[IFLA_BRPORT_MCAST_WAKEUPCALL]);
++
++		err = br_multicast_set_wakeupcall(p, wakeupcall);
++		if (err)
++			return err;
++	}
++#endif
++
+ 	br_port_flags_change(p, old_flags ^ p->flags);
+ 	return 0;
+ }
+diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
+index 32f0d61c1803..bbf7513c1d16 100644
+--- a/net/bridge/br_private.h
++++ b/net/bridge/br_private.h
+@@ -178,6 +178,10 @@ struct net_bridge_fdb_entry {
+ 	unsigned long			used;
+ 
+ 	struct rcu_head			rcu;
++
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
++	struct timer_list		wakeupcall_timer;
++#endif
+ };
+ 
+ #define MDB_PG_FLAGS_PERMANENT	BIT(0)
+@@ -256,6 +260,7 @@ struct net_bridge_port {
+ 	struct timer_list		multicast_router_timer;
+ 	struct hlist_head		mglist;
+ 	struct hlist_node		rlist;
++	u8				wakeupcall_num_rings;
+ #endif
+ 
+ #ifdef CONFIG_SYSFS
+@@ -790,6 +795,20 @@ static inline int br_multicast_igmp_type(const struct sk_buff *skb)
+ }
+ #endif
+ 
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
++void br_multicast_wakeupcall_rcv(struct net_bridge *br,
++				 struct net_bridge_port *port,
++				 struct sk_buff *skb, u16 vid);
++void br_multicast_send_wakeupcall(struct timer_list *t);
++int br_multicast_set_wakeupcall(struct net_bridge_port *p, unsigned long val);
++#else
++static inline void br_multicast_wakeupcall_rcv(struct net_bridge *br,
++					       struct net_bridge_port *port,
++					       struct sk_buff *skb, u16 vid)
++{
++}
++#endif /* CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS */
++
+ /* br_vlan.c */
+ #ifdef CONFIG_BRIDGE_VLAN_FILTERING
+ bool br_allowed_ingress(const struct net_bridge *br,
+diff --git a/net/bridge/br_sysfs_if.c b/net/bridge/br_sysfs_if.c
+index b51aefa636b6..e9f1f5ccec3e 100644
+--- a/net/bridge/br_sysfs_if.c
++++ b/net/bridge/br_sysfs_if.c
+@@ -194,6 +194,21 @@ BRPORT_ATTR_FLAG(multicast_fast_leave, BR_MULTICAST_FAST_LEAVE);
+ BRPORT_ATTR_FLAG(multicast_to_unicast, BR_MULTICAST_TO_UNICAST);
+ #endif
+ 
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
++static ssize_t show_multicast_wakeupcall(struct net_bridge_port *p, char *buf)
++{
++	return sprintf(buf, "%d\n", p->wakeupcall_num_rings);
++}
++
++static int store_multicast_wakeupcall(struct net_bridge_port *p,
++				      unsigned long v)
++{
++	return br_multicast_set_wakeupcall(p, v);
++}
++static BRPORT_ATTR(multicast_wakeupcall, 0644, show_multicast_wakeupcall,
++		   store_multicast_wakeupcall);
++#endif
++
+ static const struct brport_attribute *brport_attrs[] = {
+ 	&brport_attr_path_cost,
+ 	&brport_attr_priority,
+@@ -219,6 +234,9 @@ static const struct brport_attribute *brport_attrs[] = {
+ 	&brport_attr_multicast_router,
+ 	&brport_attr_multicast_fast_leave,
+ 	&brport_attr_multicast_to_unicast,
++#endif
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
++	&brport_attr_multicast_wakeupcall,
+ #endif
+ 	&brport_attr_proxyarp,
+ 	&brport_attr_proxyarp_wifi,
+-- 
+2.28.0.rc1
+
