Skip to content

Commit

Permalink
mcast-snooping: Add Multicast Listener Discovery support
Browse files Browse the repository at this point in the history
Add support for MLDv1 and MLDv2. The behavior is not that different from
IGMP. Packets to all-hosts address and queries are always flooded,
reports go to routers, routers are added when a query is observed, and
all MLD packets go through slow path.

Signed-off-by: Thadeu Lima de Souza Cascardo <[email protected]>
Cc: Flavio Leitner <[email protected]>
Cc: Ben Pfaff <[email protected]>
[[email protected] moved an assignment out of an 'if' statement]
Signed-off-by: Ben Pfaff <[email protected]>
  • Loading branch information
Thadeu Lima de Souza Cascardo authored and blp committed Jul 2, 2015
1 parent 964a4d5 commit 06994f8
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 17 deletions.
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Post-v2.4.0
* Group chaining (where one OpenFlow group triggers another) is
now supported.
- Support for matching and generating options with Geneve tunnels.
- Support Multicast Listener Discovery (MLDv1 and MLDv2).


v2.4.0 - xx xxx xxxx
Expand Down
25 changes: 25 additions & 0 deletions lib/flow.h
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,31 @@ static inline bool is_icmpv6(const struct flow *flow)
&& flow->nw_proto == IPPROTO_ICMPV6);
}

static inline bool is_igmp(const struct flow *flow)
{
return (flow->dl_type == htons(ETH_TYPE_IP)
&& flow->nw_proto == IPPROTO_IGMP);
}

static inline bool is_mld(const struct flow *flow)
{
return is_icmpv6(flow)
&& (flow->tp_src == htons(MLD_QUERY)
|| flow->tp_src == htons(MLD_REPORT)
|| flow->tp_src == htons(MLD_DONE)
|| flow->tp_src == htons(MLD2_REPORT));
}

static inline bool is_mld_query(const struct flow *flow)
{
return is_icmpv6(flow) && flow->tp_src == htons(MLD_QUERY);
}

static inline bool is_mld_report(const struct flow *flow)
{
return is_mld(flow) && !is_mld_query(flow);
}

