Skip to content

Commit

Permalink
Bridge: Fix ipv6 mc snooping if bridge has no ipv6 address
Browse files Browse the repository at this point in the history
The bridge is falsly dropping ipv6 mulitcast packets if there is:
 1. No ipv6 address assigned on the brigde.
 2. No external mld querier present.
 3. The internal querier enabled.

When the bridge fails to build mld queries, because it has no
ipv6 address, it slilently returns, but keeps the local querier enabled.
This specific case causes confusing packet loss.

Ipv6 multicast snooping can only work if:
 a) An external querier is present
 OR
 b) The bridge has an ipv6 address an is capable of sending own queries

Otherwise it has to forward/flood the ipv6 multicast traffic,
because snooping cannot work.

This patch fixes the issue by adding a flag to the bridge struct that
indicates that there is currently no ipv6 address assinged to the bridge
and returns a false state for the local querier in
__br_multicast_querier_exists().

Special thanks to Linus Lüssing.

Fixes: d1d81d4 ("bridge: check return value of ipv6_dev_get_saddr()")
Signed-off-by: Daniel Danzberger <daniel@dd-wrt.com>
Acked-by: Linus Lüssing <linus.luessing@c0d3.blue>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
daniel authored and davem330 committed Jun 28, 2016
1 parent f299a02 commit 0888d5f
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 4 deletions.
4 changes: 4 additions & 0 deletions net/bridge/br_multicast.c
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,11 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br,
if (ipv6_dev_get_saddr(dev_net(br->dev), br->dev, &ip6h->daddr, 0,
&ip6h->saddr)) {
kfree_skb(skb);
br->has_ipv6_addr = 0;
return NULL;
}

br->has_ipv6_addr = 1;
ipv6_eth_mc_map(&ip6h->daddr, eth->h_dest);

hopopt = (u8 *)(ip6h + 1);
Expand Down Expand Up @@ -1745,6 +1748,7 @@ void br_multicast_init(struct net_bridge *br)
br->ip6_other_query.delay_time = 0;
br->ip6_querier.port = NULL;
#endif
br->has_ipv6_addr = 1;

spin_lock_init(&br->multicast_lock);
setup_timer(&br->multicast_router_timer,
Expand Down
23 changes: 19 additions & 4 deletions net/bridge/br_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ struct net_bridge
u8 multicast_disabled:1;
u8 multicast_querier:1;
u8 multicast_query_use_ifaddr:1;
u8 has_ipv6_addr:1;

u32 hash_elasticity;
u32 hash_max;
Expand Down Expand Up @@ -588,21 +589,35 @@ static inline bool br_multicast_is_router(struct net_bridge *br)

static inline bool
__br_multicast_querier_exists(struct net_bridge *br,
struct bridge_mcast_other_query *querier)
struct bridge_mcast_other_query *querier,
const bool is_ipv6)
{
bool own_querier_enabled;

if (br->multicast_querier) {
if (is_ipv6 && !br->has_ipv6_addr)
own_querier_enabled = false;
else
own_querier_enabled = true;
} else {
own_querier_enabled = false;
}

return time_is_before_jiffies(querier->delay_time) &&
(br->multicast_querier || timer_pending(&querier->timer));
(own_querier_enabled || timer_pending(&querier->timer));
}

static inline bool br_multicast_querier_exists(struct net_bridge *br,
struct ethhdr *eth)
{
switch (eth->h_proto) {
case (htons(ETH_P_IP)):
return __br_multicast_querier_exists(br, &br->ip4_other_query);
return __br_multicast_querier_exists(br,
&br->ip4_other_query, false);
#if IS_ENABLED(CONFIG_IPV6)
case (htons(ETH_P_IPV6)):
return __br_multicast_querier_exists(br, &br->ip6_other_query);
return __br_multicast_querier_exists(br,
&br->ip6_other_query, true);
#endif
default:
return false;
Expand Down

0 comments on commit 0888d5f

Please sign in to comment.