[][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;