batman-adv: Add multicast optimization support for bridged setups

Message ID 1402531227-15447-1-git-send-email-linus.luessing@web.de (mailing list archive)
State Superseded, archived
Headers

Commit Message

Linus Lüssing June 12, 2014, midnight UTC
  So far, the recently added multicast optimizations did not support
a configuration where a bridge is on top of the soft-interface (e.g.
bat0).

Now the Linux bridge code is able to provide us with the missing bits:

Multicast Listeners: The bridge hands us a list of multicast listeners
it has detected behind the bridge which we will announce through the
mesh via the translation table, allowing other nodes to direct multicast
packets to these clients behind the bridge even with enabled multicast
optimizations.

Queriers: The bridge informs us whether there is a selected IGMP or
MLD querier behind the bridge. In that case we need to all according
IPv4 or IPv6 multicast traffic directed to us.

These two parts together allow us to serve all multicast listeners with
IPv6 link-local multicast packets even when bridges are involved and
our multicast optimizations enabled.

Signed-off-by: Linus Lüssing <linus.luessing@web.de>
---
 compat.c    |   15 ++++++
 compat.h    |   30 ++++++++++++
 main.h      |    1 +
 multicast.c |  151 ++++++++++++++++++++++++++++++++++++++++++++++++++++-------
 4 files changed, 179 insertions(+), 18 deletions(-)
  

Comments

