diff --git a/net/core/dev.c b/net/core/dev.c index bfb7bf0f2847..82d77b446c13 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -4718,6 +4718,18 @@ static inline int nf_ingress(struct sk_buff *skb, struct packet_type **pt_prev, return 0; } +static int (*embms_tm_multicast_recv)(struct sk_buff *skb) __rcu __read_mostly; +EXPORT_SYMBOL(embms_tm_multicast_recv); + +void process_embms_receive_skb(struct sk_buff *skb) +{ + int (*embms_recv)(struct sk_buff *skb); + + embms_recv = rcu_dereference(embms_tm_multicast_recv); + if (embms_recv) + embms_recv(skb); +} + static int __netif_receive_skb_core(struct sk_buff **pskb, bool pfmemalloc, struct packet_type **ppt_prev) { @@ -4798,6 +4810,8 @@ skip_taps: } #endif skb_reset_redirect(skb); + process_embms_receive_skb(skb); + skip_classify: if (pfmemalloc && !skb_pfmemalloc_protocol(skb)) goto drop; diff --git a/net/embms_kernel/embms_kernel/Makefile b/net/embms_kernel/embms_kernel/Makefile new file mode 100644 index 000000000000..3291b0d94c69 --- /dev/null +++ b/net/embms_kernel/embms_kernel/Makefile @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for Embms Kernel module. +# + +KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build + +obj-m += embms_kernel.o + +ccflags-y += -D__CHECK_ENDIAN__ + +CDEFINES += -D__CHECK_ENDIAN__ + +KBUILD_CPPFLAGS += $(CDEFINES) + +all: + $(MAKE) -C $(KERNEL_SRC) M=$(shell pwd) modules +modules_install: + $(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(shell pwd) modules_install + +clean: + $(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean + diff --git a/net/embms_kernel/embms_kernel/embms_kernel.c b/net/embms_kernel/embms_kernel/embms_kernel.c new file mode 100644 index 000000000000..e862c9d26478 --- /dev/null +++ b/net/embms_kernel/embms_kernel/embms_kernel.c @@ -0,0 +1,1025 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2013-2015, 2017, 2018, 2020, The Linux Foundation. All rights reserved. */ +/************************************************************************* + * 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" + +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 tunneling related + * structs and params + */ + +static 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("%s:", __func__); + 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); + spin_unlock_bh(&embms_conf.lock); + 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); + spin_unlock_bh(&embms_conf.lock); + 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) +{ + if (atomic_inc_return(use_count) == 1) + return 0; + + atomic_dec(use_count); + + return -EBUSY; +} + +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("%s: mcast addr :%pI4, port %u\n", + __func__, &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("%s:TMGI entry found\n", __func__); + 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("%s:Enter\n", __func__); + + new_tmgi = kzalloc(sizeof(*new_tmgi), + GFP_ATOMIC); + if (!new_tmgi) + 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("%s:", __func__); + embms_debug("New tmgi multicast addr :%pI4 , port %u\n", + &info_update->multicast_addr, + ntohs(info_update->multicast_port)); + + embms_debug("%s:Adding client entry\n", __func__); + + 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("%s: Enter\n", __func__); + + info_update = (struct tmgi_to_clnt_info_update *)buffer; + + if (!info_update) { + embms_error("%s:", __func__); + 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("%s :", __func__); + 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("%s :", __func__); + 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("%s : TMGI Entry deleted.\n", __func__); + + 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("%s: Enter\n", __func__); + + info_update = (struct tmgi_to_clnt_info_update *)buffer; + + if (!info_update) { + embms_error("%s:", __func__); + embms_error("NULL arguments passed\n"); + return -EBADPARAM; + } + + new_client = kzalloc(sizeof(*new_client), GFP_ATOMIC); + if (!new_client) + 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("%s :", __func__); + 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("%s:", __func__); + 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("%s: Enter\n", __func__); + + info_update = (struct tmgi_to_clnt_info_update *)buffer; + + if (!info_update) { + embms_error("%s:", __func__); + 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("%s:TMGI not found\n", __func__); + return -EBADPARAM; + } + /* Delete client entry for a specific tmgi*/ + + embms_debug("%s:clnt addr :%pI4,port %u\n", + __func__, &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("%s:Clnt not found\n", __func__); + 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("%s:Client entry deleted\n, __func__"); + + 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("%s:", __func__); + embms_debug("Deleted client was the last client for tmgi\n"); + embms_debug("%s:", __func__); + 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("%s: TMGI deleted\n", __func__); + } + + 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. + */ + +long embms_device_ioctl(struct file *file, unsigned int ioctl_num, + unsigned long ioctl_param) +{ + int i, error; + long ret; + 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 ret:%d\n", ret); + 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"); diff --git a/net/embms_kernel/embms_kernel/embms_kernel.h b/net/embms_kernel/embms_kernel/embms_kernel.h new file mode 100644 index 000000000000..4a446439cfa6 --- /dev/null +++ b/net/embms_kernel/embms_kernel/embms_kernel.h @@ -0,0 +1,223 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2013-2015,2017, 2018, 2020, The Linux Foundation. All rights reserved. */ +/****************************************************************** + * DESCRIPTION + * Header file for eMBMs Tunneling Module in kernel. + ******************************************************************* + */ + +#ifndef EMBMS_H +#define EMBMS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#define EMBMS_MAX_IFACE_NAME 20 + +/* Defining IP and UDP header related macros*/ + +#define UDP_CHECKSUM 0 +#define IP_VERSION 4 +#define IP_IHL 5 +#define IP_TOS 0 +#define IP_ID 1 +#define IP_FRAG_OFFSET htons(0x4000) +#define IP_TTL 64 +#define BRIDGE_IFACE "bridge0" + +#define BUF_LEN 1024 +#define TUNNELING_ON 1 +#define TUNNELING_OFF 0 + +// definitions required for IOCTL +static unsigned int dev_num = 1; +/* Embms device used for communication*/ +struct cdev embms_device; +static struct class *embms_class; +static dev_t device; +#define EMBMS_IOC_MAGIC 0x64 + +#define embms_debug pr_debug +#define embms_error pr_debug + +/* The name of the device file*/ +#define EMBMS_DEVICE_NAME "embms_tm_device" + +extern int (*embms_tm_multicast_recv)(struct sk_buff *skb); + +/** + * enum embms_action_type - Describes action to perform + * @ADD_CLIENT_ENTRY: add client entry to TMGI + * @DELETE_CLIENT_ENTRY: deelte client entry from TMGI + * @TMGI_DEACTIVATE: Delete TMGI entry + * @CLIENT_ACTIVATE_ALL_TMGI: Add client to all TMGI + * @CLIENT_DEACTIVATE_ALL_TMGI: Delete client from all TMGI + * @SESSION_DEACTIVATE: Stop session + * @SOCK_INFO: Socket information like V4 addr, port etc + * + * This enum defines the types of action which are + * supported by this module. + */ + +enum { + ADD_CLIENT_ENTRY = 0, + DELETE_CLIENT_ENTRY, + TMGI_DEACTIVATE, + CLIENT_ACTIVATE_ALL_TMGI, + CLIENT_DEACTIVATE_ALL_TMGI, + SESSION_DEACTIVATE, + SOCK_INFO +} embms_action_type; + +/** + * struct tmgi_to_clnt_info_update - information for addition/deletion + * @multicast_addr: TMGI multicast IP to receive data + * @multicast_port: TMGI multicast port to receive date + * @client_addr: Client IPV4 address for sending data + * @client_port: Client port for sending data + * @data_port: port used to send data to client + * @action_type: Action to be performed + * @iface_name: iface to listen to for data + * + * This structure contains information as to what action + * needs to be performed on TMGI-client table. It is + * sent as a parameter during an IOCTL call + */ + +struct tmgi_to_clnt_info_update { + u32 multicast_addr; + u16 multicast_port; + u32 client_addr; + u16 client_port; + u16 data_port; + u32 action_type; + char iface_name[EMBMS_MAX_IFACE_NAME]; +}; + +/** + * struct clnt_info - contains client information + * @addr: Client IPV4 address for sending packets + * @port: Client port for sending packets + * @dmac: Client DMAC address + * @client_list_ptr : list ptr used to maintain client list + * + * This structure maintains complete client information + * to be used when sending packets to client + */ + +struct clnt_info { + u32 addr; + u16 port; + u8 dmac[ETH_ALEN]; + struct list_head client_list_ptr; +}; + +/** + * struct tmgi_to_clnt_info - contains TMGI information + * @tmgi_multicast_addr: TMGI IPV4 address to listen for packets + * @tmgi_port: Client port to listen for packets + * @no_of_clients: No of clients for a TMGI + * @client_list_head : list head for client list + * @tmgi_list_ptr : list ptr to maintain tmgi list + * + * This structure maintains complete client information + * to be used when sending data to client + */ + +struct tmgi_to_clnt_info { + u32 tmgi_multicast_addr; + u16 tmgi_port; + u16 no_of_clients; + struct list_head client_list_head; + struct list_head tmgi_list_ptr; +}; + +/** + * struct embms_info_internal - stores module specific params + * @device_under_use: Used to prevent concurent access to the same device + * @embms_data_port: Source Data port used for tunnelled packets + * @embms_iface: Iface to receive embms traffic + * @embms_tunneling_status : Current EMBMS Status + * @no_of_tmgi_sessions : Number of current active TMGI sessions + * @lock : Lock for concurrency scenarios + * @ip_ident : IP identification number to be used for sent packets + * + * This tructure holds module specific information which is + * used throughout the module to maintain consistency + */ + +struct embms_info_internal { + atomic_t device_under_use; + int embms_data_port; + char embms_iface[EMBMS_MAX_IFACE_NAME]; + int embms_tunneling_status; + int no_of_tmgi_sessions; + /*lock to prevent concurrent access*/ + spinlock_t lock; + atomic_t ip_ident; +}; + +/* This ioctl is used to add a new client entry to tunneling module. + * Entry params are populated in the struct used for ioctl + */ + +#define ADD_EMBMS_TUNNEL _IOW(EMBMS_IOC_MAGIC, 0, \ + struct tmgi_to_clnt_info_update) + +/* This ioctl is used to delete a client entry for a particular + * TMGI from tunneling module. + * Entry params are populated in the struct used for ioctl + */ + +#define DEL_EMBMS_TUNNEL _IOW(EMBMS_IOC_MAGIC, 1, \ + struct tmgi_to_clnt_info_update) + +/* This ioctl is used to delete a TMGI entry completely + * from tunneling module. + * Entry params are populated in the struct used for ioctl + */ + +#define TMGI_DEACTIVATE _IOW(EMBMS_IOC_MAGIC, 2, \ + struct tmgi_to_clnt_info_update) + +/* This ioctl is used to delete client entry completely + * from tunneling module. + * Entry params are populated in the struct used for ioctl + */ + +#define CLIENT_DEACTIVATE _IOW(EMBMS_IOC_MAGIC, 3, \ + struct tmgi_to_clnt_info_update) + +/* Gets the ON/OFF status of Tunneling module*/ + +#define GET_EMBMS_TUNNELING_STATUS _IO(EMBMS_IOC_MAGIC, 4) + +/* Used to start tunneling. Argument is the port + * number to be used to send + * data to clients + */ + +#define START_EMBMS_TUNNEL _IOW(EMBMS_IOC_MAGIC, 5, \ + struct tmgi_to_clnt_info_update) + +/* Used to stop tunnleing*/ + +#define STOP_EMBMS_TUNNEL _IO(EMBMS_IOC_MAGIC, 6) + +/* Return values indicating error status*/ +#define SUCCESS 0 /* Successful operation*/ +#define FAILURE -1 /* Unsuccessful operation*/ + +/* Error Condition Values*/ +#define ENOMEM -2 /* Out of memory*/ +#define EBADPARAM -3 /* Incorrect parameters passed*/ +#define ENOEFFECT -4 /* No Effect*/ + +#endif +