* 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
&& 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)
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,
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)
#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
#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
};
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
#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)
{
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)
/* 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:
}
}
+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
}
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);
}
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) {
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 */
/* 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);
<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