diff --git a/patches/openwrt/0005-kernel-bridge-Implement-MLD-Querier-wake-up-calls-Android-bug-workaround.patch b/patches/openwrt/0005-kernel-bridge-Implement-MLD-Querier-wake-up-calls-Android-bug-workaround.patch
new file mode 100644
index 0000000000000000000000000000000000000000..54c9860b5f88598f9c69f388d268c40b96e85c28
--- /dev/null
+++ b/patches/openwrt/0005-kernel-bridge-Implement-MLD-Querier-wake-up-calls-Android-bug-workaround.patch
@@ -0,0 +1,883 @@
+From: Linus Lüssing <linus.luessing@c0d3.blue>
+Date: Sat, 1 Jan 2022 10:09:13 +0100
+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..077a563b6066cd1d3aee4b1e82328e8cc5e042ea
+--- /dev/null
++++ b/package/network/config/netifd/patches/0001-bridge-Add-multicast_wakeupcall-option.patch
+@@ -0,0 +1,142 @@
++From d23a49e6542dc068b12fbc7b6a4520f9fb3626f9 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       |  3 +++
++ system-linux.c | 13 +++++++++++++
++ 3 files changed, 25 insertions(+)
++
++--- a/device.c
+++++ b/device.c
++@@ -49,6 +49,7 @@ static const struct blobmsg_policy dev_a
++ 	[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 },
++@@ -275,6 +276,7 @@ device_merge_settings(struct device *dev
++ 	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;
++@@ -449,6 +451,11 @@ device_init_settings(struct device *dev,
++ 		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)
++@@ -1372,6 +1379,8 @@ device_dump_status(struct blob_buf *b, s
++ 			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)
++--- a/device.h
+++++ b/device.h
++@@ -44,6 +44,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,
++@@ -144,6 +145,7 @@ enum {
++ 	DEV_OPT_GRO			= (1ULL << 37),
++ 	DEV_OPT_MASTER			= (1ULL << 38),
++ 	DEV_OPT_EEE			= (1ULL << 39),
+++	DEV_OPT_MULTICAST_WAKEUPCALL	= (1ULL << 63),
++ };
++ 
++ /* events broadcasted to all users of a device */
++@@ -205,6 +207,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;
++--- a/system-linux.c
+++++ b/system-linux.c
++@@ -536,6 +536,11 @@ static void system_bridge_set_multicast_
++ 	system_set_dev_sysfs("brport/multicast_to_unicast", dev->ifname, val);
++ }
++ 
+++static void system_bridge_set_multicast_wakeupcall(struct device *dev, const char *val)
+++{
+++	system_set_dev_sysfs("brport/multicast_wakeupcall", dev->ifname, val);
+++}
+++
++ static void system_bridge_set_multicast_fast_leave(struct device *dev, const char *val)
++ {
++ 	system_set_dev_sysfs("brport/multicast_fast_leave", dev->ifname, val);
++@@ -923,8 +928,10 @@ static char *system_get_bridge(const cha
++ 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;
+++	char buf[64];
++ 
++ 	if (dev->settings.flags & DEV_OPT_MULTICAST_TO_UNICAST)
++ 		mcast_to_ucast = dev->settings.multicast_to_unicast;
++@@ -939,6 +946,12 @@ system_bridge_set_wireless(struct device
++ 	system_bridge_set_multicast_to_unicast(dev, mcast_to_ucast ? "1" : "0");
++ 	system_bridge_set_hairpin_mode(dev, hairpin ? "1" : "0");
++ 	system_bridge_set_proxyarp_wifi(dev, dev->wireless_proxyarp ? "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)
+diff --git a/target/linux/generic/config-6.6 b/target/linux/generic/config-6.6
+index 105dc0d344fa82c85b09482e051f35a282a60b08..032dd15bbb2b8603799032647f7a47625751836f 100644
+--- a/target/linux/generic/config-6.6
++++ b/target/linux/generic/config-6.6
+@@ -716,6 +716,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_MRP is not set
+ # CONFIG_BRIDGE_NETFILTER is not set
+ # CONFIG_BRIDGE_NF_EBTABLES is not set
+diff --git a/target/linux/generic/hack-6.6/602-bridge-Implement-MLD-Querier-wake-up-calls-Android-b.patch b/target/linux/generic/hack-6.6/602-bridge-Implement-MLD-Querier-wake-up-calls-Android-b.patch
+new file mode 100644
+index 0000000000000000000000000000000000000000..5a54131fab24f9cfaa9c51d51b3163bd2ecaf4c3
+--- /dev/null
++++ b/target/linux/generic/hack-6.6/602-bridge-Implement-MLD-Querier-wake-up-calls-Android-b.patch
+@@ -0,0 +1,690 @@
++From 9734fac17903cc0c67c63361525cc99f793fd6d7 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/net/addrconf.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    | 291 ++++++++++++++++++++++++++++++++++-
++ net/bridge/br_netlink.c      |  19 +++
++ net/bridge/br_private.h      |  20 +++
++ net/bridge/br_sysfs_if.c     |  18 +++
++ net/core/rtnetlink.c         |   2 +-
++ net/ipv6/mcast_snoop.c       |   3 +-
++ 12 files changed, 386 insertions(+), 10 deletions(-)
++
++diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
++index cff03a1dfd68..70b6f74653d0 100644
++--- a/include/linux/if_bridge.h
+++++ b/include/linux/if_bridge.h
++@@ -62,6 +62,7 @@ struct br_ip_list {
++ #define BR_PORT_MAB		BIT(22)
++ #define BR_NEIGH_VLAN_SUPPRESS	BIT(23)
++ #define BR_BPDU_FILTER		BIT(24)
+++#define BR_MULTICAST_WAKEUPCALL	BIT(25)
++ 
++ #define BR_DEFAULT_AGEING_TIME	(300 * HZ)
++ 
++diff --git a/include/net/addrconf.h b/include/net/addrconf.h
++index facb7a469efa..817a33d1c055 100644
++--- a/include/net/addrconf.h
+++++ b/include/net/addrconf.h
++@@ -243,6 +243,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 sk_buff *skb, struct inet6_ifaddr *ifp);
++ 
++diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
++index b29417908271..88b1c8a19bb4 100644
++--- a/include/uapi/linux/if_link.h
+++++ b/include/uapi/linux/if_link.h
++@@ -572,6 +572,7 @@ enum {
++ 	IFLA_BRPORT_NEIGH_VLAN_SUPPRESS,
++ 	IFLA_BRPORT_BACKUP_NHID,
++ 	IFLA_BRPORT_BPDU_FILTER,
+++	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 3c8ded7d3e84..1a11e22c7d51 100644
++--- a/net/bridge/Kconfig
+++++ b/net/bridge/Kconfig
++@@ -48,6 +48,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 a6d8cd9a5807..b70426806d45 100644
++--- a/net/bridge/br_fdb.c
+++++ b/net/bridge/br_fdb.c
++@@ -80,6 +80,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);
++ }
++ 
++@@ -400,6 +404,12 @@ static struct net_bridge_fdb_entry *fdb_create(struct net_bridge *br,
++ 	fdb->key.vlan_id = vid;
++ 	fdb->flags = flags;
++ 	fdb->updated = fdb->used = jiffies;
+++
+++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
+++		timer_setup(&fdb->wakeupcall_timer,
+++			    br_multicast_send_wakeupcall, 0);
+++#endif
+++
++ 	err = rhashtable_lookup_insert_fast(&br->fdb_hash_tbl, &fdb->rhnode,
++ 					    br_fdb_rht_params);
++ 	if (err) {
++diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
++index 4540c76d6079..d4644aab7dbf 100644
++--- a/net/bridge/br_input.c
+++++ b/net/bridge/br_input.c
++@@ -204,8 +204,10 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
++ 	if (dst) {
++ 		unsigned long now = jiffies;
++ 
++-		if (test_bit(BR_FDB_LOCAL, &dst->flags))
+++		if (test_bit(BR_FDB_LOCAL, &dst->flags)) {
+++			br_multicast_wakeupcall_rcv(brmctx, pmctx, skb, vid);
++ 			return br_pass_frame_up(skb, false);
+++		}
++ 
++ 		if (now != dst->used)
++ 			dst->used = now;
++diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
++index c38244d60ff8..df3a4d4dbb8f 100644
++--- a/net/bridge/br_multicast.c
+++++ b/net/bridge/br_multicast.c
++@@ -1076,15 +1076,16 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm
++ 						    const struct in6_addr *group,
++ 						    bool with_srcs, bool over_llqt,
++ 						    u8 sflag, u8 *igmp_type,
++-						    bool *need_rexmit)
+++						    bool *need_rexmit,
+++						    bool delay)
++ {
++ 	struct net_bridge_port *p = pg ? pg->key.port : NULL;
++ 	struct net_bridge_group_src *ent;
++ 	size_t pkt_size, mld_hdr_size;
++ 	unsigned long now = jiffies;
+++	unsigned long interval = 0;
++ 	struct mld2_query *mld2q;
++ 	void *csum_start = NULL;
++-	unsigned long interval;
++ 	__sum16 *csum = NULL;
++ 	struct ipv6hdr *ip6h;
++ 	struct mld_msg *mldq;
++@@ -1166,9 +1167,13 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm
++ 
++ 	/* ICMPv6 */
++ 	skb_set_transport_header(skb, skb->len);
++-	interval = ipv6_addr_any(group) ?
++-			brmctx->multicast_query_response_interval :
++-			brmctx->multicast_last_member_interval;
+++	if (delay) {
+++		interval = ipv6_addr_any(group) ?
+++				brmctx->multicast_query_response_interval :
+++				brmctx->multicast_last_member_interval;
+++		interval = jiffies_to_msecs(interval);
+++	}
+++
++ 	*igmp_type = ICMPV6_MGM_QUERY;
++ 	switch (brmctx->multicast_mld_version) {
++ 	case 1:
++@@ -1176,7 +1181,7 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm
++ 		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 = *group;
++ 		csum = &mldq->mld_cksum;
++@@ -1267,7 +1272,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge_mcast *brmctx,
++ 						    &ip6_dst, &group->dst.ip6,
++ 						    with_srcs, over_lmqt,
++ 						    sflag, igmp_type,
++-						    need_rexmit);
+++						    need_rexmit, true);
++ 	}
++ #endif
++ 	}
++@@ -1777,6 +1782,169 @@ static void br_multicast_select_own_querier(struct net_bridge_mcast *brmctx,
++ #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->key.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_mcast *brmctx,
+++				  struct net_bridge_mcast_port *pmctx,
+++				  const struct in6_addr *group)
+++{
+++	struct net_bridge_fdb_entry *fdb;
+++	unsigned long delay;
+++
+++	rcu_read_lock();
+++	hlist_for_each_entry_rcu(fdb, &brmctx->br->fdb_list, fdb_node) {
+++		if (!fdb->dst || fdb->dst->dev != pmctx->port->dev)
+++			continue;
+++
+++		/* Wake-up calls to VLANs unsupported for now */
+++		if (fdb->key.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) ?
+++				brmctx->multicast_query_response_interval :
+++				brmctx->multicast_last_member_interval;
+++		delay = get_random_u32_below(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_mcast *brmctx,
++ 				      struct net_bridge_mcast_port *pmctx,
++ 				      struct net_bridge_port_group *pg,
++@@ -1809,6 +1977,13 @@ static void __br_multicast_send_query(struct net_bridge_mcast *brmctx,
++ 			dev_net(pmctx->port->dev), NULL, skb, NULL, skb->dev,
++ 			br_dev_queue_push_xmit);
++ 
+++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
+++		if (pmctx->port->wakeupcall_num_rings &&
+++		    group->proto == htons(ETH_P_IPV6))
+++			br_multicast_schedule_wakeupcalls(brmctx, pmctx,
+++							  &group->dst.ip6);
+++#endif
+++
++ 		if (over_lmqt && with_srcs && sflag) {
++ 			over_lmqt = false;
++ 			goto again_under_lmqt;
++@@ -3976,6 +4151,99 @@ int br_multicast_rcv(struct net_bridge_mcast **brmctx,
++ 	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_mcast *brmctx,
+++					      struct net_bridge_mcast_port *pmctx,
+++					      const u8 *eth_dst)
+++{
+++	const struct in6_addr group = BR_MC_IN6_ZERO;
+++	struct in6_addr ip6_dst;
+++	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
+++	 */
+++	ipv6_addr_set(&ip6_dst, htonl(0xff020000), 0, 0, htonl(1));
+++
+++	skb = br_ip6_multicast_alloc_query(brmctx, pmctx, NULL, &ip6_dst,
+++					   &group, false, false, false,
+++					   &igmp_type, NULL, false);
+++	if (!skb)
+++		return;
+++
+++	skb->dev = pmctx->port->dev;
+++	ether_addr_copy(eth_hdr(skb)->h_dest, eth_dst);
+++
+++	br_multicast_count(brmctx->br, pmctx->port, skb, igmp_type,
+++			   BR_MCAST_DIR_TX);
+++	NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT,
+++		dev_net(pmctx->port->dev), NULL, skb, NULL, skb->dev,
+++		br_dev_queue_push_xmit);
+++}
+++
+++void br_multicast_wakeupcall_rcv(struct net_bridge_mcast *brmctx,
+++				 struct net_bridge_mcast_port *pmctx,
+++				 struct sk_buff *skb, u16 vid)
+++{
+++	if (!br_multicast_wakeupcall_check(brmctx->br, pmctx->port, skb, vid))
+++		return;
+++
+++	br_multicast_wakeupcall_send_mldq(brmctx, pmctx,
+++					  eth_hdr(skb)->h_source);
+++}
+++
+++#endif /* CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS */
+++
++ static void br_multicast_query_expired(struct net_bridge_mcast *brmctx,
++ 				       struct bridge_mcast_own_query *query,
++ 				       struct bridge_mcast_querier *querier)
++@@ -4504,6 +4772,15 @@ int br_multicast_set_vlan_router(struct net_bridge_vlan *v, u8 mcast_router)
++ 	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_mcast *brmctx,
++ 				       struct bridge_mcast_own_query *query)
++ {
++diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
++index a760d5a5ad12..8fdabad8e10a 100644
++--- a/net/bridge/br_netlink.c
+++++ b/net/bridge/br_netlink.c
++@@ -206,6 +206,9 @@ static inline size_t br_port_info_size(void)
++ 		+ nla_total_size(sizeof(u8))	/* IFLA_BRPORT_MULTICAST_ROUTER */
++ 		+ nla_total_size(sizeof(u32))	/* IFLA_BRPORT_MCAST_N_GROUPS */
++ 		+ nla_total_size(sizeof(u32))	/* IFLA_BRPORT_MCAST_MAX_GROUPS */
+++#endif
+++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
+++		+ nla_total_size(sizeof(u8))	/* IFLA_BRPORT_MCAST_WAKEUPCALL */
++ #endif
++ 		+ nla_total_size(sizeof(u16))	/* IFLA_BRPORT_GROUP_FWD_MASK */
++ 		+ nla_total_size(sizeof(u8))	/* IFLA_BRPORT_MRP_RING_OPEN */
++@@ -313,6 +316,11 @@ static int br_port_fill_attrs(struct sk_buff *skb,
++ 			br_multicast_ngroups_get_max(&p->multicast_ctx)))
++ 		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
++ 
++ 	/* we might be called only with br->lock */
++ 	rcu_read_lock();
++@@ -890,6 +898,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_VLAN_TUNNEL] = { .type = NLA_U8 },
++@@ -1051,6 +1060,16 @@ static int br_setport(struct net_bridge_port *p, struct nlattr *tb[],
++ 	}
++ #endif
++ 
+++#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
+++
++ 	if (tb[IFLA_BRPORT_GROUP_FWD_MASK]) {
++ 		u16 fwd_mask = nla_get_u16(tb[IFLA_BRPORT_GROUP_FWD_MASK]);
++ 
++diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
++index 72d80fd943a8..b237c39edd35 100644
++--- a/net/bridge/br_private.h
+++++ b/net/bridge/br_private.h
++@@ -294,6 +294,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
++ };
++ 
++ struct net_bridge_fdb_flush_desc {
++@@ -417,6 +421,7 @@ struct net_bridge_port {
++ 	u32				multicast_eht_hosts_limit;
++ 	u32				multicast_eht_hosts_cnt;
++ 	struct hlist_head		mglist;
+++	u8				wakeupcall_num_rings;
++ #endif
++ 
++ #ifdef CONFIG_SYSFS
++@@ -1504,6 +1509,21 @@ br_multicast_ctx_options_equal(const struct net_bridge_mcast *brmctx1,
++ }
++ #endif
++ 
+++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS
+++void br_multicast_wakeupcall_rcv(struct net_bridge_mcast *brmctx,
+++				 struct net_bridge_mcast_port *pmctx,
+++				 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_mcast *brmctx,
+++			    struct net_bridge_mcast_port *pmctx,
+++			    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 aee7c5902206..15ee27e1aa72 100644
++--- a/net/bridge/br_sysfs_if.c
+++++ b/net/bridge/br_sysfs_if.c
++@@ -260,6 +260,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,
++@@ -285,6 +300,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,
++diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
++index 71797d44af4c..abe17f8c4939 100644
++--- a/net/core/rtnetlink.c
+++++ b/net/core/rtnetlink.c
++@@ -61,7 +61,7 @@
++ #include "dev.h"
++ 
++ #define RTNL_MAX_TYPE		50
++-#define RTNL_SLAVE_MAX_TYPE	45
+++#define RTNL_SLAVE_MAX_TYPE	46
++ 
++ struct rtnl_link {
++ 	rtnl_doit_func		doit;
++diff --git a/net/ipv6/mcast_snoop.c b/net/ipv6/mcast_snoop.c
++index 04d5fcdfa6e0..9a5061edbaf3 100644
++--- a/net/ipv6/mcast_snoop.c
+++++ b/net/ipv6/mcast_snoop.c
++@@ -131,7 +131,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);
++@@ -150,6 +150,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.45.2
++