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

[Description]
Add ovs multicast to unicast conversion support

[Release-log]
N/A


Change-Id: I9f3990cc273dddf2545403c2740577f3eb032edc
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/6645048
Build: srv_hbgsm110
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
new file mode 100755
index 0000000..d0764d2
--- /dev/null
+++ b/autobuild_mac80211_release/target/linux/mediatek/patches-5.4/8011-ovs-add-multicast-to-unicast-support.patch
@@ -0,0 +1,366 @@
+diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c
+index 9e8a5c4..16f5187 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,
+ 		      struct sw_flow_key *key)
+ {
+ 	struct vport *vport = ovs_vport_rcu(dp, out_port);
++	struct multicast_data_base *mdb;
++	struct multicast_table *table;
++	struct multicast_table_entry *entry;
++	struct sk_buff *skb_cpy;
+ 
+ 	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,
+ 
+ 		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)) {
++				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) {
++						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__);
++								spin_unlock(&mdb->tbl_lock);
++								return;
++							}
++							memcpy(skb_cpy->data, entry->eth_addr, ETH_ALEN);
++							ovs_vport_send(vport, skb_cpy, ovs_key_mac_proto(key));
++						}
++					}
++				}
++				spin_unlock(&mdb->tbl_lock);
++				kfree_skb(skb);
++			} else
++				ovs_vport_send(vport, skb, ovs_key_mac_proto(key));
+ 		} else if (mru <= vport->dev->mtu) {
+ 			struct net *net = read_pnet(&dp->net);
+ 
+diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c
+index 4f097bd..e6be550 100644
+--- a/net/openvswitch/datapath.c
++++ b/net/openvswitch/datapath.c
+@@ -11,6 +11,7 @@
+ #include <linux/if_vlan.h>
+ #include <linux/in.h>
+ #include <linux/ip.h>
++#include <linux/igmp.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,
+ 	return err;
+ }
+ 
++static int ovs_ip4_multicast_add_group(__be32 _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;
++
++	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) {
++			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;
++				}
++			}
++			entry = kzalloc(sizeof(struct multicast_table_entry), GFP_ATOMIC);
++			if (!entry) {
++				spin_unlock(&mdb->tbl_lock);
++				return -ENOMEM;
++			}
++
++			memcpy(entry->eth_addr, entry_addr, ETH_ALEN);
++			list_add(&entry->entry_node, &table->entry_list);
++			spin_unlock(&mdb->tbl_lock);
++			return 0;
++		}
++	}
++
++	table = kzalloc(sizeof(struct multicast_table), GFP_ATOMIC);
++	if (!table) {
++		spin_unlock(&mdb->tbl_lock);
++		return -ENOMEM;
++	}
++
++	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;
++	}
++
++	memcpy(entry->eth_addr, entry_addr, ETH_ALEN);
++	list_add(&entry->entry_node, &table->entry_list);
++
++	table->group_addr.u.ip4 = _group_addr;
++	list_add(&table->mdb_node, &mdb->list_head);
++
++	spin_unlock(&mdb->tbl_lock);
++	return 0;
++}
++
++static int ovs_ip4_multicast_leave_group(__be32 _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;
++
++	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) {
++			list_for_each_entry_safe(entry, entry_tmp, &table->entry_list, entry_node) {
++				if (!memcmp(entry->eth_addr, entry_addr, ETH_ALEN)) {
++					list_del(&entry->entry_node);
++					kfree(entry);
++
++					if (list_empty(&table->entry_list)) {
++						list_del(&table->mdb_node);
++						kfree(table);
++					}
++					spin_unlock(&mdb->tbl_lock);
++					return 0;
++				}
++			}
++		}
++	}
++	spin_unlock(&mdb->tbl_lock);
++	return 0;
++}
++
++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 iphdr *ip_header;
++	struct igmphdr *igmp_hdr;
++	int i;
++	struct igmpv3_report *igmpv3_hdr;
++	u16 group_num;
++	struct igmpv3_grec *grec;
++	u8 group_type;
++	u8 aux_data_len;
++	u16 num_of_source;
++	int err;
++
++	err = ip_mc_check_igmp(skb);
++	if (err < 0)
++		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);
++
++	switch (igmp_hdr->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);
++		break;
++	case IGMP_HOST_LEAVE_MESSAGE:
++		group_addr = igmp_hdr->group;
++		ovs_ip4_multicast_leave_group(group_addr, dl_src, input_vport);
++		break;
++	case IGMPV3_HOST_MEMBERSHIP_REPORT:
++		igmpv3_hdr = (struct igmpv3_report *)igmp_hdr;
++		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;
++
++			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);
++
++			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);
++
++			grec += (8 + (num_of_source * 4) + aux_data_len);
++		}
++		break;
++	default:
++		pr_warning("%s(): error pkt\n", __func__);
++		break;
++	}
++	return 0;
++}
++
+ 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)
+ 	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);
++
+ 	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)
+ 	struct datapath *dp;
+ 	struct vport *vport;
+ 	unsigned int new_headroom;
++	struct multicast_data_base *mdb;
++	struct multicast_table *table, *table_tmp;
++	struct multicast_table_entry *entry, *entry_tmp;
+ 	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)
+ 	if (netdev_get_fwd_headroom(vport->dev) == dp->max_headroom)
+ 		update_headroom = true;
+ 
++	mdb = vport->mdb;
++	spin_lock(&mdb->tbl_lock);
++	list_for_each_entry_safe(table, table_tmp, &mdb->list_head, mdb_node) {
++		list_for_each_entry_safe(entry, entry_tmp, &table->entry_list, entry_node) {
++			list_del(&entry->entry_node);
++			kfree(entry);
++
++			if (list_empty(&table->entry_list)) {
++				list_del(&table->mdb_node);
++				kfree(table);
++			}
++		}
++	}
++	spin_unlock(&mdb->tbl_lock);
++	kfree(mdb);
++
+ 	netdev_reset_rx_headroom(vport->dev);
+ 	ovs_dp_detach_port(vport);
+ 
+diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h
+index 81e85dd..520532e 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)
+ 	return dp;
+ }
+ 
++#define IGMP_PROTOCOL_OFFSET		23
++/* support ipv4 for now */
++static inline bool is_ipv4_multicast(struct sk_buff *skb)
++{
++	struct ethhdr *eth_hdr = skb_eth_hdr(skb);
++
++	return eth_hdr->h_dest[0] == 0x01 && skb->protocol == htons(ETH_P_IP);
++}
++
++static inline bool is_igmp(struct sk_buff *skb)
++{
++	struct ethhdr *eth_hdr;
++
++	if (!skb)
++		return 0;
++
++	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;
++}
++
+ extern struct notifier_block ovs_dp_device_notifier;
+ extern struct genl_family dp_vport_genl_family;
+ 
+diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c
+index 19af0ef..77bc923 100644
+--- a/net/openvswitch/vport.c
++++ b/net/openvswitch/vport.c
+@@ -141,6 +141,14 @@ struct vport *ovs_vport_alloc(int priv_size, const struct vport_ops *ops,
+ 		return ERR_PTR(-EINVAL);
+ 	}
+ 
++	vport->mdb = kzalloc(sizeof(struct multicast_data_base), GFP_KERNEL);
++	if (!vport->mdb) {
++		kfree(vport);
++		return ERR_PTR(-ENOMEM);
++	}
++	INIT_LIST_HEAD(&vport->mdb->list_head);
++	spin_lock_init(&vport->mdb->tbl_lock);
++
+ 	return vport;
+ }
+ EXPORT_SYMBOL_GPL(ovs_vport_alloc);
+diff --git a/net/openvswitch/vport.h b/net/openvswitch/vport.h
+index 1eb7495..eb69d6c 100644
+--- a/net/openvswitch/vport.h
++++ b/net/openvswitch/vport.h
+@@ -55,6 +55,30 @@ struct vport_portids {
+ 	u32 ids[];
+ };
+ 
++struct ip_addr {
++	union {
++		__be32 ip4;
++		struct in6_addr ip6;
++	} u;
++};
++
++struct multicast_table_entry {
++	struct list_head entry_node;
++	u8 eth_addr[ETH_ALEN];
++};
++
++struct multicast_table {
++	struct list_head mdb_node;
++	struct list_head entry_list;
++	struct ip_addr group_addr;
++};
++
++struct multicast_data_base {
++	struct list_head list_head;
++	spinlock_t tbl_lock;
++};
++
++
+ /**
+  * struct vport - one port within a datapath
+  * @dev: Pointer to net_device.
+@@ -79,6 +103,8 @@ struct vport {
+ 
+ 	struct list_head detach_list;
+ 	struct rcu_head rcu;
++
++	struct multicast_data_base *mdb;
+ };
+ 
+ /**