2008-09-23 07:05:19 +04:00
/*
* File : socket . c
*
* Phonet sockets
*
* Copyright ( C ) 2008 Nokia Corporation .
*
* Contact : Remi Denis - Courmont < remi . denis - courmont @ nokia . com >
* Original author : Sakari Ailus < sakari . ailus @ nokia . com >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* 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 .
*
* 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 St , Fifth Floor , Boston , MA
* 02110 - 1301 USA
*/
# include <linux/kernel.h>
# include <linux/net.h>
2008-10-05 22:14:48 +04:00
# include <linux/poll.h>
2008-09-23 07:05:19 +04:00
# include <net/sock.h>
# include <net/tcp_states.h>
# include <linux/phonet.h>
# include <net/phonet/phonet.h>
2008-10-05 22:14:48 +04:00
# include <net/phonet/pep.h>
2008-09-23 07:05:19 +04:00
# include <net/phonet/pn_dev.h>
static int pn_socket_release ( struct socket * sock )
{
struct sock * sk = sock - > sk ;
if ( sk ) {
sock - > sk = NULL ;
sk - > sk_prot - > close ( sk , 0 ) ;
}
return 0 ;
}
2009-11-09 05:17:01 +03:00
# define PN_HASHSIZE 16
# define PN_HASHMASK (PN_HASHSIZE-1)
2008-09-23 07:05:19 +04:00
static struct {
2009-11-09 05:17:01 +03:00
struct hlist_head hlist [ PN_HASHSIZE ] ;
2008-09-23 07:05:19 +04:00
spinlock_t lock ;
2009-11-09 05:17:01 +03:00
} pnsocks ;
void __init pn_sock_init ( void )
{
unsigned i ;
for ( i = 0 ; i < PN_HASHSIZE ; i + + )
INIT_HLIST_HEAD ( pnsocks . hlist + i ) ;
spin_lock_init ( & pnsocks . lock ) ;
}
static struct hlist_head * pn_hash_list ( u16 obj )
{
return pnsocks . hlist + ( obj & PN_HASHMASK ) ;
}
2008-09-23 07:05:19 +04:00
/*
* Find address based on socket address , match only certain fields .
* Also grab sock if it was found . Remember to sock_put it later .
*/
2008-12-04 02:42:56 +03:00
struct sock * pn_find_sock_by_sa ( struct net * net , const struct sockaddr_pn * spn )
2008-09-23 07:05:19 +04:00
{
struct hlist_node * node ;
struct sock * sknode ;
struct sock * rval = NULL ;
u16 obj = pn_sockaddr_get_object ( spn ) ;
u8 res = spn - > spn_resource ;
2009-11-09 05:17:01 +03:00
struct hlist_head * hlist = pn_hash_list ( obj ) ;
2008-09-23 07:05:19 +04:00
spin_lock_bh ( & pnsocks . lock ) ;
2009-11-09 05:17:01 +03:00
sk_for_each ( sknode , node , hlist ) {
2008-09-23 07:05:19 +04:00
struct pn_sock * pn = pn_sk ( sknode ) ;
BUG_ON ( ! pn - > sobject ) ; /* unbound socket */
2008-12-04 02:42:56 +03:00
if ( ! net_eq ( sock_net ( sknode ) , net ) )
continue ;
2008-09-23 07:05:19 +04:00
if ( pn_port ( obj ) ) {
/* Look up socket by port */
if ( pn_port ( pn - > sobject ) ! = pn_port ( obj ) )
continue ;
} else {
/* If port is zero, look up by resource */
if ( pn - > resource ! = res )
continue ;
}
2009-11-30 03:55:45 +03:00
if ( pn_addr ( pn - > sobject ) & &
pn_addr ( pn - > sobject ) ! = pn_addr ( obj ) )
2008-09-23 07:05:19 +04:00
continue ;
rval = sknode ;
sock_hold ( sknode ) ;
break ;
}
spin_unlock_bh ( & pnsocks . lock ) ;
return rval ;
2009-10-14 04:48:27 +04:00
}
/* Deliver a broadcast packet (only in bottom-half) */
void pn_deliver_sock_broadcast ( struct net * net , struct sk_buff * skb )
{
2009-11-09 05:17:01 +03:00
struct hlist_head * hlist = pnsocks . hlist ;
unsigned h ;
2009-10-14 04:48:27 +04:00
spin_lock ( & pnsocks . lock ) ;
2009-11-09 05:17:01 +03:00
for ( h = 0 ; h < PN_HASHSIZE ; h + + ) {
struct hlist_node * node ;
struct sock * sknode ;
2009-10-14 04:48:27 +04:00
2009-11-09 05:17:01 +03:00
sk_for_each ( sknode , node , hlist ) {
struct sk_buff * clone ;
2008-09-23 07:05:19 +04:00
2009-11-09 05:17:01 +03:00
if ( ! net_eq ( sock_net ( sknode ) , net ) )
continue ;
if ( ! sock_flag ( sknode , SOCK_BROADCAST ) )
continue ;
clone = skb_clone ( skb , GFP_ATOMIC ) ;
if ( clone ) {
sock_hold ( sknode ) ;
sk_receive_skb ( sknode , clone , 0 ) ;
}
2009-10-15 23:29:14 +04:00
}
2009-11-09 05:17:01 +03:00
hlist + + ;
2009-10-14 04:48:27 +04:00
}
spin_unlock ( & pnsocks . lock ) ;
2008-09-23 07:05:19 +04:00
}
void pn_sock_hash ( struct sock * sk )
{
2009-11-09 05:17:01 +03:00
struct hlist_head * hlist = pn_hash_list ( pn_sk ( sk ) - > sobject ) ;
2008-09-23 07:05:19 +04:00
spin_lock_bh ( & pnsocks . lock ) ;
2009-11-09 05:17:01 +03:00
sk_add_node ( sk , hlist ) ;
2008-09-23 07:05:19 +04:00
spin_unlock_bh ( & pnsocks . lock ) ;
}
EXPORT_SYMBOL ( pn_sock_hash ) ;
void pn_sock_unhash ( struct sock * sk )
{
spin_lock_bh ( & pnsocks . lock ) ;
sk_del_node_init ( sk ) ;
spin_unlock_bh ( & pnsocks . lock ) ;
}
EXPORT_SYMBOL ( pn_sock_unhash ) ;
2009-09-23 07:17:10 +04:00
static DEFINE_MUTEX ( port_mutex ) ;
2008-09-23 07:05:19 +04:00
static int pn_socket_bind ( struct socket * sock , struct sockaddr * addr , int len )
{
struct sock * sk = sock - > sk ;
struct pn_sock * pn = pn_sk ( sk ) ;
struct sockaddr_pn * spn = ( struct sockaddr_pn * ) addr ;
int err ;
u16 handle ;
u8 saddr ;
if ( sk - > sk_prot - > bind )
return sk - > sk_prot - > bind ( sk , addr , len ) ;
if ( len < sizeof ( struct sockaddr_pn ) )
return - EINVAL ;
if ( spn - > spn_family ! = AF_PHONET )
return - EAFNOSUPPORT ;
handle = pn_sockaddr_get_object ( ( struct sockaddr_pn * ) addr ) ;
saddr = pn_addr ( handle ) ;
2008-12-04 02:42:56 +03:00
if ( saddr & & phonet_address_lookup ( sock_net ( sk ) , saddr ) )
2008-09-23 07:05:19 +04:00
return - EADDRNOTAVAIL ;
lock_sock ( sk ) ;
if ( sk - > sk_state ! = TCP_CLOSE | | pn_port ( pn - > sobject ) ) {
err = - EINVAL ; /* attempt to rebind */
goto out ;
}
2009-09-23 07:17:10 +04:00
WARN_ON ( sk_hashed ( sk ) ) ;
mutex_lock ( & port_mutex ) ;
2008-09-23 07:05:19 +04:00
err = sk - > sk_prot - > get_port ( sk , pn_port ( handle ) ) ;
if ( err )
2009-09-23 07:17:10 +04:00
goto out_port ;
2008-09-23 07:05:19 +04:00
/* get_port() sets the port, bind() sets the address if applicable */
pn - > sobject = pn_object ( saddr , pn_port ( pn - > sobject ) ) ;
pn - > resource = spn - > spn_resource ;
/* Enable RX on the socket */
sk - > sk_prot - > hash ( sk ) ;
2009-09-23 07:17:10 +04:00
out_port :
mutex_unlock ( & port_mutex ) ;
2008-09-23 07:05:19 +04:00
out :
release_sock ( sk ) ;
return err ;
}
static int pn_socket_autobind ( struct socket * sock )
{
struct sockaddr_pn sa ;
int err ;
memset ( & sa , 0 , sizeof ( sa ) ) ;
sa . spn_family = AF_PHONET ;
err = pn_socket_bind ( sock , ( struct sockaddr * ) & sa ,
sizeof ( struct sockaddr_pn ) ) ;
if ( err ! = - EINVAL )
return err ;
BUG_ON ( ! pn_port ( pn_sk ( sock - > sk ) - > sobject ) ) ;
return 0 ; /* socket was already bound */
}
2008-10-05 22:14:48 +04:00
static int pn_socket_accept ( struct socket * sock , struct socket * newsock ,
int flags )
{
struct sock * sk = sock - > sk ;
struct sock * newsk ;
int err ;
newsk = sk - > sk_prot - > accept ( sk , flags , & err ) ;
if ( ! newsk )
return err ;
lock_sock ( newsk ) ;
sock_graft ( newsk , newsock ) ;
newsock - > state = SS_CONNECTED ;
release_sock ( newsk ) ;
return 0 ;
}
2008-09-23 07:05:19 +04:00
static int pn_socket_getname ( struct socket * sock , struct sockaddr * addr ,
int * sockaddr_len , int peer )
{
struct sock * sk = sock - > sk ;
struct pn_sock * pn = pn_sk ( sk ) ;
memset ( addr , 0 , sizeof ( struct sockaddr_pn ) ) ;
addr - > sa_family = AF_PHONET ;
if ( ! peer ) /* Race with bind() here is userland's problem. */
pn_sockaddr_set_object ( ( struct sockaddr_pn * ) addr ,
pn - > sobject ) ;
* sockaddr_len = sizeof ( struct sockaddr_pn ) ;
return 0 ;
}
2008-10-05 22:14:48 +04:00
static unsigned int pn_socket_poll ( struct file * file , struct socket * sock ,
poll_table * wait )
{
struct sock * sk = sock - > sk ;
struct pep_sock * pn = pep_sk ( sk ) ;
unsigned int mask = 0 ;
poll_wait ( file , & sock - > wait , wait ) ;
switch ( sk - > sk_state ) {
case TCP_LISTEN :
return hlist_empty ( & pn - > ackq ) ? 0 : POLLIN ;
case TCP_CLOSE :
return POLLERR ;
}
if ( ! skb_queue_empty ( & sk - > sk_receive_queue ) )
mask | = POLLIN | POLLRDNORM ;
2008-10-05 22:15:43 +04:00
if ( ! skb_queue_empty ( & pn - > ctrlreq_queue ) )
mask | = POLLPRI ;
if ( ! mask & & sk - > sk_state = = TCP_CLOSE_WAIT )
2008-10-05 22:14:48 +04:00
return POLLHUP ;
2008-12-18 02:48:31 +03:00
if ( sk - > sk_state = = TCP_ESTABLISHED & & atomic_read ( & pn - > tx_credits ) )
2008-10-05 22:14:48 +04:00
mask | = POLLOUT | POLLWRNORM | POLLWRBAND ;
return mask ;
}
2008-09-23 07:05:19 +04:00
static int pn_socket_ioctl ( struct socket * sock , unsigned int cmd ,
unsigned long arg )
{
struct sock * sk = sock - > sk ;
struct pn_sock * pn = pn_sk ( sk ) ;
if ( cmd = = SIOCPNGETOBJECT ) {
struct net_device * dev ;
u16 handle ;
u8 saddr ;
if ( get_user ( handle , ( __u16 __user * ) arg ) )
return - EFAULT ;
lock_sock ( sk ) ;
if ( sk - > sk_bound_dev_if )
dev = dev_get_by_index ( sock_net ( sk ) ,
sk - > sk_bound_dev_if ) ;
else
dev = phonet_device_get ( sock_net ( sk ) ) ;
if ( dev & & ( dev - > flags & IFF_UP ) )
saddr = phonet_address_get ( dev , pn_addr ( handle ) ) ;
else
saddr = PN_NO_ADDR ;
release_sock ( sk ) ;
if ( dev )
dev_put ( dev ) ;
if ( saddr = = PN_NO_ADDR )
return - EHOSTUNREACH ;
handle = pn_object ( saddr , pn_port ( pn - > sobject ) ) ;
return put_user ( handle , ( __u16 __user * ) arg ) ;
}
return sk - > sk_prot - > ioctl ( sk , cmd , arg ) ;
}
2008-10-05 22:14:48 +04:00
static int pn_socket_listen ( struct socket * sock , int backlog )
{
struct sock * sk = sock - > sk ;
int err = 0 ;
if ( sock - > state ! = SS_UNCONNECTED )
return - EINVAL ;
if ( pn_socket_autobind ( sock ) )
return - ENOBUFS ;
lock_sock ( sk ) ;
if ( sk - > sk_state ! = TCP_CLOSE ) {
err = - EINVAL ;
goto out ;
}
sk - > sk_state = TCP_LISTEN ;
sk - > sk_ack_backlog = 0 ;
sk - > sk_max_ack_backlog = backlog ;
out :
release_sock ( sk ) ;
return err ;
}
2008-09-23 07:05:19 +04:00
static int pn_socket_sendmsg ( struct kiocb * iocb , struct socket * sock ,
struct msghdr * m , size_t total_len )
{
struct sock * sk = sock - > sk ;
if ( pn_socket_autobind ( sock ) )
return - EAGAIN ;
return sk - > sk_prot - > sendmsg ( iocb , sk , m , total_len ) ;
}
const struct proto_ops phonet_dgram_ops = {
. family = AF_PHONET ,
. owner = THIS_MODULE ,
. release = pn_socket_release ,
. bind = pn_socket_bind ,
. connect = sock_no_connect ,
. socketpair = sock_no_socketpair ,
. accept = sock_no_accept ,
. getname = pn_socket_getname ,
. poll = datagram_poll ,
. ioctl = pn_socket_ioctl ,
. listen = sock_no_listen ,
. shutdown = sock_no_shutdown ,
. setsockopt = sock_no_setsockopt ,
. getsockopt = sock_no_getsockopt ,
# ifdef CONFIG_COMPAT
. compat_setsockopt = sock_no_setsockopt ,
. compat_getsockopt = sock_no_getsockopt ,
# endif
. sendmsg = pn_socket_sendmsg ,
. recvmsg = sock_common_recvmsg ,
. mmap = sock_no_mmap ,
. sendpage = sock_no_sendpage ,
} ;
2008-10-05 22:14:48 +04:00
const struct proto_ops phonet_stream_ops = {
. family = AF_PHONET ,
. owner = THIS_MODULE ,
. release = pn_socket_release ,
. bind = pn_socket_bind ,
. connect = sock_no_connect ,
. socketpair = sock_no_socketpair ,
. accept = pn_socket_accept ,
. getname = pn_socket_getname ,
. poll = pn_socket_poll ,
. ioctl = pn_socket_ioctl ,
. listen = pn_socket_listen ,
. shutdown = sock_no_shutdown ,
2008-10-05 22:16:16 +04:00
. setsockopt = sock_common_setsockopt ,
. getsockopt = sock_common_getsockopt ,
2008-10-05 22:14:48 +04:00
# ifdef CONFIG_COMPAT
2008-10-05 22:16:16 +04:00
. compat_setsockopt = compat_sock_common_setsockopt ,
. compat_getsockopt = compat_sock_common_getsockopt ,
2008-10-05 22:14:48 +04:00
# endif
. sendmsg = pn_socket_sendmsg ,
. recvmsg = sock_common_recvmsg ,
. mmap = sock_no_mmap ,
. sendpage = sock_no_sendpage ,
} ;
EXPORT_SYMBOL ( phonet_stream_ops ) ;
2008-09-23 07:05:19 +04:00
/* allocate port for a socket */
int pn_sock_get_port ( struct sock * sk , unsigned short sport )
{
static int port_cur ;
2008-12-04 02:42:56 +03:00
struct net * net = sock_net ( sk ) ;
2008-09-23 07:05:19 +04:00
struct pn_sock * pn = pn_sk ( sk ) ;
struct sockaddr_pn try_sa ;
struct sock * tmpsk ;
memset ( & try_sa , 0 , sizeof ( struct sockaddr_pn ) ) ;
try_sa . spn_family = AF_PHONET ;
2009-09-23 07:17:10 +04:00
WARN_ON ( ! mutex_is_locked ( & port_mutex ) ) ;
2008-09-23 07:05:19 +04:00
if ( ! sport ) {
/* search free port */
2008-09-23 07:08:39 +04:00
int port , pmin , pmax ;
2008-09-23 07:05:19 +04:00
2008-09-23 07:08:39 +04:00
phonet_get_local_port_range ( & pmin , & pmax ) ;
2008-09-23 07:05:19 +04:00
for ( port = pmin ; port < = pmax ; port + + ) {
port_cur + + ;
if ( port_cur < pmin | | port_cur > pmax )
port_cur = pmin ;
pn_sockaddr_set_port ( & try_sa , port_cur ) ;
2008-12-04 02:42:56 +03:00
tmpsk = pn_find_sock_by_sa ( net , & try_sa ) ;
2008-09-23 07:05:19 +04:00
if ( tmpsk = = NULL ) {
sport = port_cur ;
goto found ;
} else
sock_put ( tmpsk ) ;
}
} else {
/* try to find specific port */
pn_sockaddr_set_port ( & try_sa , sport ) ;
2008-12-04 02:42:56 +03:00
tmpsk = pn_find_sock_by_sa ( net , & try_sa ) ;
2008-09-23 07:05:19 +04:00
if ( tmpsk = = NULL )
/* No sock there! We can use that port... */
goto found ;
else
sock_put ( tmpsk ) ;
}
/* the port must be in use already */
return - EADDRINUSE ;
found :
pn - > sobject = pn_object ( pn_addr ( pn - > sobject ) , sport ) ;
return 0 ;
}
EXPORT_SYMBOL ( pn_sock_get_port ) ;
2009-07-21 05:57:57 +04:00
2009-08-17 14:35:49 +04:00
# ifdef CONFIG_PROC_FS
2009-07-21 05:57:57 +04:00
static struct sock * pn_sock_get_idx ( struct seq_file * seq , loff_t pos )
{
struct net * net = seq_file_net ( seq ) ;
2009-11-09 05:17:01 +03:00
struct hlist_head * hlist = pnsocks . hlist ;
2009-07-21 05:57:57 +04:00
struct hlist_node * node ;
struct sock * sknode ;
2009-11-09 05:17:01 +03:00
unsigned h ;
2009-07-21 05:57:57 +04:00
2009-11-09 05:17:01 +03:00
for ( h = 0 ; h < PN_HASHSIZE ; h + + ) {
sk_for_each ( sknode , node , hlist ) {
if ( ! net_eq ( net , sock_net ( sknode ) ) )
continue ;
if ( ! pos )
return sknode ;
pos - - ;
}
hlist + + ;
2009-07-21 05:57:57 +04:00
}
return NULL ;
}
static struct sock * pn_sock_get_next ( struct seq_file * seq , struct sock * sk )
{
struct net * net = seq_file_net ( seq ) ;
do
sk = sk_next ( sk ) ;
while ( sk & & ! net_eq ( net , sock_net ( sk ) ) ) ;
return sk ;
}
static void * pn_sock_seq_start ( struct seq_file * seq , loff_t * pos )
__acquires ( pnsocks . lock )
{
spin_lock_bh ( & pnsocks . lock ) ;
return * pos ? pn_sock_get_idx ( seq , * pos - 1 ) : SEQ_START_TOKEN ;
}
static void * pn_sock_seq_next ( struct seq_file * seq , void * v , loff_t * pos )
{
struct sock * sk ;
if ( v = = SEQ_START_TOKEN )
sk = pn_sock_get_idx ( seq , 0 ) ;
else
sk = pn_sock_get_next ( seq , v ) ;
( * pos ) + + ;
return sk ;
}
static void pn_sock_seq_stop ( struct seq_file * seq , void * v )
__releases ( pnsocks . lock )
{
spin_unlock_bh ( & pnsocks . lock ) ;
}
static int pn_sock_seq_show ( struct seq_file * seq , void * v )
{
int len ;
if ( v = = SEQ_START_TOKEN )
seq_printf ( seq , " %s%n " , " pt loc rem rs st tx_queue rx_queue "
" uid inode ref pointer drops " , & len ) ;
else {
struct sock * sk = v ;
struct pn_sock * pn = pn_sk ( sk ) ;
seq_printf ( seq , " %2d %04X:%04X:%02X %02X %08X:%08X %5d %lu "
" %d %p %d%n " ,
sk - > sk_protocol , pn - > sobject , 0 , pn - > resource ,
sk - > sk_state ,
sk_wmem_alloc_get ( sk ) , sk_rmem_alloc_get ( sk ) ,
sock_i_uid ( sk ) , sock_i_ino ( sk ) ,
atomic_read ( & sk - > sk_refcnt ) , sk ,
atomic_read ( & sk - > sk_drops ) , & len ) ;
}
seq_printf ( seq , " %*s \n " , 127 - len , " " ) ;
return 0 ;
}
static const struct seq_operations pn_sock_seq_ops = {
. start = pn_sock_seq_start ,
. next = pn_sock_seq_next ,
. stop = pn_sock_seq_stop ,
. show = pn_sock_seq_show ,
} ;
static int pn_sock_open ( struct inode * inode , struct file * file )
{
2009-08-11 07:12:07 +04:00
return seq_open_net ( inode , file , & pn_sock_seq_ops ,
sizeof ( struct seq_net_private ) ) ;
2009-07-21 05:57:57 +04:00
}
const struct file_operations pn_sock_seq_fops = {
. owner = THIS_MODULE ,
. open = pn_sock_open ,
. read = seq_read ,
. llseek = seq_lseek ,
2009-08-11 07:12:07 +04:00
. release = seq_release_net ,
2009-07-21 05:57:57 +04:00
} ;
2009-08-17 14:35:49 +04:00
# endif