static inline bool is_stp(const struct flow *flow)
{
return (eth_addr_equals(flow->dl_dst, eth_addr_stp)
Expand Down
71 changes: 71 additions & 0 deletions lib/mcast-snooping.c
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,77 @@ mcast_snooping_add_report(struct mcast_snooping *ms,
return count;
}

int
mcast_snooping_add_mld(struct mcast_snooping *ms,
const struct dp_packet *p,
uint16_t vlan, void *port)
{
const struct in6_addr *addr;
size_t offset;
const struct mld_header *mld;
const struct mld2_record *record;
int count = 0;
int ngrp;
bool ret;

offset = (char *) dp_packet_l4(p) - (char *) dp_packet_data(p);
mld = dp_packet_at(p, offset, MLD_HEADER_LEN);
if (!mld) {
return 0;
}
ngrp = ntohs(mld->ngrp);
offset += MLD_HEADER_LEN;
addr = dp_packet_at(p, offset, sizeof(struct in6_addr));

switch (mld->type) {
case MLD_REPORT:
ret = mcast_snooping_add_group(ms, addr, vlan, port);
if (ret) {
count++;
}
break;
case MLD_DONE:
ret = mcast_snooping_leave_group(ms, addr, vlan, port);
if (ret) {
count++;
}
break;
case MLD2_REPORT:
while (ngrp--) {
record = dp_packet_at(p, offset, sizeof(struct mld2_record));
if (!record) {
break;
}
/* Only consider known record types. */
if (record->type >= IGMPV3_MODE_IS_INCLUDE
&& record->type <= IGMPV3_BLOCK_OLD_SOURCES) {
struct in6_addr maddr;
memcpy(maddr.s6_addr, record->maddr.be16, 16);
addr = &maddr;
/*
* If record is INCLUDE MODE and there are no sources, it's
* equivalent to a LEAVE.
*/
if (record->nsrcs == htons(0)
&& (record->type == IGMPV3_MODE_IS_INCLUDE
|| record->type == IGMPV3_CHANGE_TO_INCLUDE_MODE)) {
ret = mcast_snooping_leave_group(ms, addr, vlan, port);
} else {
ret = mcast_snooping_add_group(ms, addr, vlan, port);
}
if (ret) {
count++;
}
}
offset += sizeof(*record)
+ ntohs(record->nsrcs) * sizeof(struct in6_addr)
+ record->aux_len;
}
}

return count;
}

bool
mcast_snooping_leave_group(struct mcast_snooping *ms,
const struct in6_addr *addr,
Expand Down
4 changes: 4 additions & 0 deletions lib/mcast-snooping.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ int mcast_snooping_add_report(struct mcast_snooping *ms,
const struct dp_packet *p,
uint16_t vlan, void *port)
OVS_REQ_WRLOCK(ms->rwlock);
int mcast_snooping_add_mld(struct mcast_snooping *ms,
const struct dp_packet *p,
uint16_t vlan, void *port)
OVS_REQ_WRLOCK(ms->rwlock);
bool mcast_snooping_leave_group(struct mcast_snooping *ms,
const struct in6_addr *addr,
uint16_t vlan, void *port)
Expand Down
1 change: 1 addition & 0 deletions lib/packets.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "unaligned.h"

const struct in6_addr in6addr_exact = IN6ADDR_EXACT_INIT;
const struct in6_addr in6addr_all_hosts = IN6ADDR_ALL_HOSTS_INIT;

/* Parses 's' as a 16-digit hexadecimal number representing a datapath ID. On
* success stores the dpid into '*dpidp' and returns true, on failure stores 0
Expand Down
40 changes: 40 additions & 0 deletions lib/packets.h
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,9 @@ BUILD_ASSERT_DECL(IGMPV3_RECORD_LEN == sizeof(struct igmpv3_record));
#define IGMP_HOST_LEAVE_MESSAGE 0x17
#define IGMPV3_HOST_MEMBERSHIP_REPORT 0x22 /* V3 version of 0x12 */

/*
* IGMPv3 and MLDv2 use the same codes.
*/
#define IGMPV3_MODE_IS_INCLUDE 1
#define IGMPV3_MODE_IS_EXCLUDE 2
#define IGMPV3_CHANGE_TO_INCLUDE_MODE 3
Expand Down Expand Up @@ -716,6 +719,35 @@ struct ovs_nd_msg {
};
BUILD_ASSERT_DECL(ND_MSG_LEN == sizeof(struct ovs_nd_msg));

/*
* Use the same struct for MLD and MLD2, naming members as the defined fields in
* in the corresponding version of the protocol, though they are reserved in the
* other one.
*/
#define MLD_HEADER_LEN 8
struct mld_header {
uint8_t type;
uint8_t code;
ovs_be16 csum;
ovs_be16 mrd;
ovs_be16 ngrp;
};
BUILD_ASSERT_DECL(MLD_HEADER_LEN == sizeof(struct mld_header));

#define MLD2_RECORD_LEN 20
struct mld2_record {
uint8_t type;
uint8_t aux_len;
ovs_be16 nsrcs;
union ovs_16aligned_in6_addr maddr;
};
BUILD_ASSERT_DECL(MLD2_RECORD_LEN == sizeof(struct mld2_record));

#define MLD_QUERY 130
#define MLD_REPORT 131
#define MLD_DONE 132
#define MLD2_REPORT 143

/* The IPv6 flow label is in the lower 20 bits of the first 32-bit word. */
#define IPV6_LABEL_MASK 0x000fffff

Expand All @@ -737,6 +769,10 @@ extern const struct in6_addr in6addr_exact;
#define IN6ADDR_EXACT_INIT { { { 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, \
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff } } }

extern const struct in6_addr in6addr_all_hosts;
#define IN6ADDR_ALL_HOSTS_INIT { { { 0xff,0x02,0x00,0x00,0x00,0x00,0x00,0x00, \
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01 } } }

