2010-12-13 14:19:28 +03:00
/*
2012-01-01 03:41:38 +04:00
* Copyright ( C ) 2007 - 2012 B . A . T . M . A . N . contributors :
2010-12-13 14:19:28 +03:00
*
* Marek Lindner
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of version 2 of the GNU General Public
* License 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA
* 02110 - 1301 , USA
*
*/
# include "main.h"
# include <linux/debugfs.h>
# include <linux/slab.h>
# include "icmp_socket.h"
# include "send.h"
# include "hash.h"
# include "originator.h"
# include "hard-interface.h"
static struct socket_client * socket_client_hash [ 256 ] ;
static void bat_socket_add_packet ( struct socket_client * socket_client ,
struct icmp_packet_rr * icmp_packet ,
size_t icmp_len ) ;
void bat_socket_init ( void )
{
memset ( socket_client_hash , 0 , sizeof ( socket_client_hash ) ) ;
}
static int bat_socket_open ( struct inode * inode , struct file * file )
{
unsigned int i ;
struct socket_client * socket_client ;
nonseekable_open ( inode , file ) ;
2011-05-15 01:14:54 +04:00
socket_client = kmalloc ( sizeof ( * socket_client ) , GFP_KERNEL ) ;
2010-12-13 14:19:28 +03:00
if ( ! socket_client )
return - ENOMEM ;
for ( i = 0 ; i < ARRAY_SIZE ( socket_client_hash ) ; i + + ) {
if ( ! socket_client_hash [ i ] ) {
socket_client_hash [ i ] = socket_client ;
break ;
}
}
if ( i = = ARRAY_SIZE ( socket_client_hash ) ) {
pr_err ( " Error - can't add another packet client: "
" maximum number of clients reached \n " ) ;
kfree ( socket_client ) ;
return - EXFULL ;
}
INIT_LIST_HEAD ( & socket_client - > queue_list ) ;
socket_client - > queue_len = 0 ;
socket_client - > index = i ;
socket_client - > bat_priv = inode - > i_private ;
spin_lock_init ( & socket_client - > lock ) ;
init_waitqueue_head ( & socket_client - > queue_wait ) ;
file - > private_data = socket_client ;
inc_module_count ( ) ;
return 0 ;
}
static int bat_socket_release ( struct inode * inode , struct file * file )
{
struct socket_client * socket_client = file - > private_data ;
struct socket_packet * socket_packet ;
struct list_head * list_pos , * list_pos_tmp ;
spin_lock_bh ( & socket_client - > lock ) ;
/* for all packets in the queue ... */
list_for_each_safe ( list_pos , list_pos_tmp , & socket_client - > queue_list ) {
socket_packet = list_entry ( list_pos ,
struct socket_packet , list ) ;
list_del ( list_pos ) ;
kfree ( socket_packet ) ;
}
socket_client_hash [ socket_client - > index ] = NULL ;
spin_unlock_bh ( & socket_client - > lock ) ;
kfree ( socket_client ) ;
dec_module_count ( ) ;
return 0 ;
}
static ssize_t bat_socket_read ( struct file * file , char __user * buf ,
size_t count , loff_t * ppos )
{
struct socket_client * socket_client = file - > private_data ;
struct socket_packet * socket_packet ;
size_t packet_len ;
int error ;
if ( ( file - > f_flags & O_NONBLOCK ) & & ( socket_client - > queue_len = = 0 ) )
return - EAGAIN ;
if ( ( ! buf ) | | ( count < sizeof ( struct icmp_packet ) ) )
return - EINVAL ;
if ( ! access_ok ( VERIFY_WRITE , buf , count ) )
return - EFAULT ;
error = wait_event_interruptible ( socket_client - > queue_wait ,
socket_client - > queue_len ) ;
if ( error )
return error ;
spin_lock_bh ( & socket_client - > lock ) ;
socket_packet = list_first_entry ( & socket_client - > queue_list ,
struct socket_packet , list ) ;
list_del ( & socket_packet - > list ) ;
socket_client - > queue_len - - ;
spin_unlock_bh ( & socket_client - > lock ) ;
2011-12-10 18:28:36 +04:00
packet_len = min ( count , socket_packet - > icmp_len ) ;
error = copy_to_user ( buf , & socket_packet - > icmp_packet , packet_len ) ;
2010-12-13 14:19:28 +03:00
kfree ( socket_packet ) ;
if ( error )
return - EFAULT ;
return packet_len ;
}
static ssize_t bat_socket_write ( struct file * file , const char __user * buff ,
size_t len , loff_t * off )
{
struct socket_client * socket_client = file - > private_data ;
struct bat_priv * bat_priv = socket_client - > bat_priv ;
2011-04-20 17:40:58 +04:00
struct hard_iface * primary_if = NULL ;
2010-12-13 14:19:28 +03:00
struct sk_buff * skb ;
struct icmp_packet_rr * icmp_packet ;
2011-02-10 17:33:53 +03:00
struct orig_node * orig_node = NULL ;
struct neigh_node * neigh_node = NULL ;
2010-12-13 14:19:28 +03:00
size_t packet_len = sizeof ( struct icmp_packet ) ;
if ( len < sizeof ( struct icmp_packet ) ) {
bat_dbg ( DBG_BATMAN , bat_priv ,
" Error - can't send packet from char device: "
" invalid packet size \n " ) ;
return - EINVAL ;
}
2011-04-20 17:40:58 +04:00
primary_if = primary_if_get_selected ( bat_priv ) ;
if ( ! primary_if ) {
len = - EFAULT ;
goto out ;
}
2010-12-13 14:19:28 +03:00
if ( len > = sizeof ( struct icmp_packet_rr ) )
packet_len = sizeof ( struct icmp_packet_rr ) ;
skb = dev_alloc_skb ( packet_len + sizeof ( struct ethhdr ) ) ;
2011-04-20 17:40:58 +04:00
if ( ! skb ) {
len = - ENOMEM ;
goto out ;
}
2010-12-13 14:19:28 +03:00
skb_reserve ( skb , sizeof ( struct ethhdr ) ) ;
icmp_packet = ( struct icmp_packet_rr * ) skb_put ( skb , packet_len ) ;
2011-12-10 18:28:35 +04:00
if ( copy_from_user ( icmp_packet , buff , packet_len ) ) {
2010-12-13 14:19:28 +03:00
len = - EFAULT ;
goto free_skb ;
}
2011-11-20 18:47:38 +04:00
if ( icmp_packet - > header . packet_type ! = BAT_ICMP ) {
2010-12-13 14:19:28 +03:00
bat_dbg ( DBG_BATMAN , bat_priv ,
" Error - can't send packet from char device: "
" got bogus packet type (expected: BAT_ICMP) \n " ) ;
len = - EINVAL ;
goto free_skb ;
}
if ( icmp_packet - > msg_type ! = ECHO_REQUEST ) {
bat_dbg ( DBG_BATMAN , bat_priv ,
" Error - can't send packet from char device: "
" got bogus message type (expected: ECHO_REQUEST) \n " ) ;
len = - EINVAL ;
goto free_skb ;
}
icmp_packet - > uid = socket_client - > index ;
2011-11-20 18:47:38 +04:00
if ( icmp_packet - > header . version ! = COMPAT_VERSION ) {
2010-12-13 14:19:28 +03:00
icmp_packet - > msg_type = PARAMETER_PROBLEM ;
2011-11-20 18:47:38 +04:00
icmp_packet - > header . version = COMPAT_VERSION ;
2010-12-13 14:19:28 +03:00
bat_socket_add_packet ( socket_client , icmp_packet , packet_len ) ;
goto free_skb ;
}
if ( atomic_read ( & bat_priv - > mesh_state ) ! = MESH_ACTIVE )
goto dst_unreach ;
2011-02-18 15:28:09 +03:00
orig_node = orig_hash_find ( bat_priv , icmp_packet - > dst ) ;
2010-12-13 14:19:28 +03:00
if ( ! orig_node )
2011-03-15 01:43:37 +03:00
goto dst_unreach ;
2011-02-10 17:33:53 +03:00
2011-03-15 01:43:37 +03:00
neigh_node = orig_node_get_router ( orig_node ) ;
2011-02-10 17:33:53 +03:00
if ( ! neigh_node )
2011-03-15 01:43:37 +03:00
goto dst_unreach ;
2010-12-13 14:19:28 +03:00
2011-01-19 23:01:44 +03:00
if ( ! neigh_node - > if_incoming )
2010-12-13 14:19:28 +03:00
goto dst_unreach ;
2011-01-19 23:01:44 +03:00
if ( neigh_node - > if_incoming - > if_status ! = IF_ACTIVE )
2010-12-13 14:19:28 +03:00
goto dst_unreach ;
memcpy ( icmp_packet - > orig ,
2011-04-20 17:40:58 +04:00
primary_if - > net_dev - > dev_addr , ETH_ALEN ) ;
2010-12-13 14:19:28 +03:00
if ( packet_len = = sizeof ( struct icmp_packet_rr ) )
2011-02-10 17:33:53 +03:00
memcpy ( icmp_packet - > rr ,
2011-01-19 23:01:44 +03:00
neigh_node - > if_incoming - > net_dev - > dev_addr , ETH_ALEN ) ;
2010-12-13 14:19:28 +03:00
2011-01-19 23:01:44 +03:00
send_skb_packet ( skb , neigh_node - > if_incoming , neigh_node - > addr ) ;
2010-12-13 14:19:28 +03:00
goto out ;
dst_unreach :
icmp_packet - > msg_type = DESTINATION_UNREACHABLE ;
bat_socket_add_packet ( socket_client , icmp_packet , packet_len ) ;
free_skb :
kfree_skb ( skb ) ;
out :
2011-04-20 17:40:58 +04:00
if ( primary_if )
hardif_free_ref ( primary_if ) ;
2011-02-10 17:33:53 +03:00
if ( neigh_node )
neigh_node_free_ref ( neigh_node ) ;
if ( orig_node )
2011-02-18 15:28:10 +03:00
orig_node_free_ref ( orig_node ) ;
2010-12-13 14:19:28 +03:00
return len ;
}
static unsigned int bat_socket_poll ( struct file * file , poll_table * wait )
{
struct socket_client * socket_client = file - > private_data ;
poll_wait ( file , & socket_client - > queue_wait , wait ) ;
if ( socket_client - > queue_len > 0 )
return POLLIN | POLLRDNORM ;
return 0 ;
}
static const struct file_operations fops = {
. owner = THIS_MODULE ,
. open = bat_socket_open ,
. release = bat_socket_release ,
. read = bat_socket_read ,
. write = bat_socket_write ,
. poll = bat_socket_poll ,
. llseek = no_llseek ,
} ;
int bat_socket_setup ( struct bat_priv * bat_priv )
{
struct dentry * d ;
if ( ! bat_priv - > debug_dir )
goto err ;
d = debugfs_create_file ( ICMP_SOCKET , S_IFREG | S_IWUSR | S_IRUSR ,
bat_priv - > debug_dir , bat_priv , & fops ) ;
if ( d )
goto err ;
return 0 ;
err :
return 1 ;
}
static void bat_socket_add_packet ( struct socket_client * socket_client ,
struct icmp_packet_rr * icmp_packet ,
size_t icmp_len )
{
struct socket_packet * socket_packet ;
2011-05-15 01:14:54 +04:00
socket_packet = kmalloc ( sizeof ( * socket_packet ) , GFP_ATOMIC ) ;
2010-12-13 14:19:28 +03:00
if ( ! socket_packet )
return ;
INIT_LIST_HEAD ( & socket_packet - > list ) ;
memcpy ( & socket_packet - > icmp_packet , icmp_packet , icmp_len ) ;
socket_packet - > icmp_len = icmp_len ;
spin_lock_bh ( & socket_client - > lock ) ;
/* while waiting for the lock the socket_client could have been
* deleted */
if ( ! socket_client_hash [ icmp_packet - > uid ] ) {
spin_unlock_bh ( & socket_client - > lock ) ;
kfree ( socket_packet ) ;
return ;
}
list_add_tail ( & socket_packet - > list , & socket_client - > queue_list ) ;
socket_client - > queue_len + + ;
if ( socket_client - > queue_len > 100 ) {
socket_packet = list_first_entry ( & socket_client - > queue_list ,
struct socket_packet , list ) ;
list_del ( & socket_packet - > list ) ;
kfree ( socket_packet ) ;
socket_client - > queue_len - - ;
}
spin_unlock_bh ( & socket_client - > lock ) ;
wake_up ( & socket_client - > queue_wait ) ;
}
void bat_socket_receive_packet ( struct icmp_packet_rr * icmp_packet ,
size_t icmp_len )
{
struct socket_client * hash = socket_client_hash [ icmp_packet - > uid ] ;
if ( hash )
bat_socket_add_packet ( hash , icmp_packet , icmp_len ) ;
}