2606 lines
89 KiB
Diff
2606 lines
89 KiB
Diff
|
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], ð_dst[0], ETH_ALEN / 2);
|
||
|
++ memcpy(&ip6_dst.s6_addr[13], ð_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
|
||
|
+
|