Marek Lindner June 17, 2014, 2:50 p.m. UTC | #1
On Thursday 12 June 2014 02:00:27 Linus Lüssing wrote:
> @@ -30,17 +53,21 @@
>   * Collect multicast addresses of the local multicast listeners
>   * on the given soft interface, dev, in the given mcast_list.
>   *
> + * If there is a bridge interface on top of dev, collect from that one
> + * instead.
> + *
>   * Returns -ENOMEM on memory allocation error or the number of
>   * items added to the mcast_list otherwise.
>   */
>  static int batadv_mcast_mla_softif_get(struct net_device *dev,
>  				       struct hlist_head *mcast_list)
>  {
> +	struct net_device *bridge = batadv_mcast_get_bridge(dev);
>  	struct netdev_hw_addr *mc_list_entry;
>  	struct batadv_hw_addr *new;
>  	int ret = 0;
> 
> -	netif_addr_lock_bh(dev);
> +	netif_addr_lock_bh(bridge ? bridge : dev);
>  	netdev_for_each_mc_addr(mc_list_entry, dev) {
>  		new = kmalloc(sizeof(*new), GFP_ATOMIC);
>  		if (!new) {

Maybe I am missing something but shouldn't we fetch the entries from the 
bridge interface instead of dev ?


>  /**
> + * batadv_mcast_mla_br_addr_cpy - copy a bridge multicast address
> + * @dst: destination to write to - a multicast MAC address
> + * @src: source to read from - a multicast IP address
> + *
> + * Converts a given multicast IPv4/IPv6 address from a bridge
> + * to its matching multicast MAC address and copies it into the given
> + * destination buffer.
> + *
> + * Caller needs to make sure the destination buffer can hold
> + * at least ETH_ALEN bytes.
> + */
> +static void batadv_mcast_mla_br_addr_cpy(char *dst, const struct br_ip
> *src) +{
> +	if (src->proto == htons(ETH_P_IP)) {
> +		/* RFC 1112 */
> +		memcpy(dst, "\x01\x00\x5e", 3);
> +		memcpy(dst + 3, ((char *)&src->u.ip4) + 1, ETH_ALEN - 3);
> +		dst[3] &= 0x7F;
> +	}
> +#if IS_ENABLED(CONFIG_IPV6)
> +	else if (src->proto == htons(ETH_P_IPV6)) {
> +		/* RFC 2464 */
> +		memcpy(dst, "\x33\x33", 2);
> +		memcpy(dst + 2, &src->u.ip6.s6_addr32[3],
> +		       sizeof(src->u.ip6.s6_addr32[3]));
> +	}
> +#endif
> +	else
> +		memset(dst, 0, ETH_ALEN);
> +}

Is there no cleaner way to do this ? "\x01\x00\x5e" and "\x33\x33" looks 
pretty hackish. Are there no defines for mcast prefixes somewhere ?


> +/**
> + * batadv_mcast_mla_bridge_get - get bridged-in multicast listeners
> + * @dev: a bridge slave whose bridge to collect multicast addresses from
> + * @mcast_list: a list to put found addresses into
> + *
> + * Collects multicast addresses of the bridged-in multicast listeners
> + * from the bridge on top of the given soft interface, dev, in the
> + * given mcast_list.
> + *
> + * Returns -ENOMEM on memory allocation error or the number of
> + * items added to the mcast_list otherwise.
> + */
> +static int batadv_mcast_mla_bridge_get(struct net_device *dev,
> +				       struct hlist_head *mcast_list)
> +{

That is probably where my confusion is coming from. Why do we query the bridge 
interface again ?


> @@ -195,19 +306,18 @@ static bool batadv_mcast_mla_tvlv_update(struct
> batadv_priv *bat_priv) mcast_data.flags = BATADV_NO_FLAGS;
>  	memset(mcast_data.reserved, 0, sizeof(mcast_data.reserved));
> 
> -	/* Avoid attaching MLAs, if there is a bridge on top of our soft
> -	 * interface, we don't support that yet (TODO)
> -	 */
> -	if (batadv_mcast_has_bridge(bat_priv)) {
> -		if (bat_priv->mcast.enabled) {
> -			batadv_tvlv_container_unregister(bat_priv,
> -							 BATADV_TVLV_MCAST, 1);
> -			bat_priv->mcast.enabled = false;
> -		}
> +	if (!batadv_mcast_has_bridge(bat_priv))
> +		goto skip;
> 
> -		return false;
> -	}
> +	mcast_data.flags |= BATADV_MCAST_WANT_ALL_UNSNOOPABLES;
> +
> +	if (br_multicast_has_querier_adjacent(bat_priv->soft_iface, ETH_P_IP))
> +		mcast_data.flags |= BATADV_MCAST_WANT_ALL_IPV4;
> 
> +	if (br_multicast_has_querier_adjacent(bat_priv->soft_iface, ETH_P_IPV6))
> +		mcast_data.flags |= BATADV_MCAST_WANT_ALL_IPV6;
> +
> +skip:
>  	if (!bat_priv->mcast.enabled ||
>  	    mcast_data.flags != bat_priv->mcast.flags) {
>  		batadv_tvlv_container_register(bat_priv, BATADV_TVLV_MCAST, 1,

Please find a more intuitive goto label ('skip' is quite generic).


> @@ -233,13 +344,17 @@ void batadv_mcast_mla_update(struct batadv_priv
> *bat_priv) int ret;
> 
>  	if (!batadv_mcast_mla_tvlv_update(bat_priv))
> -		goto update;
> +		goto skip;
> 
>  	ret = batadv_mcast_mla_softif_get(soft_iface, &mcast_list);
>  	if (ret < 0)
>  		goto out;
> 
> -update:
> +	ret = batadv_mcast_mla_bridge_get(soft_iface, &mcast_list);
> +	if (ret < 0)
> +		goto out;
> +
> +skip:
>  	batadv_mcast_mla_tt_retract(bat_priv, &mcast_list);
>  	batadv_mcast_mla_tt_add(bat_priv, &mcast_list);

Again, 'skip' isn't very self-explanatory.

Cheers,
Marek
  
Linus Lüssing June 17, 2014, 10:24 p.m. UTC | #2
On Tue, Jun 17, 2014 at 10:50:34PM +0800, Marek Lindner wrote:
> On Thursday 12 June 2014 02:00:27 Linus Lüssing wrote:
> > -	netif_addr_lock_bh(dev);
> > +	netif_addr_lock_bh(bridge ? bridge : dev);
> >  	netdev_for_each_mc_addr(mc_list_entry, dev) {
> >  		new = kmalloc(sizeof(*new), GFP_ATOMIC);
> >  		if (!new) {
> 
> Maybe I am missing something but shouldn't we fetch the entries from the 
> bridge interface instead of dev ?

Ups, right, good catch! The 'netdev_for_each...' should be for "bridge",
not "dev" if "bridge" exists.

> > +static void batadv_mcast_mla_br_addr_cpy(char *dst, const struct br_ip
> > *src) +{
> > +	if (src->proto == htons(ETH_P_IP)) {
> > +		/* RFC 1112 */
> > +		memcpy(dst, "\x01\x00\x5e", 3);
> > +		memcpy(dst + 3, ((char *)&src->u.ip4) + 1, ETH_ALEN - 3);
> > +		dst[3] &= 0x7F;
> > +	}
> > +#if IS_ENABLED(CONFIG_IPV6)
> > +	else if (src->proto == htons(ETH_P_IPV6)) {
> > +		/* RFC 2464 */
> > +		memcpy(dst, "\x33\x33", 2);
> > +		memcpy(dst + 2, &src->u.ip6.s6_addr32[3],
> > +		       sizeof(src->u.ip6.s6_addr32[3]));
> > +	}
> > +#endif
> > +	else
> > +		memset(dst, 0, ETH_ALEN);
> > +}
> 
> Is there no cleaner way to do this ? "\x01\x00\x5e" and "\x33\x33" looks 
> pretty hackish. Are there no defines for mcast prefixes somewhere ?

Oh, indeed there is: The bridge code pointed me to
"ip_eth_mc_map()" and "ipv6_eth_mc_map()", making that a one-liner
each :).

> 
> 
> > +/**
> > + * batadv_mcast_mla_bridge_get - get bridged-in multicast listeners
> > + * @dev: a bridge slave whose bridge to collect multicast addresses from
> > + * @mcast_list: a list to put found addresses into
> > + *
> > + * Collects multicast addresses of the bridged-in multicast listeners
> > + * from the bridge on top of the given soft interface, dev, in the
> > + * given mcast_list.
> > + *
> > + * Returns -ENOMEM on memory allocation error or the number of
> > + * items added to the mcast_list otherwise.
> > + */
> > +static int batadv_mcast_mla_bridge_get(struct net_device *dev,
> > +				       struct hlist_head *mcast_list)
> > +{
> 
> That is probably where my confusion is coming from. Why do we query the bridge 
> interface again ?

A batman-adv node needs to know on/behind which other batman-adv
node a multicast listener exists to be able to know where it needs
to forward multicast packets to. A multicast listener can either
sit on bat0, on a bridge on top of bat0. Or in this case, that's
what "mla_bridge_get" is for, behind the bridge.

We usually don't have direct access to the kernel of weird Windows
machines behind a bridge. Luckily a protocol, Multicast Listener
Discovery, exists to detect such multicast listeners.

The bridge code has multicast snooping already, so it already
memorizes what multicast listeners it has behind it's bridge.
That's what we are fetching here. To add these to our local TT to
announce them through the mesh.

> 
> 
> > @@ -195,19 +306,18 @@ static bool batadv_mcast_mla_tvlv_update(struct
> > batadv_priv *bat_priv) mcast_data.flags = BATADV_NO_FLAGS;
> >  	memset(mcast_data.reserved, 0, sizeof(mcast_data.reserved));
> > 
> > -	/* Avoid attaching MLAs, if there is a bridge on top of our soft
> > -	 * interface, we don't support that yet (TODO)
> > -	 */
> > -	if (batadv_mcast_has_bridge(bat_priv)) {
> > -		if (bat_priv->mcast.enabled) {
> > -			batadv_tvlv_container_unregister(bat_priv,
> > -							 BATADV_TVLV_MCAST, 1);
> > -			bat_priv->mcast.enabled = false;
> > -		}
> > +	if (!batadv_mcast_has_bridge(bat_priv))
> > +		goto skip;
> > 
> > -		return false;
> > -	}
> > +	mcast_data.flags |= BATADV_MCAST_WANT_ALL_UNSNOOPABLES;
> > +
> > +	if (br_multicast_has_querier_adjacent(bat_priv->soft_iface, ETH_P_IP))
> > +		mcast_data.flags |= BATADV_MCAST_WANT_ALL_IPV4;
> > 
> > +	if (br_multicast_has_querier_adjacent(bat_priv->soft_iface, ETH_P_IPV6))
> > +		mcast_data.flags |= BATADV_MCAST_WANT_ALL_IPV6;
> > +
> > +skip:
> >  	if (!bat_priv->mcast.enabled ||
> >  	    mcast_data.flags != bat_priv->mcast.flags) {
> >  		batadv_tvlv_container_register(bat_priv, BATADV_TVLV_MCAST, 1,
> 
> Please find a more intuitive goto label ('skip' is quite generic).

Oki doki, good point.

> 
> 
> > @@ -233,13 +344,17 @@ void batadv_mcast_mla_update(struct batadv_priv
> > *bat_priv) int ret;
> > 
> >  	if (!batadv_mcast_mla_tvlv_update(bat_priv))
> > -		goto update;
> > +		goto skip;
> > 
> >  	ret = batadv_mcast_mla_softif_get(soft_iface, &mcast_list);
> >  	if (ret < 0)
> >  		goto out;
> > 
> > -update:
> > +	ret = batadv_mcast_mla_bridge_get(soft_iface, &mcast_list);
> > +	if (ret < 0)
> > +		goto out;
> > +
> > +skip:
> >  	batadv_mcast_mla_tt_retract(bat_priv, &mcast_list);
> >  	batadv_mcast_mla_tt_add(bat_priv, &mcast_list);
> 
> Again, 'skip' isn't very self-explanatory.

K, right!

> 
> Cheers,
> Marek
> 

Cheers, Linus
  
Marek Lindner June 18, 2014, 4:53 a.m. UTC | #3
On Wednesday 18 June 2014 00:24:14 Linus Lüssing wrote:
> A batman-adv node needs to know on/behind which other batman-adv
> node a multicast listener exists to be able to know where it needs
> to forward multicast packets to. A multicast listener can either
> sit on bat0, on a bridge on top of bat0. Or in this case, that's
> what "mla_bridge_get" is for, behind the bridge.
> 
> We usually don't have direct access to the kernel of weird Windows
> machines behind a bridge. Luckily a protocol, Multicast Listener
> Discovery, exists to detect such multicast listeners.
> 
> The bridge code has multicast snooping already, so it already
> memorizes what multicast listeners it has behind it's bridge.
> That's what we are fetching here. To add these to our local TT to
> announce them through the mesh.

Thanks for the clarifications but that does not address my question. To be 
more precise: In batadv_mcast_mla_update() your code is calling 
batadv_mcast_mla_softif_get() which queries the bridge interface (if bridged). 
A couple of lines later batadv_mcast_mla_bridge_get() is called (if bridged). 
It looks to me the code queries the bridge twice ?

Then, I am comparing the kernel doc to get the difference but I see none:

 * batadv_mcast_mla_softif_get - get softif multicast listeners
 * @dev: the device to collect multicast addresses from
 * @mcast_list: a list to put found addresses into
 *
 * Collect multicast addresses of the local multicast listeners
 * on the given soft interface, dev, in the given mcast_list.
 *
 * If there is a bridge interface on top of dev, collect from that one
 * instead.

 ===============================================================

 * batadv_mcast_mla_bridge_get - get bridged-in multicast listeners
 * @dev: a bridge slave whose bridge to collect multicast addresses from
 * @mcast_list: a list to put found addresses into
 *
 * Collects multicast addresses of the bridged-in multicast listeners
 * from the bridge on top of the given soft interface, dev, in the
 * given mcast_list.

What is the difference between these calls and why doesn't the code explain 
that difference ?

Cheers,
Marek
  
Linus Lüssing June 20, 2014, 3:57 p.m. UTC | #4
On Wed, Jun 18, 2014 at 12:53:20PM +0800, Marek Lindner wrote:
> Thanks for the clarifications but that does not address my question. To be 
> more precise: In batadv_mcast_mla_update() your code is calling 
> batadv_mcast_mla_softif_get() which queries the bridge interface (if bridged). 
> A couple of lines later batadv_mcast_mla_bridge_get() is called (if bridged). 
> It looks to me the code queries the bridge twice ?
> 
> Then, I am comparing the kernel doc to get the difference but I see none:
> 
>  * batadv_mcast_mla_softif_get - get softif multicast listeners
>  * @dev: the device to collect multicast addresses from
>  * @mcast_list: a list to put found addresses into
>  *
>  * Collect multicast addresses of the local multicast listeners
>  * on the given soft interface, dev, in the given mcast_list.
>  *
>  * If there is a bridge interface on top of dev, collect from that one
>  * instead.
> 
>  ===============================================================
> 
>  * batadv_mcast_mla_bridge_get - get bridged-in multicast listeners
>  * @dev: a bridge slave whose bridge to collect multicast addresses from
>  * @mcast_list: a list to put found addresses into
>  *
>  * Collects multicast addresses of the bridged-in multicast listeners
>  * from the bridge on top of the given soft interface, dev, in the
>  * given mcast_list.
> 
> What is the difference between these calls and why doesn't the code explain 
> that difference ?

Okay, another try, now with pictures :) :


So far we are doing this:

https://metameute.de/~tux/batman-adv/multicast-listener-fetching-no-bridge.png

So we are able to fetch local listeners (with "local" I mean
listeners which are present on the same kernel) from bat0
via batadv_mcast_mla_softif_get(). And are announcing them in the
mesh cloud so that multicast senders know which nodes they need to
send their data to.

This box with the local listener could be an embedded router running
batman-adv receiving music via multicast and playing it directly over
its USB sound card.

---

With this patch batadv_mla_bridge_get() is being added:

https://metameute.de/~tux/batman-adv/multicast-listener-fetching-with-bridge.png

Now we are able to detect bridged-in listeners (with "bridged-in" I
mean listeners behind the bridge of a batman-adv node, some
alien device). For instance this could be a non-Linux laptop
wanting to receive the same or a different multicast music stream.

Also note, that in the bridge-case batadv_mcast_mla_softif_get()
gets its local listener information from a different device now,
marked in orange: Just like you wouldn't use IP addresses+routes
on bat0 anymore but br0 instead, also the local multicast
listeners will be registering to br0 instead of bat0 now. That's
where the "bridge ? bridge : dev" code changes in this patch for
batadv_mcast_mla_softif_get() come from.

---

tl;dr: batadv_mla_softif_get() is for multicast listeners *on* a
batman-adv node, batadv_mla_bridge_get() for multicast listeners
*behind* the bridge of a batman-adv node.


If the pictures are somehow helpful and if others feel this is
worth a new wiki page in the batadv-multicast-docs, I can create
one. (Also, I hope especially the second one isn't too overloaded,
if someone has ideas how to simplify it, I'd be glad to hear about
it.)

Cheers, Linus
  
Marek Lindner June 21, 2014, 6:57 a.m. UTC | #5
On Friday 20 June 2014 17:57:12 Linus Lüssing wrote:
> tl;dr: batadv_mla_softif_get() is for multicast listeners *on* a
> batman-adv node, batadv_mla_bridge_get() for multicast listeners
> *behind* the bridge of a batman-adv node.

Ok, I get it now. The diagrams certainly helped. Please highlight the 
difference between these functions in the kernel doc.

Thanks,
Marek
  
Simon Wunderlich June 21, 2014, 12:09 p.m. UTC | #6
> On Wed, Jun 18, 2014 at 12:53:20PM +0800, Marek Lindner wrote:
> > Thanks for the clarifications but that does not address my question. To
> > be more precise: In batadv_mcast_mla_update() your code is calling
> > batadv_mcast_mla_softif_get() which queries the bridge interface (if
> > bridged). A couple of lines later batadv_mcast_mla_bridge_get() is
> > called (if bridged). It looks to me the code queries the bridge twice ?
> > 
> > Then, I am comparing the kernel doc to get the difference but I see none:
> >  * batadv_mcast_mla_softif_get - get softif multicast listeners
> >  * @dev: the device to collect multicast addresses from
> >  * @mcast_list: a list to put found addresses into
> >  *
> >  * Collect multicast addresses of the local multicast listeners
> >  * on the given soft interface, dev, in the given mcast_list.
> >  *
> >  * If there is a bridge interface on top of dev, collect from that one
> >  * instead.
> >  
> >  ===============================================================
> >  
> >  * batadv_mcast_mla_bridge_get - get bridged-in multicast listeners
> >  * @dev: a bridge slave whose bridge to collect multicast addresses from
> >  * @mcast_list: a list to put found addresses into
> >  *
> >  * Collects multicast addresses of the bridged-in multicast listeners
> >  * from the bridge on top of the given soft interface, dev, in the
> >  * given mcast_list.
> > 
> > What is the difference between these calls and why doesn't the code
> > explain that difference ?
> 
> Okay, another try, now with pictures :) :
> 
> 
> So far we are doing this:
> 
> https://metameute.de/~tux/batman-adv/multicast-listener-fetching-no-bridge.
> png
> 
> So we are able to fetch local listeners (with "local" I mean
> listeners which are present on the same kernel) from bat0
> via batadv_mcast_mla_softif_get(). And are announcing them in the
> mesh cloud so that multicast senders know which nodes they need to
> send their data to.
> 
> This box with the local listener could be an embedded router running
> batman-adv receiving music via multicast and playing it directly over
> its USB sound card.
> 
> ---
> 
> With this patch batadv_mla_bridge_get() is being added:
> 
> https://metameute.de/~tux/batman-adv/multicast-listener-fetching-with-bridg
> e.png
> 
> Now we are able to detect bridged-in listeners (with "bridged-in" I
> mean listeners behind the bridge of a batman-adv node, some
> alien device). For instance this could be a non-Linux laptop
> wanting to receive the same or a different multicast music stream.
> 
> Also note, that in the bridge-case batadv_mcast_mla_softif_get()
> gets its local listener information from a different device now,
> marked in orange: Just like you wouldn't use IP addresses+routes
> on bat0 anymore but br0 instead, also the local multicast
> listeners will be registering to br0 instead of bat0 now. That's
> where the "bridge ? bridge : dev" code changes in this patch for
> batadv_mcast_mla_softif_get() come from.
> 
> ---
> 
> tl;dr: batadv_mla_softif_get() is for multicast listeners *on* a
> batman-adv node, batadv_mla_bridge_get() for multicast listeners
> *behind* the bridge of a batman-adv node.
> 
> 
> If the pictures are somehow helpful and if others feel this is
> worth a new wiki page in the batadv-multicast-docs, I can create
> one. (Also, I hope especially the second one isn't too overloaded,
> if someone has ideas how to simplify it, I'd be glad to hear about
> it.)

Thanks for the pictures! I think it would be great to have them in the wiki in 
any case, so please add them (maybe as a subpage for now). :)

I'm still preparing to get this patch tested, I can't test it in my usual 
kvm/OpenWRT environment as it depends on the latest kernel ...  but I'm 
preparing that now. :)

Cheers,
	Simon
  

Patch

diff --git a/compat.c b/compat.c
index 3dbf9d2..7187a34 100644
--- a/compat.c
+++ b/compat.c
@@ -109,3 +109,18 @@  void batadv_free_rcu_tvlv_handler(struct rcu_head *rcu)
 }
 
 #endif /* < KERNEL_VERSION(3, 0, 0) */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0)
+
+int br_multicast_list_adjacent(struct net_device *dev,
+			       struct list_head *br_ip_list)
+{
+	return 0;
+}
+
+bool br_multicast_has_querier_adjacent(struct net_device *dev, int proto)
+{
+	return true;
+}
+
+#endif /* < KERNEL_VERSION(3, 16, 0) */
diff --git a/compat.h b/compat.h
index 5eb5fe6..24a86cd 100644
--- a/compat.h
+++ b/compat.h
@@ -240,6 +240,12 @@  static inline void skb_reset_mac_len(struct sk_buff *skb)
 
 #endif /* < KERNEL_VERSION(3, 0, 0) */
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0)
+
+#define IS_ENABLED(x) defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+
+#endif /* < KERNEL_VERSION(3, 1, 0) */
+
 #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0)
 
 #define batadv_interface_add_vid(x, y, z) \
@@ -438,4 +444,28 @@  static int __batadv_interface_kill_vid(struct net_device *dev, __be16 proto,\
 
 #endif /* < KERNEL_VERSION(3, 14, 0) */
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0)
+
+int br_multicast_list_adjacent(struct net_device *dev,
+			       struct list_head *br_ip_list);
+bool br_multicast_has_querier_adjacent(struct net_device *dev, int proto);
+
+struct br_ip {
+	union {
+		__be32  ip4;
+#if IS_ENABLED(CONFIG_IPV6)
+		struct in6_addr ip6;
+#endif
+	} u;
+	__be16          proto;
+	__u16           vid;
+};
+
+struct br_ip_list {
+	struct list_head list;
+	struct br_ip addr;
+};
+
+#endif /* < KERNEL_VERSION(3, 16, 0) */
+
 #endif /* _NET_BATMAN_ADV_COMPAT_H_ */
diff --git a/main.h b/main.h
index 87e7196..d34dc5d 100644
--- a/main.h
+++ b/main.h
@@ -169,6 +169,7 @@  enum batadv_uev_type {
 #include <linux/netdevice.h>	/* netdevice */
 #include <linux/etherdevice.h>  /* ethernet address classification */
 #include <linux/if_ether.h>	/* ethernet header */
+#include <linux/if_bridge.h>	/* bridge / multicast-snooping communication */
 #include <linux/poll.h>		/* poll_table */
 #include <linux/kthread.h>	/* kernel threads */
 #include <linux/pkt_sched.h>	/* schedule types */
diff --git a/multicast.c b/multicast.c
index 96b66fd..e4a8acd 100644
--- a/multicast.c
+++ b/multicast.c
@@ -23,6 +23,29 @@ 
 #include "multicast.h"
 
 /**
+ * batadv_mcast_get_bridge - get the bridge on top of the softif if it exists
+ * @soft_iface: netdev struct of the mesh interface
+ *
+ * Returns either a bridge interface on top of our soft interface or
+ * NULL if no such bridge exists.
+ */
+static struct net_device *batadv_mcast_get_bridge(struct net_device *soft_iface)
+{
+	struct net_device *upper = soft_iface;
+
+	rcu_read_lock();
+	do {
+		upper = netdev_master_upper_dev_get_rcu(upper);
+	} while (upper && !(upper->priv_flags & IFF_EBRIDGE));
+
+	if (upper)
+		dev_hold(upper);
+	rcu_read_unlock();
+
+	return upper;
+}
+
+/**
  * batadv_mcast_mla_softif_get - get softif multicast listeners
  * @dev: the device to collect multicast addresses from
  * @mcast_list: a list to put found addresses into
@@ -30,17 +53,21 @@ 
  * Collect multicast addresses of the local multicast listeners
  * on the given soft interface, dev, in the given mcast_list.
  *
+ * If there is a bridge interface on top of dev, collect from that one
+ * instead.
+ *
  * Returns -ENOMEM on memory allocation error or the number of
  * items added to the mcast_list otherwise.
  */
 static int batadv_mcast_mla_softif_get(struct net_device *dev,
 				       struct hlist_head *mcast_list)
 {
+	struct net_device *bridge = batadv_mcast_get_bridge(dev);
 	struct netdev_hw_addr *mc_list_entry;
 	struct batadv_hw_addr *new;
 	int ret = 0;
 
-	netif_addr_lock_bh(dev);
+	netif_addr_lock_bh(bridge ? bridge : dev);
 	netdev_for_each_mc_addr(mc_list_entry, dev) {
 		new = kmalloc(sizeof(*new), GFP_ATOMIC);
 		if (!new) {
@@ -52,7 +79,10 @@  static int batadv_mcast_mla_softif_get(struct net_device *dev,
 		hlist_add_head(&new->list, mcast_list);
 		ret++;
 	}
-	netif_addr_unlock_bh(dev);
+	netif_addr_unlock_bh(bridge ? bridge : dev);
+
+	if (bridge)
+		dev_put(bridge);
 
 	return ret;
 }
@@ -78,6 +108,87 @@  static bool batadv_mcast_mla_is_duplicate(uint8_t *mcast_addr,
 }
 
 /**
+ * batadv_mcast_mla_br_addr_cpy - copy a bridge multicast address
+ * @dst: destination to write to - a multicast MAC address
+ * @src: source to read from - a multicast IP address
+ *
+ * Converts a given multicast IPv4/IPv6 address from a bridge
+ * to its matching multicast MAC address and copies it into the given
+ * destination buffer.
+ *
+ * Caller needs to make sure the destination buffer can hold
+ * at least ETH_ALEN bytes.
+ */
+static void batadv_mcast_mla_br_addr_cpy(char *dst, const struct br_ip *src)
+{
+	if (src->proto == htons(ETH_P_IP)) {
+		/* RFC 1112 */
+		memcpy(dst, "\x01\x00\x5e", 3);
+		memcpy(dst + 3, ((char *)&src->u.ip4) + 1, ETH_ALEN - 3);
+		dst[3] &= 0x7F;
+	}
+#if IS_ENABLED(CONFIG_IPV6)
+	else if (src->proto == htons(ETH_P_IPV6)) {
+		/* RFC 2464 */
+		memcpy(dst, "\x33\x33", 2);
+		memcpy(dst + 2, &src->u.ip6.s6_addr32[3],
+		       sizeof(src->u.ip6.s6_addr32[3]));
+	}
+#endif
+	else
+		memset(dst, 0, ETH_ALEN);
+}
+
+/**
+ * batadv_mcast_mla_bridge_get - get bridged-in multicast listeners
+ * @dev: a bridge slave whose bridge to collect multicast addresses from
+ * @mcast_list: a list to put found addresses into
+ *
+ * Collects multicast addresses of the bridged-in multicast listeners
+ * from the bridge on top of the given soft interface, dev, in the
+ * given mcast_list.
+ *
+ * Returns -ENOMEM on memory allocation error or the number of
+ * items added to the mcast_list otherwise.
+ */
+static int batadv_mcast_mla_bridge_get(struct net_device *dev,
+				       struct hlist_head *mcast_list)
+{
+	struct list_head bridge_mcast_list = LIST_HEAD_INIT(bridge_mcast_list);
+	struct br_ip_list *br_ip_entry, *tmp;
+	struct batadv_hw_addr *new;
+	uint8_t mcast_addr[ETH_ALEN];
+	int ret;
+
+	ret = br_multicast_list_adjacent(dev, &bridge_mcast_list);
+	if (ret < 0)
+		goto out;
+
+	list_for_each_entry(br_ip_entry, &bridge_mcast_list, list) {
+		batadv_mcast_mla_br_addr_cpy(mcast_addr, &br_ip_entry->addr);
+		if (batadv_mcast_mla_is_duplicate(mcast_addr, mcast_list))
+			continue;
+
+		new = kmalloc(sizeof(*new), GFP_ATOMIC);
+		if (!new) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		ether_addr_copy(new->addr, mcast_addr);
+		hlist_add_head(&new->list, mcast_list);
+	}
+
+out:
+	list_for_each_entry_safe(br_ip_entry, tmp, &bridge_mcast_list, list) {
+		list_del(&br_ip_entry->list);
+		kfree(br_ip_entry);
+	}
+
+	return ret;
+}
+
+/**
  * batadv_mcast_mla_list_free - free a list of multicast addresses
  * @mcast_list: the list to free
  *
@@ -185,8 +296,8 @@  static bool batadv_mcast_has_bridge(struct batadv_priv *bat_priv)
  * Updates the own multicast tvlv with our current multicast related settings,
  * capabilities and inabilities.
  *
- * Returns true if the tvlv container is registered afterwards. Otherwise
- * returns false.
+ * Returns false if we want all IPv4 && IPv6 multicast traffic and true
+ * otherwise.
  */
 static bool batadv_mcast_mla_tvlv_update(struct batadv_priv *bat_priv)
 {
@@ -195,19 +306,18 @@  static bool batadv_mcast_mla_tvlv_update(struct batadv_priv *bat_priv)
 	mcast_data.flags = BATADV_NO_FLAGS;
 	memset(mcast_data.reserved, 0, sizeof(mcast_data.reserved));
 
-	/* Avoid attaching MLAs, if there is a bridge on top of our soft
-	 * interface, we don't support that yet (TODO)
-	 */
-	if (batadv_mcast_has_bridge(bat_priv)) {
-		if (bat_priv->mcast.enabled) {
-			batadv_tvlv_container_unregister(bat_priv,
-							 BATADV_TVLV_MCAST, 1);
-			bat_priv->mcast.enabled = false;
-		}
+	if (!batadv_mcast_has_bridge(bat_priv))
+		goto skip;
 
-		return false;
-	}
+	mcast_data.flags |= BATADV_MCAST_WANT_ALL_UNSNOOPABLES;
+
+	if (br_multicast_has_querier_adjacent(bat_priv->soft_iface, ETH_P_IP))
+		mcast_data.flags |= BATADV_MCAST_WANT_ALL_IPV4;
 
+	if (br_multicast_has_querier_adjacent(bat_priv->soft_iface, ETH_P_IPV6))
+		mcast_data.flags |= BATADV_MCAST_WANT_ALL_IPV6;
+
+skip:
 	if (!bat_priv->mcast.enabled ||
 	    mcast_data.flags != bat_priv->mcast.flags) {
 		batadv_tvlv_container_register(bat_priv, BATADV_TVLV_MCAST, 1,
@@ -216,7 +326,8 @@  static bool batadv_mcast_mla_tvlv_update(struct batadv_priv *bat_priv)
 		bat_priv->mcast.enabled = true;
 	}
 
-	return true;
+	return !(mcast_data.flags &
+		 (BATADV_MCAST_WANT_ALL_IPV4 + BATADV_MCAST_WANT_ALL_IPV6));
 }
 
 /**
@@ -233,13 +344,17 @@  void batadv_mcast_mla_update(struct batadv_priv *bat_priv)
 	int ret;
 
 	if (!batadv_mcast_mla_tvlv_update(bat_priv))
-		goto update;
+		goto skip;
 
 	ret = batadv_mcast_mla_softif_get(soft_iface, &mcast_list);
 	if (ret < 0)
 		goto out;
 
-update:
+	ret = batadv_mcast_mla_bridge_get(soft_iface, &mcast_list);
+	if (ret < 0)
+		goto out;
+
+skip:
 	batadv_mcast_mla_tt_retract(bat_priv, &mcast_list);
 	batadv_mcast_mla_tt_add(bat_priv, &mcast_list);