OVS currently monitors multicast group actvity, however, this wasn't used during bond failovers. This could cause problems with deployments that don't use LACP, resulting in a failure to recieve multicast traffic on the new primary until the next multicast query or report.
This patch changes adds IGMP and MLD packets to the failover actions that occur after a primary member change after learning packets are sent. Signed-off-by: Mike Pattrick <[email protected]> --- NEWS | 3 + lib/mcast-snooping.c | 70 +++++++++++++-- lib/mcast-snooping.h | 23 ++++- lib/packets.c | 170 +++++++++++++++++++++++++++++++++++ lib/packets.h | 16 ++++ ofproto/bond.c | 31 +++++++ ofproto/bond.h | 2 + ofproto/ofproto-dpif-xlate.c | 8 +- ofproto/ofproto-dpif.c | 94 +++++++++++++++++++ tests/mcast-snooping.at | 105 ++++++++++++++++++++++ 10 files changed, 507 insertions(+), 15 deletions(-) diff --git a/NEWS b/NEWS index de1a030ad..677504833 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,8 @@ Post-v4.0.0 -------------------- + - Bonding: + * Send IGMP and MLD packets on bonding failover when mcast snooping is + enabled and the bond can send learning packets. v4.0.0 - 17 Aug 2026 diff --git a/lib/mcast-snooping.c b/lib/mcast-snooping.c index c236343a2..4c1557de8 100644 --- a/lib/mcast-snooping.c +++ b/lib/mcast-snooping.c @@ -336,7 +336,12 @@ mcast_snooping_flush_group__(struct mcast_snooping *ms, struct mcast_group *grp) OVS_REQ_WRLOCK(ms->rwlock) { + struct mcast_group_source *src; + ovs_assert(ovs_list_is_empty(&grp->bundle_lru)); + LIST_FOR_EACH_POP (src, node, &grp->sources) { + free(src); + } hmap_remove(&ms->table, &grp->hmap_node); ovs_list_remove(&grp->group_node); free(grp); @@ -411,6 +416,42 @@ mcast_snooping_prune_expired(struct mcast_snooping *ms, return expired; } +static void +mcast_group_add_source(struct mcast_snooping *ms, + struct mcast_group *grp, + const struct in6_addr *src_addr, + struct eth_addr src_mac) + OVS_REQ_WRLOCK(ms->rwlock) +{ + struct mcast_group_source *src; + + while (!ovs_list_is_empty(&grp->sources)) { + src = CONTAINER_OF(ovs_list_front(&grp->sources), + struct mcast_group_source, node); + + if (src->expires > time_now()) { + break; + } + ovs_list_remove(&src->node); + free(src); + } + + LIST_FOR_EACH (src, node, &grp->sources) { + if (ipv6_addr_equals(&src->addr, src_addr)) { + ovs_list_remove(&src->node); + ovs_list_push_back(&grp->sources, &src->node); + src->expires = time_now() + ms->idle_time; + return; + } + } + + src = xmalloc(sizeof *src); + src->addr = *src_addr; + src->mac = src_mac; + src->expires = time_now() + ms->idle_time; + ovs_list_push_back(&grp->sources, &src->node); +} + /* Add a multicast group to the mdb. If it exists, then * move to the last position in the LRU list. */ @@ -418,7 +459,9 @@ bool mcast_snooping_add_group(struct mcast_snooping *ms, const struct in6_addr *addr, uint16_t vlan, void *port, - enum mcast_group_proto grp_proto) + enum mcast_group_proto grp_proto, + const struct in6_addr *src_addr, + struct eth_addr src_mac) OVS_REQ_WRLOCK(ms->rwlock) { bool learned; @@ -447,6 +490,7 @@ mcast_snooping_add_group(struct mcast_snooping *ms, grp->addr = *addr; grp->vlan = vlan; ovs_list_init(&grp->bundle_lru); + ovs_list_init(&grp->sources); learned = true; ms->need_revalidate = true; COVERAGE_INC(mcast_snooping_learned); @@ -454,6 +498,7 @@ mcast_snooping_add_group(struct mcast_snooping *ms, ovs_list_remove(&grp->group_node); } mcast_group_insert_bundle(ms, grp, port, ms->idle_time); + mcast_group_add_source(ms, grp, src_addr, src_mac); /* update the protocol version. */ grp->protocol_version = grp_proto; @@ -466,17 +511,22 @@ mcast_snooping_add_group(struct mcast_snooping *ms, bool mcast_snooping_add_group4(struct mcast_snooping *ms, ovs_be32 ip4, uint16_t vlan, void *port, - enum mcast_group_proto grp_proto) + enum mcast_group_proto grp_proto, + ovs_be32 src_ip4, struct eth_addr src_mac) OVS_REQ_WRLOCK(ms->rwlock) { struct in6_addr addr = in6_addr_mapped_ipv4(ip4); - return mcast_snooping_add_group(ms, &addr, vlan, port, grp_proto); + struct in6_addr src_addr = in6_addr_mapped_ipv4(src_ip4); + + return mcast_snooping_add_group(ms, &addr, vlan, port, grp_proto, + &src_addr, src_mac); } int mcast_snooping_add_report(struct mcast_snooping *ms, const struct dp_packet *p, - uint16_t vlan, void *port) + uint16_t vlan, void *port, + ovs_be32 src_ip4, struct eth_addr src_mac) { ovs_be32 ip4; size_t offset; @@ -514,7 +564,8 @@ mcast_snooping_add_report(struct mcast_snooping *ms, ret = mcast_snooping_leave_group4(ms, ip4, vlan, port); } else { ret = mcast_snooping_add_group4(ms, ip4, vlan, port, - MCAST_GROUP_IGMPV3); + MCAST_GROUP_IGMPV3, src_ip4, + src_mac); } if (ret) { count++; @@ -528,7 +579,9 @@ mcast_snooping_add_report(struct mcast_snooping *ms, int mcast_snooping_add_mld(struct mcast_snooping *ms, const struct dp_packet *p, - uint16_t vlan, void *port) + uint16_t vlan, void *port, + const struct in6_addr *src_addr, + struct eth_addr src_mac) { const struct in6_addr *addr; size_t offset; @@ -558,7 +611,7 @@ mcast_snooping_add_mld(struct mcast_snooping *ms, switch (mld->type) { case MLD_REPORT: ret = mcast_snooping_add_group(ms, addr, vlan, port, - MCAST_GROUP_MLDV1); + MCAST_GROUP_MLDV1, src_addr, src_mac); if (ret) { count++; } @@ -591,7 +644,8 @@ mcast_snooping_add_mld(struct mcast_snooping *ms, ret = mcast_snooping_leave_group(ms, addr, vlan, port); } else { ret = mcast_snooping_add_group(ms, addr, vlan, port, - MCAST_GROUP_MLDV2); + MCAST_GROUP_MLDV2, + src_addr, src_mac); } if (ret) { count++; diff --git a/lib/mcast-snooping.h b/lib/mcast-snooping.h index de42cf826..7aa2987ae 100644 --- a/lib/mcast-snooping.h +++ b/lib/mcast-snooping.h @@ -69,6 +69,15 @@ struct mcast_group { /* Contains struct mcast_group_bundle (ports), least recently used * at the front, most recently used at the back. */ struct ovs_list bundle_lru OVS_GUARDED; + + struct ovs_list sources OVS_GUARDED; +}; + +struct mcast_group_source { + struct ovs_list node OVS_GUARDED; + struct in6_addr addr OVS_GUARDED; + struct eth_addr mac OVS_GUARDED; + time_t expires; }; /* The bundle associated to the multicast group. @@ -198,19 +207,25 @@ mcast_snooping_lookup4(const struct mcast_snooping *ms, ovs_be32 ip4, bool mcast_snooping_add_group(struct mcast_snooping *ms, const struct in6_addr *addr, uint16_t vlan, void *port, - enum mcast_group_proto grp_proto) + enum mcast_group_proto grp_proto, + const struct in6_addr *src_addr, + struct eth_addr src_mac) OVS_REQ_WRLOCK(ms->rwlock); bool mcast_snooping_add_group4(struct mcast_snooping *ms, ovs_be32 ip4, uint16_t vlan, void *port, - enum mcast_group_proto grp_proto) + enum mcast_group_proto grp_proto, + ovs_be32 src_ip4, struct eth_addr src_mac) OVS_REQ_WRLOCK(ms->rwlock); int mcast_snooping_add_report(struct mcast_snooping *ms, const struct dp_packet *p, - uint16_t vlan, void *port) + uint16_t vlan, void *port, + ovs_be32 src_ip4, struct eth_addr src_mac) OVS_REQ_WRLOCK(ms->rwlock); int mcast_snooping_add_mld(struct mcast_snooping *ms, const struct dp_packet *p, - uint16_t vlan, void *port) + uint16_t vlan, void *port, + const struct in6_addr *src_addr, + struct eth_addr src_mac) OVS_REQ_WRLOCK(ms->rwlock); bool mcast_snooping_leave_group(struct mcast_snooping *ms, const struct in6_addr *addr, diff --git a/lib/packets.c b/lib/packets.c index 80c41e4b6..89a0223b2 100644 --- a/lib/packets.c +++ b/lib/packets.c @@ -1866,6 +1866,176 @@ compose_ipv6(struct dp_packet *packet, uint8_t proto, return data; } +static void +multicast_to_ethernet(struct eth_addr *eth, ovs_be32 ip4) +{ + uint8_t *ip = (uint8_t *)&ip4; + + eth->ea[0] = 0x01; + eth->ea[1] = 0x00; + eth->ea[2] = 0x5e; + eth->ea[3] = ip[1] & 0x7f; + eth->ea[4] = ip[2]; + eth->ea[5] = ip[3]; +} + +void +compose_igmpv2(struct dp_packet *packet, const struct in6_addr *gaddr, + const struct in6_addr *saddr, struct eth_addr smac) +{ + ovs_be32 group_ip = in6_addr_get_mapped_ipv4(gaddr); + ovs_be32 src_ip = in6_addr_get_mapped_ipv4(saddr); + struct igmp_header *igmp; + struct eth_addr eth_dst; + struct ip_header *ip; + size_t igmp_len; + + multicast_to_ethernet(ð_dst, group_ip); + igmp_len = IGMP_HEADER_LEN; + + ip = eth_compose(packet, eth_dst, smac, ETH_TYPE_IP, + IP_HEADER_LEN + igmp_len); + ip->ip_ihl_ver = IP_IHL_VER(5,4); + ip->ip_tos = IP_DSCP_CS6; + ip->ip_tot_len = htons(IP_HEADER_LEN + igmp_len); + ip->ip_ttl = 1; + ip->ip_proto = IPPROTO_IGMP; + put_16aligned_be32(&ip->ip_src, src_ip); + put_16aligned_be32(&ip->ip_dst, group_ip); + ip->ip_csum = csum(ip, IP_HEADER_LEN); + + igmp = ALIGNED_CAST(struct igmp_header *, ip + 1); + + igmp->igmp_type = IGMPV2_HOST_MEMBERSHIP_REPORT; + put_16aligned_be32(&igmp->group, group_ip); + igmp->igmp_csum = csum(igmp, igmp_len); +} + + +void +compose_igmpv3(struct dp_packet *packet, const struct in6_addr *gaddr, + const struct in6_addr *saddr, struct eth_addr smac) +{ + ovs_be32 group_ip = in6_addr_get_mapped_ipv4(gaddr); + ovs_be32 src_ip = in6_addr_get_mapped_ipv4(saddr); + struct igmpv3_record *record; + struct igmpv3_header *igmp3; + struct eth_addr eth_dst; + struct ip_header *ip; + size_t igmp_len; + + multicast_to_ethernet(ð_dst, group_ip); + igmp_len = IGMPV3_HEADER_LEN + IGMPV3_RECORD_LEN + sizeof(ovs_be32); + + ip = eth_compose(packet, eth_dst, smac, ETH_TYPE_IP, + IP_HEADER_LEN + igmp_len); + ip->ip_ihl_ver = IP_IHL_VER(5,4); + ip->ip_tos = IP_DSCP_CS6; + ip->ip_tot_len = htons(IP_HEADER_LEN + igmp_len); + ip->ip_ttl = 1; + ip->ip_proto = IPPROTO_IGMP; + put_16aligned_be32(&ip->ip_src, src_ip); + put_16aligned_be32(&ip->ip_dst, htonl(0xe0000016)); + ip->ip_csum = csum(ip, IP_HEADER_LEN); + + igmp3 = ALIGNED_CAST(struct igmpv3_header *, ip + 1); + record = ALIGNED_CAST(struct igmpv3_record *, igmp3 + 1); + + igmp3->type = IGMPV3_HOST_MEMBERSHIP_REPORT; + igmp3->ngrp = htons(1); + record->type = IGMPV3_MODE_IS_INCLUDE; + record->nsrcs = htons(1); + put_16aligned_be32(&record->maddr, group_ip); + put_16aligned_be32(ALIGNED_CAST(ovs_16aligned_be32 *, record + 1), src_ip); + igmp3->csum = csum(igmp3, igmp_len); +} + +void +compose_mldv1(struct dp_packet *packet, const struct in6_addr *gaddr, + const struct in6_addr *saddr, struct eth_addr smac) +{ + struct eth_addr eth_dst; + struct ip6_hop_hdr *hbh; + struct mld_header *mld; + struct in6_addr *grp; + struct in6_addr dst; + size_t mld_len; + + dst = *gaddr; + mld_len = sizeof *hbh + sizeof dst; + + ipv6_multicast_to_ethernet(ð_dst, &dst); + eth_compose(packet, eth_dst, smac, ETH_TYPE_IPV6, IPV6_HEADER_LEN); + + hbh = compose_ipv6(packet, IPPROTO_HOPOPTS, saddr, &dst, + 0, 0, 1, mld_len + IP6_HOP_HDR_LEN); + hbh->nexthdr = IPPROTO_ICMPV6; + hbh->hdrlen = 0; + hbh->opts[0] = 0x05; /* Router Alert. */ + hbh->opts[1] = 2; + hbh->opts[2] = 0; + hbh->opts[3] = 0; /* MLD. */ + hbh->opts[4] = 0x01; /* PadN. */ + hbh->opts[5] = 0; + + mld = ALIGNED_CAST(struct mld_header *, hbh + 1); + grp = ALIGNED_CAST(struct in6_addr *, mld + 1); + + mld->type = MLD_REPORT; + memcpy(grp, gaddr, sizeof *grp); + mld->csum = 0; + mld->csum = packet_csum_upperlayer6(dp_packet_l3(packet), + mld, IPPROTO_ICMPV6, mld_len); +} + +void +compose_mldv2(struct dp_packet *packet, const struct in6_addr *gaddr, + const struct in6_addr *saddr, struct eth_addr smac) +{ + struct in6_addr dst = in6addr_any; + struct mld2_record *record; + struct eth_addr eth_dst; + struct ip6_hop_hdr *hbh; + struct mld_header *mld; + struct in6_addr *src6; + size_t mld_len; + + /* ff02::16, the "all MLDv2 routers" group. */ + dst.s6_addr[0] = 0xff; + dst.s6_addr[1] = 0x02; + dst.s6_addr[15] = 0x16; + mld_len = sizeof *mld + sizeof *record + sizeof *src6; + + ipv6_multicast_to_ethernet(ð_dst, &dst); + eth_compose(packet, eth_dst, smac, ETH_TYPE_IPV6, IPV6_HEADER_LEN); + hbh = compose_ipv6(packet, IPPROTO_HOPOPTS, saddr, &dst, 0, 0, 1, + mld_len + IP6_HOP_HDR_LEN); + hbh->nexthdr = IPPROTO_ICMPV6; + hbh->hdrlen = 0; + hbh->opts[0] = 0x05; /* Router Alert. */ + hbh->opts[1] = 2; + hbh->opts[2] = 0; + hbh->opts[3] = 0; /* MLD. */ + hbh->opts[4] = 0x01; /* PadN. */ + hbh->opts[5] = 0; + + mld = ALIGNED_CAST(struct mld_header *, hbh + 1); + record = ALIGNED_CAST(struct mld2_record *, mld + 1); + src6 = ALIGNED_CAST(struct in6_addr *, record + 1); + + mld->type = MLD2_REPORT; + mld->ngrp = htons(1); + record->type = IGMPV3_MODE_IS_INCLUDE; + record->nsrcs = htons(1); + memcpy(record->maddr.be16, gaddr->s6_addr, 16); + *src6 = *saddr; + + mld->csum = 0; + mld->csum = packet_csum_upperlayer6(dp_packet_l3(packet), mld, + IPPROTO_ICMPV6, mld_len); + +} + /* Compose an IPv6 Neighbor Discovery Neighbor Solicitation message. */ void compose_nd_ns(struct dp_packet *b, bool multicast, diff --git a/lib/packets.h b/lib/packets.h index ea0bda840..f1f850f27 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -1016,6 +1016,14 @@ struct ip6_rt_hdr { }; BUILD_ASSERT_DECL(IP6_RT_HDR_LEN == sizeof(struct ip6_rt_hdr)); +#define IP6_HOP_HDR_LEN 8 +struct ip6_hop_hdr { + uint8_t nexthdr; + uint8_t hdrlen; + uint8_t opts[6]; +}; +BUILD_ASSERT_DECL(IP6_HOP_HDR_LEN == sizeof(struct ip6_hop_hdr)); + #define ICMP6_HEADER_LEN 4 struct icmp6_header { uint8_t icmp6_type; @@ -1673,6 +1681,14 @@ void compose_arp(struct dp_packet *, uint16_t arp_op, const struct eth_addr arp_sha, const struct eth_addr arp_tha, bool broadcast, ovs_be32 arp_spa, ovs_be32 arp_tpa); +void compose_igmpv2(struct dp_packet *packet, const struct in6_addr *gaddr, + const struct in6_addr *saddr, struct eth_addr smac); +void compose_igmpv3(struct dp_packet *packet, const struct in6_addr *gaddr, + const struct in6_addr *saddr, struct eth_addr smac); +void compose_mldv1(struct dp_packet *packet, const struct in6_addr *gaddr, + const struct in6_addr *saddr, struct eth_addr smac); +void compose_mldv2(struct dp_packet *packet, const struct in6_addr *gaddr, + const struct in6_addr *saddr, struct eth_addr smac); void compose_nd_ns(struct dp_packet *, bool multicast, struct eth_addr eth_src, struct eth_addr eth_dst, diff --git a/ofproto/bond.c b/ofproto/bond.c index ef481a360..d2a671813 100644 --- a/ofproto/bond.c +++ b/ofproto/bond.c @@ -134,6 +134,7 @@ struct bond { int rebalance_interval; /* Interval between rebalances, in ms. */ long long int next_rebalance; /* Next rebalancing time. */ bool send_learning_packets; + bool send_mcast_packets; uint32_t recirc_id; /* Non zero if recirculation can be used.*/ struct hmap pr_rule_ops; /* Helps to maintain post recirculation rules.*/ @@ -577,6 +578,7 @@ bond_active_member_changed(struct bond *bond) bond->active_member_mac = eth_addr_zero; } bond->active_member_changed = true; + bond->send_mcast_packets = true; if (!bond_is_balanced(bond)) { bond->bond_revalidate = true; } @@ -859,6 +861,35 @@ bond_compose_learning_packet(struct bond *bond, const struct eth_addr eth_src, ovs_rwlock_unlock(&rwlock); return packet; } + +bool +bond_should_send_mcast(struct bond *bond) +{ + bool send; + + ovs_rwlock_wrlock(&rwlock); + send = bond->send_mcast_packets && may_send_learning_packets(bond); + bond->send_mcast_packets = false; + ovs_rwlock_unlock(&rwlock); + return send; +} + +void * +bond_get_active_member(const struct bond *bond, struct eth_addr *mac) +{ + void *aux; + + ovs_rwlock_rdlock(&rwlock); + if (bond->active_member) { + aux = bond->active_member->aux; + netdev_get_etheraddr(bond->active_member->netdev, mac); + } else { + aux = NULL; + *mac = eth_addr_zero; + } + ovs_rwlock_unlock(&rwlock); + return aux; +} static bool diff --git a/ofproto/bond.h b/ofproto/bond.h index 3aa5c640d..a7d0c35ab 100644 --- a/ofproto/bond.h +++ b/ofproto/bond.h @@ -94,6 +94,8 @@ struct dp_packet *bond_compose_learning_packet(struct bond *, uint16_t vlan, void **port_aux); bool bond_get_changed_active_member(const char *name, struct eth_addr *mac, bool force); +bool bond_should_send_mcast(struct bond *); +void *bond_get_active_member(const struct bond *, struct eth_addr *mac); /* Packet processing. */ enum bond_verdict { diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index 4e7d6fb40..6c4f2e99c 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -2873,7 +2873,7 @@ update_mcast_snooping_table4__(const struct xlate_ctx *ctx, ? MCAST_GROUP_IGMPV1 : MCAST_GROUP_IGMPV2; if (mcast_snooping_add_group4(ms, ip4, vlan, in_xbundle->ofbundle, - grp_proto)) { + grp_proto, flow->nw_src, flow->dl_src)) { xlate_report_debug(ctx, OFT_DETAIL, "multicast snooping learned that " IP_FMT" is on port %s in VLAN %d", @@ -2897,7 +2897,8 @@ update_mcast_snooping_table4__(const struct xlate_ctx *ctx, break; case IGMPV3_HOST_MEMBERSHIP_REPORT: count = mcast_snooping_add_report(ms, packet, vlan, - in_xbundle->ofbundle); + in_xbundle->ofbundle, flow->nw_src, + flow->dl_src); if (count) { xlate_report_debug(ctx, OFT_DETAIL, "multicast snooping processed " "%d addresses on port %s in VLAN %d", @@ -2943,7 +2944,8 @@ update_mcast_snooping_table6__(const struct xlate_ctx *ctx, case MLD_REPORT: case MLD_DONE: case MLD2_REPORT: - count = mcast_snooping_add_mld(ms, packet, vlan, in_xbundle->ofbundle); + count = mcast_snooping_add_mld(ms, packet, vlan, in_xbundle->ofbundle, + &flow->ipv6_src, flow->dl_src); if (count) { xlate_report_debug(ctx, OFT_DETAIL, "multicast snooping processed " "%d addresses on port %s in VLAN %d", diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index 0f0f71d14..23e679eca 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -3805,6 +3805,96 @@ bundle_send_learning_packets(struct ofbundle *bundle) } } +static void +bundle_send_mcast_packets(struct ofbundle *bundle) +{ + struct ofproto_dpif *ofproto = bundle->ofproto; + struct mcast_snooping *ms = ofproto->ms; + struct ofport_dpif *port; + struct eth_addr eth_src; + struct mcast_group *grp; + int n_packets = 0, n_errors = 0, error = 0; + + if (!ms || !mcast_snooping_enabled(ms)) { + return; + } + + port = bond_get_active_member(bundle->bond, ð_src); + if (!port) { + return; + } + + struct pkt_list { + struct ovs_list list_node; + struct dp_packet *pkt; + } *pkt_node; + struct ovs_list packets; + + ovs_list_init(&packets); + ovs_rwlock_rdlock(&ms->rwlock); + HMAP_FOR_EACH (grp, hmap_node, &ms->table) { + struct mcast_group_source *src; + + LIST_FOR_EACH (src, node, &grp->sources) { + pkt_node = xmalloc(sizeof *pkt_node); + pkt_node->pkt = dp_packet_new(0); + + switch (grp->protocol_version) { + case MCAST_GROUP_IGMPV1: + case MCAST_GROUP_IGMPV2: + compose_igmpv2(pkt_node->pkt, &grp->addr, &src->addr, + src->mac); + break; + case MCAST_GROUP_IGMPV3: + compose_igmpv3(pkt_node->pkt, &grp->addr, &src->addr, + src->mac); + break; + case MCAST_GROUP_MLDV1: + compose_mldv1(pkt_node->pkt, &grp->addr, &src->addr, + src->mac); + break; + case MCAST_GROUP_MLDV2: + compose_mldv2(pkt_node->pkt, &grp->addr, &src->addr, + src->mac); + break; + default: + OVS_NOT_REACHED(); + } + + if (grp->vlan) { + eth_push_vlan(pkt_node->pkt, htons(ETH_TYPE_VLAN), + htons(grp->vlan)); + } + + ovs_list_push_back(&packets, &pkt_node->list_node); + } + } + ovs_rwlock_unlock(&ms->rwlock); + + error = n_packets = n_errors = 0; + LIST_FOR_EACH_POP (pkt_node, list_node, &packets) { + int ret = ofproto_dpif_send_packet(port, false, pkt_node->pkt); + dp_packet_delete(pkt_node->pkt); + free(pkt_node); + + n_packets++; + if (ret) { + error = ret; + n_errors++; + } + } + + if (n_errors) { + static struct vlog_rate_limit rll = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rll, "bond %s: %d errors sending %d multicast " + "membership packets, last error was: %s", + bundle->name, n_errors, n_packets, ovs_strerror(error)); + } else { + VLOG_DBG("bond %s: sent %d multicast membership packets", + bundle->name, n_packets); + } +} + static void bundle_run(struct ofbundle *bundle) { @@ -3825,6 +3915,10 @@ bundle_run(struct ofbundle *bundle) if (bond_should_send_learning_packets(bundle->bond)) { bundle_send_learning_packets(bundle); } + + if (bond_should_send_mcast(bundle->bond)) { + bundle_send_mcast_packets(bundle); + } } } diff --git a/tests/mcast-snooping.at b/tests/mcast-snooping.at index adbb66c70..08d8c486e 100644 --- a/tests/mcast-snooping.at +++ b/tests/mcast-snooping.at @@ -687,3 +687,108 @@ AT_CHECK([ovs-appctl mdb/show br0], [0], [dnl 1 0 MLDv1 ff02::1:ff0e:4c67 0 ]) AT_CLEANUP + +AT_SETUP([mcast - send IGMP/MLD reports on bond failover]) +OVS_VSWITCHD_START([]) + +AT_CHECK([ + ovs-vsctl set bridge br0 \ + datapath_type=dummy \ + mcast_snooping_enable=true \ + other-config:mcast-snooping-disable-flood-unregistered=true +], [0]) + +AT_CHECK([ovs-ofctl add-flow br0 action=normal]) + +AT_CHECK([ovs-vsctl add-bond br0 bond0 p1 p2 bond_mode=active-backup -- dnl + set interface p1 type=dummy ofport_request=1 -- dnl + set interface p2 type=dummy ofport_request=2 -- dnl + set interface p1 options:tx_pcap=p1.pcap -- dnl + set interface p2 options:tx_pcap=p2.pcap -- dnl + add-port br0 ph -- set interface ph type=dummy ofport_request=3], [0]) + +AT_CHECK([ovs-appctl bond/set-active-member bond0 p1], [0], [ignore]) + +ovs-appctl time/stop + +dnl Send IGMPv2 packet +dnl load_contrib("igmp") +dnl Ether(dst="01:00:5e:01:01:01", src="50:54:00:00:00:11") / +dnl IP(src="10.0.0.99", dst="239.1.1.1", tos=0, ttl=1) / +dnl IGMP(type=0x16, gaddr="239.1.1.1") +AT_CHECK([ovs-appctl netdev-dummy/receive ph ]dnl +[01005e0101015054000000110800]dnl +[4500001c000100000102bf7a0a000063ef010101]dnl +[1614f9e8ef010101]) + +dnl Send IGMPv3 packet +dnl Ether(dst="01:00:5e:01:01:32", src="50:54:00:00:00:11") / +dnl IP(src="10.0.0.99", dst="239.1.1.50", tos=0xc0, ttl=1, id=0) / +dnl IGMPv3(type=0x22) / +dnl IGMPv3mr(numgrp=1, records=[IGMPv3gr(rtype=1, maddr="239.1.1.50", srcaddrs="10.0.0.99")]) +AT_CHECK([ovs-appctl netdev-dummy/receive ph ]dnl +[01005e0101325054000000110800]dnl +[45c00028000000000102be7e0a000063ef010132]dnl +[2200e2660000000101000001ef0101320a000063]) + +dnl Send MLDv1 packet +dnl Ether(dst="33:33:ff:0e:4c:67", src="00:0c:29:0e:4c:67") / +dnl IPv6(src="::", dst="ff02::1:ff0e:4c67") / +dnl IPv6ExtHdrHopByHop(autopad=True, options=[RouterAlert(otype=5, value=0)]) / +dnl ICMPv6MLReport(type="MLD Report", code=0, mrd=0, mladdr="ff02::1:ff0e:4c67") +AT_CHECK([ovs-appctl netdev-dummy/receive ph ]dnl +[3333ff0e4c67000c290e4c6786dd]dnl +[600000000020004000000000000000000000000000000000ff0200000000000000000001ff0e4c67]dnl +[3a00050200000100]dnl +[8300e7b800000000ff0200000000000000000001ff0e4c67]) + +dnl Send MLDv2 +dnl Ether(dst="33:33:00:00:00:16", src="00:0c:29:0e:4c:67") / +dnl IPv6(src="::", dst="ff02::16", hlim=1) / +dnl IPv6ExtHdrHopByHop(autopad=True, options=[RouterAlert(otype=5, value=0)]) / +dnl ICMPv6MLReport2(records=[ICMPv6MLDMultAddrRec(rtype=2, dst="ff02::1:ff0e:4d00", sources=[])]) +AT_CHECK([ovs-appctl netdev-dummy/receive ph ]dnl +[333300000016000c290e4c6786dd]dnl +[600000000024000100000000000000000000000000000000ff020000000000000000000000000016]dnl +[3a00050200000100]dnl +[8f00247c0000000102000000ff0200000000000000000001ff0e4d00]) + +OVS_WAIT_UNTIL([ovs-appctl mdb/show br0 | grep -q ff02::1:ff0e:4d00]) +AT_CHECK([ovs-appctl mdb/show br0 | grep 0 | sort], [0], [dnl + 3 0 IGMPv2 239.1.1.1 0 + 3 0 IGMPv3 239.1.1.50 0 + 3 0 MLDv1 ff02::1:ff0e:4c67 0 + 3 0 MLDv2 ff02::1:ff0e:4d00 0 +]) + +dnl Force failover +AT_CHECK([ovs-appctl bond/set-active-member bond0 p2], [0], [ignore]) +ovs-appctl time/warp 1000 + +dnl Check IGMPv2 packet +OVS_WAIT_UNTIL([ovs-pcap p2.pcap | grep -q ]dnl +[01005e0101015054000000110800]dnl +[45c0001c000000000102bebb0a000063ef010101]dnl +[1600f9fcef010101]) + +dnl Check IGMPv3 packet +OVS_WAIT_UNTIL([ovs-pcap p2.pcap | grep -q ]dnl +[01005e0101325054000000110800]dnl +[45c00028000000000102ce9b0a000063e0000016]dnl +[2200e2660000000101000001ef0101320a000063]) + +dnl Check MLDv1 packet +OVS_WAIT_UNTIL([ovs-pcap p2.pcap | grep -q ]dnl +[3333ff0e4c67000c290e4c6786dd]dnl +[600000000020000100000000000000000000000000000000ff0200000000000000000001ff0e4c67]dnl +[3a000502000001008300e7b800000000ff0200000000000000000001ff0e4c67]) + +dnl Check MLDv2 packet +OVS_WAIT_UNTIL([ovs-pcap p2.pcap | grep -q ]dnl +[333300000016000c290e4c6786dd]dnl +[600000000034000100000000000000000000000000000000ff020000000000000000000000000016]dnl +[3a000502000001008f00256b0000000101000001ff0200000000000000000001ff0e4d0000000000000000000000000000000000]) + + +OVS_VSWITCHD_STOP +AT_CLEANUP -- 2.56.0 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
