switch (ntohs(igmp_type)) {
case IGMP_HOST_MEMBERSHIP_REPORT:
case IGMPV2_HOST_MEMBERSHIP_REPORT:
+ case IGMPV3_HOST_MEMBERSHIP_REPORT:
case IGMP_HOST_LEAVE_MESSAGE:
return true;
}
}
static uint32_t
-mcast_table_hash(const struct mcast_snooping *ms, ovs_be32 grp_ip4,
- uint16_t vlan)
+mcast_table_hash(const struct mcast_snooping *ms,
+ const struct in6_addr *grp_addr, uint16_t vlan)
{
- return hash_3words((OVS_FORCE uint32_t) grp_ip4, vlan, ms->secret);
+ return hash_bytes(grp_addr->s6_addr, 16,
+ hash_2words(ms->secret, vlan));
}
static struct mcast_group_bundle *
/* Searches 'ms' for and returns an mcast group for destination address
* 'dip' in 'vlan'. */
struct mcast_group *
-mcast_snooping_lookup(const struct mcast_snooping *ms, ovs_be32 dip,
- uint16_t vlan)
+mcast_snooping_lookup(const struct mcast_snooping *ms,
+ const struct in6_addr *dip, uint16_t vlan)
OVS_REQ_RDLOCK(ms->rwlock)
{
struct mcast_group *grp;
hash = mcast_table_hash(ms, dip, vlan);
HMAP_FOR_EACH_WITH_HASH (grp, hmap_node, hash, &ms->table) {
- if (grp->vlan == vlan && grp->ip4 == dip) {
+ if (grp->vlan == vlan && ipv6_addr_equals(&grp->addr, dip)) {
return grp;
}
}
return NULL;
}
+static inline void
+in6_addr_set_mapped_ipv4(struct in6_addr *addr, ovs_be32 ip4)
+{
+ union ovs_16aligned_in6_addr *taddr = (void *) addr;
+ memset(taddr->be16, 0, sizeof(taddr->be16));
+ taddr->be16[5] = OVS_BE16_MAX;
+ put_16aligned_be32(&taddr->be32[3], ip4);
+}
+
+struct mcast_group *
+mcast_snooping_lookup4(const struct mcast_snooping *ms, ovs_be32 ip4,
+ uint16_t vlan)
+ OVS_REQ_RDLOCK(ms->rwlock)
+{
+ struct in6_addr addr;
+ in6_addr_set_mapped_ipv4(&addr, ip4);
+ return mcast_snooping_lookup(ms, &addr, vlan);
+}
+
/* If the LRU list is not empty, stores the least-recently-used entry
* in '*e' and returns true. Otherwise, if the LRU list is empty,
* stores NULL in '*e' and return false. */
mcast_snooping_flush_group(struct mcast_snooping *ms, struct mcast_group *grp)
OVS_REQ_WRLOCK(ms->rwlock)
{
- struct mcast_group_bundle *b, *next_b;
+ struct mcast_group_bundle *b;
- LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) {
- list_remove(&b->bundle_node);
+ LIST_FOR_EACH_POP (b, bundle_node, &grp->bundle_lru) {
free(b);
}
mcast_snooping_flush_group__(ms, grp);
* move to the last position in the LRU list.
*/
bool
-mcast_snooping_add_group(struct mcast_snooping *ms, ovs_be32 ip4,
+mcast_snooping_add_group(struct mcast_snooping *ms,
+ const struct in6_addr *addr,
uint16_t vlan, void *port)
OVS_REQ_WRLOCK(ms->rwlock)
{
}
learned = false;
- grp = mcast_snooping_lookup(ms, ip4, vlan);
+ grp = mcast_snooping_lookup(ms, addr, vlan);
if (!grp) {
- uint32_t hash = mcast_table_hash(ms, ip4, vlan);
+ uint32_t hash = mcast_table_hash(ms, addr, vlan);
if (hmap_count(&ms->table) >= ms->max_entries) {
group_get_lru(ms, &grp);
grp = xmalloc(sizeof *grp);
hmap_insert(&ms->table, &grp->hmap_node, hash);
- grp->ip4 = ip4;
+ grp->addr = *addr;
grp->vlan = vlan;
list_init(&grp->bundle_lru);
learned = true;
}
bool
-mcast_snooping_leave_group(struct mcast_snooping *ms, ovs_be32 ip4,
+mcast_snooping_add_group4(struct mcast_snooping *ms, ovs_be32 ip4,
+ uint16_t vlan, void *port)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ struct in6_addr addr;
+ in6_addr_set_mapped_ipv4(&addr, ip4);
+ return mcast_snooping_add_group(ms, &addr, vlan, port);
+}
+
+int
+mcast_snooping_add_report(struct mcast_snooping *ms,
+ const struct dp_packet *p,
+ uint16_t vlan, void *port)
+{
+ ovs_be32 ip4;
+ size_t offset;
+ const struct igmpv3_header *igmpv3;
+ const struct igmpv3_record *record;
+ int count = 0;
+ int ngrp;
+
+ offset = (char *) dp_packet_l4(p) - (char *) dp_packet_data(p);
+ igmpv3 = dp_packet_at(p, offset, IGMPV3_HEADER_LEN);
+ if (!igmpv3) {
+ return 0;
+ }
+ ngrp = ntohs(igmpv3->ngrp);
+ offset += IGMPV3_HEADER_LEN;
+ while (ngrp--) {
+ bool ret;
+ record = dp_packet_at(p, offset, sizeof(struct igmpv3_record));
+ if (!record) {
+ break;
+ }
+ /* Only consider known record types. */
+ if (record->type < IGMPV3_MODE_IS_INCLUDE
+ || record->type > IGMPV3_BLOCK_OLD_SOURCES) {
+ continue;
+ }
+ ip4 = get_16aligned_be32(&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_group4(ms, ip4, vlan, port);
+ } else {
+ ret = mcast_snooping_add_group4(ms, ip4, vlan, port);
+ }
+ if (ret) {
+ count++;
+ }
+ offset += sizeof(*record)
+ + ntohs(record->nsrcs) * sizeof(ovs_be32) + record->aux_len;
+ }
+ 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,
uint16_t vlan, void *port)
OVS_REQ_WRLOCK(ms->rwlock)
{
return false;
}
- grp = mcast_snooping_lookup(ms, ip4, vlan);
+ grp = mcast_snooping_lookup(ms, addr, vlan);
if (grp && mcast_group_delete_bundle(ms, grp, port)) {
ms->need_revalidate = true;
return true;
return false;
}
+bool
+mcast_snooping_leave_group4(struct mcast_snooping *ms, ovs_be32 ip4,
+ uint16_t vlan, void *port)
+{
+ struct in6_addr addr;
+ in6_addr_set_mapped_ipv4(&addr, ip4);
+ return mcast_snooping_leave_group(ms, &addr, vlan, port);
+}
+
\f
/* Router ports. */