Skip to content

Commit

Permalink
Merge branch 'bridge-igmpv3-mldv2-support'
Browse files Browse the repository at this point in the history
Nikolay Aleksandrov says:

====================
bridge: add support for IGMPv3 and MLDv2 querier

This patch-set adds support for IGMPv3 and MLDv2 querier in the bridge.
Two new options which can be toggled via netlink and sysfs are added that
control the version per-bridge:
 multicast_igmp_version - default 2, can be set to 3
 multicast_mld_version - default 1, can be set to 2 (this option is
                         disabled if CONFIG_IPV6=n)

Note that the names do not include "querier", I think that these options
can be re-used later as more IGMPv3 support is added to the bridge so we
can avoid adding more options to switch between v2 and v3 behaviour.

The set uses the already existing br_ip{4,6}_multicast_alloc_query
functions and adds the appropriate header based on the chosen version.

For the initial support I have removed the compatibility implementation
(RFC3376 sec 7.3.1, 7.3.2; RFC3810 sec 8.3.1, 8.3.2), because there are
some details that we need to sort out.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Nov 21, 2016
2 parents fc51f2b + aa2ae3e commit 2fcb58a
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 39 deletions.
2 changes: 2 additions & 0 deletions include/uapi/linux/if_link.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ enum {
IFLA_BR_PAD,
IFLA_BR_VLAN_STATS_ENABLED,
IFLA_BR_MCAST_STATS_ENABLED,
IFLA_BR_MCAST_IGMP_VERSION,
IFLA_BR_MCAST_MLD_VERSION,
__IFLA_BR_MAX,
};

Expand Down
166 changes: 129 additions & 37 deletions net/bridge/br_multicast.c
Original file line number Diff line number Diff line change
Expand Up @@ -365,13 +365,18 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
__be32 group,
u8 *igmp_type)
{
struct igmpv3_query *ihv3;
size_t igmp_hdr_size;
struct sk_buff *skb;
struct igmphdr *ih;
struct ethhdr *eth;
struct iphdr *iph;

igmp_hdr_size = sizeof(*ih);
if (br->multicast_igmp_version == 3)
igmp_hdr_size = sizeof(*ihv3);
skb = netdev_alloc_skb_ip_align(br->dev, sizeof(*eth) + sizeof(*iph) +
sizeof(*ih) + 4);
igmp_hdr_size + 4);
if (!skb)
goto out;

Expand All @@ -396,7 +401,7 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
iph->version = 4;
iph->ihl = 6;
iph->tos = 0xc0;
iph->tot_len = htons(sizeof(*iph) + sizeof(*ih) + 4);
iph->tot_len = htons(sizeof(*iph) + igmp_hdr_size + 4);
iph->id = 0;
iph->frag_off = htons(IP_DF);
iph->ttl = 1;
Expand All @@ -412,17 +417,37 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
skb_put(skb, 24);

skb_set_transport_header(skb, skb->len);
ih = igmp_hdr(skb);
*igmp_type = IGMP_HOST_MEMBERSHIP_QUERY;
ih->type = IGMP_HOST_MEMBERSHIP_QUERY;
ih->code = (group ? br->multicast_last_member_interval :
br->multicast_query_response_interval) /
(HZ / IGMP_TIMER_SCALE);
ih->group = group;
ih->csum = 0;
ih->csum = ip_compute_csum((void *)ih, sizeof(struct igmphdr));
skb_put(skb, sizeof(*ih));

switch (br->multicast_igmp_version) {
case 2:
ih = igmp_hdr(skb);
ih->type = IGMP_HOST_MEMBERSHIP_QUERY;
ih->code = (group ? br->multicast_last_member_interval :
br->multicast_query_response_interval) /
(HZ / IGMP_TIMER_SCALE);
ih->group = group;
ih->csum = 0;
ih->csum = ip_compute_csum((void *)ih, sizeof(*ih));
break;
case 3:
ihv3 = igmpv3_query_hdr(skb);
ihv3->type = IGMP_HOST_MEMBERSHIP_QUERY;
ihv3->code = (group ? br->multicast_last_member_interval :
br->multicast_query_response_interval) /
(HZ / IGMP_TIMER_SCALE);
ihv3->group = group;
ihv3->qqic = br->multicast_query_interval / HZ;
ihv3->nsrcs = 0;
ihv3->resv = 0;
ihv3->suppress = 0;
ihv3->qrv = 2;
ihv3->csum = 0;
ihv3->csum = ip_compute_csum((void *)ihv3, sizeof(*ihv3));
break;
}

skb_put(skb, igmp_hdr_size);
__skb_pull(skb, sizeof(*eth));

out:
Expand All @@ -434,15 +459,20 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br,
const struct in6_addr *grp,
u8 *igmp_type)
{
struct sk_buff *skb;
struct mld2_query *mld2q;
unsigned long interval;
struct ipv6hdr *ip6h;
struct mld_msg *mldq;
size_t mld_hdr_size;
struct sk_buff *skb;
struct ethhdr *eth;
u8 *hopopt;
unsigned long interval;

mld_hdr_size = sizeof(*mldq);
if (br->multicast_mld_version == 2)
mld_hdr_size = sizeof(*mld2q);
skb = netdev_alloc_skb_ip_align(br->dev, sizeof(*eth) + sizeof(*ip6h) +
8 + sizeof(*mldq));
8 + mld_hdr_size);
if (!skb)
goto out;

Expand All @@ -461,7 +491,7 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br,
ip6h = ipv6_hdr(skb);

*(__force __be32 *)ip6h = htonl(0x60000000);
ip6h->payload_len = htons(8 + sizeof(*mldq));
ip6h->payload_len = htons(8 + mld_hdr_size);
ip6h->nexthdr = IPPROTO_HOPOPTS;
ip6h->hop_limit = 1;
ipv6_addr_set(&ip6h->daddr, htonl(0xff020000), 0, 0, htonl(1));
Expand Down Expand Up @@ -489,26 +519,47 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br,

/* ICMPv6 */
skb_set_transport_header(skb, skb->len);
mldq = (struct mld_msg *) icmp6_hdr(skb);

interval = ipv6_addr_any(grp) ?
br->multicast_query_response_interval :
br->multicast_last_member_interval;

*igmp_type = ICMPV6_MGM_QUERY;
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_reserved = 0;
mldq->mld_mca = *grp;

/* checksum */
mldq->mld_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
sizeof(*mldq), IPPROTO_ICMPV6,
csum_partial(mldq,
sizeof(*mldq), 0));
skb_put(skb, sizeof(*mldq));
switch (br->multicast_mld_version) {
case 1:
mldq = (struct mld_msg *)icmp6_hdr(skb);
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_reserved = 0;
mldq->mld_mca = *grp;
mldq->mld_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
sizeof(*mldq), IPPROTO_ICMPV6,
csum_partial(mldq,
sizeof(*mldq),
0));
break;
case 2:
mld2q = (struct mld2_query *)icmp6_hdr(skb);
mld2q->mld2q_mrc = ntohs((u16)jiffies_to_msecs(interval));
mld2q->mld2q_type = ICMPV6_MGM_QUERY;
mld2q->mld2q_code = 0;
mld2q->mld2q_cksum = 0;
mld2q->mld2q_resv1 = 0;
mld2q->mld2q_resv2 = 0;
mld2q->mld2q_suppress = 0;
mld2q->mld2q_qrv = 2;
mld2q->mld2q_nsrcs = 0;
mld2q->mld2q_qqic = br->multicast_query_interval / HZ;
mld2q->mld2q_mca = *grp;
mld2q->mld2q_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
sizeof(*mld2q),
IPPROTO_ICMPV6,
csum_partial(mld2q,
sizeof(*mld2q),
0));
break;
}
skb_put(skb, mld_hdr_size);

__skb_pull(skb, sizeof(*eth));

