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 <casca...@redhat.com> --- lib/flow.h | 25 +++++++++++++ lib/mcast-snooping.c | 67 ++++++++++++++++++++++++++++++++++ lib/mcast-snooping.h | 4 +++ lib/packets.c | 1 + lib/packets.h | 40 +++++++++++++++++++++ ofproto/ofproto-dpif-xlate.c | 85 +++++++++++++++++++++++++++++++++++++------- 6 files changed, 209 insertions(+), 13 deletions(-) diff --git a/lib/flow.h b/lib/flow.h index 70554e4..e68dd74 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 f2684f3..aee80b1 100644 --- a/lib/mcast-snooping.c +++ b/lib/mcast-snooping.c @@ -499,6 +499,73 @@ 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) { + continue; + } + addr = &record->maddr; + /* + * If record is INCLUDE MODE and there are no sources, it's equivalent + * to a LEAVE. + */ + if (ntohs(record->nsrcs) == 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 e3d15e4..99c314d 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 d04fffc..c7ea24c 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 b5cd6ab..136376b 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -568,6 +568,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 @@ -575,6 +578,35 @@ BUILD_ASSERT_DECL(IGMPV3_RECORD_LEN == sizeof(struct igmpv3_record)); #define IGMPV3_ALLOW_NEW_SOURCES 5 #define IGMPV3_BLOCK_OLD_SOURCES 6 +/* + * 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; + struct 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 + #define SCTP_HEADER_LEN 12 struct sctp_header { ovs_be16 sctp_src; @@ -726,6 +758,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) { @@ -744,6 +780,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 1490f0f..e48f872 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -1998,12 +1998,12 @@ 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, + ovs_be32 ip4, 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); @@ -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: + if ((count = mcast_snooping_add_mld(ms, packet, vlan, + in_xbundle->ofbundle))) { + 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, + flow->igmp_group_ip4, 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,10 @@ 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); -- 2.4.2 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev