/************************************************************************* * EMBMS.C ************************************************************************* */ /************************************************************************* * ----------------------------------------------------------------------- * Copyright (c) 2013-2015, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "embms_kernel.h" /* Embms device used for communication*/ struct miscdevice embms_device; 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 == NULL)) { 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(u_int32_t addr, u_int16_t 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 == NULL) { 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 == NULL) { 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 == NULL) { /* 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 == NULL) { 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 == NULL) 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 == NULL) { 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 == NULL) { 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, (__force u32)(new_client->addr)); if (NULL == 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 != NULL) { if (chk_clnt_entry(tmgi, info_update) != NULL) { 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 == NULL) { 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 == NULL) { 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 == NULL) { /* 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 (NULL != iface_info) { if (memcmp(iface_info->ifa_label, BRIDGE_IFACE, strlen(BRIDGE_IFACE)) == 0) break; iface_info = iface_info->ifa_next; } if (iface_info != NULL) { 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, }; /*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_device.name = kzalloc(sizeof(EMBMS_DEVICE_NAME), GFP_KERNEL); strlcpy(embms_device.name, EMBMS_DEVICE_NAME, sizeof(EMBMS_DEVICE_NAME)); embms_device.fops = &embms_device_fops; embms_device.minor = MISC_DYNAMIC_MINOR; 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 = misc_register(&embms_device); 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 == NULL) RCU_INIT_POINTER(embms_tm_multicast_recv, handle_multicast_stream); return ret; fail_init: misc_deregister(&embms_device); return ret; } /*Cleanup - unregister the appropriate file from proc*/ static void __exit stop_embms(void) { misc_deregister(&embms_device); 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");