static inline bool ipv6_addr_equals(const struct in6_addr *a,
const struct in6_addr *b)
{
Expand All @@ -755,6 +791,10 @@ static inline bool ipv6_mask_is_exact(const struct in6_addr *mask) {
return ipv6_addr_equals(mask, &in6addr_exact);
}

static inline bool ipv6_is_all_hosts(const struct in6_addr *addr) {
return ipv6_addr_equals(addr, &in6addr_all_hosts);
}

static inline bool dl_type_is_ip_any(ovs_be16 dl_type)
{
return dl_type == htons(ETH_TYPE_IP)
Expand Down
86 changes: 73 additions & 13 deletions ofproto/ofproto-dpif-xlate.c
Original file line number Diff line number Diff line change
Expand Up @@ -1998,16 +1998,16 @@ update_learning_table(const struct xbridge *xbridge,
/* Updates multicast snooping table 'ms' given that a packet matching 'flow'
* was received on 'in_xbundle' in 'vlan' and is either Report or Query. */
static void
update_mcast_snooping_table__(const struct xbridge *xbridge,
const struct flow *flow,
struct mcast_snooping *ms,
ovs_be32 ip4, int vlan,
struct xbundle *in_xbundle,
const struct dp_packet *packet)
update_mcast_snooping_table4__(const struct xbridge *xbridge,
const struct flow *flow,
struct mcast_snooping *ms, int vlan,
struct xbundle *in_xbundle,
const struct dp_packet *packet)
OVS_REQ_WRLOCK(ms->rwlock)
{
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 30);
int count;
ovs_be32 ip4 = flow->igmp_group_ip4;

switch (ntohs(flow->tp_src)) {
case IGMP_HOST_MEMBERSHIP_REPORT:
Expand Down Expand Up @@ -2045,6 +2045,39 @@ update_mcast_snooping_table__(const struct xbridge *xbridge,
}
}

static void
update_mcast_snooping_table6__(const struct xbridge *xbridge,
const struct flow *flow,
struct mcast_snooping *ms, int vlan,
struct xbundle *in_xbundle,
const struct dp_packet *packet)
OVS_REQ_WRLOCK(ms->rwlock)
{
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 30);
int count;

switch (ntohs(flow->tp_src)) {
case MLD_QUERY:
if (!ipv6_addr_equals(&flow->ipv6_src, &in6addr_any)
&& mcast_snooping_add_mrouter(ms, vlan, in_xbundle->ofbundle)) {
VLOG_DBG_RL(&rl, "bridge %s: multicast snooping query on port %s"
"in VLAN %d",
xbridge->name, in_xbundle->name, vlan);
}
break;
case MLD_REPORT:
case MLD_DONE:
case MLD2_REPORT:
count = mcast_snooping_add_mld(ms, packet, vlan, in_xbundle->ofbundle);
if (count) {
VLOG_DBG_RL(&rl, "bridge %s: multicast snooping processed %d "
"addresses on port %s in VLAN %d",
xbridge->name, count, in_xbundle->name, vlan);
}
break;
}
}

/* Updates multicast snooping table 'ms' given that a packet matching 'flow'
* was received on 'in_xbundle' in 'vlan'. */
static void
Expand Down Expand Up @@ -2075,8 +2108,13 @@ update_mcast_snooping_table(const struct xbridge *xbridge,
}

if (!mcast_xbundle || mcast_xbundle != in_xbundle) {
update_mcast_snooping_table__(xbridge, flow, ms, flow->igmp_group_ip4,
vlan, in_xbundle, packet);
if (flow->dl_type == htons(ETH_TYPE_IP)) {
update_mcast_snooping_table4__(xbridge, flow, ms, vlan,
in_xbundle, packet);
} else {
update_mcast_snooping_table6__(xbridge, flow, ms, vlan,
in_xbundle, packet);
}
}
ovs_rwlock_unlock(&ms->rwlock);
}
Expand Down Expand Up @@ -2280,11 +2318,11 @@ xlate_normal(struct xlate_ctx *ctx)
if (mcast_snooping_enabled(ctx->xbridge->ms)
&& !eth_addr_is_broadcast(flow->dl_dst)
&& eth_addr_is_multicast(flow->dl_dst)
&& flow->dl_type == htons(ETH_TYPE_IP)) {
&& is_ip_any(flow)) {
struct mcast_snooping *ms = ctx->xbridge->ms;
struct mcast_group *grp;
struct mcast_group *grp = NULL;

if (flow->nw_proto == IPPROTO_IGMP) {
if (is_igmp(flow)) {
if (mcast_snooping_is_membership(flow->tp_src) ||
mcast_snooping_is_query(flow->tp_src)) {
if (ctx->xin->may_learn) {
Expand Down Expand Up @@ -2317,8 +2355,26 @@ xlate_normal(struct xlate_ctx *ctx)
xlate_normal_flood(ctx, in_xbundle, vlan);
}
return;
} else if (is_mld(flow)) {
ctx->xout->slow |= SLOW_ACTION;
if (ctx->xin->may_learn) {
update_mcast_snooping_table(ctx->xbridge, flow, vlan,
in_xbundle, ctx->xin->packet);
}
if (is_mld_report(flow)) {
ovs_rwlock_rdlock(&ms->rwlock);
xlate_normal_mcast_send_mrouters(ctx, ms, in_xbundle, vlan);
xlate_normal_mcast_send_rports(ctx, ms, in_xbundle, vlan);
ovs_rwlock_unlock(&ms->rwlock);
} else {
xlate_report(ctx, "MLD query, flooding");
xlate_normal_flood(ctx, in_xbundle, vlan);
}
} else {
if (ip_is_local_multicast(flow->nw_dst)) {
if ((flow->dl_type == htons(ETH_TYPE_IP)
&& ip_is_local_multicast(flow->nw_dst))
|| (flow->dl_type == htons(ETH_TYPE_IPV6)
&& ipv6_is_all_hosts(&flow->ipv6_dst))) {
/* RFC4541: section 2.1.2, item 2: Packets with a dst IP
* address in the 224.0.0.x range which are not IGMP must
* be forwarded on all ports */
Expand All @@ -2330,7 +2386,11 @@ xlate_normal(struct xlate_ctx *ctx)

/* forwarding to group base ports */
ovs_rwlock_rdlock(&ms->rwlock);
grp = mcast_snooping_lookup4(ms, flow->nw_dst, vlan);
if (flow->dl_type == htons(ETH_TYPE_IP)) {
grp = mcast_snooping_lookup4(ms, flow->nw_dst, vlan);
} else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
grp = mcast_snooping_lookup(ms, &flow->ipv6_dst, vlan);
}
if (grp) {
xlate_normal_mcast_send_group(ctx, ms, grp, in_xbundle, vlan);
xlate_normal_mcast_send_fports(ctx, ms, in_xbundle, vlan);
Expand Down
9 changes: 5 additions & 4 deletions vswitchd/vswitch.xml
Original file line number Diff line number Diff line change
Expand Up @@ -937,10 +937,11 @@

<group title="Multicast Snooping Configuration">
Multicast snooping (RFC 4541) monitors the Internet Group Management
Protocol (IGMP) traffic between hosts and multicast routers. The
switch uses what IGMP snooping learns to forward multicast traffic
only to interfaces that are connected to interested receivers.
Currently it supports IGMPv1, IGMPv2 and IGMPv3 protocols.
Protocol (IGMP) and Multicast Listener Discovery traffic between hosts
and multicast routers. The switch uses what IGMP and MLD snooping
learns to forward multicast traffic only to interfaces that are connected
to interested receivers. Currently it supports IGMPv1, IGMPv2, IGMPv3,
MLDv1 and MLDv2 protocols.

<column name="mcast_snooping_enable">
Enable multicast snooping on the bridge. For now, the default
Expand Down

0 comments on commit 06994f8

Please sign in to comment.