net/embms-kernel : EMBMS Tunneling Module

This module is used to tunnel multicast
packets received on an interface to WLAN
clients which have registered for these
packet streams. Mainly used to tunnel
EMBMS packets received on TMGI streams.

Change-Id: I996077342261ec2cb688e98319c6881d2dd7cce2
Acked-by: Mohit Pahuja <mpahuja@qti.qualcomm.com>
Signed-off-by: Ravinder konka <rkonka@codeaurora.org>
diff --git a/net/embms_kernel/embms_kernel.c b/net/embms_kernel/embms_kernel.c
new file mode 100644
index 0000000..7b79574
--- /dev/null
+++ b/net/embms_kernel/embms_kernel.c
@@ -0,0 +1,1050 @@
+/*************************************************************************
+ * -----------------------------------------------------------------------
+ * Copyright (c) 2013-2015, 2017, The Linux Foundation. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ * -----------------------------------------------------------------------
+
+ * DESCRIPTION
+ * Main file for eMBMs Tunneling Module in kernel.
+ *************************************************************************
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <net/ip.h>
+#include <linux/uaccess.h>
+#include <linux/types.h>
+#include <linux/version.h>
+#include <linux/etherdevice.h>
+
+#include <linux/inetdevice.h>
+#include <linux/netfilter.h>
+#include <net/arp.h>
+#include <net/neighbour.h>
+
+#include <linux/skbuff.h>
+#include <linux/list.h>
+#include <linux/in.h>
+#include <net/netfilter/nf_conntrack.h>
+#include <linux/miscdevice.h>
+#include "embms_kernel.h"
+
+struct embms_info_internal embms_conf;
+
+/* Global structures used for tunneling. These include
+ * iphdr and udphdr which are appended to skbs for
+ * tunneling, net_device and tunnleing related
+ * structs and params
+ */
+
+unsigned char hdr_buff[sizeof(struct iphdr) + sizeof(struct udphdr)];
+struct iphdr *iph_global;
+struct udphdr *udph_global;
+struct net_device *dev_global;
+
+static struct tmgi_to_clnt_info tmgi_to_clnt_map_tbl;
+
+/* handle_multicast_stream - packet forwarding
+ * function for multicast stream
+ * Main use case is for EMBMS Over Softap feature
+ */
+
+static int handle_multicast_stream(struct sk_buff *skb)
+{
+	struct iphdr *iph;
+	struct udphdr *udph;
+	struct in_device *in_dev;
+	unsigned char *tmp_ptr = NULL;
+	struct sk_buff *skb_new = NULL;
+	struct sk_buff *skb_cpy = NULL;
+	struct clnt_info *temp_client = NULL;
+	struct tmgi_to_clnt_info *temp_tmgi = NULL;
+	struct list_head *tmgi_entry_ptr, *prev_tmgi_entry_ptr;
+	struct list_head *clnt_ptr, *prev_clnt_ptr;
+	int hdr_size = sizeof(*udph) + sizeof(*iph) + ETH_HLEN;
+
+	/* only IP packets */
+	if (htons(ETH_P_IP) != skb->protocol) {
+		embms_error("Not an IP packet\n");
+		return 0;
+	}
+
+	if (embms_conf.embms_tunneling_status == TUNNELING_OFF) {
+		embms_debug("Tunneling Disabled. Can't process packets\n");
+		return 0;
+	}
+
+	if (unlikely(memcmp(skb->dev->name, embms_conf.embms_iface,
+			    strlen(embms_conf.embms_iface)) != 0)) {
+		embms_error("Packet received on %s iface. NOT an EMBMS Iface\n",
+			    skb->dev->name);
+		return 0;
+	}
+
+	/* Check if dst ip of packet is same as multicast ip of any tmgi*/
+
+	iph = (struct iphdr *)skb->data;
+	udph = (struct udphdr *)(skb->data + sizeof(struct iphdr));
+
+	spin_lock_bh(&embms_conf.lock);
+
+	list_for_each_safe(tmgi_entry_ptr, prev_tmgi_entry_ptr,
+			   &tmgi_to_clnt_map_tbl.tmgi_list_ptr) {
+		temp_tmgi = list_entry(tmgi_entry_ptr,
+				       struct tmgi_to_clnt_info,
+				       tmgi_list_ptr);
+
+		if ((temp_tmgi->tmgi_multicast_addr == iph->daddr) &&
+		    (temp_tmgi->tmgi_port == udph->dest))
+			break;
+	}
+
+	if (tmgi_entry_ptr == &tmgi_to_clnt_map_tbl.tmgi_list_ptr) {
+		embms_error("handle_multicast_stream:");
+		embms_error("could not find matchin tmgi entry\n");
+		spin_unlock_bh(&embms_conf.lock);
+		return 0;
+	}
+
+	/* Found a matching tmgi entry. Realloc headroom to
+	 * accommodate new Ethernet, IP and UDP header
+	 */
+
+	skb_new = skb_realloc_headroom(skb, hdr_size);
+	if (unlikely(!skb_new)) {
+		embms_error("Can't allocate headroom\n");
+		spin_unlock_bh(&embms_conf.lock);
+		return 0;
+	}
+
+	/* push skb->data and copy IP and UDP headers*/
+
+	tmp_ptr = skb_push(skb_new,
+			   sizeof(struct udphdr) + sizeof(struct iphdr));
+
+	iph = (struct iphdr *)tmp_ptr;
+	udph = (struct udphdr *)(tmp_ptr + sizeof(struct iphdr));
+
+	memcpy(tmp_ptr, hdr_buff, hdr_size - ETH_HLEN);
+	udph->len = htons(skb_new->len - sizeof(struct iphdr));
+	iph->tot_len = htons(skb_new->len);
+
+	list_for_each_safe(clnt_ptr, prev_clnt_ptr,
+			   &temp_tmgi->client_list_head) {
+		temp_client = list_entry(clnt_ptr,
+					 struct clnt_info,
+					 client_list_ptr);
+
+		/* Make a copy of skb_new with new IP and UDP header.
+		 * We can't use skb_new or its clone here since we need to
+		 * constantly change dst ip and dst port which is not possible
+		 * for shared memory as is the case with skb_new.
+		 */
+
+		skb_cpy = skb_copy(skb_new, GFP_ATOMIC);
+		if (unlikely(!skb_cpy)) {
+			embms_error("Can't copy skb\n");
+			kfree_skb(skb_new);
+			return 0;
+		}
+
+		iph = (struct iphdr *)skb_cpy->data;
+		udph = (struct udphdr *)(skb_cpy->data + sizeof(struct iphdr));
+
+		iph->id = htons(atomic_inc_return(&embms_conf.ip_ident));
+
+		/* Calculate checksum for new IP and UDP header*/
+
+		udph->dest = temp_client->port;
+		skb_cpy->csum = csum_partial((char *)udph,
+					     ntohs(udph->len),
+					     skb_cpy->csum);
+
+		iph->daddr = temp_client->addr;
+		ip_send_check(iph);
+
+		udph->check = 0;
+		udph->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
+						ntohs(udph->len),
+						IPPROTO_UDP,
+						skb_cpy->csum);
+
+		if (udph->check == 0)
+			udph->check = CSUM_MANGLED_0;
+
+		if (unlikely(!dev_global)) {
+			embms_error("Global device NULL\n");
+			kfree_skb(skb_cpy);
+			kfree_skb(skb_new);
+			return 0;
+		}
+
+		/* update device info and add MAC header*/
+
+		skb_cpy->dev = dev_global;
+
+		skb_cpy->dev->header_ops->create(skb_cpy, skb_cpy->dev,
+						ETH_P_IP, temp_client->dmac,
+						NULL, skb_cpy->len);
+		dev_queue_xmit(skb_cpy);
+	}
+
+	spin_unlock_bh(&embms_conf.lock);
+	kfree_skb(skb_new);
+	return 1;
+}
+
+static int check_embms_device(atomic_t *use_count)
+{
+	int ret;
+
+	if (atomic_inc_return(use_count) == 1) {
+		ret = 0;
+	} else {
+		atomic_dec(use_count);
+		ret = -EBUSY;
+	}
+	return ret;
+}
+
+static int embms_device_open(struct inode *inode, struct file *file)
+{
+	/*Check if the device is busy*/
+	if (check_embms_device(&embms_conf.device_under_use)) {
+		embms_error("embms_tm_open : EMBMS device busy\n");
+		return -EBUSY;
+	}
+
+	try_module_get(THIS_MODULE);
+	return SUCCESS;
+}
+
+static int embms_device_release(struct inode *inode, struct file *file)
+{
+	/* Reduce device use count before leaving*/
+	embms_debug("Releasing EMBMS device..\n");
+	atomic_dec(&embms_conf.device_under_use);
+	embms_conf.embms_tunneling_status = TUNNELING_OFF;
+	module_put(THIS_MODULE);
+	return SUCCESS;
+}
+
+static struct tmgi_to_clnt_info *check_for_tmgi_entry(u32 addr,
+						      u16 port)
+{
+	struct list_head *tmgi_ptr, *prev_tmgi_ptr;
+	struct tmgi_to_clnt_info *temp_tmgi = NULL;
+
+	embms_debug("check_for_tmgi_entry: mcast addr :%pI4, port %u\n",
+		    &addr, ntohs(port));
+
+	list_for_each_safe(tmgi_ptr,
+			   prev_tmgi_ptr,
+			   &tmgi_to_clnt_map_tbl.tmgi_list_ptr) {
+		temp_tmgi = list_entry(tmgi_ptr,
+				       struct tmgi_to_clnt_info,
+				       tmgi_list_ptr);
+
+		if ((temp_tmgi->tmgi_multicast_addr == addr) &&
+		    (temp_tmgi->tmgi_port == port)) {
+			embms_debug("check_for_tmgi_entry:TMGI entry found\n");
+			return temp_tmgi;
+		}
+	}
+	return NULL;
+}
+
+static struct clnt_info *chk_clnt_entry(struct tmgi_to_clnt_info *tmgi,
+					struct tmgi_to_clnt_info_update *clnt)
+{
+	struct list_head *clnt_ptr, *prev_clnt_ptr;
+	struct clnt_info *temp_client = NULL;
+
+	embms_debug("check_for_client_entry: clnt addr :%pI4, port %u\n",
+		    &clnt->client_addr, ntohs(clnt->client_port));
+
+	list_for_each_safe(clnt_ptr,
+			   prev_clnt_ptr,
+			   &tmgi->client_list_head) {
+		temp_client = list_entry(clnt_ptr,
+					 struct clnt_info,
+					 client_list_ptr);
+		if ((temp_client->addr == clnt->client_addr) &&
+		    (temp_client->port == clnt->client_port)) {
+			embms_debug("Clnt entry present\n");
+			return temp_client;
+		}
+	}
+	return NULL;
+}
+
+static int add_new_tmgi_entry(struct tmgi_to_clnt_info_update *info_update,
+			      struct clnt_info *clnt)
+{
+	struct tmgi_to_clnt_info *new_tmgi = NULL;
+
+	embms_debug("add_new_tmgi_entry:Enter\n");
+
+	new_tmgi = kzalloc(sizeof(*new_tmgi),
+			   GFP_ATOMIC);
+	if (!new_tmgi) {
+		embms_error("add_new_tmgi_entry: mem alloc failed\n");
+		return -ENOMEM;
+	}
+
+	memset(new_tmgi, 0, sizeof(struct tmgi_to_clnt_info));
+
+	new_tmgi->tmgi_multicast_addr = info_update->multicast_addr;
+	new_tmgi->tmgi_port = info_update->multicast_port;
+
+	embms_debug("add_new_tmgi_entry:");
+	embms_debug("New tmgi multicast addr :%pI4 , port %u\n",
+		    &info_update->multicast_addr,
+		    ntohs(info_update->multicast_port));
+
+	embms_debug("add_new_tmgi_entry:Adding client entry\n");
+
+	spin_lock_bh(&embms_conf.lock);
+
+	INIT_LIST_HEAD(&new_tmgi->client_list_head);
+	list_add(&clnt->client_list_ptr,
+		 &new_tmgi->client_list_head);
+	new_tmgi->no_of_clients++;
+
+	/* Once above steps are done successfully,
+	 * we add tmgi entry to our local table
+	 */
+
+	list_add(&new_tmgi->tmgi_list_ptr,
+		 &tmgi_to_clnt_map_tbl.tmgi_list_ptr);
+	embms_conf.no_of_tmgi_sessions++;
+
+	spin_unlock_bh(&embms_conf.lock);
+
+	return SUCCESS;
+}
+
+static void print_tmgi_to_client_table(void)
+{
+	int i, j;
+	struct clnt_info *temp_client = NULL;
+	struct tmgi_to_clnt_info *temp_tmgi = NULL;
+	struct list_head *tmgi_entry_ptr, *prev_tmgi_entry_ptr;
+	struct list_head *clnt_ptr, *prev_clnt_ptr;
+
+	embms_debug("====================================================\n");
+	embms_debug("Printing TMGI to Client Table :\n");
+	embms_debug("No of Active TMGIs : %d\n",
+		    embms_conf.no_of_tmgi_sessions);
+	embms_debug("====================================================\n\n");
+
+	if (embms_conf.no_of_tmgi_sessions > 0) {
+		i = 1;
+		list_for_each_safe(tmgi_entry_ptr, prev_tmgi_entry_ptr,
+				   &tmgi_to_clnt_map_tbl.tmgi_list_ptr) {
+			temp_tmgi = list_entry(tmgi_entry_ptr,
+					       struct tmgi_to_clnt_info,
+					       tmgi_list_ptr);
+
+			embms_debug("TMGI entry %d :\n", i);
+			embms_debug("TMGI multicast addr : %pI4 , port %u\n\n",
+				    &temp_tmgi->tmgi_multicast_addr,
+				    ntohs(temp_tmgi->tmgi_port));
+			embms_debug("No of clients : %d\n",
+				    temp_tmgi->no_of_clients);
+			j = 1;
+
+			list_for_each_safe(clnt_ptr, prev_clnt_ptr,
+					   &temp_tmgi->client_list_head) {
+				temp_client = list_entry(clnt_ptr,
+							 struct clnt_info,
+							 client_list_ptr);
+				embms_debug("Client entry %d :\n", j);
+				embms_debug("client addr : %pI4 , port %u\n\n",
+					    &temp_client->addr,
+					    ntohs(temp_client->port));
+				j++;
+			}
+			i++;
+			embms_debug("===========================================\n\n");
+		}
+	} else {
+		embms_debug("No TMGI entries to Display\n");
+	}
+	embms_debug("==================================================================\n\n");
+}
+
+/**
+ * delete_tmgi_entry_from_table() - deletes tmgi from global tmgi-client table
+ * @buffer:	Buffer containing TMGI info for deletion.
+ *
+ * This function completely removes the TMGI from
+ * global TMGI-client table, along with the client list
+ * so that no packets for this TMGI are processed
+ *
+ * Return: Success on deleting TMGI entry, error otherwise.
+ */
+
+int delete_tmgi_entry_from_table(char *buffer)
+{
+	int i;
+	struct tmgi_to_clnt_info_update *info_update;
+	char message_buffer[sizeof(struct tmgi_to_clnt_info_update)];
+	struct clnt_info *temp_client = NULL;
+	struct tmgi_to_clnt_info *temp_tmgi = NULL;
+	struct list_head *tmgi_entry_ptr, *prev_tmgi_entry_ptr;
+	struct list_head *clnt_ptr, *prev_clnt_ptr;
+
+	embms_debug("delete_tmgi_entry_from_table: Enter\n");
+
+	info_update = (struct tmgi_to_clnt_info_update *)buffer;
+
+	if (!info_update) {
+		embms_error("delete_tmgi_entry_from_table:");
+		embms_error("NULL arguments passed\n");
+		return -EBADPARAM;
+	}
+
+	/* This function is used to delete a specific TMGI entry
+	 * when that particular TMGI goes down
+	 * Search for the TMGI entry in our local table
+	 */
+	if (embms_conf.no_of_tmgi_sessions == 0) {
+		embms_error("TMGI count 0. Nothing to delete\n");
+		return SUCCESS;
+	}
+
+	temp_tmgi = check_for_tmgi_entry(info_update->multicast_addr,
+					 info_update->multicast_port);
+
+	if (!temp_tmgi) {
+		/* TMGI entry was not found in our local table*/
+		embms_error("delete_client_entry_from_table :");
+		embms_error("Desired TMGI entry not found\n");
+		return -EBADPARAM;
+	}
+
+	spin_lock_bh(&embms_conf.lock);
+
+	/* We need to free memory allocated to client entries
+	 * for a particular TMGI entry
+	 */
+
+	list_for_each_safe(clnt_ptr, prev_clnt_ptr,
+			   &temp_tmgi->client_list_head) {
+		temp_client = list_entry(clnt_ptr,
+					 struct clnt_info,
+					 client_list_ptr);
+		embms_debug("delete_tmgi_entry_from_table :");
+		embms_debug("Client addr to delete :%pI4 , port %u\n",
+			    &temp_client->addr, ntohs(temp_client->port));
+		list_del(&temp_client->client_list_ptr);
+		temp_tmgi->no_of_clients--;
+		kfree(temp_client);
+	}
+
+	/* Free memory allocated to tmgi entry*/
+
+	list_del(&temp_tmgi->tmgi_list_ptr);
+	kfree(temp_tmgi);
+	embms_conf.no_of_tmgi_sessions--;
+
+	spin_unlock_bh(&embms_conf.lock);
+
+	embms_debug("delete_tmgi_entry_from_table : TMGI Entry deleted.\n");
+
+	return SUCCESS;
+}
+
+/**
+ * delete_client_entry_from_all_tmgi() - deletes client from all tmgi lists
+ * @buffer:	Buffer containing client info for deletion.
+ *
+ * This function completely removes a client from
+ * all TMGIs in global TMGI-client table. Also delets TMGI
+ * entries if no more clients are there
+ *
+ * Return: Success on deleting client entry, error otherwise.
+ */
+int delete_client_entry_from_all_tmgi(char *buffer)
+{
+	int i;
+	struct tmgi_to_clnt_info_update *info_update;
+	char message_buffer[sizeof(struct tmgi_to_clnt_info_update)];
+	struct clnt_info *temp_client = NULL;
+	struct tmgi_to_clnt_info *tmgi = NULL;
+	struct list_head *tmgi_entry_ptr, *prev_tmgi_entry_ptr;
+	struct list_head *clnt_ptr, *prev_clnt_ptr;
+
+	/* We use this function when we want to delete any
+	 * client entry from all TMGI entries. This scenario
+	 * happens when any client disconnects and hence
+	 * we need to clean all realted client entries
+	 * in our mapping table
+	 */
+
+	embms_debug("del_clnt_from_all_tmgi: Enter\n");
+
+	info_update = (struct tmgi_to_clnt_info_update *)buffer;
+
+	if (!info_update) {
+		embms_error("del_clnt_from_all_tmgi:");
+		embms_error("NULL arguments passed\n");
+		return -EBADPARAM;
+	}
+
+	/* We start checking from first TMGI entry and if client
+	 * entry is found in client entries of any TMGI, we clean
+	 * up that client entry from that TMGI entry
+	 */
+	if (embms_conf.no_of_tmgi_sessions == 0)
+		return SUCCESS;
+
+	list_for_each_safe(tmgi_entry_ptr, prev_tmgi_entry_ptr,
+			   &tmgi_to_clnt_map_tbl.tmgi_list_ptr) {
+		tmgi = list_entry(tmgi_entry_ptr,
+				  struct tmgi_to_clnt_info,
+				  tmgi_list_ptr);
+
+		temp_client = chk_clnt_entry(tmgi, info_update);
+		if (!temp_client)
+			continue;
+
+		spin_lock_bh(&embms_conf.lock);
+
+		list_del(&temp_client->client_list_ptr);
+		tmgi->no_of_clients--;
+		kfree(temp_client);
+
+		spin_unlock_bh(&embms_conf.lock);
+
+		temp_client = NULL;
+
+		if (tmgi->no_of_clients == 0) {
+			/* Deleted clnt was the only clnt for
+			 * that TMGI we need to delete TMGI
+			 * entry from table
+			 */
+			embms_debug("del_clnt_from_all_tmgi:");
+			embms_debug("Deleted client was ");
+			embms_debug("last client for tmgi\n");
+			embms_debug("del_clnt_from_all_tmgi:");
+			embms_debug("Delting tmgi as it has ");
+			embms_debug("zero clients.TMGI IP ");
+			embms_debug(":%pI4 , port %u\n",
+				    &tmgi->tmgi_multicast_addr,
+				    ntohs(tmgi->tmgi_port));
+
+			spin_lock_bh(&embms_conf.lock);
+
+			list_del(&tmgi->tmgi_list_ptr);
+			embms_conf.no_of_tmgi_sessions--;
+			kfree(tmgi);
+
+			spin_unlock_bh(&embms_conf.lock);
+
+			embms_debug("del_clnt_from_all_tmgi:");
+			embms_debug("TMGI entry deleted\n");
+		}
+	}
+
+	embms_debug("del_clnt_from_all_tmgi Successful\n");
+	return SUCCESS;
+}
+
+/**
+ * add_client_entry_to_table() - add client entry to specified TMGI
+ * @buffer:	Buffer containing client info for addition.
+ *
+ * This function adds a client to the specified TMGI in
+ * the global TMGI-client table. If TMGI entry is not
+ * present, it adds a new TMGI entry and adds client
+ * entry to it.
+ *
+ * Return: Success on adding client entry, error otherwise.
+ */
+int add_client_entry_to_table(char *buffer)
+{
+	int i, ret;
+	struct tmgi_to_clnt_info_update *info_update;
+	char message_buffer[sizeof(struct tmgi_to_clnt_info_update)];
+	struct clnt_info *new_client = NULL;
+	struct clnt_info *temp_client = NULL;
+	struct tmgi_to_clnt_info *new_tmgi = NULL;
+	struct tmgi_to_clnt_info *tmgi = NULL;
+	struct list_head *tmgi_entry_ptr, *prev_tmgi_entry_ptr;
+	struct list_head *clnt_ptr, *prev_clnt_ptr;
+	struct neighbour *neigh_entry;
+	struct in_device *iface_dev;
+	struct in_ifaddr *iface_info;
+
+	embms_debug("add_client_entry_to_table: Enter\n");
+
+	info_update = (struct tmgi_to_clnt_info_update *)buffer;
+
+	if (!info_update) {
+		embms_error("add_client_entry_to_table:");
+		embms_error("NULL arguments passed\n");
+		return -EBADPARAM;
+	}
+
+	new_client = kzalloc(sizeof(*new_client), GFP_ATOMIC);
+	if (!new_client) {
+		embms_error("add_client_entry_to_table:");
+		embms_error("Cannot allocate memory\n");
+		return -ENOMEM;
+	}
+
+	new_client->addr = info_update->client_addr;
+	new_client->port = info_update->client_port;
+
+	neigh_entry = __ipv4_neigh_lookup(dev_global,
+					  (u32)(new_client->addr));
+	if (!neigh_entry) {
+		embms_error("add_client_entry_to_table :");
+		embms_error("Can't find neighbour entry\n");
+		kfree(new_client);
+		return -EBADPARAM;
+	}
+
+	ether_addr_copy(new_client->dmac, neigh_entry->ha);
+
+	embms_debug("DMAC of client : %pM\n", new_client->dmac);
+
+	embms_debug("add_client_entry_to_table:");
+	embms_debug("New client addr :%pI4 , port %u\n",
+		    &info_update->client_addr,
+		    ntohs(info_update->client_port));
+
+	if (embms_conf.no_of_tmgi_sessions == 0) {
+		/* TMGI Client mapping table is empty.
+		 * First client entry is being added
+		 */
+
+		embms_debug("tmgi_to_clnt_map_tbl is empty\n");
+
+		ret = add_new_tmgi_entry(info_update, new_client);
+		if (ret != SUCCESS) {
+			kfree(new_client);
+			new_client = NULL;
+		}
+
+		goto exit_add;
+	}
+
+	/* In this case, table already has some entries
+	 * and we need to search for the specific tmgi entry
+	 * for which client entry is to be added
+	 */
+
+	tmgi = check_for_tmgi_entry(info_update->multicast_addr,
+				    info_update->multicast_port);
+	if (tmgi) {
+		if (chk_clnt_entry(tmgi, info_update)) {
+			kfree(new_client);
+			return -ENOEFFECT;
+		}
+
+		/* Adding client to the client list
+		 * for the specified TMGI
+		 */
+
+		spin_lock_bh(&embms_conf.lock);
+
+		list_add(&new_client->client_list_ptr,
+			 &tmgi->client_list_head);
+			tmgi->no_of_clients++;
+
+		spin_unlock_bh(&embms_conf.lock);
+
+		ret = SUCCESS;
+	} else {
+		/* TMGI specified in the message was not found in
+		 * mapping table.Hence, we need to add a new entry
+		 * for this TMGI and add the specified client to the client
+		 * list
+		 */
+
+		embms_debug("TMGI entry not present. Adding tmgi entry\n");
+
+		ret = add_new_tmgi_entry(info_update, new_client);
+		if (ret != SUCCESS) {
+			kfree(new_client);
+			new_client = NULL;
+		}
+	}
+
+exit_add:
+	return ret;
+}
+
+/**
+ * delete_client_entry_from_table() - delete client entry from specified TMGI
+ * @buffer:	Buffer containing client info for deletion.
+ *
+ * This function deletes a client from the specified TMGI in
+ * the global TMGI-client table. If this was the last client
+ * entry, it also deletes the TMGI entry.
+ *
+ * Return: Success on deleting client entry, error otherwise.
+ */
+int delete_client_entry_from_table(char *buffer)
+{
+	int i;
+	struct tmgi_to_clnt_info_update *info_update;
+	char message_buffer[sizeof(struct tmgi_to_clnt_info_update)];
+	struct clnt_info *temp_client = NULL;
+	struct tmgi_to_clnt_info *temp_tmgi = NULL;
+	struct list_head *tmgi_entry_ptr, *prev_tmgi_entry_ptr;
+	struct list_head *clnt_ptr, *prev_clnt_ptr;
+
+	embms_debug("delete_client_entry_from_table: Enter\n");
+
+	info_update = (struct tmgi_to_clnt_info_update *)buffer;
+
+	if (!info_update) {
+		embms_error("delete_client_entry_from_table:");
+		embms_error("NULL arguments passed\n");
+		return -EBADPARAM;
+	}
+
+	/* Search for the TMGI entry*/
+	if (embms_conf.no_of_tmgi_sessions == 0)
+		return SUCCESS;
+
+	temp_tmgi = check_for_tmgi_entry(info_update->multicast_addr,
+					 info_update->multicast_port);
+
+	if (!temp_tmgi) {
+		embms_error("delete_client_entry_from_table:TMGI not found\n");
+		return -EBADPARAM;
+	}
+	/* Delete client entry for a specific tmgi*/
+
+	embms_debug("delete_client_entry_from_table:clnt addr :%pI4,port %u\n",
+		    &info_update->client_addr,
+		    ntohs(info_update->client_port));
+
+	temp_client = chk_clnt_entry(temp_tmgi, info_update);
+
+	if (!temp_client) {
+		/* Specified client entry was not found in client list
+		 * of specified TMGI
+		 */
+		embms_error("delete_client_entry_from_table:Clnt not found\n");
+		return -EBADPARAM;
+	}
+
+	spin_lock_bh(&embms_conf.lock);
+
+	list_del(&temp_client->client_list_ptr);
+	temp_tmgi->no_of_clients--;
+
+	spin_unlock_bh(&embms_conf.lock);
+
+	kfree(temp_client);
+	temp_client = NULL;
+
+	embms_debug("delete_client_entry_from_table:Client entry deleted\n");
+
+	if (temp_tmgi->no_of_clients == 0) {
+		/* If deleted client was the only client for that TMGI
+		 * we need to delete TMGI entry from table
+		 */
+		embms_debug("delete_client_entry_from_table:");
+		embms_debug("Deleted client was the last client for tmgi\n");
+		embms_debug("delete_client_entry_from_table:");
+		embms_debug("Deleting tmgi since it has zero clients\n");
+
+		spin_lock_bh(&embms_conf.lock);
+
+		list_del(&temp_tmgi->tmgi_list_ptr);
+		embms_conf.no_of_tmgi_sessions--;
+		kfree(temp_tmgi);
+
+		spin_unlock_bh(&embms_conf.lock);
+
+		embms_debug("delete_client_entry_from_table: TMGI deleted\n");
+	}
+
+	if (embms_conf.no_of_tmgi_sessions == 0)
+		embms_conf.embms_tunneling_status = TUNNELING_OFF;
+
+	return SUCCESS;
+}
+
+/**
+ * embms_device_ioctl() - handle IOCTL calls to device
+ * @file:	File descriptor of file opened from userspace process
+ * @ioctl_num:	IOCTL to use
+ * @ioctl_param:	IOCTL parameters/arguments
+ *
+ * This function is called whenever a process tries to do
+ * an ioctl on our device file. As per the IOCTL number,
+ * it calls various functions to manipulate global
+ * TMGI-client table
+ *
+ * Return: Success if functoin call returns SUCCESS, error otherwise.
+ */
+
+int embms_device_ioctl(struct file *file, unsigned int ioctl_num,
+		       unsigned long ioctl_param)
+{
+	int i, ret, error;
+	char *temp;
+	char buffer[BUF_LEN];
+	struct in_device *iface_dev;
+	struct in_ifaddr *iface_info;
+	struct tmgi_to_clnt_info_update *info_update;
+	char __user *argp = (char __user *)ioctl_param;
+
+	memset(buffer, 0, BUF_LEN);
+
+	/* Switch according to the ioctl called*/
+	switch (ioctl_num) {
+	case ADD_EMBMS_TUNNEL:
+		if (copy_from_user(buffer, argp,
+				   sizeof(struct tmgi_to_clnt_info_update)))
+			return -EFAULT;
+
+		ret = add_client_entry_to_table(buffer);
+		print_tmgi_to_client_table();
+		break;
+
+	case DEL_EMBMS_TUNNEL:
+		if (copy_from_user(buffer, argp,
+				   sizeof(struct tmgi_to_clnt_info_update)))
+			return -EFAULT;
+
+		ret = delete_client_entry_from_table(buffer);
+		print_tmgi_to_client_table();
+		break;
+
+	case TMGI_DEACTIVATE:
+		if (copy_from_user(buffer, argp,
+				   sizeof(struct tmgi_to_clnt_info_update)))
+			return -EFAULT;
+
+		ret = delete_tmgi_entry_from_table(buffer);
+		print_tmgi_to_client_table();
+		break;
+
+	case CLIENT_DEACTIVATE:
+		if (copy_from_user(buffer, argp,
+				   sizeof(struct tmgi_to_clnt_info_update)))
+			return -EFAULT;
+
+		ret = delete_client_entry_from_all_tmgi(buffer);
+		print_tmgi_to_client_table();
+		break;
+
+	case GET_EMBMS_TUNNELING_STATUS:
+		/* This ioctl is both input (ioctl_param) and
+		 * output (the return value of this function)
+		 */
+		embms_debug("Sending tunneling status : %d\n",
+			    embms_conf.embms_tunneling_status);
+		ret = embms_conf.embms_tunneling_status;
+		break;
+
+	case START_EMBMS_TUNNEL:
+
+		if (copy_from_user(buffer, argp,
+				   sizeof(struct tmgi_to_clnt_info_update)))
+			return -EFAULT;
+
+		info_update = (struct tmgi_to_clnt_info_update *)buffer;
+		embms_conf.embms_data_port = info_update->data_port;
+		udph_global->source = embms_conf.embms_data_port;
+
+		memset(embms_conf.embms_iface, 0, EMBMS_MAX_IFACE_NAME);
+		memcpy(embms_conf.embms_iface, info_update->iface_name,
+		       EMBMS_MAX_IFACE_NAME);
+
+		embms_conf.embms_tunneling_status = TUNNELING_ON;
+		embms_debug("Starting Tunneling. Embms_data_port  = %d\n",
+			    ntohs(embms_conf.embms_data_port));
+		embms_debug("Embms Data Iface = %s\n", embms_conf.embms_iface);
+		ret = SUCCESS;
+
+		/*Initialise dev_global to bridge device*/
+		dev_global = __dev_get_by_name(&init_net, BRIDGE_IFACE);
+		if (!dev_global) {
+			embms_error("Error in getting device info\n");
+			ret = FAILURE;
+		} else {
+			iface_dev = (struct in_device *)dev_global->ip_ptr;
+			iface_info = iface_dev->ifa_list;
+			while (iface_info) {
+				if (memcmp(iface_info->ifa_label,
+					   BRIDGE_IFACE,
+					   strlen(BRIDGE_IFACE)) == 0)
+					break;
+
+				iface_info = iface_info->ifa_next;
+			}
+			if (iface_info) {
+				embms_debug("IP address of %s iface is %pI4\n",
+					    BRIDGE_IFACE,
+					    &iface_info->ifa_address);
+				/*Populate source addr for header*/
+				iph_global->saddr = iface_info->ifa_address;
+				ret = SUCCESS;
+			} else {
+				embms_debug("Could not find iface address\n");
+				ret = FAILURE;
+			}
+		}
+
+		break;
+
+	case STOP_EMBMS_TUNNEL:
+
+		embms_conf.embms_tunneling_status = TUNNELING_OFF;
+		embms_debug("Stopped Tunneling..\n");
+		ret = SUCCESS;
+		break;
+	}
+
+	return ret;
+}
+
+/* Module Declarations
+ * This structure will hold the functions to be called
+ * when a process does something to the device we
+ * created. Since a pointer to this structure is kept in
+ * the devices table, it can't be local to
+ * init_module. NULL is for unimplemented functions.
+ */
+static const struct file_operations embms_device_fops = {
+	.owner = THIS_MODULE,
+	.open = embms_device_open,
+	.release = embms_device_release,
+	.read = NULL,
+	.write = NULL,
+	.unlocked_ioctl = embms_device_ioctl,
+};
+
+static int embms_ioctl_init(void)
+{
+	int ret;
+	struct device *dev;
+
+	ret = alloc_chrdev_region(&device, 0, dev_num, EMBMS_DEVICE_NAME);
+	if (ret) {
+		embms_error("device_alloc err\n");
+		goto dev_alloc_err;
+	}
+
+	embms_class = class_create(THIS_MODULE, EMBMS_DEVICE_NAME);
+	if (IS_ERR(embms_class)) {
+		embms_error("class_create err\n");
+		goto class_err;
+	}
+
+	dev = device_create(embms_class, NULL, device,
+			    &embms_conf, EMBMS_DEVICE_NAME);
+	if (IS_ERR(dev)) {
+		embms_error("device_create err\n");
+		goto device_err;
+	}
+
+	cdev_init(&embms_device, &embms_device_fops);
+	ret = cdev_add(&embms_device, device, dev_num);
+	if (ret) {
+		embms_error("cdev_add err\n");
+		goto cdev_add_err;
+	}
+
+	embms_debug("ioctl init OK!!\n");
+	return 0;
+
+cdev_add_err:
+	device_destroy(embms_class, device);
+device_err:
+	class_destroy(embms_class);
+class_err:
+	unregister_chrdev_region(device, dev_num);
+dev_alloc_err:
+	return -ENODEV;
+}
+
+static void embms_ioctl_deinit(void)
+{
+	cdev_del(&embms_device);
+	device_destroy(embms_class, device);
+	class_destroy(embms_class);
+	unregister_chrdev_region(device, dev_num);
+}
+
+/*Initialize the module - Register the misc device*/
+static int __init start_embms(void)
+{
+	int ret = 0;
+
+	iph_global = (struct iphdr *)hdr_buff;
+	udph_global = (struct udphdr *)(hdr_buff + sizeof(struct iphdr));
+
+	embms_conf.embms_tunneling_status = TUNNELING_OFF;
+	embms_conf.no_of_tmgi_sessions = 0;
+	embms_conf.embms_data_port = 0;
+	atomic_set(&embms_conf.device_under_use, 0);
+	atomic_set(&embms_conf.ip_ident, 0);
+	spin_lock_init(&embms_conf.lock);
+
+	embms_debug("Registering embms device\n");
+
+	ret = embms_ioctl_init();
+	if (ret) {
+		embms_error("embms device failed to register");
+		goto fail_init;
+	}
+
+	INIT_LIST_HEAD(&tmgi_to_clnt_map_tbl.tmgi_list_ptr);
+
+	memset(hdr_buff, 0, sizeof(struct udphdr) + sizeof(struct iphdr));
+	udph_global->check = UDP_CHECKSUM;
+	iph_global->version = IP_VERSION;
+	iph_global->ihl = IP_IHL;
+	iph_global->tos = IP_TOS;
+	iph_global->frag_off = IP_FRAG_OFFSET;
+	iph_global->ttl = IP_TTL;
+	iph_global->protocol = IPPROTO_UDP;
+
+	dev_global = NULL;
+
+	if (!embms_tm_multicast_recv)
+		RCU_INIT_POINTER(embms_tm_multicast_recv,
+				 handle_multicast_stream);
+
+	return ret;
+
+fail_init:
+	embms_ioctl_deinit();
+	return ret;
+}
+
+/*Cleanup - unregister the appropriate file from proc*/
+
+static void __exit stop_embms(void)
+{
+	embms_ioctl_deinit();
+
+	if (rcu_dereference(embms_tm_multicast_recv))
+		RCU_INIT_POINTER(embms_tm_multicast_recv, NULL);
+
+	embms_debug("unregister_chrdev done\n");
+}
+
+module_init(start_embms);
+module_exit(stop_embms);
+MODULE_LICENSE("GPL v2");