[][openvswitch][mt7986][Add ovs multicast to unicast conversion support]

[Description]
Add Add ovs multicast to unicast conversion with ipv6 support

[Release-log]
N/A


Change-Id: I17eb6cef1a48fa68036bd23a8e926444b87ef3d0
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/6794529
diff --git a/autobuild_mac80211_release/target/linux/mediatek/patches-5.4/8011-ovs-add-multicast-to-unicast-support.patch b/autobuild_mac80211_release/target/linux/mediatek/patches-5.4/8011-ovs-add-multicast-to-unicast-support.patch
index d0764d2..b3dd782 100755
--- a/autobuild_mac80211_release/target/linux/mediatek/patches-5.4/8011-ovs-add-multicast-to-unicast-support.patch
+++ b/autobuild_mac80211_release/target/linux/mediatek/patches-5.4/8011-ovs-add-multicast-to-unicast-support.patch
@@ -1,5 +1,5 @@
 diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c
-index 9e8a5c4..16f5187 100644
+index 9e8a5c4..9104255 100644
 --- a/net/openvswitch/actions.c
 +++ b/net/openvswitch/actions.c
 @@ -919,6 +919,10 @@ static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port,
@@ -13,21 +13,24 @@
  
  	if (likely(vport)) {
  		u16 mru = OVS_CB(skb)->mru;
-@@ -933,7 +937,28 @@ static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port,
+@@ -933,7 +937,31 @@ static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port,
  
  		if (likely(!mru ||
  		           (skb->len <= mru + vport->dev->hard_header_len))) {
 -			ovs_vport_send(vport, skb, ovs_key_mac_proto(key));
-+			if (is_ipv4_multicast(skb) && !is_igmp(skb)) {
++			if (is_multicast_addr(skb) && !is_igmp_mld(skb)) {
 +				mdb = vport->mdb;
 +				spin_lock(&mdb->tbl_lock);
 +				list_for_each_entry(table, &mdb->list_head, mdb_node) {
-+					if (table->group_addr.u.ip4 == key->ipv4.addr.dst) {
++					if ((key->eth.type == htons(ETH_P_IP) &&
++						table->group_addr.u.ip4 == key->ipv4.addr.dst) ||
++						(key->eth.type == htons(ETH_P_IPV6) &&
++						ipv6_addr_equal(&table->group_addr.u.ip6, &key->ipv6.addr.dst))) {
 +						list_for_each_entry(entry, &table->entry_list, entry_node) {
 +							skb_cpy = skb_copy(skb, GFP_ATOMIC);
 +							if (!skb_cpy) {
 +								kfree_skb(skb);
-+								pr_err("%s(): error\n", __func__);
++								pr_err("%s(): skb copy error\n", __func__);
 +								spin_unlock(&mdb->tbl_lock);
 +								return;
 +							}
@@ -44,96 +47,95 @@
  			struct net *net = read_pnet(&dp->net);
  
 diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c
-index 4f097bd..e6be550 100644
+index 4f097bd..2f39f07 100644
 --- a/net/openvswitch/datapath.c
 +++ b/net/openvswitch/datapath.c
-@@ -11,6 +11,7 @@
+@@ -11,6 +11,9 @@
  #include <linux/if_vlan.h>
  #include <linux/in.h>
  #include <linux/ip.h>
 +#include <linux/igmp.h>
++#include <net/mld.h>
++#include <linux/icmpv6.h>
  #include <linux/jhash.h>
  #include <linux/delay.h>
  #include <linux/time.h>
-@@ -530,6 +531,166 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
+@@ -530,6 +533,262 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
  	return err;
  }
  
-+static int ovs_ip4_multicast_add_group(__be32 _group_addr,
-+										const u8 *entry_addr,
-+										struct vport *input_vport)
++static int ovs_multicast_add_group(struct ip_addr *_group_addr,
++									const u8 *entry_addr,
++									struct vport *input_vport)
 +{
 +	struct multicast_data_base *mdb;
 +	struct multicast_table *table;
 +	struct multicast_table_entry *entry;
-+
-+	if (ipv4_is_local_multicast(_group_addr))
-+		return 0;
++	int err;
 +
 +	mdb = input_vport->mdb;
 +	spin_lock(&mdb->tbl_lock);
 +	list_for_each_entry(table, &mdb->list_head, mdb_node) {
-+		if (table->group_addr.u.ip4 == _group_addr) {
++		if (!memcmp(&table->group_addr.u, &_group_addr->u, sizeof(struct ip_addr))) {
 +			list_for_each_entry(entry, &table->entry_list, entry_node) {
-+				if (!memcmp(entry->eth_addr, entry_addr, ETH_ALEN)) {
-+					spin_unlock(&mdb->tbl_lock);
-+					return 0;
-+				}
++				if (ether_addr_equal(entry->eth_addr, entry_addr))
++					goto out;
 +			}
++
 +			entry = kzalloc(sizeof(struct multicast_table_entry), GFP_ATOMIC);
 +			if (!entry) {
-+				spin_unlock(&mdb->tbl_lock);
-+				return -ENOMEM;
++				err = -ENOMEM;
++				goto err;
 +			}
 +
 +			memcpy(entry->eth_addr, entry_addr, ETH_ALEN);
 +			list_add(&entry->entry_node, &table->entry_list);
-+			spin_unlock(&mdb->tbl_lock);
-+			return 0;
++			goto out;
 +		}
 +	}
 +
 +	table = kzalloc(sizeof(struct multicast_table), GFP_ATOMIC);
 +	if (!table) {
-+		spin_unlock(&mdb->tbl_lock);
-+		return -ENOMEM;
++		err = -ENOMEM;
++		goto err;
 +	}
 +
 +	INIT_LIST_HEAD(&table->entry_list);
 +	entry = kzalloc(sizeof(struct multicast_table_entry), GFP_ATOMIC);
 +	if (!entry) {
 +		kfree(table);
-+		spin_unlock(&mdb->tbl_lock);
-+		return -ENOMEM;
++		err = -ENOMEM;
++		goto err;
 +	}
 +
 +	memcpy(entry->eth_addr, entry_addr, ETH_ALEN);
 +	list_add(&entry->entry_node, &table->entry_list);
 +
-+	table->group_addr.u.ip4 = _group_addr;
++	table->group_addr.u = _group_addr->u;
 +	list_add(&table->mdb_node, &mdb->list_head);
 +
++out:
++	err = 0;
++err:
 +	spin_unlock(&mdb->tbl_lock);
-+	return 0;
++	return err;
 +}
 +
-+static int ovs_ip4_multicast_leave_group(__be32 _group_addr,
-+										const u8 *entry_addr,
-+										struct vport *input_vport)
++static int ovs_multicast_leave_group(struct ip_addr *_group_addr,
++									const u8 *entry_addr,
++									struct vport *input_vport)
 +{
 +	struct multicast_data_base *mdb;
 +	struct multicast_table *table, *table_tmp;
 +	struct multicast_table_entry *entry, *entry_tmp;
-+
-+	if (ipv4_is_local_multicast(_group_addr))
-+		return 0;
++	int err;
 +
 +	mdb = input_vport->mdb;
 +	spin_lock(&mdb->tbl_lock);
 +	list_for_each_entry_safe(table, table_tmp, &mdb->list_head, mdb_node) {
-+		if (table->group_addr.u.ip4 == _group_addr) {
++		if (!memcmp(&table->group_addr.u, &_group_addr->u, sizeof(struct ip_addr))) {
 +			list_for_each_entry_safe(entry, entry_tmp, &table->entry_list, entry_node) {
-+				if (!memcmp(entry->eth_addr, entry_addr, ETH_ALEN)) {
++				if (ether_addr_equal(entry->eth_addr, entry_addr)) {
 +					list_del(&entry->entry_node);
 +					kfree(entry);
 +
@@ -141,23 +143,26 @@
 +						list_del(&table->mdb_node);
 +						kfree(table);
 +					}
-+					spin_unlock(&mdb->tbl_lock);
-+					return 0;
++
++					goto out;
 +				}
 +			}
 +		}
 +	}
++
++out:
++	err = 0;
 +	spin_unlock(&mdb->tbl_lock);
-+	return 0;
++	return err;
 +}
 +
 +static int ovs_multicast_ipv4_rcv(struct sk_buff *skb, struct vport *input_vport)
 +{
 +	struct ethhdr *eth_hdr;
 +	const u8 *dl_src;
-+	__be32 group_addr;
++	struct ip_addr group_addr = {0};
 +	struct iphdr *ip_header;
-+	struct igmphdr *igmp_hdr;
++	struct igmphdr *igmp_header;
 +	int i;
 +	struct igmpv3_report *igmpv3_hdr;
 +	u16 group_num;
@@ -168,71 +173,167 @@
 +	int err;
 +
 +	err = ip_mc_check_igmp(skb);
-+	if (err < 0)
++	if (err)
 +		return 0;
 +
 +	eth_hdr = skb_eth_hdr(skb);
 +	dl_src = eth_hdr->h_source;
-+	ip_header = (struct iphdr *)(skb->data + 14);
-+	igmp_hdr = (struct igmphdr *)((u8 *)ip_header + ip_header->ihl * 4);
++	ip_header = ip_hdr(skb);
++	igmp_header = igmp_hdr(skb);
 +
-+	switch (igmp_hdr->type) {
++	switch (igmp_header->type) {
 +	case IGMP_HOST_MEMBERSHIP_REPORT:
 +	case IGMPV2_HOST_MEMBERSHIP_REPORT:
-+		group_addr = igmp_hdr->group;
-+		ovs_ip4_multicast_add_group(group_addr, dl_src, input_vport);
++		group_addr.u.ip4 = igmp_header->group;
++		if (ipv4_is_local_multicast(group_addr.u.ip4))
++			return 0;
++		ovs_multicast_add_group(&group_addr, dl_src, input_vport);
 +		break;
 +	case IGMP_HOST_LEAVE_MESSAGE:
-+		group_addr = igmp_hdr->group;
-+		ovs_ip4_multicast_leave_group(group_addr, dl_src, input_vport);
++		group_addr.u.ip4 = igmp_header->group;
++		if (ipv4_is_local_multicast(group_addr.u.ip4))
++			return 0;
++		ovs_multicast_leave_group(&group_addr, dl_src, input_vport);
 +		break;
 +	case IGMPV3_HOST_MEMBERSHIP_REPORT:
-+		igmpv3_hdr = (struct igmpv3_report *)igmp_hdr;
++		igmpv3_hdr = (struct igmpv3_report *)igmp_header;
 +		group_num = ntohs(igmpv3_hdr->ngrec);
 +		grec = igmpv3_hdr->grec;
-+		//group_num = ntohs(*(u16 *)(igmp_hdr + 6));
-+		//group = igmp_hdr + 8;
++
 +		for (i = 0; i < group_num; i++) {
 +			group_type = grec->grec_type;
 +			aux_data_len = grec->grec_auxwords;
 +			num_of_source = ntohs(grec->grec_nsrcs);
-+			group_addr = grec->grec_mca;
++			group_addr.u.ip4 = grec->grec_mca;
++			if (ipv4_is_local_multicast(group_addr.u.ip4))
++				return 0;
 +
 +			if (group_type == IGMPV3_MODE_IS_EXCLUDE ||
 +				group_type == IGMPV3_CHANGE_TO_EXCLUDE ||
 +				group_type == IGMPV3_ALLOW_NEW_SOURCES)
-+				ovs_ip4_multicast_add_group(group_addr, dl_src, input_vport);
++				ovs_multicast_add_group(&group_addr, dl_src, input_vport);
 +
 +			if (group_type == IGMPV3_MODE_IS_INCLUDE ||
 +				group_type == IGMPV3_CHANGE_TO_INCLUDE ||
 +				group_type == IGMPV3_BLOCK_OLD_SOURCES)
 +				if (num_of_source == 0)
-+					ovs_ip4_multicast_leave_group(group_addr, dl_src, input_vport);
++					ovs_multicast_leave_group(&group_addr, dl_src, input_vport);
 +
 +			grec += (8 + (num_of_source * 4) + aux_data_len);
 +		}
 +		break;
++	case IGMP_HOST_MEMBERSHIP_QUERY:
++		break;
++	default:
++		pr_warning("%s(): error packet type 0x%x\n", __func__, igmp_header->type);
++		break;
++	}
++	return 0;
++}
++
++static int ovs_multicast_ipv6_rcv(struct sk_buff *skb, struct vport *input_vport)
++{
++	const u8 *dl_src;
++	struct mld_msg *mld_hdr;
++	struct ip_addr group_addr = {0};
++	struct icmp6hdr *icmpv6_hdr;
++	u16 group_num;
++	struct mld2_grec *grec;
++	u8 group_type;
++	u8 aux_data_len;
++	u16 num_of_source;
++	int i;
++	int err;
++
++	err = ipv6_mc_check_mld(skb);
++	if (err)
++		return err;
++
++	mld_hdr = (struct mld_msg *)skb_transport_header(skb);
++	dl_src = skb_eth_hdr(skb)->h_source;
++
++	switch (mld_hdr->mld_type) {
++	case ICMPV6_MGM_REPORT:
++		group_addr.u.ip6 = mld_hdr->mld_mca;
++		if (ipv6_addr_is_ll_all_nodes(&group_addr.u.ip6))
++			return 0;
++		ovs_multicast_add_group(&group_addr, dl_src, input_vport);
++		break;
++	case ICMPV6_MGM_REDUCTION:
++		group_addr.u.ip6 = mld_hdr->mld_mca;
++		if (ipv6_addr_is_ll_all_nodes(&group_addr.u.ip6))
++			return 0;
++		ovs_multicast_leave_group(&group_addr, dl_src, input_vport);
++		break;
++	case ICMPV6_MLD2_REPORT:
++		icmpv6_hdr = icmp6_hdr(skb);
++		group_num = ntohs(icmpv6_hdr->icmp6_dataun.un_data16[1]);
++		grec = (struct mld2_grec *)(skb_transport_header(skb) + sizeof(struct icmp6hdr));
++
++		for (i = 0; i < group_num; i++) {
++			group_type = grec->grec_type;
++			aux_data_len = grec->grec_auxwords;
++			num_of_source = ntohs(grec->grec_nsrcs);
++			group_addr.u.ip6 = grec->grec_mca;
++			if (ipv6_addr_is_ll_all_nodes(&group_addr.u.ip6))
++				return 0;
++
++			if (group_type == MLD2_MODE_IS_EXCLUDE ||
++				group_type == MLD2_CHANGE_TO_EXCLUDE ||
++				group_type == MLD2_ALLOW_NEW_SOURCES)
++				ovs_multicast_add_group(&group_addr, dl_src, input_vport);
++			else if ((group_type == MLD2_MODE_IS_INCLUDE ||
++					group_type == MLD2_CHANGE_TO_INCLUDE ||
++					group_type == MLD2_BLOCK_OLD_SOURCES) &&
++					num_of_source == 0)
++				ovs_multicast_leave_group(&group_addr, dl_src, input_vport);
++
++			grec += (4 + (num_of_source + 1) * sizeof(struct in6_addr) + aux_data_len);
++		}
++		break;
++	case ICMPV6_MGM_QUERY:
++		break;
 +	default:
-+		pr_warning("%s(): error pkt\n", __func__);
++		pr_warning("%s(): error packet type 0x%x\n", __func__, mld_hdr->mld_type);
 +		break;
 +	}
++
 +	return 0;
 +}
 +
++static int ovs_multicast_rcv(struct sk_buff *skb, struct vport *input_vport)
++{
++	int ret = 0;
++
++	if (!skb)
++		return -EINVAL;
++
++	switch (skb->protocol) {
++	case htons(ETH_P_IP):
++		ret = ovs_multicast_ipv4_rcv(skb, input_vport);
++		break;
++	case htons(ETH_P_IPV6):
++		ret = ovs_multicast_ipv6_rcv(skb, input_vport);
++		break;
++	}
++
++	return ret;
++}
++
  static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info)
  {
  	struct ovs_header *ovs_header = info->userhdr;
-@@ -604,6 +765,9 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info)
+@@ -604,6 +863,9 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info)
  	OVS_CB(packet)->input_vport = input_vport;
  	sf_acts = rcu_dereference(flow->sf_acts);
  
-+	if (is_igmp(packet))
-+		ovs_multicast_ipv4_rcv(packet, input_vport);
++	if (is_multicast_addr(packet))
++		ovs_multicast_rcv(packet, input_vport);
 +
  	local_bh_disable();
  	err = ovs_execute_actions(dp, packet, sf_acts, &flow->key);
  	local_bh_enable();
-@@ -2183,6 +2347,9 @@ static int ovs_vport_cmd_del(struct sk_buff *skb, struct genl_info *info)
+@@ -2183,6 +2445,9 @@ static int ovs_vport_cmd_del(struct sk_buff *skb, struct genl_info *info)
  	struct datapath *dp;
  	struct vport *vport;
  	unsigned int new_headroom;
@@ -242,7 +343,7 @@
  	int err;
  
  	reply = ovs_vport_cmd_alloc_info();
-@@ -2210,6 +2377,22 @@ static int ovs_vport_cmd_del(struct sk_buff *skb, struct genl_info *info)
+@@ -2210,6 +2475,22 @@ static int ovs_vport_cmd_del(struct sk_buff *skb, struct genl_info *info)
  	if (netdev_get_fwd_headroom(vport->dev) == dp->max_headroom)
  		update_headroom = true;
  
@@ -266,36 +367,43 @@
  	ovs_dp_detach_port(vport);
  
 diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h
-index 81e85dd..520532e 100644
+index 81e85dd..6830d3b 100644
 --- a/net/openvswitch/datapath.h
 +++ b/net/openvswitch/datapath.h
-@@ -215,6 +215,31 @@ static inline struct datapath *get_dp(struct net *net, int dp_ifindex)
+@@ -215,6 +215,38 @@ static inline struct datapath *get_dp(struct net *net, int dp_ifindex)
  	return dp;
  }
  
-+#define IGMP_PROTOCOL_OFFSET		23
-+/* support ipv4 for now */
-+static inline bool is_ipv4_multicast(struct sk_buff *skb)
++static inline bool is_multicast_addr(struct sk_buff *skb)
 +{
-+	struct ethhdr *eth_hdr = skb_eth_hdr(skb);
++	struct ethhdr *eth_hdr;
 +
-+	return eth_hdr->h_dest[0] == 0x01 && skb->protocol == htons(ETH_P_IP);
++	if (!skb)
++		return 0;
++
++	eth_hdr = skb_eth_hdr(skb);
++
++	return (eth_hdr->h_dest[0] == 0x01 && skb->protocol == htons(ETH_P_IP)) ||
++			(eth_hdr->h_dest[0] == 0x33 && skb->protocol == htons(ETH_P_IPV6));
 +}
 +
-+static inline bool is_igmp(struct sk_buff *skb)
++static inline bool is_igmp_mld(struct sk_buff *skb)
 +{
 +	struct ethhdr *eth_hdr;
++	int err = 0;
 +
 +	if (!skb)
-+		return 0;
++		return err;
 +
 +	eth_hdr = skb_eth_hdr(skb);
 +
-+	if (eth_hdr->h_dest[0] == 0x01 &&
-+		skb->protocol == htons(ETH_P_IP))
-+		return (*(skb->data + IGMP_PROTOCOL_OFFSET) == IPPROTO_IGMP);
-+	else
-+		return 0;
++	if (skb->protocol == htons(ETH_P_IP)) {
++		err = ip_hdr(skb)->protocol == IPPROTO_IGMP;
++	} else if (skb->protocol == htons(ETH_P_IPV6)) {
++		err = !ipv6_mc_check_mld(skb);
++	}
++
++	return err;
 +}
 +
  extern struct notifier_block ovs_dp_device_notifier;