From 06994f879c9d12c164eb4abd44c2455a9eb96bec Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Wed, 1 Jul 2015 16:12:12 -0300 Subject: [PATCH] mcast-snooping: Add Multicast Listener Discovery support 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 Cc: Flavio Leitner Cc: Ben Pfaff [blp@nicira.com moved an assignment out of an 'if' statement] Signed-off-by: Ben Pfaff --- NEWS | 1 + lib/flow.h | 25 +++++++++++ lib/mcast-snooping.c | 71 +++++++++++++++++++++++++++++ lib/mcast-snooping.h | 4 ++ lib/packets.c | 1 + lib/packets.h | 40 +++++++++++++++++ ofproto/ofproto-dpif-xlate.c | 86 ++++++++++++++++++++++++++++++------ vswitchd/vswitch.xml | 9 ++-- 8 files changed, 220 insertions(+), 17 deletions(-) diff --git a/NEWS b/NEWS index c59f3a8d8..d6db7d068 100644 --- a/NEWS +++ b/NEWS @@ -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 diff --git a/lib/flow.h b/lib/flow.h index 384a03150..a1c6e9751 100644 --- a/lib/flow.h +++ b/lib/flow.h @@ -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) diff --git a/lib/mcast-snooping.c b/lib/mcast-snooping.c index 39463fa1c..ba4141e0c 100644 --- a/lib/mcast-snooping.c +++ b/lib/mcast-snooping.c @@ -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, diff --git a/lib/mcast-snooping.h b/lib/mcast-snooping.h index e3d15e41c..99c314db1 100644 --- a/lib/mcast-snooping.h +++ b/lib/mcast-snooping.h @@ -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) diff --git a/lib/packets.c b/lib/packets.c index d0ad0b614..b37abde29 100644 --- a/lib/packets.c +++ b/lib/packets.c @@ -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 diff --git a/lib/packets.h b/lib/packets.h index 62aa34ea5..c709af522 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -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 @@ -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 @@ -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) { @@ -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) diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index aa486624f..541708746 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -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: @@ -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 @@ -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); } @@ -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) { @@ -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 */ @@ -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); diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml index fddd45b55..b1d30f656 100644 --- a/vswitchd/vswitch.xml +++ b/vswitchd/vswitch.xml @@ -937,10 +937,11 @@ 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. Enable multicast snooping on the bridge. For now, the default -- 2.20.1