Expand Down Expand Up @@ -608,7 +659,8 @@ static struct net_bridge_mdb_entry *br_multicast_get_group(
}

struct net_bridge_mdb_entry *br_multicast_new_group(struct net_bridge *br,
struct net_bridge_port *port, struct br_ip *group)
struct net_bridge_port *p,
struct br_ip *group)
{
struct net_bridge_mdb_htable *mdb;
struct net_bridge_mdb_entry *mp;
Expand All @@ -624,7 +676,7 @@ struct net_bridge_mdb_entry *br_multicast_new_group(struct net_bridge *br,
}

hash = br_ip_hash(mdb, group);
mp = br_multicast_get_group(br, port, group, hash);
mp = br_multicast_get_group(br, p, group, hash);
switch (PTR_ERR(mp)) {
case 0:
break;
Expand Down Expand Up @@ -681,9 +733,9 @@ static int br_multicast_add_group(struct net_bridge *br,
struct net_bridge_port *port,
struct br_ip *group)
{
struct net_bridge_mdb_entry *mp;
struct net_bridge_port_group *p;
struct net_bridge_port_group __rcu **pp;
struct net_bridge_port_group *p;
struct net_bridge_mdb_entry *mp;
unsigned long now = jiffies;
int err;

Expand Down Expand Up @@ -861,9 +913,9 @@ static void br_multicast_send_query(struct net_bridge *br,
struct net_bridge_port *port,
struct bridge_mcast_own_query *own_query)
{
unsigned long time;
struct br_ip br_group;
struct bridge_mcast_other_query *other_query = NULL;
struct br_ip br_group;
unsigned long time;

if (!netif_running(br->dev) || br->multicast_disabled ||
!br->multicast_querier)
Expand Down Expand Up @@ -1831,7 +1883,9 @@ void br_multicast_init(struct net_bridge *br)

br->ip4_other_query.delay_time = 0;
br->ip4_querier.port = NULL;
br->multicast_igmp_version = 2;
#if IS_ENABLED(CONFIG_IPV6)
br->multicast_mld_version = 1;
br->ip6_other_query.delay_time = 0;
br->ip6_querier.port = NULL;
#endif
Expand Down Expand Up @@ -2132,6 +2186,44 @@ int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val)
return err;
}

int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val)
{
/* Currently we support only version 2 and 3 */
switch (val) {
case 2:
case 3:
break;
default:
return -EINVAL;
}

spin_lock_bh(&br->multicast_lock);
br->multicast_igmp_version = val;
spin_unlock_bh(&br->multicast_lock);

return 0;
}

#if IS_ENABLED(CONFIG_IPV6)
int br_multicast_set_mld_version(struct net_bridge *br, unsigned long val)
{
/* Currently we support version 1 and 2 */
switch (val) {
case 1:
case 2:
break;
default:
return -EINVAL;
}

spin_lock_bh(&br->multicast_lock);
br->multicast_mld_version = val;
spin_unlock_bh(&br->multicast_lock);

return 0;
}
#endif

/**
* br_multicast_list_adjacent - Returns snooped multicast addresses
* @dev: The bridge port adjacent to which to retrieve addresses
Expand Down
34 changes: 32 additions & 2 deletions net/bridge/br_netlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,8 @@ static const struct nla_policy br_policy[IFLA_BR_MAX + 1] = {
[IFLA_BR_VLAN_DEFAULT_PVID] = { .type = NLA_U16 },
[IFLA_BR_VLAN_STATS_ENABLED] = { .type = NLA_U8 },
[IFLA_BR_MCAST_STATS_ENABLED] = { .type = NLA_U8 },
[IFLA_BR_MCAST_IGMP_VERSION] = { .type = NLA_U8 },
[IFLA_BR_MCAST_MLD_VERSION] = { .type = NLA_U8 },
};

static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
Expand Down Expand Up @@ -1069,6 +1071,26 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
mcast_stats = nla_get_u8(data[IFLA_BR_MCAST_STATS_ENABLED]);
br->multicast_stats_enabled = !!mcast_stats;
}

if (data[IFLA_BR_MCAST_IGMP_VERSION]) {
__u8 igmp_version;

igmp_version = nla_get_u8(data[IFLA_BR_MCAST_IGMP_VERSION]);
err = br_multicast_set_igmp_version(br, igmp_version);
if (err)
return err;
}

#if IS_ENABLED(CONFIG_IPV6)
if (data[IFLA_BR_MCAST_MLD_VERSION]) {
__u8 mld_version;

mld_version = nla_get_u8(data[IFLA_BR_MCAST_MLD_VERSION]);
err = br_multicast_set_mld_version(br, mld_version);
if (err)
return err;
}
#endif
#endif
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
if (data[IFLA_BR_NF_CALL_IPTABLES]) {
Expand Down Expand Up @@ -1135,6 +1157,8 @@ static size_t br_get_size(const struct net_device *brdev)
nla_total_size_64bit(sizeof(u64)) + /* IFLA_BR_MCAST_QUERY_INTVL */
nla_total_size_64bit(sizeof(u64)) + /* IFLA_BR_MCAST_QUERY_RESPONSE_INTVL */
nla_total_size_64bit(sizeof(u64)) + /* IFLA_BR_MCAST_STARTUP_QUERY_INTVL */
nla_total_size(sizeof(u8)) + /* IFLA_BR_MCAST_IGMP_VERSION */
nla_total_size(sizeof(u8)) + /* IFLA_BR_MCAST_MLD_VERSION */
#endif
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
nla_total_size(sizeof(u8)) + /* IFLA_BR_NF_CALL_IPTABLES */
Expand Down Expand Up @@ -1210,9 +1234,15 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
nla_put_u32(skb, IFLA_BR_MCAST_LAST_MEMBER_CNT,
br->multicast_last_member_count) ||
nla_put_u32(skb, IFLA_BR_MCAST_STARTUP_QUERY_CNT,
br->multicast_startup_query_count))
br->multicast_startup_query_count) ||
nla_put_u8(skb, IFLA_BR_MCAST_IGMP_VERSION,
br->multicast_igmp_version))
return -EMSGSIZE;

