From patchwork Mon Jan 27 13:20:35 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dumitru Ceara X-Patchwork-Id: 1229836 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.133; helo=hemlock.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=IU+yRMdh; dkim-atps=neutral Received: from hemlock.osuosl.org (smtp2.osuosl.org [140.211.166.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 485r3K31Ynz9sRs for ; Tue, 28 Jan 2020 00:20:45 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by hemlock.osuosl.org (Postfix) with ESMTP id A031E87933; Mon, 27 Jan 2020 13:20:43 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from hemlock.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 6EKnR4aYAGqN; Mon, 27 Jan 2020 13:20:43 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by hemlock.osuosl.org (Postfix) with ESMTP id 66898878F7; Mon, 27 Jan 2020 13:20:43 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 4F4D2C0175; Mon, 27 Jan 2020 13:20:43 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from hemlock.osuosl.org (smtp2.osuosl.org [140.211.166.133]) by lists.linuxfoundation.org (Postfix) with ESMTP id 174CFC0171 for ; Mon, 27 Jan 2020 13:20:42 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by hemlock.osuosl.org (Postfix) with ESMTP id 0F73B87938 for ; Mon, 27 Jan 2020 13:20:42 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from hemlock.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id clNcHp6HefM0 for ; Mon, 27 Jan 2020 13:20:41 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [205.139.110.120]) by hemlock.osuosl.org (Postfix) with ESMTPS id BB43C87924 for ; Mon, 27 Jan 2020 13:20:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1580131240; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=0Q3Cp3Tzr2G4NvUR+ODXqkCiZfASq5bKX5nExH+d9xA=; b=IU+yRMdhvpHZjPuWPo6mqfIe2gJSVi1B2Jmc5Ywear8m0xl+UVFDsD9u/VC62cUWQZ2ceO kbyvMj+boeM2ALPch0/lOtjzHVGeH4iHJRc5oa9rfIY58F7kpySxQEB/uAHudsK5ffreCM Oja67uuNPRE+IxIJY9uzpOx+uTbYg6g= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-424-ZcNbImOiOIijkNRP2E7hIg-1; Mon, 27 Jan 2020 08:20:39 -0500 Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 8992B1005F6B for ; Mon, 27 Jan 2020 13:20:38 +0000 (UTC) Received: from dceara.remote.csb (ovpn-117-215.ams2.redhat.com [10.36.117.215]) by smtp.corp.redhat.com (Postfix) with ESMTP id 0618560BF4 for ; Mon, 27 Jan 2020 13:20:37 +0000 (UTC) From: Dumitru Ceara To: dev@openvswitch.org Date: Mon, 27 Jan 2020 14:20:35 +0100 Message-Id: <20200127132031.10214.68758.stgit@dceara.remote.csb> In-Reply-To: <20200127132014.10214.19808.stgit@dceara.remote.csb> References: <20200127132014.10214.19808.stgit@dceara.remote.csb> User-Agent: StGit/0.17.1-dirty MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-MC-Unique: ZcNbImOiOIijkNRP2E7hIg-1 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v2 ovn 1/2] ovn-northd: Fix ipv4.mcast logical field. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Acked-by: Mark Michelson Signed-off-by: Dumitru Ceara --- lib/logical-fields.c | 3 ++- northd/ovn-northd.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/logical-fields.c b/lib/logical-fields.c index 8fb591c..5748b67 100644 --- a/lib/logical-fields.c +++ b/lib/logical-fields.c @@ -158,7 +158,8 @@ ovn_init_symtab(struct shash *symtab) expr_symtab_add_field(symtab, "ip4.dst", MFF_IPV4_DST, "ip4", false); expr_symtab_add_predicate(symtab, "ip4.src_mcast", "ip4.src[28..31] == 0xe"); - expr_symtab_add_predicate(symtab, "ip4.mcast", "ip4.dst[28..31] == 0xe"); + expr_symtab_add_predicate(symtab, "ip4.mcast", + "eth.mcast && ip4.dst[28..31] == 0xe"); expr_symtab_add_predicate(symtab, "icmp4", "ip4 && ip.proto == 1"); expr_symtab_add_field(symtab, "icmp4.type", MFF_ICMPV4_TYPE, "icmp4", diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index d094587..b2bf461 100644 --- a/northd/ovn-northd.c +++ b/northd/ovn-northd.c @@ -6305,7 +6305,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, * ports - RFC 4541, section 2.1.2, item 2. */ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 85, - "ip4 && ip4.dst == 224.0.0.0/24", + "ip4.mcast && ip4.dst == 224.0.0.0/24", "outport = \""MC_FLOOD"\"; output;"); /* Forward uregistered IP multicast to routers with relay enabled From patchwork Mon Jan 27 13:20:46 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dumitru Ceara X-Patchwork-Id: 1229837 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.138; helo=whitealder.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=ccuYa2uq; dkim-atps=neutral Received: from whitealder.osuosl.org (smtp1.osuosl.org [140.211.166.138]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 485r3k6YGNz9s1x for ; Tue, 28 Jan 2020 00:21:04 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by whitealder.osuosl.org (Postfix) with ESMTP id 93AF185C9A; Mon, 27 Jan 2020 13:21:02 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from whitealder.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id QPC2jX6DThqW; Mon, 27 Jan 2020 13:21:02 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by whitealder.osuosl.org (Postfix) with ESMTP id 37C6984BA5; Mon, 27 Jan 2020 13:21:02 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 1FB86C0171; Mon, 27 Jan 2020 13:21:02 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from silver.osuosl.org (smtp3.osuosl.org [140.211.166.136]) by lists.linuxfoundation.org (Postfix) with ESMTP id 68092C0171 for ; Mon, 27 Jan 2020 13:21:01 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by silver.osuosl.org (Postfix) with ESMTP id 4C4FC1FD16 for ; Mon, 27 Jan 2020 13:21:01 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from silver.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id cGyF5gx0DCX0 for ; Mon, 27 Jan 2020 13:20:56 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [205.139.110.120]) by silver.osuosl.org (Postfix) with ESMTPS id 64E382049F for ; Mon, 27 Jan 2020 13:20:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1580131255; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=69RCljCkr2mNjrPaNt4RXbc32LFI3Y1U5s06vM6jxGI=; b=ccuYa2uqKlUwDJDFMwPyslF4MKknK7m+EHv4GKjPHlkbY6oWzKJxZw1Z1ZD9GgAMzVjY5y 3M+14jmekhu3iwKy/A5hbEySI2CfVFAvKbFZ7mQtd+X4SKp5sXwt8Ul39SbbzR/EuJnl48 jLAbU8PrzvYonSK5q/mbKCOeU+sJH/o= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-154-cgS1HlDtPomEkAVduJ5fWQ-1; Mon, 27 Jan 2020 08:20:50 -0500 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id B04DF800D41 for ; Mon, 27 Jan 2020 13:20:49 +0000 (UTC) Received: from dceara.remote.csb (ovpn-117-215.ams2.redhat.com [10.36.117.215]) by smtp.corp.redhat.com (Postfix) with ESMTP id B40915DF2B for ; Mon, 27 Jan 2020 13:20:48 +0000 (UTC) From: Dumitru Ceara To: dev@openvswitch.org Date: Mon, 27 Jan 2020 14:20:46 +0100 Message-Id: <20200127132043.10214.84127.stgit@dceara.remote.csb> In-Reply-To: <20200127132014.10214.19808.stgit@dceara.remote.csb> References: <20200127132014.10214.19808.stgit@dceara.remote.csb> User-Agent: StGit/0.17.1-dirty MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-MC-Unique: cgS1HlDtPomEkAVduJ5fWQ-1 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v2 ovn 2/2] ovn: Add MLD support. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Extend the existing infrastructure used for IPv4 multicast to IPv6 multicast: - snoop MLDv1 & MLDv2 reports. - if multicast querier is configured, generate MLDv2 queries. - support IPv6 multicast relay. - support static flood configuration for IPv6 multicast too. Acked-by: Mark Michelson Signed-off-by: Dumitru Ceara --- NEWS | 1 controller/pinctrl.c | 359 +++++++++++++++++++++++------ lib/logical-fields.c | 33 +++ lib/ovn-l7.h | 97 ++++++++ northd/ovn-northd.8.xml | 22 ++ northd/ovn-northd.c | 103 ++++++-- ovn-nb.xml | 4 ovn-sb.ovsschema | 5 ovn-sb.xml | 5 tests/ovn.at | 579 +++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 1099 insertions(+), 109 deletions(-) diff --git a/NEWS b/NEWS index 9e7d601..2b8cd6f 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ Post-OVS-v2.12.0 - Added IPv6 NAT support for OVN routers. - Added Stateless Floating IP support in OVN. - Added Forwarding Group support in OVN. + - Added support for MLD Snooping and MLD Querier. v2.12.0 - 03 Sep 2019 --------------------- diff --git a/controller/pinctrl.c b/controller/pinctrl.c index 452ca8a..f7e31f9 100644 --- a/controller/pinctrl.c +++ b/controller/pinctrl.c @@ -263,7 +263,7 @@ static void ip_mcast_sync( struct ovsdb_idl_index *sbrec_igmp_groups, struct ovsdb_idl_index *sbrec_ip_multicast) OVS_REQUIRES(pinctrl_mutex); -static void pinctrl_ip_mcast_handle_igmp( +static void pinctrl_ip_mcast_handle( struct rconn *swconn, const struct flow *ip_flow, struct dp_packet *pkt_in, @@ -1908,8 +1908,8 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg) &userdata); break; case ACTION_OPCODE_IGMP: - pinctrl_ip_mcast_handle_igmp(swconn, &headers, &packet, - &pin.flow_metadata, &userdata); + pinctrl_ip_mcast_handle(swconn, &headers, &packet, &pin.flow_metadata, + &userdata); break; case ACTION_OPCODE_PUT_ARP: @@ -3198,32 +3198,55 @@ pinctrl_compose_ipv4(struct dp_packet *packet, struct eth_addr eth_src, packet->packet_type = htonl(PT_ETH); struct eth_header *eh = dp_packet_put_zeros(packet, sizeof *eh); - eh->eth_dst = eth_dst; - eh->eth_src = eth_src; - struct ip_header *nh = dp_packet_put_zeros(packet, sizeof *nh); + eh->eth_dst = eth_dst; + eh->eth_src = eth_src; eh->eth_type = htons(ETH_TYPE_IP); dp_packet_set_l3(packet, nh); nh->ip_ihl_ver = IP_IHL_VER(5, 4); - nh->ip_tot_len = htons(sizeof(struct ip_header) + ip_payload_len); + nh->ip_tot_len = htons(sizeof *nh + ip_payload_len); nh->ip_tos = IP_DSCP_CS6; nh->ip_proto = ip_proto; nh->ip_frag_off = htons(IP_DF); - /* Setting tos and ttl to 0 and 1 respectively. */ packet_set_ipv4(packet, ipv4_src, ipv4_dst, 0, ttl); nh->ip_csum = 0; nh->ip_csum = csum(nh, sizeof *nh); } +static void +pinctrl_compose_ipv6(struct dp_packet *packet, struct eth_addr eth_src, + struct eth_addr eth_dst, struct in6_addr *ipv6_src, + struct in6_addr *ipv6_dst, uint8_t ip_proto, uint8_t ttl, + uint16_t ip_payload_len) +{ + dp_packet_clear(packet); + packet->packet_type = htonl(PT_ETH); + + struct eth_header *eh = dp_packet_put_zeros(packet, sizeof *eh); + struct ip6_hdr *nh = dp_packet_put_zeros(packet, sizeof *nh); + + eh->eth_dst = eth_dst; + eh->eth_src = eth_src; + eh->eth_type = htons(ETH_TYPE_IPV6); + dp_packet_set_l3(packet, nh); + + nh->ip6_vfc = 0x60; + nh->ip6_nxt = ip_proto; + nh->ip6_plen = htons(ip_payload_len); + + packet_set_ipv6(packet, ipv6_src, ipv6_dst, 0, 0, ttl); +} + /* * Multicast snooping configuration. */ struct ip_mcast_snoop_cfg { bool enabled; - bool querier_enabled; + bool querier_v4_enabled; + bool querier_v6_enabled; uint32_t table_size; /* Max number of allowed multicast groups. */ uint32_t idle_time_s; /* Idle timeout for multicast groups. */ @@ -3231,10 +3254,19 @@ struct ip_mcast_snoop_cfg { uint32_t query_max_resp_s; /* Multicast query max-response field. */ uint32_t seq_no; /* Used for flushing learnt groups. */ - struct eth_addr query_eth_src; /* Src ETH address used for queries. */ - struct eth_addr query_eth_dst; /* Dst ETH address used for queries. */ - ovs_be32 query_ipv4_src; /* Src IPv4 address used for queries. */ - ovs_be32 query_ipv4_dst; /* Dsc IPv4 address used for queries. */ + struct eth_addr query_eth_src; /* Src ETH address used for queries. */ + struct eth_addr query_eth_v4_dst; /* Dst ETH address used for IGMP + * queries. + */ + struct eth_addr query_eth_v6_dst; /* Dst ETH address used for MLD + * queries. + */ + + ovs_be32 query_ipv4_src; /* Src IPv4 address used for queries. */ + ovs_be32 query_ipv4_dst; /* Dsc IPv4 address used for queries. */ + + struct in6_addr query_ipv6_src; /* Src IPv6 address used for queries. */ + struct in6_addr query_ipv6_dst; /* Dsc IPv6 address used for queries. */ }; /* @@ -3264,6 +3296,9 @@ struct ip_mcast_snoop_state { /* Only default vlan supported for now. */ #define IP_MCAST_VLAN 1 +/* MLD router-alert IPv6 extension header value. */ +static const uint8_t mld_router_alert[4] = {0x05, 0x02, 0x00, 0x00}; + /* Multicast snooping information stored independently by datapath key. * Protected by pinctrl_mutex. pinctrl_handler has RW access and pinctrl_main * has RO access. @@ -3277,7 +3312,7 @@ static struct ovs_list mcast_query_list; /* Multicast config information stored independently by datapath key. * Protected by pinctrl_mutex. pinctrl_handler has RO access and pinctrl_main - * has RW access. Read accesses from pinctrl_ip_mcast_handle_igmp() can be + * has RW access. Read accesses from pinctrl_ip_mcast_handle() can be * performed without taking the lock as they are executed in the pinctrl_main * thread. */ @@ -3292,8 +3327,10 @@ ip_mcast_snoop_cfg_load(struct ip_mcast_snoop_cfg *cfg, memset(cfg, 0, sizeof *cfg); cfg->enabled = (ip_mcast->enabled && ip_mcast->enabled[0]); - cfg->querier_enabled = + bool querier_enabled = (cfg->enabled && ip_mcast->querier && ip_mcast->querier[0]); + cfg->querier_v4_enabled = querier_enabled; + cfg->querier_v6_enabled = querier_enabled; if (ip_mcast->table_size) { cfg->table_size = ip_mcast->table_size[0]; @@ -3324,30 +3361,56 @@ ip_mcast_snoop_cfg_load(struct ip_mcast_snoop_cfg *cfg, cfg->seq_no = ip_mcast->seq_no; - if (cfg->querier_enabled) { + if (querier_enabled) { /* Try to parse the source ETH address. */ if (!ip_mcast->eth_src || !eth_addr_from_string(ip_mcast->eth_src, &cfg->query_eth_src)) { VLOG_WARN_RL(&rl, "IGMP Querier enabled with invalid ETH src address"); - /* Failed to parse the IPv4 source address. Disable the querier. */ - cfg->querier_enabled = false; + /* Failed to parse the ETH source address. Disable the querier. */ + cfg->querier_v4_enabled = false; + cfg->querier_v6_enabled = false; } - /* Try to parse the source IP address. */ - if (!ip_mcast->ip4_src || - !ip_parse(ip_mcast->ip4_src, &cfg->query_ipv4_src)) { - VLOG_WARN_RL(&rl, - "IGMP Querier enabled with invalid IPv4 src address"); - /* Failed to parse the IPv4 source address. Disable the querier. */ - cfg->querier_enabled = false; + /* Try to parse the source IPv4 address. */ + if (cfg->querier_v4_enabled) { + if (!ip_mcast->ip4_src || + !ip_parse(ip_mcast->ip4_src, &cfg->query_ipv4_src)) { + VLOG_WARN_RL(&rl, + "IGMP Querier enabled with invalid IPv4 " + "src address"); + /* Failed to parse the IPv4 source address. Disable the + * querier. + */ + cfg->querier_v4_enabled = false; + } + + /* IGMP queries must be sent to 224.0.0.1. */ + cfg->query_eth_v4_dst = + (struct eth_addr)ETH_ADDR_C(01, 00, 5E, 00, 00, 01); + cfg->query_ipv4_dst = htonl(0xe0000001); } - /* IGMP queries must be sent to 224.0.0.1. */ - cfg->query_eth_dst = - (struct eth_addr)ETH_ADDR_C(01, 00, 5E, 00, 00, 01); - cfg->query_ipv4_dst = htonl(0xe0000001); + /* Try to parse the source IPv6 address. */ + if (cfg->querier_v6_enabled) { + if (!ip_mcast->ip6_src || + !ipv6_parse(ip_mcast->ip6_src, &cfg->query_ipv6_src)) { + VLOG_WARN_RL(&rl, + "MLD Querier enabled with invalid IPv6 " + "src address"); + /* Failed to parse the IPv6 source address. Disable the + * querier. + */ + cfg->querier_v6_enabled = false; + } + + /* MLD queries must be sent to ALL-HOSTS (ff02::1). */ + cfg->query_eth_v6_dst = + (struct eth_addr)ETH_ADDR_C(33, 33, 00, 00, 00, 00); + cfg->query_ipv6_dst = + (struct in6_addr)IN6ADDR_ALL_HOSTS_INIT; + } } } @@ -3455,9 +3518,15 @@ ip_mcast_snoop_configure(struct ip_mcast_snoop *ip_ms, ip_mcast_snoop_flush(ip_ms); } - if (ip_ms->cfg.querier_enabled && !cfg->querier_enabled) { + bool old_querier_enabled = + (ip_ms->cfg.querier_v4_enabled || ip_ms->cfg.querier_v6_enabled); + + bool querier_enabled = + (cfg->querier_v4_enabled || cfg->querier_v6_enabled); + + if (old_querier_enabled && !querier_enabled) { ovs_list_remove(&ip_ms->query_node); - } else if (!ip_ms->cfg.querier_enabled && cfg->querier_enabled) { + } else if (!old_querier_enabled && querier_enabled) { ovs_list_push_back(&mcast_query_list, &ip_ms->query_node); } } else { @@ -3526,7 +3595,7 @@ ip_mcast_snoop_remove(struct ip_mcast_snoop *ip_ms) { hmap_remove(&mcast_snoop_map, &ip_ms->hmap_node); - if (ip_ms->cfg.querier_enabled) { + if (ip_ms->cfg.querier_v4_enabled || ip_ms->cfg.querier_v6_enabled) { ovs_list_remove(&ip_ms->query_node); } @@ -3664,7 +3733,8 @@ ip_mcast_sync(struct ovsdb_idl_txn *ovnsb_idl_txn, * - or the group has expired. */ SBREC_IGMP_GROUP_FOR_EACH_BYINDEX (sbrec_igmp, sbrec_igmp_groups) { - ovs_be32 group_addr; + ovs_be32 group_v4_addr; + struct in6_addr group_addr; if (!sbrec_igmp->datapath) { continue; @@ -3681,14 +3751,15 @@ ip_mcast_sync(struct ovsdb_idl_txn *ovnsb_idl_txn, continue; } - if (!ip_parse(sbrec_igmp->address, &group_addr)) { + if (ip_parse(sbrec_igmp->address, &group_v4_addr)) { + group_addr = in6_addr_mapped_ipv4(group_v4_addr); + } else if (!ipv6_parse(sbrec_igmp->address, &group_addr)) { continue; } ovs_rwlock_rdlock(&ip_ms->ms->rwlock); struct mcast_group *mc_group = - mcast_snooping_lookup4(ip_ms->ms, group_addr, - IP_MCAST_VLAN); + mcast_snooping_lookup(ip_ms->ms, &group_addr, IP_MCAST_VLAN); if (!mc_group || ovs_list_is_empty(&mc_group->bundle_lru)) { igmp_group_delete(sbrec_igmp); @@ -3742,54 +3813,29 @@ ip_mcast_sync(struct ovsdb_idl_txn *ovnsb_idl_txn, } } -static void -pinctrl_ip_mcast_handle_igmp(struct rconn *swconn OVS_UNUSED, +static bool +pinctrl_ip_mcast_handle_igmp(struct ip_mcast_snoop *ip_ms, const struct flow *ip_flow, struct dp_packet *pkt_in, - const struct match *md, - struct ofpbuf *userdata OVS_UNUSED) - OVS_NO_THREAD_SAFETY_ANALYSIS + void *port_key_data) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - - /* This action only works for IP packets, and the switch should only send - * us IP packets this way, but check here just to be sure. - */ - if (ip_flow->dl_type != htons(ETH_TYPE_IP)) { - VLOG_WARN_RL(&rl, - "IGMP action on non-IP packet (eth_type 0x%"PRIx16")", - ntohs(ip_flow->dl_type)); - return; - } - - int64_t dp_key = ntohll(md->flow.metadata); - uint32_t port_key = md->flow.regs[MFF_LOG_INPORT - MFF_REG0]; - const struct igmp_header *igmp; size_t offset; offset = (char *) dp_packet_l4(pkt_in) - (char *) dp_packet_data(pkt_in); igmp = dp_packet_at(pkt_in, offset, IGMP_HEADER_LEN); if (!igmp || csum(igmp, dp_packet_l4_size(pkt_in)) != 0) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); VLOG_WARN_RL(&rl, "multicast snooping received bad IGMP checksum"); - return; + return false; } ovs_be32 ip4 = ip_flow->igmp_group_ip4; - - struct ip_mcast_snoop *ip_ms = ip_mcast_snoop_find(dp_key); - if (!ip_ms || !ip_ms->cfg.enabled) { - /* IGMP snooping is not configured or is disabled. */ - return; - } - - void *port_key_data = (void *)(uintptr_t)port_key; - bool group_change = false; + /* Only default VLAN is supported for now. */ ovs_rwlock_wrlock(&ip_ms->ms->rwlock); switch (ntohs(ip_flow->tp_src)) { - /* Only default VLAN is supported for now. */ case IGMP_HOST_MEMBERSHIP_REPORT: case IGMPV2_HOST_MEMBERSHIP_REPORT: group_change = @@ -3816,27 +3862,118 @@ pinctrl_ip_mcast_handle_igmp(struct rconn *swconn OVS_UNUSED, break; } ovs_rwlock_unlock(&ip_ms->ms->rwlock); + return group_change; +} - if (group_change) { - notify_pinctrl_main(); +static bool +pinctrl_ip_mcast_handle_mld(struct ip_mcast_snoop *ip_ms, + const struct flow *ip_flow, + struct dp_packet *pkt_in, + void *port_key_data) +{ + const struct mld_header *mld; + size_t offset; + + offset = (char *) dp_packet_l4(pkt_in) - (char *) dp_packet_data(pkt_in); + mld = dp_packet_at(pkt_in, offset, MLD_HEADER_LEN); + + if (!mld || packet_csum_upperlayer6(dp_packet_l3(pkt_in), + mld, IPPROTO_ICMPV6, + dp_packet_l4_size(pkt_in)) != 0) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, + "multicast snooping received bad MLD checksum"); + return false; } + + bool group_change = false; + + /* Only default VLAN is supported for now. */ + ovs_rwlock_wrlock(&ip_ms->ms->rwlock); + switch (ntohs(ip_flow->tp_src)) { + case MLD_QUERY: + /* Shouldn't be receiving any of these since we are the multicast + * router. Store them for now. + */ + if (!ipv6_addr_equals(&ip_flow->ipv6_src, &in6addr_any)) { + group_change = + mcast_snooping_add_mrouter(ip_ms->ms, IP_MCAST_VLAN, + port_key_data); + } + break; + case MLD_REPORT: + case MLD_DONE: + case MLD2_REPORT: + group_change = + mcast_snooping_add_mld(ip_ms->ms, pkt_in, IP_MCAST_VLAN, + port_key_data); + break; + } + ovs_rwlock_unlock(&ip_ms->ms->rwlock); + return group_change; } -static long long int -ip_mcast_querier_send(struct rconn *swconn, struct ip_mcast_snoop *ip_ms, - long long int current_time) +static void +pinctrl_ip_mcast_handle(struct rconn *swconn OVS_UNUSED, + const struct flow *ip_flow, + struct dp_packet *pkt_in, + const struct match *md, + struct ofpbuf *userdata OVS_UNUSED) + OVS_NO_THREAD_SAFETY_ANALYSIS { - if (current_time < ip_ms->query_time_ms) { - return ip_ms->query_time_ms; + uint16_t dl_type = ntohs(ip_flow->dl_type); + + /* This action only works for IP packets, and the switch should only send + * us IP packets this way, but check here just to be sure. + */ + if (dl_type != ETH_TYPE_IP && dl_type != ETH_TYPE_IPV6) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, + "IGMP action on non-IP packet (eth_type 0x%"PRIx16")", + dl_type); + return; } + int64_t dp_key = ntohll(md->flow.metadata); + + struct ip_mcast_snoop *ip_ms = ip_mcast_snoop_find(dp_key); + if (!ip_ms || !ip_ms->cfg.enabled) { + /* IGMP snooping is not configured or is disabled. */ + return; + } + + uint32_t port_key = md->flow.regs[MFF_LOG_INPORT - MFF_REG0]; + void *port_key_data = (void *)(uintptr_t)port_key; + + switch (dl_type) { + case ETH_TYPE_IP: + if (pinctrl_ip_mcast_handle_igmp(ip_ms, ip_flow, pkt_in, + port_key_data)) { + notify_pinctrl_main(); + } + break; + case ETH_TYPE_IPV6: + if (pinctrl_ip_mcast_handle_mld(ip_ms, ip_flow, pkt_in, + port_key_data)) { + notify_pinctrl_main(); + } + break; + default: + OVS_NOT_REACHED(); + break; + } +} + +static void +ip_mcast_querier_send_igmp(struct rconn *swconn, struct ip_mcast_snoop *ip_ms) +{ /* Compose a multicast query. */ uint64_t packet_stub[128 / 8]; struct dp_packet packet; dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); pinctrl_compose_ipv4(&packet, ip_ms->cfg.query_eth_src, - ip_ms->cfg.query_eth_dst, + ip_ms->cfg.query_eth_v4_dst, ip_ms->cfg.query_ipv4_src, ip_ms->cfg.query_ipv4_dst, IPPROTO_IGMP, 1, sizeof(struct igmpv3_query_header)); @@ -3873,6 +4010,78 @@ ip_mcast_querier_send(struct rconn *swconn, struct ip_mcast_snoop *ip_ms, queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); dp_packet_uninit(&packet); ofpbuf_uninit(&ofpacts); +} + +static void +ip_mcast_querier_send_mld(struct rconn *swconn, struct ip_mcast_snoop *ip_ms) +{ + /* Compose a multicast query. */ + uint64_t packet_stub[128 / 8]; + struct dp_packet packet; + + dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); + pinctrl_compose_ipv6(&packet, ip_ms->cfg.query_eth_src, + ip_ms->cfg.query_eth_v6_dst, + &ip_ms->cfg.query_ipv6_src, + &ip_ms->cfg.query_ipv6_dst, + IPPROTO_HOPOPTS, 1, + IPV6_EXT_HEADER_LEN + MLD_QUERY_HEADER_LEN); + + struct ipv6_ext_header *ext_hdr = + dp_packet_put_zeros(&packet, IPV6_EXT_HEADER_LEN); + packet_set_ipv6_ext_header(ext_hdr, IPPROTO_ICMPV6, 0, mld_router_alert, + ARRAY_SIZE(mld_router_alert)); + + struct mld_header *mh = + dp_packet_put_zeros(&packet, MLD_QUERY_HEADER_LEN); + dp_packet_set_l4(&packet, mh); + + /* MLD query max-response in milliseconds. */ + uint16_t max_response = ip_ms->cfg.query_max_resp_s * 1000; + uint8_t qqic = ip_ms->cfg.query_max_resp_s; + struct in6_addr unspecified = { { { 0 } } }; + packet_set_mld_query(&packet, max_response, &unspecified, false, 0, qqic); + + /* Inject multicast query. */ + uint64_t ofpacts_stub[4096 / 8]; + struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); + enum ofp_version version = rconn_get_version(swconn); + put_load(ip_ms->dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts); + put_load(OVN_MCAST_FLOOD_TUNNEL_KEY, MFF_LOG_OUTPORT, 0, 32, &ofpacts); + put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY, 1, &ofpacts); + struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts); + resubmit->in_port = OFPP_CONTROLLER; + resubmit->table_id = OFTABLE_LOCAL_OUTPUT; + + struct ofputil_packet_out po = { + .packet = dp_packet_data(&packet), + .packet_len = dp_packet_size(&packet), + .buffer_id = UINT32_MAX, + .ofpacts = ofpacts.data, + .ofpacts_len = ofpacts.size, + }; + match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); + enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); + queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); + dp_packet_uninit(&packet); + ofpbuf_uninit(&ofpacts); +} + +static long long int +ip_mcast_querier_send(struct rconn *swconn, struct ip_mcast_snoop *ip_ms, + long long int current_time) +{ + if (current_time < ip_ms->query_time_ms) { + return ip_ms->query_time_ms; + } + + if (ip_ms->cfg.querier_v4_enabled) { + ip_mcast_querier_send_igmp(swconn, ip_ms); + } + + if (ip_ms->cfg.querier_v6_enabled) { + ip_mcast_querier_send_mld(swconn, ip_ms); + } /* Set the next query time. */ ip_ms->query_time_ms = current_time + ip_ms->cfg.query_interval_s * 1000; diff --git a/lib/logical-fields.c b/lib/logical-fields.c index 5748b67..25ace58 100644 --- a/lib/logical-fields.c +++ b/lib/logical-fields.c @@ -138,6 +138,8 @@ ovn_init_symtab(struct shash *symtab) expr_symtab_add_predicate(symtab, "eth.bcast", "eth.dst == ff:ff:ff:ff:ff:ff"); expr_symtab_add_subfield(symtab, "eth.mcast", NULL, "eth.dst[40]"); + expr_symtab_add_predicate(symtab, "eth.mcastv6", + "eth.dst[32..47] == 0x3333"); expr_symtab_add_field(symtab, "vlan.tci", MFF_VLAN_TCI, NULL, false); expr_symtab_add_predicate(symtab, "vlan.present", "vlan.tci[12]"); @@ -173,6 +175,27 @@ ovn_init_symtab(struct shash *symtab) expr_symtab_add_field(symtab, "ip6.dst", MFF_IPV6_DST, "ip6", false); expr_symtab_add_field(symtab, "ip6.label", MFF_IPV6_LABEL, "ip6", false); + /* Predefined IPv6 multicast groups (RFC 4291, 2.7.1). */ + expr_symtab_add_predicate(symtab, "ip6.mcast_rsvd", + "ip6.dst[116..127] == 0xff0 && " + "ip6.dst[0..111] == 0x0"); + expr_symtab_add_predicate(symtab, "ip6.mcast_all_nodes", + "ip6.dst == ff01::1 || ip6.dst == ff02::1"); + expr_symtab_add_predicate(symtab, "ip6.mcast_all_rtrs", + "ip6.dst == ff01::2 || ip6.dst == ff02::2 || " + "ip6.dst == ff05::2"); + expr_symtab_add_predicate(symtab, "ip6.mcast_sol_node", + "ip6.dst == ff02::1:ff00:0000/104"); + expr_symtab_add_predicate(symtab, "ip6.mcast_flood", + "eth.mcastv6 && " + "(ip6.mcast_rsvd || " + "ip6.mcast_all_nodes || " + "ip6.mcast_all_rtrs || " + "ip6.mcast_sol_node)"); + + expr_symtab_add_predicate(symtab, "ip6.mcast", + "eth.mcastv6 && ip6.dst[120..127] == 0xff"); + expr_symtab_add_predicate(symtab, "icmp6", "ip6 && ip.proto == 58"); expr_symtab_add_field(symtab, "icmp6.type", MFF_ICMPV6_TYPE, "icmp6", true); @@ -208,6 +231,16 @@ ovn_init_symtab(struct shash *symtab) expr_symtab_add_field(symtab, "nd.sll", MFF_ND_SLL, "nd_ns", false); expr_symtab_add_field(symtab, "nd.tll", MFF_ND_TLL, "nd_na", false); + /* MLDv1 packets use link-local source addresses + * (RFC 2710 and RFC 3810). + */ + expr_symtab_add_predicate(symtab, "mldv1", + "ip6.src == fe80::/10 && " + "icmp6.type == {130, 131, 132}"); + /* MLDv2 packets are sent to ff02::16 (RFC 3810, 5.2.14) */ + expr_symtab_add_predicate(symtab, "mldv2", + "ip6.dst == ff02::16 && icmp6.type == 143"); + expr_symtab_add_predicate(symtab, "tcp", "ip.proto == 6"); expr_symtab_add_field(symtab, "tcp.src", MFF_TCP_SRC, "tcp", false); expr_symtab_add_field(symtab, "tcp.dst", MFF_TCP_DST, "tcp", false); diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h index 375b770..f20d86c 100644 --- a/lib/ovn-l7.h +++ b/lib/ovn-l7.h @@ -14,12 +14,14 @@ * limitations under the License. */ -#ifndef OVN_DHCP_H -#define OVN_DHCP_H 1 +#ifndef OVN_L7_H +#define OVN_L7_H 1 #include #include #include +#include "csum.h" +#include "dp-packet.h" #include "openvswitch/hmap.h" #include "hash.h" #include "ovn/logical-fields.h" @@ -357,4 +359,93 @@ controller_event_opts_destroy(struct controller_event_options *opts) } } -#endif /* OVN_DHCP_H */ +static inline bool +ipv6_addr_is_routable_multicast(const struct in6_addr *ip) { + if (!ipv6_addr_is_multicast(ip)) { + return false; + } + + /* Check multicast group scope, RFC 4291, 2.7. */ + switch (ip->s6_addr[1] & 0x0F) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x0F: + return false; + default: + return true; + } +} + +#define IPV6_EXT_HEADER_LEN 8 +struct ipv6_ext_header { + uint8_t ip6_nxt_proto; + uint8_t len; + uint8_t values[6]; +}; +BUILD_ASSERT_DECL(IPV6_EXT_HEADER_LEN == sizeof(struct ipv6_ext_header)); + +/* Sets the IPv6 extension header fields (next proto and length) and + * copies the first max 6 values to the header. Returns the number of values + * copied to the header. + */ +static inline size_t +packet_set_ipv6_ext_header(struct ipv6_ext_header *ext_hdr, uint8_t ip_proto, + uint8_t ext_len, const uint8_t *values, + size_t n_values) +{ + ext_hdr->ip6_nxt_proto = ip_proto; + ext_hdr->len = (ext_len >= 8 ? ext_len - 8 : 0); + if (OVS_UNLIKELY(n_values > 6)) { + n_values = 6; + } + memcpy(&ext_hdr->values, values, n_values); + return n_values; +} + +#define MLD_QUERY_HEADER_LEN 28 +struct mld_query_header { + uint8_t type; + uint8_t code; + ovs_be16 csum; + ovs_be16 max_resp; + ovs_be16 rsvd; + struct in6_addr group; + uint8_t srs_qrv; + uint8_t qqic; + ovs_be16 nsrcs; +}; +BUILD_ASSERT_DECL(MLD_QUERY_HEADER_LEN == sizeof(struct mld_query_header)); + +/* Sets the MLD type to MLD_QUERY and populates the MLD query header + * 'packet'. 'packet' must be a valid MLD query packet with its l4 + * offset properly populated. + */ +static inline void +packet_set_mld_query(struct dp_packet *packet, uint16_t max_resp, + const struct in6_addr *group, + bool srs, uint8_t qrv, uint8_t qqic) +{ + struct mld_query_header *mqh = dp_packet_l4(packet); + mqh->type = MLD_QUERY; + mqh->code = 0; + mqh->max_resp = htons(max_resp); + mqh->rsvd = 0; + memcpy(&mqh->group, group, sizeof mqh->group); + + /* See RFC 3810 5.1.8. */ + if (qrv > 7) { + qrv = 0; + } + + mqh->srs_qrv = (srs << 3 | qrv); + mqh->qqic = qqic; + mqh->nsrcs = 0; + + struct ovs_16aligned_ip6_hdr *nh6 = dp_packet_l3(packet); + mqh->csum = 0; + mqh->csum = packet_csum_upperlayer6(nh6, mqh, IPPROTO_ICMPV6, sizeof *mqh); +} + +#endif /* OVN_L7_H */ diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml index bcb320b..96e8262 100644 --- a/northd/ovn-northd.8.xml +++ b/northd/ovn-northd.8.xml @@ -1069,9 +1069,9 @@ output;
  • - A priority-100 flow that punts all IGMP packets to - ovn-controller if IGMP snooping is enabled on the - logical switch. The flow also forwards the IGMP packets to the + A priority-100 flow that punts all IGMP/MLD packets to + ovn-controller if multicast snooping is enabled on the + logical switch. The flow also forwards the IGMP/MLD packets to the MC_MROUTER_STATIC multicast group, which ovn-northd populates with all the logical ports that have @@ -1096,6 +1096,14 @@ output;
  • + A priority-85 flow that forwards all IP multicast traffic destined to + reserved multicast IPv6 addresses (RFC 4291, 2.7.1, e.g., + Solicited-Node multicast) to the MC_FLOOD multicast + group, which ovn-northd populates with all enabled + logical ports. +
  • + +
  • A priority-80 flow that forwards all unregistered IP multicast traffic to the MC_STATIC multicast group, which ovn-northd populates with all the logical ports that @@ -1560,6 +1568,14 @@ next;
  • + A priority-96 flow explicitly allows IPv6 multicast traffic that is + supposed to reach the router pipeline (e.g., neighbor solicitations + and traffic destined to the All-Routers multicast group). +

    +
  • + +
  • +

    A priority-95 flow allows IP multicast traffic if :mcast_relay='true', otherwise drops it. diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index b2bf461..6026730 100644 --- a/northd/ovn-northd.c +++ b/northd/ovn-northd.c @@ -498,13 +498,18 @@ struct mcast_switch_info { * flushed. */ int64_t query_interval; /* Interval between multicast queries. */ - char *eth_src; /* ETH src address of the multicast queries. */ - char *ipv4_src; /* IP src address of the multicast queries. */ + char *eth_src; /* ETH src address of the queries. */ + char *ipv4_src; /* IPv4 src address of the queries. */ + char *ipv6_src; /* IPv6 src address of the queries. */ + int64_t query_max_response; /* Expected time after which reports should * be received for queries that were sent out. */ - uint32_t active_flows; /* Current number of active IP multicast + uint32_t active_v4_flows; /* Current number of active IPv4 multicast + * flows. + */ + uint32_t active_v6_flows; /* Current number of active IPv6 multicast * flows. */ }; @@ -854,12 +859,15 @@ init_mcast_info_for_switch_datapath(struct ovn_datapath *od) nullable_xstrdup(smap_get(&od->nbs->other_config, "mcast_eth_src")); mcast_sw_info->ipv4_src = nullable_xstrdup(smap_get(&od->nbs->other_config, "mcast_ip4_src")); + mcast_sw_info->ipv6_src = + nullable_xstrdup(smap_get(&od->nbs->other_config, "mcast_ip6_src")); mcast_sw_info->query_max_response = smap_get_ullong(&od->nbs->other_config, "mcast_query_max_response", OVN_MCAST_DEFAULT_QUERY_MAX_RESPONSE_S); - mcast_sw_info->active_flows = 0; + mcast_sw_info->active_v4_flows = 0; + mcast_sw_info->active_v6_flows = 0; } static void @@ -887,6 +895,7 @@ destroy_mcast_info_for_switch_datapath(struct ovn_datapath *od) free(mcast_sw_info->eth_src); free(mcast_sw_info->ipv4_src); + free(mcast_sw_info->ipv6_src); } static void @@ -927,6 +936,10 @@ store_mcast_info_for_switch_datapath(const struct sbrec_ip_multicast *sb, if (mcast_sw_info->ipv4_src) { sbrec_ip_multicast_set_ip4_src(sb, mcast_sw_info->ipv4_src); } + + if (mcast_sw_info->ipv6_src) { + sbrec_ip_multicast_set_ip6_src(sb, mcast_sw_info->ipv6_src); + } } static void @@ -6301,6 +6314,10 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100, "ip4 && ip.proto == 2", ds_cstr(&actions)); + /* Punt MLD traffic to controller. */ + ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100, + "mldv1 || mldv2", ds_cstr(&actions)); + /* Flood all IP multicast traffic destined to 224.0.0.X to all * ports - RFC 4541, section 2.1.2, item 2. */ @@ -6308,6 +6325,13 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, "ip4.mcast && ip4.dst == 224.0.0.0/24", "outport = \""MC_FLOOD"\"; output;"); + /* Flood all IPv6 multicast traffic destined to reserved + * multicast IPs (RFC 4291, 2.7.1). + */ + ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 85, + "ip6.mcast_flood", + "outport = \""MC_FLOOD"\"; output;"); + /* Forward uregistered IP multicast to routers with relay enabled * and to any ports configured to flood IP multicast traffic. * If configured to flood unregistered traffic this will be @@ -6337,7 +6361,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80, - "ip4 && ip4.mcast", ds_cstr(&actions)); + "ip4.mcast || ip6.mcast", ds_cstr(&actions)); } } @@ -6346,7 +6370,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } free(svc_check_match); - /* Ingress table 17: Add IP multicast flows learnt from IGMP + /* Ingress table 17: Add IP multicast flows learnt from IGMP/MLD * (priority 90). */ struct ovn_igmp_group *igmp_group; @@ -6355,19 +6379,27 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, continue; } + ds_clear(&match); + ds_clear(&actions); + struct mcast_switch_info *mcast_sw_info = &igmp_group->datapath->mcast_info.sw; - if (mcast_sw_info->active_flows >= mcast_sw_info->table_size) { - continue; + if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) { + if (mcast_sw_info->active_v4_flows >= mcast_sw_info->table_size) { + continue; + } + mcast_sw_info->active_v4_flows++; + ds_put_format(&match, "eth.mcast && ip4 && ip4.dst == %s ", + igmp_group->mcgroup.name); + } else { + if (mcast_sw_info->active_v6_flows >= mcast_sw_info->table_size) { + continue; + } + mcast_sw_info->active_v6_flows++; + ds_put_format(&match, "eth.mcast && ip6 && ip6.dst == %s ", + igmp_group->mcgroup.name); } - mcast_sw_info->active_flows++; - - ds_clear(&match); - ds_clear(&actions); - - ds_put_format(&match, "eth.mcast && ip4 && ip4.dst == %s ", - igmp_group->mcgroup.name); /* Also flood traffic to all multicast routers with relay enabled. */ if (mcast_sw_info->flood_relay) { @@ -7714,8 +7746,15 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, "ip4.dst == 0.0.0.0/8", "drop;"); + /* Allow IPv6 multicast traffic that's supposed to reach the + * router pipeline (e.g., neighbor solicitations). + */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 96, "ip6.mcast_flood", + "next;"); + /* Allow multicast if relay enabled (priority 95). */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 95, "ip4.mcast", + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 95, + "ip4.mcast || ip6.mcast", od->mcast_info.rtr.relay ? "next;" : "drop;"); /* Drop ARP packets (priority 85). ARP request packets for router's own @@ -9207,8 +9246,13 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) { ds_clear(&match); ds_clear(&actions); - ds_put_format(&match, "ip4 && ip4.dst == %s ", - igmp_group->mcgroup.name); + if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) { + ds_put_format(&match, "ip4 && ip4.dst == %s ", + igmp_group->mcgroup.name); + } else { + ds_put_format(&match, "ip6 && ip6.dst == %s ", + igmp_group->mcgroup.name); + } if (od->mcast_info.rtr.flood_static) { ds_put_cstr(&actions, "clone { " @@ -9227,11 +9271,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, * ports. */ if (od->mcast_info.rtr.flood_static) { - ds_clear(&match); ds_clear(&actions); - ds_put_format(&match, "ip4.mcast"); ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, - "ip4.mcast", + "ip4.mcast || ip6.mcast", "clone { " "outport = \""MC_STATIC"\"; " "ip.ttl--; " @@ -9278,7 +9320,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, } ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 500, - "ip4.mcast", "next;"); + "ip4.mcast || ip6.mcast", "next;"); } /* Local router ingress table ARP_RESOLVE: ARP Resolution. @@ -9823,7 +9865,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, if (op->od->mcast_info.rtr.relay) { ds_clear(&match); ds_clear(&actions); - ds_put_format(&match, "ip4.mcast && outport == %s", + ds_put_format(&match, "(ip4.mcast || ip6.mcast) && outport == %s", op->json_key); ds_put_format(&actions, "eth.src = %s; output;", op->lrp_networks.ea_s); @@ -10515,10 +10557,19 @@ build_mcast_groups(struct northd_context *ctx, struct ovn_igmp_group *igmp_group; LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) { + struct in6_addr *address = &igmp_group->address; + + /* For IPv6 only relay routable multicast groups + * (RFC 4291 2.7). + */ + if (!IN6_IS_ADDR_V4MAPPED(address) && + !ipv6_addr_is_routable_multicast(address)) { + continue; + } + struct ovn_igmp_group *igmp_group_rtr = ovn_igmp_group_add(ctx, igmp_groups, router_port->od, - &igmp_group->address, - igmp_group->mcgroup.name); + address, igmp_group->mcgroup.name); struct ovn_port **router_igmp_ports = xmalloc(sizeof *router_igmp_ports); router_igmp_ports[0] = router_port; @@ -11481,6 +11532,8 @@ main(int argc, char *argv[]) add_column_noalert(ovnsb_idl_loop.idl, &sbrec_ip_multicast_col_ip4_src); add_column_noalert(ovnsb_idl_loop.idl, + &sbrec_ip_multicast_col_ip6_src); + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_ip_multicast_col_table_size); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_ip_multicast_col_idle_timeout); diff --git a/ovn-nb.xml b/ovn-nb.xml index 8c9ef90..7ff0bde 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -350,6 +350,10 @@ Configures the source IPv4 address for queries originated by the logical switch. + + Configures the source IPv6 address for queries originated by the + logical switch. + diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema index 56af0ed..d89f8db 100644 --- a/ovn-sb.ovsschema +++ b/ovn-sb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Southbound", - "version": "2.6.0", - "cksum": "4271405686 21646", + "version": "2.7.0", + "cksum": "4286723485 21693", "tables": { "SB_Global": { "columns": { @@ -374,6 +374,7 @@ "querier": {"type": {"key": "boolean", "min": 0, "max": 1}}, "eth_src": {"type": "string"}, "ip4_src": {"type": "string"}, + "ip6_src": {"type": "string"}, "table_size": {"type": {"key": "integer", "min": 0, "max": 1}}, "idle_timeout": {"type": {"key": "integer", diff --git a/ovn-sb.xml b/ovn-sb.xml index 93bbb86..cf29430 100644 --- a/ovn-sb.xml +++ b/ovn-sb.xml @@ -3778,7 +3778,7 @@ tcp.flags = RST; The ovn-controller process that runs on OVN hypervisor - nodes uses the following columns to determine field values in IGMP + nodes uses the following columns to determine field values in IGMP/MLD queries that it originates: Source Ethernet address. @@ -3786,6 +3786,9 @@ tcp.flags = RST; Source IPv4 address. + + Source IPv6 address. + Value (in seconds) to be used as "max-response" field in multicast queries. Default: 1 second. diff --git a/tests/ovn.at b/tests/ovn.at index 89e2b83..89c4a82 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -15761,6 +15761,585 @@ OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty]) OVN_CLEANUP([hv1], [hv2]) AT_CLEANUP +AT_SETUP([ovn -- MLD snoop/querier/relay]) +ovn_start + +# Logical network: +# Three logical switches (sw1-sw3) connected to a logical router (rtr). +# sw1: +# - subnet 10::/64 +# - 2 ports bound on hv1 (sw1-p11, sw1-p12) +# - 2 ports bound on hv2 (sw1-p21, sw1-p22) +# sw2: +# - subnet 20::/64 +# - 1 port bound on hv1 (sw2-p1) +# - 1 port bound on hv2 (sw2-p2) +# - MLD Querier from 20::fe +# sw3: +# - subnet 30::/64 +# - 1 port bound on hv1 (sw3-p1) +# - 1 port bound on hv2 (sw3-p2) + +reset_pcap_file() { + local iface=$1 + local pcap_file=$2 + ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ +options:rxq_pcap=dummy-rx.pcap + rm -f ${pcap_file}*.pcap + ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ +options:rxq_pcap=${pcap_file}-rx.pcap +} + +# +# send_mld_v2_report INPORT HV ETH_SRC IP_SRC GROUP REC_TYPE +# MLD_CSUM OUTFILE +# +# This shell function causes an MLDv2 report to be received on INPORT of HV. +# The packet's content has Ethernet destination 33:33:00:00:00:16 and source +# ETH_SRC (exactly 12 hex digits). Ethernet type is set to IPv6. +# GROUP is the IPv6 multicast group to be joined/to leave (based on REC_TYPE). +# REC_TYPE == 04: join GROUP +# REC_TYPE == 03: leave GROUP +# The packet hexdump is also stored in OUTFILE. +# +send_mld_v2_report() { + local inport=$1 hv=$2 eth_src=$3 ip_src=$4 group=$5 + local rec_type=$6 mld_chksum=$7 outfile=$8 + + local eth_dst=333300000016 + local ip_dst=ff020000000000000000000000000016 + local ip_ttl=01 + local ip_ra_opt=3a00050200000100 + + local mld_type=8f + local mld_code=00 + local num_rec=0001 + local aux_dlen=00 + local num_src=0000 + + local eth=${eth_dst}${eth_src}86dd + local ip=60000000002400${ip_ttl}${ip_src}${ip_dst}${ip_ra_opt} + local mld=${mld_type}${mld_code}${mld_chksum}0000${num_rec}${rec_type}${aux_dlen}${num_src}${group} + local packet=${eth}${ip}${mld} + + echo ${packet} >> ${outfile} + as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} +} + +# +# store_mld_query ETH_SRC IP_SRC OUTFILE +# +# This shell function builds an MLD general query from ETH_SRC and IP_SRC +# and stores the hexdump of the packet in OUTFILE. +# +store_mld_query() { + local eth_src=$1 ip_src=$2 outfile=$3 + + local eth_dst=333300000000 + local ip_dst=ff020000000000000000000000000001 + local ip_ttl=01 + local ip_ra_opt=3a00050200000000 + + local mld_type=82 + local mld_code=00 + local max_resp=03e8 + local mld_chksum=59be + local addr=00000000000000000000000000000000 + + local eth=${eth_dst}${eth_src}86dd + local ip=60000000002400${ip_ttl}${ip_src}${ip_dst}${ip_ra_opt} + local mld=${mld_type}${mld_code}${mld_chksum}${max_resp}0000${addr}00010000 + local packet=${eth}${ip}${mld} + + echo ${packet} >> ${outfile} +} + +# +# send_ip_multicast_pkt INPORT HV ETH_SRC ETH_DST IP_SRC IP_DST IP_LEN TTL +# IP_PROTO DATA +# +# This shell function causes an IP multicast packet to be received on INPORT +# of HV. +# +send_ip_multicast_pkt() { + local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 + local ip_src=$5 ip_dst=$6 ip_len=$7 ip_ttl=$8 proto=$9 + local data=${10} + + local eth=${eth_dst}${eth_src}86dd + local ip=60000000${ip_len}${proto}${ip_ttl}${ip_src}${ip_dst} + local packet=${eth}${ip}${data} + + as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} +} + +# +# store_ip_multicast_pkt ETH_SRC ETH_DST IP_SRC IP_DST IP_LEN TTL +# IP_PROTO DATA OUTFILE +# +# This shell function builds an IP multicast packet and stores the hexdump of +# the packet in OUTFILE. +# +store_ip_multicast_pkt() { + local eth_src=$1 eth_dst=$2 + local ip_src=$3 ip_dst=$4 ip_len=$5 ip_ttl=$6 proto=$7 + local data=$8 outfile=$9 + + local eth=${eth_dst}${eth_src}86dd + local ip=60000000${ip_len}${proto}${ip_ttl}${ip_src}${ip_dst} + local packet=${eth}${ip}${data} + + echo ${packet} >> ${outfile} +} + +ovn-nbctl ls-add sw1 +ovn-nbctl ls-add sw2 +ovn-nbctl ls-add sw3 + +ovn-nbctl lsp-add sw1 sw1-p11 +ovn-nbctl lsp-add sw1 sw1-p12 +ovn-nbctl lsp-add sw1 sw1-p21 +ovn-nbctl lsp-add sw1 sw1-p22 +ovn-nbctl lsp-add sw2 sw2-p1 +ovn-nbctl lsp-add sw2 sw2-p2 +ovn-nbctl lsp-add sw3 sw3-p1 +ovn-nbctl lsp-add sw3 sw3-p2 + +ovn-nbctl lr-add rtr +ovn-nbctl lrp-add rtr rtr-sw1 00:00:00:00:01:00 10::fe/64 +ovn-nbctl lrp-add rtr rtr-sw2 00:00:00:00:02:00 20::fe/64 +ovn-nbctl lrp-add rtr rtr-sw3 00:00:00:00:03:00 30::fe/64 + +ovn-nbctl lsp-add sw1 sw1-rtr \ + -- lsp-set-type sw1-rtr router \ + -- lsp-set-addresses sw1-rtr 00:00:00:00:01:00 \ + -- lsp-set-options sw1-rtr router-port=rtr-sw1 +ovn-nbctl lsp-add sw2 sw2-rtr \ + -- lsp-set-type sw2-rtr router \ + -- lsp-set-addresses sw2-rtr 00:00:00:00:02:00 \ + -- lsp-set-options sw2-rtr router-port=rtr-sw2 +ovn-nbctl lsp-add sw3 sw3-rtr \ + -- lsp-set-type sw3-rtr router \ + -- lsp-set-addresses sw3-rtr 00:00:00:00:03:00 \ + -- lsp-set-options sw3-rtr router-port=rtr-sw3 + +net_add n1 +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 +ovs-vsctl -- add-port br-int hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=sw1-p11 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ + ofport-request=1 +ovs-vsctl -- add-port br-int hv1-vif2 -- \ + set interface hv1-vif2 external-ids:iface-id=sw1-p12 \ + options:tx_pcap=hv1/vif2-tx.pcap \ + options:rxq_pcap=hv1/vif2-rx.pcap \ + ofport-request=1 +ovs-vsctl -- add-port br-int hv1-vif3 -- \ + set interface hv1-vif3 external-ids:iface-id=sw2-p1 \ + options:tx_pcap=hv1/vif3-tx.pcap \ + options:rxq_pcap=hv1/vif3-rx.pcap \ + ofport-request=1 +ovs-vsctl -- add-port br-int hv1-vif4 -- \ + set interface hv1-vif4 external-ids:iface-id=sw3-p1 \ + options:tx_pcap=hv1/vif4-tx.pcap \ + options:rxq_pcap=hv1/vif4-rx.pcap \ + ofport-request=1 + +sim_add hv2 +as hv2 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.2 +ovs-vsctl -- add-port br-int hv2-vif1 -- \ + set interface hv2-vif1 external-ids:iface-id=sw1-p21 \ + options:tx_pcap=hv2/vif1-tx.pcap \ + options:rxq_pcap=hv2/vif1-rx.pcap \ + ofport-request=1 +ovs-vsctl -- add-port br-int hv2-vif2 -- \ + set interface hv2-vif2 external-ids:iface-id=sw1-p22 \ + options:tx_pcap=hv2/vif2-tx.pcap \ + options:rxq_pcap=hv2/vif2-rx.pcap \ + ofport-request=1 +ovs-vsctl -- add-port br-int hv2-vif3 -- \ + set interface hv2-vif3 external-ids:iface-id=sw2-p2 \ + options:tx_pcap=hv2/vif3-tx.pcap \ + options:rxq_pcap=hv2/vif3-rx.pcap \ + ofport-request=1 +ovs-vsctl -- add-port br-int hv2-vif4 -- \ + set interface hv2-vif4 external-ids:iface-id=sw3-p2 \ + options:tx_pcap=hv2/vif4-tx.pcap \ + options:rxq_pcap=hv2/vif4-rx.pcap \ + ofport-request=1 + +OVN_POPULATE_ARP + +# Enable multicast snooping on sw1. +ovn-nbctl set Logical_Switch sw1 \ + other_config:mcast_querier="false" \ + other_config:mcast_snoop="true" + +# No IGMP/MLD query should be generated by sw1 (mcast_querier="false"). +> expected +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected]) +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) +OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected]) + +ovn-nbctl --wait=hv sync + +# Inject MLD Join for ff0a:dead:beef::1 on sw1-p11. +send_mld_v2_report hv1-vif1 hv1 \ + 000000000001 10000000000000000000000000000001 \ + ff0adeadbeef00000000000000000001 04 c0e4 \ + /dev/null +# Inject MLD Join for ff0a:dead:beef::1 on sw1-p21. +send_mld_v2_report hv2-vif1 hv2 \ + 000000000002 10000000000000000000000000000002 \ + ff0adeadbeef00000000000000000001 04 c0e3 \ + /dev/null + +# Check that the IP multicast group is learned on both hv. +OVS_WAIT_UNTIL([ + total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c` + test "${total_entries}" = "2" +]) + +# Send traffic and make sure it gets forwarded only on the two ports that +# joined. +> expected +> expected_empty +send_ip_multicast_pkt hv1-vif2 hv1 \ + 000000000001 333300000001 \ + 10000000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 01 11 \ + 93407a69000e1b5e61736461640a + +store_ip_multicast_pkt \ + 000000000001 333300000001 \ + 10000000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 01 11 \ + 93407a69000e1b5e61736461640a \ + expected + +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) + +# Inject MLD Leave for ff0a:dead:beef::1 on sw1-p11. +send_mld_v2_report hv1-vif1 hv1 \ + 000000000001 10000000000000000000000000000001 \ + ff0adeadbeef00000000000000000001 03 c1e4 \ + /dev/null + +# Check IGMP_Group table on both HV. +OVS_WAIT_UNTIL([ + total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c` + test "${total_entries}" = "1" +]) + +# Send traffic and make sure it gets forwarded only on the port that joined. +as hv1 reset_pcap_file hv1-vif1 hv1/vif1 +as hv2 reset_pcap_file hv2-vif1 hv2/vif1 +> expected +> expected_empty +send_ip_multicast_pkt hv1-vif2 hv1 \ + 000000000001 333300000001 \ + 10000000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 01 11 \ + 93407a69000e1b5e61736461640a + +store_ip_multicast_pkt \ + 000000000001 333300000001 \ + 10000000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 01 11 \ + 93407a69000e1b5e61736461640a \ + expected + +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) + +# Flush IP multicast groups. +ovn-sbctl ip-multicast-flush sw1 +OVS_WAIT_UNTIL([ + total_entries=`ovn-sbctl find IGMP_Group | grep " ff0a:dead:beef::1" -c` + test "${total_entries}" = "0" +]) + +# Enable multicast snooping and querier on sw2 and set query interval to +# minimum. +ovn-nbctl set Logical_Switch sw2 \ + other_config:mcast_snoop="true" \ + other_config:mcast_querier="true" \ + other_config:mcast_query_interval=1 \ + other_config:mcast_eth_src="00:00:00:00:02:fe" \ + other_config:mcast_ip6_src="2000::fe" + +# Wait for 1 query interval (1 sec) and check that two queries are generated. +> expected +store_mld_query 0000000002fe 200000000000000000000000000000fe expected +store_mld_query 0000000002fe 200000000000000000000000000000fe expected +sleep 1 + +OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected]) +OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected]) + +# Disable multicast querier on sw2. +ovn-nbctl set Logical_Switch sw2 \ + other_config:mcast_querier="false" + +# Enable multicast snooping on sw3. +ovn-nbctl set Logical_Switch sw3 \ + other_config:mcast_querier="false" \ + other_config:mcast_snoop="true" + +# Send traffic from sw3 and make sure rtr doesn't relay it. +> expected_empty + +as hv1 reset_pcap_file hv1-vif1 hv1/vif1 +as hv1 reset_pcap_file hv1-vif2 hv1/vif2 +as hv1 reset_pcap_file hv1-vif3 hv1/vif3 +as hv1 reset_pcap_file hv1-vif4 hv1/vif4 +as hv2 reset_pcap_file hv2-vif1 hv2/vif1 +as hv2 reset_pcap_file hv2-vif2 hv2/vif2 +as hv2 reset_pcap_file hv2-vif3 hv2/vif3 +as hv2 reset_pcap_file hv2-vif4 hv2/vif4 + +send_ip_multicast_pkt hv2-vif4 hv2 \ + 000000000001 333300000001 \ + 00100000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 02 11 \ + 93407a69000e2b4e61736461640a + +# Sleep a bit to make sure no traffic is received and then check. +sleep 1 +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty]) + +# Enable multicast relay on rtr +ovn-nbctl set logical_router rtr \ + options:mcast_relay="true" + +# Inject MLD Join for ff0a:dead:beef::1 on sw1-p11. +send_mld_v2_report hv1-vif1 hv1 \ + 000000000001 10000000000000000000000000000001 \ + ff0adeadbeef00000000000000000001 04 c0e4 \ + /dev/null + +# Inject MLD Join for ff0a:dead:beef::1 on sw2-p2. +send_mld_v2_report hv2-vif3 hv2 \ + 000000000001 10000000000000000000000000000001 \ + ff0adeadbeef00000000000000000001 04 c0e4 \ + /dev/null + +# Check that the IGMP Group is learned by all switches. +OVS_WAIT_UNTIL([ + total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c` + test "${total_entries}" = "2" +]) + +# Send traffic from sw3 and make sure it is relayed by rtr. +# to ports that joined. +> expected_routed_sw1 +> expected_routed_sw2 +> expected_empty + +as hv1 reset_pcap_file hv1-vif1 hv1/vif1 +as hv1 reset_pcap_file hv1-vif2 hv1/vif2 +as hv1 reset_pcap_file hv1-vif3 hv1/vif3 +as hv1 reset_pcap_file hv1-vif4 hv1/vif4 +as hv2 reset_pcap_file hv2-vif1 hv2/vif1 +as hv2 reset_pcap_file hv2-vif2 hv2/vif2 +as hv2 reset_pcap_file hv2-vif3 hv2/vif3 +as hv2 reset_pcap_file hv2-vif4 hv2/vif4 + +send_ip_multicast_pkt hv2-vif4 hv2 \ + 000000000001 333300000001 \ + 10000000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 02 11 \ + 93407a69000e1b5e61736461640a +store_ip_multicast_pkt \ + 000000000100 333300000001 \ + 10000000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 01 11 \ + 93407a69000e1b5e61736461640a \ + expected_routed_sw1 +store_ip_multicast_pkt \ + 000000000200 333300000001 \ + 10000000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 01 11 \ + 93407a69000e1b5e61736461640a \ + expected_routed_sw2 + +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_routed_sw1]) +OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_routed_sw2]) +OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty]) + +# Inject MLD Join for 239.0.1.68 on sw3-p1. +send_mld_v2_report hv1-vif4 hv1 \ + 000000000001 10000000000000000000000000000001 \ + ff0adeadbeef00000000000000000001 04 c0e4 \ + /dev/null + +# Check that the Multicast Group is learned by all switches. +OVS_WAIT_UNTIL([ + total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c` + test "${total_entries}" = "3" +]) + +# Send traffic from sw3 and make sure it is relayed by rtr +# to ports that joined. +> expected_routed_sw1 +> expected_routed_sw2 +> expected_switched +> expected_empty + +as hv1 reset_pcap_file hv1-vif1 hv1/vif1 +as hv1 reset_pcap_file hv1-vif2 hv1/vif2 +as hv1 reset_pcap_file hv1-vif3 hv1/vif3 +as hv1 reset_pcap_file hv1-vif4 hv1/vif4 +as hv2 reset_pcap_file hv2-vif1 hv2/vif1 +as hv2 reset_pcap_file hv2-vif2 hv2/vif2 +as hv2 reset_pcap_file hv2-vif3 hv2/vif3 +as hv2 reset_pcap_file hv2-vif4 hv2/vif4 + +send_ip_multicast_pkt hv2-vif4 hv2 \ + 000000000001 333300000001 \ + 10000000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 02 11 \ + 93407a69000e1b5e61736461640a +store_ip_multicast_pkt \ + 000000000100 333300000001 \ + 10000000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 01 11 \ + 93407a69000e1b5e61736461640a \ + expected_routed_sw1 +store_ip_multicast_pkt \ + 000000000200 333300000001 \ + 10000000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 01 11 \ + 93407a69000e1b5e61736461640a \ + expected_routed_sw2 +store_ip_multicast_pkt \ + 000000000001 333300000001 \ + 10000000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 02 11 \ + 93407a69000e1b5e61736461640a \ + expected_switched + +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_routed_sw1]) +OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_routed_sw2]) +OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_switched]) +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty]) + +# Flush multicast groups. +ovn-sbctl ip-multicast-flush sw1 +ovn-sbctl ip-multicast-flush sw2 +ovn-sbctl ip-multicast-flush sw3 +OVS_WAIT_UNTIL([ + total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c` + test "${total_entries}" = "0" +]) + +as hv1 reset_pcap_file hv1-vif1 hv1/vif1 +as hv1 reset_pcap_file hv1-vif2 hv1/vif2 +as hv1 reset_pcap_file hv1-vif3 hv1/vif3 +as hv1 reset_pcap_file hv1-vif4 hv1/vif4 +as hv2 reset_pcap_file hv2-vif1 hv2/vif1 +as hv2 reset_pcap_file hv2-vif2 hv2/vif2 +as hv2 reset_pcap_file hv2-vif3 hv2/vif3 +as hv2 reset_pcap_file hv2-vif4 hv2/vif4 + +> expected_empty +> expected_switched +> expected_routed +> expected_reports + +# Enable mcast_flood on sw1-p11 +ovn-nbctl set Logical_Switch_Port sw1-p11 options:mcast_flood='true' + +# Enable mcast_flood_reports on sw1-p21 +ovn-nbctl set Logical_Switch_Port sw1-p21 options:mcast_flood_reports='true' +# Enable mcast_flood on rtr-sw2 +ovn-nbctl set Logical_Router_Port rtr-sw2 options:mcast_flood='true' +# Enable mcast_flood on sw2-p1 +ovn-nbctl set Logical_Switch_Port sw2-p1 options:mcast_flood='true' + +ovn-nbctl --wait=hv sync + +# Inject MLD Join for ff0a:dead:beef::1 on sw1-p12. +send_mld_v2_report hv1-vif2 hv1 \ + 000000000001 10000000000000000000000000000001 \ + ff0adeadbeef00000000000000000001 04 c0e4 \ + expected_reports + +# Check that the IP multicast group is learned. +OVS_WAIT_UNTIL([ + total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c` + test "${total_entries}" = "1" +]) + +# Send traffic from sw1-p21 +send_ip_multicast_pkt hv2-vif1 hv2 \ + 000000000001 333300000001 \ + 00100000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 02 11 \ + 93407a69000e2b4e61736461640a +store_ip_multicast_pkt \ + 000000000001 333300000001 \ + 00100000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 02 11 \ + 93407a69000e2b4e61736461640a \ + expected_switched +store_ip_multicast_pkt \ + 000000000200 333300000001 \ + 00100000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 01 11 \ + 93407a69000e2b4e61736461640a \ + expected_routed + +# Sleep a bit to make sure no duplicate traffic is received +sleep 1 + +# Check that traffic is switched to sw1-p11 and sw1-p12 +# Check that MLD join is flooded on sw1-p21 +# Check that traffic is routed by rtr to rtr-sw2 and then switched to sw2-p1 +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_switched]) +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_switched]) +OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_routed]) +OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_reports]) +OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) +OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty]) + +OVN_CLEANUP([hv1], [hv2]) +AT_CLEANUP + AT_SETUP([ovn -- unixctl socket]) ovn_start