bridge: disable snooping if there is no querier

If there is no querier on a link then we won't get periodic reports and
therefore won't be able to learn about multicast listeners behind ports,
potentially leading to lost multicast packets, especially for multicast
listeners that joined before the creation of the bridge.

These lost multicast packets can appear since c5c2326059
("bridge: Add multicast_querier toggle and disable queries by default")
in particular.

With this patch we are flooding multicast packets if our querier is
disabled and if we didn't detect any other querier.

A grace period of the Maximum Response Delay of the querier is added to
give multicast responses enough time to arrive and to be learned from
before disabling the flooding behaviour again.

Signed-off-by: Linus Lüssing <linus.luessing@web.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Linus Lüssing 2013-08-01 01:06:20 +02:00 committed by David S. Miller
parent cf3c4c0306
commit b00589af3b
4 changed files with 46 additions and 11 deletions

View file

@ -70,7 +70,8 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
}
mdst = br_mdb_get(br, skb, vid);
if (mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb))
if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
br_multicast_querier_exists(br))
br_multicast_deliver(mdst, skb);
else
br_flood_deliver(br, skb, false);

View file

@ -101,7 +101,8 @@ int br_handle_frame_finish(struct sk_buff *skb)
unicast = false;
} else if (is_multicast_ether_addr(dest)) {
mdst = br_mdb_get(br, skb, vid);
if (mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) {
if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
br_multicast_querier_exists(br)) {
if ((mdst && mdst->mglist) ||
br_multicast_is_router(br))
skb2 = skb;

View file

@ -1014,6 +1014,16 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
}
#endif
static void br_multicast_update_querier_timer(struct net_bridge *br,
unsigned long max_delay)
{
if (!timer_pending(&br->multicast_querier_timer))
br->multicast_querier_delay_time = jiffies + max_delay;
mod_timer(&br->multicast_querier_timer,
jiffies + br->multicast_querier_interval);
}
/*
* Add port to router_list
* list is maintained ordered by pointer value
@ -1064,11 +1074,11 @@ static void br_multicast_mark_router(struct net_bridge *br,
static void br_multicast_query_received(struct net_bridge *br,
struct net_bridge_port *port,
int saddr)
int saddr,
unsigned long max_delay)
{
if (saddr)
mod_timer(&br->multicast_querier_timer,
jiffies + br->multicast_querier_interval);
br_multicast_update_querier_timer(br, max_delay);
else if (timer_pending(&br->multicast_querier_timer))
return;
@ -1096,8 +1106,6 @@ static int br_ip4_multicast_query(struct net_bridge *br,
(port && port->state == BR_STATE_DISABLED))
goto out;
br_multicast_query_received(br, port, !!iph->saddr);
group = ih->group;
if (skb->len == sizeof(*ih)) {
@ -1121,6 +1129,8 @@ static int br_ip4_multicast_query(struct net_bridge *br,
IGMPV3_MRC(ih3->code) * (HZ / IGMP_TIMER_SCALE) : 1;
}
br_multicast_query_received(br, port, !!iph->saddr, max_delay);
if (!group)
goto out;
@ -1176,8 +1186,6 @@ static int br_ip6_multicast_query(struct net_bridge *br,
(port && port->state == BR_STATE_DISABLED))
goto out;
br_multicast_query_received(br, port, !ipv6_addr_any(&ip6h->saddr));
if (skb->len == sizeof(*mld)) {
if (!pskb_may_pull(skb, sizeof(*mld))) {
err = -EINVAL;
@ -1198,6 +1206,9 @@ static int br_ip6_multicast_query(struct net_bridge *br,
max_delay = mld2q->mld2q_mrc ? MLDV2_MRC(ntohs(mld2q->mld2q_mrc)) : 1;
}
br_multicast_query_received(br, port, !ipv6_addr_any(&ip6h->saddr),
max_delay);
if (!group)
goto out;
@ -1643,6 +1654,8 @@ void br_multicast_init(struct net_bridge *br)
br->multicast_querier_interval = 255 * HZ;
br->multicast_membership_interval = 260 * HZ;
br->multicast_querier_delay_time = 0;
spin_lock_init(&br->multicast_lock);
setup_timer(&br->multicast_router_timer,
br_multicast_local_router_expired, 0);
@ -1831,6 +1844,8 @@ int br_multicast_toggle(struct net_bridge *br, unsigned long val)
int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
{
unsigned long max_delay;
val = !!val;
spin_lock_bh(&br->multicast_lock);
@ -1838,8 +1853,14 @@ int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
goto unlock;
br->multicast_querier = val;
if (val)
br_multicast_start_querier(br);
if (!val)
goto unlock;
max_delay = br->multicast_query_response_interval;
if (!timer_pending(&br->multicast_querier_timer))
br->multicast_querier_delay_time = jiffies + max_delay;
br_multicast_start_querier(br);
unlock:
spin_unlock_bh(&br->multicast_lock);

View file

@ -267,6 +267,7 @@ struct net_bridge
unsigned long multicast_query_interval;
unsigned long multicast_query_response_interval;
unsigned long multicast_startup_query_interval;
unsigned long multicast_querier_delay_time;
spinlock_t multicast_lock;
struct net_bridge_mdb_htable __rcu *mdb;
@ -501,6 +502,13 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
(br->multicast_router == 1 &&
timer_pending(&br->multicast_router_timer));
}
static inline bool br_multicast_querier_exists(struct net_bridge *br)
{
return time_is_before_jiffies(br->multicast_querier_delay_time) &&
(br->multicast_querier ||
timer_pending(&br->multicast_querier_timer));
}
#else
static inline int br_multicast_rcv(struct net_bridge *br,
struct net_bridge_port *port,
@ -557,6 +565,10 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
{
return 0;
}
static inline bool br_multicast_querier_exists(struct net_bridge *br)
{
return false;
}
static inline void br_mdb_init(void)
{
}