#if IS_ENABLED(CONFIG_IPV6)
if (nla_put_u8(skb, IFLA_BR_MCAST_MLD_VERSION,
br->multicast_mld_version))
return -EMSGSIZE;
#endif
clockval = jiffies_to_clock_t(br->multicast_last_member_interval);
if (nla_put_u64_64bit(skb, IFLA_BR_MCAST_LAST_MEMBER_INTVL, clockval,
IFLA_BR_PAD))
Expand Down
7 changes: 7 additions & 0 deletions net/bridge/br_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@ struct net_bridge
u32 multicast_last_member_count;
u32 multicast_startup_query_count;

u8 multicast_igmp_version;

unsigned long multicast_last_member_interval;
unsigned long multicast_membership_interval;
unsigned long multicast_querier_interval;
Expand All @@ -353,6 +355,7 @@ struct net_bridge
struct bridge_mcast_other_query ip6_other_query;
struct bridge_mcast_own_query ip6_own_query;
struct bridge_mcast_querier ip6_querier;
u8 multicast_mld_version;
#endif /* IS_ENABLED(CONFIG_IPV6) */
#endif

Expand Down Expand Up @@ -582,6 +585,10 @@ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val);
int br_multicast_toggle(struct net_bridge *br, unsigned long val);
int br_multicast_set_querier(struct net_bridge *br, unsigned long val);
int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val);
int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val);
#if IS_ENABLED(CONFIG_IPV6)
int br_multicast_set_mld_version(struct net_bridge *br, unsigned long val);
#endif
struct net_bridge_mdb_entry *
br_mdb_ip_get(struct net_bridge_mdb_htable *mdb, struct br_ip *dst);
struct net_bridge_mdb_entry *
Expand Down
Loading

0 comments on commit 2fcb58a

Please sign in to comment.