2017-06-14 21:37:14 +03:00
/*
* Pluggable TCP upper layer protocol support .
*
* Copyright ( c ) 2016 - 2017 , Mellanox Technologies . All rights reserved .
* Copyright ( c ) 2016 - 2017 , Dave Watson < davejwatson @ fb . com > . All rights reserved .
*
*/
# include <linux/module.h>
# include <linux/mm.h>
# include <linux/types.h>
# include <linux/list.h>
# include <linux/gfp.h>
# include <net/tcp.h>
static DEFINE_SPINLOCK ( tcp_ulp_list_lock ) ;
static LIST_HEAD ( tcp_ulp_list ) ;
/* Simple linear search, don't expect many entries! */
static struct tcp_ulp_ops * tcp_ulp_find ( const char * name )
{
struct tcp_ulp_ops * e ;
list_for_each_entry_rcu ( e , & tcp_ulp_list , list ) {
if ( strcmp ( e - > name , name ) = = 0 )
return e ;
}
return NULL ;
}
static const struct tcp_ulp_ops * __tcp_ulp_find_autoload ( const char * name )
{
const struct tcp_ulp_ops * ulp = NULL ;
rcu_read_lock ( ) ;
ulp = tcp_ulp_find ( name ) ;
# ifdef CONFIG_MODULES
if ( ! ulp & & capable ( CAP_NET_ADMIN ) ) {
rcu_read_unlock ( ) ;
request_module ( " %s " , name ) ;
rcu_read_lock ( ) ;
ulp = tcp_ulp_find ( name ) ;
}
# endif
if ( ! ulp | | ! try_module_get ( ulp - > owner ) )
ulp = NULL ;
rcu_read_unlock ( ) ;
return ulp ;
}
/* Attach new upper layer protocol to the list
* of available protocols .
*/
int tcp_register_ulp ( struct tcp_ulp_ops * ulp )
{
int ret = 0 ;
spin_lock ( & tcp_ulp_list_lock ) ;
if ( tcp_ulp_find ( ulp - > name ) ) {
pr_notice ( " %s already registered or non-unique name \n " ,
ulp - > name ) ;
ret = - EEXIST ;
} else {
list_add_tail_rcu ( & ulp - > list , & tcp_ulp_list ) ;
}
spin_unlock ( & tcp_ulp_list_lock ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( tcp_register_ulp ) ;
void tcp_unregister_ulp ( struct tcp_ulp_ops * ulp )
{
spin_lock ( & tcp_ulp_list_lock ) ;
list_del_rcu ( & ulp - > list ) ;
spin_unlock ( & tcp_ulp_list_lock ) ;
synchronize_rcu ( ) ;
}
EXPORT_SYMBOL_GPL ( tcp_unregister_ulp ) ;
/* Build string with list of available upper layer protocl values */
void tcp_get_available_ulp ( char * buf , size_t maxlen )
{
struct tcp_ulp_ops * ulp_ops ;
size_t offs = 0 ;
2017-06-23 04:57:55 +03:00
* buf = ' \0 ' ;
2017-06-14 21:37:14 +03:00
rcu_read_lock ( ) ;
list_for_each_entry_rcu ( ulp_ops , & tcp_ulp_list , list ) {
offs + = snprintf ( buf + offs , maxlen - offs ,
" %s%s " ,
offs = = 0 ? " " : " " , ulp_ops - > name ) ;
}
rcu_read_unlock ( ) ;
}
void tcp_cleanup_ulp ( struct sock * sk )
{
struct inet_connection_sock * icsk = inet_csk ( sk ) ;
if ( ! icsk - > icsk_ulp_ops )
return ;
if ( icsk - > icsk_ulp_ops - > release )
icsk - > icsk_ulp_ops - > release ( sk ) ;
module_put ( icsk - > icsk_ulp_ops - > owner ) ;
}
/* Change upper layer protocol for socket */
int tcp_set_ulp ( struct sock * sk , const char * name )
{
struct inet_connection_sock * icsk = inet_csk ( sk ) ;
const struct tcp_ulp_ops * ulp_ops ;
int err = 0 ;
if ( icsk - > icsk_ulp_ops )
return - EEXIST ;
ulp_ops = __tcp_ulp_find_autoload ( name ) ;
if ( ! ulp_ops )
err = - ENOENT ;
else
err = ulp_ops - > init ( sk ) ;
if ( err )
goto out ;
icsk - > icsk_ulp_ops = ulp_ops ;
out :
return err ;
}