2005-04-17 02:20:36 +04:00
/*
* af_llc . c - LLC User Interface SAPs
* Description :
* Functions in this module are implementation of socket based llc
* communications for the Linux operating system . Support of llc class
* one and class two is provided via SOCK_DGRAM and SOCK_STREAM
* respectively .
*
* An llc2 connection is ( mac + sap ) , only one llc2 sap connection
* is allowed per mac . Though one sap may have multiple mac + sap
* connections .
*
* Copyright ( c ) 2001 by Jay Schulist < jschlst @ samba . org >
* 2002 - 2003 by Arnaldo Carvalho de Melo < acme @ conectiva . com . br >
*
* This program can be redistributed or modified under the terms of the
* GNU General Public License as published by the Free Software Foundation .
* This program is distributed without any warranty or implied warranty
* of merchantability or fitness for a particular purpose .
*
* See the GNU General Public License for more details .
*/
2005-09-22 11:43:05 +04:00
# include <linux/compiler.h>
2005-04-17 02:20:36 +04:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/rtnetlink.h>
# include <linux/init.h>
# include <net/llc.h>
# include <net/llc_sap.h>
# include <net/llc_pdu.h>
# include <net/llc_conn.h>
2005-08-10 07:08:28 +04:00
# include <net/tcp_states.h>
2005-04-17 02:20:36 +04:00
/* remember: uninitialized global data is zeroed because its in .bss */
static u16 llc_ui_sap_last_autoport = LLC_SAP_DYN_START ;
static u16 llc_ui_sap_link_no_max [ 256 ] ;
static struct sockaddr_llc llc_ui_addrnull ;
2005-12-22 23:49:22 +03:00
static const struct proto_ops llc_ui_ops ;
2005-04-17 02:20:36 +04:00
2005-09-22 11:26:14 +04:00
static int llc_ui_wait_for_conn ( struct sock * sk , long timeout ) ;
static int llc_ui_wait_for_disc ( struct sock * sk , long timeout ) ;
static int llc_ui_wait_for_busy_core ( struct sock * sk , long timeout ) ;
2005-04-17 02:20:36 +04:00
#if 0
# define dprintk(args...) printk(KERN_DEBUG args)
# else
# define dprintk(args...)
# endif
/**
* llc_ui_next_link_no - return the next unused link number for a sap
* @ sap : Address of sap to get link number from .
*
* Return the next unused link number for a given sap .
*/
2006-03-21 08:27:43 +03:00
static inline u16 llc_ui_next_link_no ( int sap )
2005-04-17 02:20:36 +04:00
{
return llc_ui_sap_link_no_max [ sap ] + + ;
}
/**
* llc_proto_type - return eth protocol for ARP header type
* @ arphrd : ARP header type .
*
* Given an ARP header type return the corresponding ethernet protocol .
*/
2006-11-08 11:26:05 +03:00
static inline __be16 llc_proto_type ( u16 arphrd )
2005-04-17 02:20:36 +04:00
{
return arphrd = = ARPHRD_IEEE802_TR ?
2007-02-09 17:25:01 +03:00
htons ( ETH_P_TR_802_2 ) : htons ( ETH_P_802_2 ) ;
2005-04-17 02:20:36 +04:00
}
/**
* llc_ui_addr_null - determines if a address structure is null
* @ addr : Address to test if null .
*/
2006-03-21 08:27:43 +03:00
static inline u8 llc_ui_addr_null ( struct sockaddr_llc * addr )
2005-04-17 02:20:36 +04:00
{
return ! memcmp ( addr , & llc_ui_addrnull , sizeof ( * addr ) ) ;
}
/**
* llc_ui_header_len - return length of llc header based on operation
* @ sk : Socket which contains a valid llc socket type .
* @ addr : Complete sockaddr_llc structure received from the user .
*
* Provide the length of the llc header depending on what kind of
* operation the user would like to perform and the type of socket .
* Returns the correct llc header length .
*/
2006-03-21 08:27:43 +03:00
static inline u8 llc_ui_header_len ( struct sock * sk , struct sockaddr_llc * addr )
2005-04-17 02:20:36 +04:00
{
u8 rc = LLC_PDU_LEN_U ;
if ( addr - > sllc_test | | addr - > sllc_xid )
rc = LLC_PDU_LEN_U ;
else if ( sk - > sk_type = = SOCK_STREAM )
rc = LLC_PDU_LEN_I ;
return rc ;
}
/**
* llc_ui_send_data - send data via reliable llc2 connection
* @ sk : Connection the socket is using .
* @ skb : Data the user wishes to send .
* @ addr : Source and destination fields provided by the user .
* @ noblock : can we block waiting for data ?
*
* Send data via reliable llc2 connection .
* Returns 0 upon success , non - zero if action did not succeed .
*/
static int llc_ui_send_data ( struct sock * sk , struct sk_buff * skb , int noblock )
{
struct llc_sock * llc = llc_sk ( sk ) ;
int rc = 0 ;
2007-02-09 17:25:01 +03:00
if ( unlikely ( llc_data_accept_state ( llc - > state ) | |
2005-11-15 08:57:46 +03:00
llc - > remote_busy_flag | |
llc - > p_flag ) ) {
2005-09-22 11:26:14 +04:00
long timeout = sock_sndtimeo ( sk , noblock ) ;
2005-04-17 02:20:36 +04:00
rc = llc_ui_wait_for_busy_core ( sk , timeout ) ;
}
2005-09-22 11:32:10 +04:00
if ( unlikely ( ! rc ) )
2005-04-17 02:20:36 +04:00
rc = llc_build_and_send_pkt ( sk , skb ) ;
return rc ;
}
static void llc_ui_sk_init ( struct socket * sock , struct sock * sk )
{
sk - > sk_type = sock - > type ;
sk - > sk_sleep = & sock - > wait ;
sk - > sk_socket = sock ;
sock - > sk = sk ;
sock - > ops = & llc_ui_ops ;
}
static struct proto llc_proto = {
2006-03-21 08:27:23 +03:00
. name = " LLC " ,
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. obj_size = sizeof ( struct llc_sock ) ,
} ;
/**
* llc_ui_create - alloc and init a new llc_ui socket
* @ sock : Socket to initialize and attach allocated sk to .
* @ protocol : Unused .
*
* Allocate and initialize a new llc_ui socket , validate the user wants a
* socket type we have available .
* Returns 0 upon success , negative upon failure .
*/
static int llc_ui_create ( struct socket * sock , int protocol )
{
struct sock * sk ;
int rc = - ESOCKTNOSUPPORT ;
2005-09-22 10:59:22 +04:00
if ( likely ( sock - > type = = SOCK_DGRAM | | sock - > type = = SOCK_STREAM ) ) {
2005-04-17 02:20:36 +04:00
rc = - ENOMEM ;
sk = llc_sk_alloc ( PF_LLC , GFP_KERNEL , & llc_proto ) ;
if ( sk ) {
rc = 0 ;
llc_ui_sk_init ( sock , sk ) ;
}
}
return rc ;
}
/**
* llc_ui_release - shutdown socket
* @ sock : Socket to release .
*
* Shutdown and deallocate an existing socket .
*/
static int llc_ui_release ( struct socket * sock )
{
struct sock * sk = sock - > sk ;
struct llc_sock * llc ;
2005-09-22 10:59:22 +04:00
if ( unlikely ( sk = = NULL ) )
2005-04-17 02:20:36 +04:00
goto out ;
sock_hold ( sk ) ;
lock_sock ( sk ) ;
llc = llc_sk ( sk ) ;
dprintk ( " %s: closing local(%02X) remote(%02X) \n " , __FUNCTION__ ,
llc - > laddr . lsap , llc - > daddr . lsap ) ;
if ( ! llc_send_disc ( sk ) )
llc_ui_wait_for_disc ( sk , sk - > sk_rcvtimeo ) ;
2006-03-21 08:28:11 +03:00
if ( ! sock_flag ( sk , SOCK_ZAPPED ) ) {
llc_sap_put ( llc - > sap ) ;
2005-04-17 02:20:36 +04:00
llc_sap_remove_socket ( llc - > sap , sk ) ;
2006-03-21 08:28:11 +03:00
}
2005-04-17 02:20:36 +04:00
release_sock ( sk ) ;
if ( llc - > dev )
dev_put ( llc - > dev ) ;
sock_put ( sk ) ;
llc_sk_free ( sk ) ;
out :
return 0 ;
}
/**
* llc_ui_autoport - provide dynamically allocate SAP number
*
* Provide the caller with a dynamically allocated SAP number according
* to the rules that are set in this function . Returns : 0 , upon failure ,
* SAP number otherwise .
*/
static int llc_ui_autoport ( void )
{
struct llc_sap * sap ;
int i , tries = 0 ;
while ( tries < LLC_SAP_DYN_TRIES ) {
for ( i = llc_ui_sap_last_autoport ;
i < LLC_SAP_DYN_STOP ; i + = 2 ) {
sap = llc_sap_find ( i ) ;
if ( ! sap ) {
llc_ui_sap_last_autoport = i + 2 ;
goto out ;
}
2005-09-22 11:43:05 +04:00
llc_sap_put ( sap ) ;
2005-04-17 02:20:36 +04:00
}
llc_ui_sap_last_autoport = LLC_SAP_DYN_START ;
tries + + ;
}
i = 0 ;
out :
return i ;
}
/**
2005-09-22 10:56:26 +04:00
* llc_ui_autobind - automatically bind a socket to a sap
* @ sock : socket to bind
* @ addr : address to connect to
*
* Used by llc_ui_connect and llc_ui_sendmsg when the user hasn ' t
* specifically used llc_ui_bind to bind to an specific address / sap
2005-04-17 02:20:36 +04:00
*
* Returns : 0 upon success , negative otherwise .
*/
static int llc_ui_autobind ( struct socket * sock , struct sockaddr_llc * addr )
{
struct sock * sk = sock - > sk ;
struct llc_sock * llc = llc_sk ( sk ) ;
struct llc_sap * sap ;
int rc = - EINVAL ;
if ( ! sock_flag ( sk , SOCK_ZAPPED ) )
goto out ;
rc = - ENODEV ;
llc - > dev = dev_getfirstbyhwtype ( addr - > sllc_arphrd ) ;
if ( ! llc - > dev )
goto out ;
rc = - EUSERS ;
llc - > laddr . lsap = llc_ui_autoport ( ) ;
if ( ! llc - > laddr . lsap )
goto out ;
rc = - EBUSY ; /* some other network layer is using the sap */
sap = llc_sap_open ( llc - > laddr . lsap , NULL ) ;
if ( ! sap )
goto out ;
memcpy ( llc - > laddr . mac , llc - > dev - > dev_addr , IFHWADDRLEN ) ;
memcpy ( & llc - > addr , addr , sizeof ( llc - > addr ) ) ;
/* assign new connection to its SAP */
llc_sap_add_socket ( sap , sk ) ;
sock_reset_flag ( sk , SOCK_ZAPPED ) ;
rc = 0 ;
out :
return rc ;
}
/**
* llc_ui_bind - bind a socket to a specific address .
* @ sock : Socket to bind an address to .
* @ uaddr : Address the user wants the socket bound to .
* @ addrlen : Length of the uaddr structure .
*
* Bind a socket to a specific address . For llc a user is able to bind to
2005-09-22 10:56:26 +04:00
* a specific sap only or mac + sap .
2005-04-17 02:20:36 +04:00
* If the user desires to bind to a specific mac + sap , it is possible to
* have multiple sap connections via multiple macs .
* Bind and autobind for that matter must enforce the correct sap usage
* otherwise all hell will break loose .
* Returns : 0 upon success , negative otherwise .
*/
static int llc_ui_bind ( struct socket * sock , struct sockaddr * uaddr , int addrlen )
{
struct sockaddr_llc * addr = ( struct sockaddr_llc * ) uaddr ;
struct sock * sk = sock - > sk ;
struct llc_sock * llc = llc_sk ( sk ) ;
struct llc_sap * sap ;
int rc = - EINVAL ;
dprintk ( " %s: binding %02X \n " , __FUNCTION__ , addr - > sllc_sap ) ;
2005-09-22 10:59:22 +04:00
if ( unlikely ( ! sock_flag ( sk , SOCK_ZAPPED ) | | addrlen ! = sizeof ( * addr ) ) )
2005-04-17 02:20:36 +04:00
goto out ;
rc = - EAFNOSUPPORT ;
2005-09-22 10:59:22 +04:00
if ( unlikely ( addr - > sllc_family ! = AF_LLC ) )
2005-04-17 02:20:36 +04:00
goto out ;
2005-09-22 11:44:55 +04:00
rc = - ENODEV ;
rtnl_lock ( ) ;
llc - > dev = dev_getbyhwaddr ( addr - > sllc_arphrd , addr - > sllc_mac ) ;
rtnl_unlock ( ) ;
if ( ! llc - > dev )
goto out ;
2005-04-17 02:20:36 +04:00
if ( ! addr - > sllc_sap ) {
rc = - EUSERS ;
addr - > sllc_sap = llc_ui_autoport ( ) ;
if ( ! addr - > sllc_sap )
goto out ;
}
sap = llc_sap_find ( addr - > sllc_sap ) ;
if ( ! sap ) {
sap = llc_sap_open ( addr - > sllc_sap , NULL ) ;
rc = - EBUSY ; /* some other network layer is using the sap */
if ( ! sap )
goto out ;
2005-09-22 11:43:05 +04:00
llc_sap_hold ( sap ) ;
2005-04-17 02:20:36 +04:00
} else {
struct llc_addr laddr , daddr ;
struct sock * ask ;
memset ( & laddr , 0 , sizeof ( laddr ) ) ;
memset ( & daddr , 0 , sizeof ( daddr ) ) ;
/*
2007-05-09 10:57:56 +04:00
* FIXME : check if the address is multicast ,
2005-04-17 02:20:36 +04:00
* only SOCK_DGRAM can do this .
*/
memcpy ( laddr . mac , addr - > sllc_mac , IFHWADDRLEN ) ;
laddr . lsap = addr - > sllc_sap ;
rc = - EADDRINUSE ; /* mac + sap clash. */
ask = llc_lookup_established ( sap , & daddr , & laddr ) ;
if ( ask ) {
sock_put ( ask ) ;
2005-09-22 11:43:05 +04:00
goto out_put ;
2005-04-17 02:20:36 +04:00
}
}
llc - > laddr . lsap = addr - > sllc_sap ;
memcpy ( llc - > laddr . mac , addr - > sllc_mac , IFHWADDRLEN ) ;
memcpy ( & llc - > addr , addr , sizeof ( llc - > addr ) ) ;
/* assign new connection to its SAP */
llc_sap_add_socket ( sap , sk ) ;
sock_reset_flag ( sk , SOCK_ZAPPED ) ;
rc = 0 ;
2005-09-22 11:43:05 +04:00
out_put :
llc_sap_put ( sap ) ;
2005-04-17 02:20:36 +04:00
out :
return rc ;
}
/**
* llc_ui_shutdown - shutdown a connect llc2 socket .
* @ sock : Socket to shutdown .
* @ how : What part of the socket to shutdown .
*
* Shutdown a connected llc2 socket . Currently this function only supports
* shutting down both sends and receives ( 2 ) , we could probably make this
* function such that a user can shutdown only half the connection but not
* right now .
* Returns : 0 upon success , negative otherwise .
*/
static int llc_ui_shutdown ( struct socket * sock , int how )
{
struct sock * sk = sock - > sk ;
int rc = - ENOTCONN ;
lock_sock ( sk ) ;
2005-09-22 10:59:22 +04:00
if ( unlikely ( sk - > sk_state ! = TCP_ESTABLISHED ) )
2005-04-17 02:20:36 +04:00
goto out ;
rc = - EINVAL ;
if ( how ! = 2 )
goto out ;
rc = llc_send_disc ( sk ) ;
if ( ! rc )
rc = llc_ui_wait_for_disc ( sk , sk - > sk_rcvtimeo ) ;
/* Wake up anyone sleeping in poll */
sk - > sk_state_change ( sk ) ;
out :
release_sock ( sk ) ;
return rc ;
}
/**
* llc_ui_connect - Connect to a remote llc2 mac + sap .
* @ sock : Socket which will be connected to the remote destination .
* @ uaddr : Remote and possibly the local address of the new connection .
* @ addrlen : Size of uaddr structure .
* @ flags : Operational flags specified by the user .
*
* Connect to a remote llc2 mac + sap . The caller must specify the
* destination mac and address to connect to . If the user hasn ' t previously
* called bind ( 2 ) with a smac the address of the first interface of the
* specified arp type will be used .
* This function will autobind if user did not previously call bind .
* Returns : 0 upon success , negative otherwise .
*/
static int llc_ui_connect ( struct socket * sock , struct sockaddr * uaddr ,
int addrlen , int flags )
{
struct sock * sk = sock - > sk ;
struct llc_sock * llc = llc_sk ( sk ) ;
struct sockaddr_llc * addr = ( struct sockaddr_llc * ) uaddr ;
int rc = - EINVAL ;
lock_sock ( sk ) ;
2005-09-22 10:59:22 +04:00
if ( unlikely ( addrlen ! = sizeof ( * addr ) ) )
2005-04-17 02:20:36 +04:00
goto out ;
rc = - EAFNOSUPPORT ;
2005-09-22 10:59:22 +04:00
if ( unlikely ( addr - > sllc_family ! = AF_LLC ) )
goto out ;
if ( unlikely ( sk - > sk_type ! = SOCK_STREAM ) )
goto out ;
rc = - EALREADY ;
if ( unlikely ( sock - > state = = SS_CONNECTING ) )
2005-04-17 02:20:36 +04:00
goto out ;
/* bind connection to sap if user hasn't done it. */
if ( sock_flag ( sk , SOCK_ZAPPED ) ) {
/* bind to sap with null dev, exclusive */
rc = llc_ui_autobind ( sock , addr ) ;
if ( rc )
goto out ;
}
2005-09-22 11:46:44 +04:00
llc - > daddr . lsap = addr - > sllc_sap ;
memcpy ( llc - > daddr . mac , addr - > sllc_mac , IFHWADDRLEN ) ;
2005-04-17 02:20:36 +04:00
sock - > state = SS_CONNECTING ;
sk - > sk_state = TCP_SYN_SENT ;
llc - > link = llc_ui_next_link_no ( llc - > sap - > laddr . lsap ) ;
2005-09-22 10:53:35 +04:00
rc = llc_establish_connection ( sk , llc - > dev - > dev_addr ,
2005-04-17 02:20:36 +04:00
addr - > sllc_mac , addr - > sllc_sap ) ;
if ( rc ) {
dprintk ( " %s: llc_ui_send_conn failed :-( \n " , __FUNCTION__ ) ;
sock - > state = SS_UNCONNECTED ;
sk - > sk_state = TCP_CLOSE ;
goto out ;
}
2005-09-22 11:22:39 +04:00
if ( sk - > sk_state = = TCP_SYN_SENT ) {
2005-09-22 11:26:14 +04:00
const long timeo = sock_sndtimeo ( sk , flags & O_NONBLOCK ) ;
2005-09-22 11:22:39 +04:00
if ( ! timeo | | ! llc_ui_wait_for_conn ( sk , timeo ) )
goto out ;
rc = sock_intr_errno ( timeo ) ;
if ( signal_pending ( current ) )
goto out ;
}
if ( sk - > sk_state = = TCP_CLOSE )
goto sock_error ;
sock - > state = SS_CONNECTED ;
rc = 0 ;
2005-04-17 02:20:36 +04:00
out :
release_sock ( sk ) ;
return rc ;
2005-09-22 11:22:39 +04:00
sock_error :
rc = sock_error ( sk ) ? : - ECONNABORTED ;
sock - > state = SS_UNCONNECTED ;
goto out ;
2005-04-17 02:20:36 +04:00
}
/**
* llc_ui_listen - allow a normal socket to accept incoming connections
* @ sock : Socket to allow incoming connections on .
* @ backlog : Number of connections to queue .
*
* Allow a normal socket to accept incoming connections .
* Returns 0 upon success , negative otherwise .
*/
static int llc_ui_listen ( struct socket * sock , int backlog )
{
struct sock * sk = sock - > sk ;
int rc = - EINVAL ;
lock_sock ( sk ) ;
2005-09-22 10:59:22 +04:00
if ( unlikely ( sock - > state ! = SS_UNCONNECTED ) )
2005-04-17 02:20:36 +04:00
goto out ;
rc = - EOPNOTSUPP ;
2005-09-22 10:59:22 +04:00
if ( unlikely ( sk - > sk_type ! = SOCK_STREAM ) )
2005-04-17 02:20:36 +04:00
goto out ;
rc = - EAGAIN ;
if ( sock_flag ( sk , SOCK_ZAPPED ) )
goto out ;
rc = 0 ;
if ( ! ( unsigned ) backlog ) /* BSDism */
backlog = 1 ;
sk - > sk_max_ack_backlog = backlog ;
if ( sk - > sk_state ! = TCP_LISTEN ) {
sk - > sk_ack_backlog = 0 ;
sk - > sk_state = TCP_LISTEN ;
}
sk - > sk_socket - > flags | = __SO_ACCEPTCON ;
out :
release_sock ( sk ) ;
return rc ;
}
2005-09-22 11:26:14 +04:00
static int llc_ui_wait_for_disc ( struct sock * sk , long timeout )
2005-04-17 02:20:36 +04:00
{
2005-09-22 11:22:39 +04:00
DEFINE_WAIT ( wait ) ;
int rc = 0 ;
2005-04-17 02:20:36 +04:00
2005-09-22 11:26:14 +04:00
while ( 1 ) {
prepare_to_wait ( sk - > sk_sleep , & wait , TASK_INTERRUPTIBLE ) ;
if ( sk_wait_event ( sk , & timeout , sk - > sk_state = = TCP_CLOSE ) )
break ;
2005-04-17 02:20:36 +04:00
rc = - ERESTARTSYS ;
if ( signal_pending ( current ) )
break ;
rc = - EAGAIN ;
if ( ! timeout )
break ;
2005-09-22 11:22:39 +04:00
rc = 0 ;
2005-04-17 02:20:36 +04:00
}
2005-09-22 11:22:39 +04:00
finish_wait ( sk - > sk_sleep , & wait ) ;
2005-04-17 02:20:36 +04:00
return rc ;
}
2005-09-22 11:26:14 +04:00
static int llc_ui_wait_for_conn ( struct sock * sk , long timeout )
2005-04-17 02:20:36 +04:00
{
2005-09-22 11:22:39 +04:00
DEFINE_WAIT ( wait ) ;
2005-04-17 02:20:36 +04:00
2005-09-22 11:26:14 +04:00
while ( 1 ) {
prepare_to_wait ( sk - > sk_sleep , & wait , TASK_INTERRUPTIBLE ) ;
if ( sk_wait_event ( sk , & timeout , sk - > sk_state ! = TCP_SYN_SENT ) )
break ;
2005-09-22 11:22:39 +04:00
if ( signal_pending ( current ) | | ! timeout )
2005-04-17 02:20:36 +04:00
break ;
}
2005-09-22 11:22:39 +04:00
finish_wait ( sk - > sk_sleep , & wait ) ;
return timeout ;
2005-04-17 02:20:36 +04:00
}
2005-09-22 11:37:07 +04:00
static int llc_ui_wait_for_busy_core ( struct sock * sk , long timeout )
2005-04-17 02:20:36 +04:00
{
2005-09-22 11:22:39 +04:00
DEFINE_WAIT ( wait ) ;
2005-09-22 11:37:07 +04:00
struct llc_sock * llc = llc_sk ( sk ) ;
int rc ;
2005-04-17 02:20:36 +04:00
2005-09-22 11:26:14 +04:00
while ( 1 ) {
2005-09-22 11:22:39 +04:00
prepare_to_wait ( sk - > sk_sleep , & wait , TASK_INTERRUPTIBLE ) ;
2005-09-22 11:37:07 +04:00
rc = 0 ;
2005-09-22 11:26:14 +04:00
if ( sk_wait_event ( sk , & timeout ,
( sk - > sk_shutdown & RCV_SHUTDOWN ) | |
2005-09-22 11:37:07 +04:00
( ! llc_data_accept_state ( llc - > state ) & &
2005-11-15 08:57:46 +03:00
! llc - > remote_busy_flag & &
2005-09-22 11:37:07 +04:00
! llc - > p_flag ) ) )
2005-04-17 02:20:36 +04:00
break ;
rc = - ERESTARTSYS ;
if ( signal_pending ( current ) )
break ;
rc = - EAGAIN ;
if ( ! timeout )
break ;
}
2005-09-22 11:22:39 +04:00
finish_wait ( sk - > sk_sleep , & wait ) ;
2005-04-17 02:20:36 +04:00
return rc ;
}
2005-09-22 12:14:33 +04:00
static int llc_wait_data ( struct sock * sk , long timeo )
2005-04-17 02:20:36 +04:00
{
int rc ;
2005-09-22 11:26:14 +04:00
while ( 1 ) {
2005-09-22 11:37:07 +04:00
/*
* POSIX 1003.1 g mandates this order .
*/
2005-12-14 10:22:19 +03:00
rc = sock_error ( sk ) ;
if ( rc )
2005-09-22 11:37:07 +04:00
break ;
2005-04-17 02:20:36 +04:00
rc = 0 ;
2005-09-22 11:37:07 +04:00
if ( sk - > sk_shutdown & RCV_SHUTDOWN )
2005-04-17 02:20:36 +04:00
break ;
2005-09-22 11:37:07 +04:00
rc = - EAGAIN ;
if ( ! timeo )
break ;
rc = sock_intr_errno ( timeo ) ;
2005-04-17 02:20:36 +04:00
if ( signal_pending ( current ) )
break ;
2005-09-22 11:37:07 +04:00
rc = 0 ;
if ( sk_wait_data ( sk , & timeo ) )
2005-04-17 02:20:36 +04:00
break ;
}
return rc ;
}
/**
* llc_ui_accept - accept a new incoming connection .
* @ sock : Socket which connections arrive on .
* @ newsock : Socket to move incoming connection to .
* @ flags : User specified operational flags .
*
* Accept a new incoming connection .
* Returns 0 upon success , negative otherwise .
*/
static int llc_ui_accept ( struct socket * sock , struct socket * newsock , int flags )
{
struct sock * sk = sock - > sk , * newsk ;
struct llc_sock * llc , * newllc ;
struct sk_buff * skb ;
int rc = - EOPNOTSUPP ;
dprintk ( " %s: accepting on %02X \n " , __FUNCTION__ ,
2007-02-09 17:25:01 +03:00
llc_sk ( sk ) - > laddr . lsap ) ;
2005-04-17 02:20:36 +04:00
lock_sock ( sk ) ;
2005-09-22 10:59:22 +04:00
if ( unlikely ( sk - > sk_type ! = SOCK_STREAM ) )
2005-04-17 02:20:36 +04:00
goto out ;
rc = - EINVAL ;
2005-09-22 10:59:22 +04:00
if ( unlikely ( sock - > state ! = SS_UNCONNECTED | |
sk - > sk_state ! = TCP_LISTEN ) )
2005-04-17 02:20:36 +04:00
goto out ;
/* wait for a connection to arrive. */
2005-09-22 11:22:39 +04:00
if ( skb_queue_empty ( & sk - > sk_receive_queue ) ) {
2005-09-22 11:37:07 +04:00
rc = llc_wait_data ( sk , sk - > sk_rcvtimeo ) ;
2005-09-22 11:22:39 +04:00
if ( rc )
goto out ;
}
2005-04-17 02:20:36 +04:00
dprintk ( " %s: got a new connection on %02X \n " , __FUNCTION__ ,
2007-02-09 17:25:01 +03:00
llc_sk ( sk ) - > laddr . lsap ) ;
2005-04-17 02:20:36 +04:00
skb = skb_dequeue ( & sk - > sk_receive_queue ) ;
rc = - EINVAL ;
if ( ! skb - > sk )
goto frees ;
rc = 0 ;
newsk = skb - > sk ;
/* attach connection to a new socket. */
llc_ui_sk_init ( newsock , newsk ) ;
sock_reset_flag ( newsk , SOCK_ZAPPED ) ;
newsk - > sk_state = TCP_ESTABLISHED ;
newsock - > state = SS_CONNECTED ;
llc = llc_sk ( sk ) ;
newllc = llc_sk ( newsk ) ;
memcpy ( & newllc - > addr , & llc - > addr , sizeof ( newllc - > addr ) ) ;
newllc - > link = llc_ui_next_link_no ( newllc - > laddr . lsap ) ;
/* put original socket back into a clean listen state. */
sk - > sk_state = TCP_LISTEN ;
sk - > sk_ack_backlog - - ;
dprintk ( " %s: ok success on %02X, client on %02X \n " , __FUNCTION__ ,
llc_sk ( sk ) - > addr . sllc_sap , newllc - > daddr . lsap ) ;
frees :
kfree_skb ( skb ) ;
out :
release_sock ( sk ) ;
return rc ;
}
/**
* llc_ui_recvmsg - copy received data to the socket user .
* @ sock : Socket to copy data from .
* @ msg : Various user space related information .
2005-09-22 15:29:08 +04:00
* @ len : Size of user buffer .
2005-04-17 02:20:36 +04:00
* @ flags : User specified flags .
*
* Copy received data to the socket user .
* Returns non - negative upon success , negative otherwise .
*/
static int llc_ui_recvmsg ( struct kiocb * iocb , struct socket * sock ,
2005-09-22 15:29:08 +04:00
struct msghdr * msg , size_t len , int flags )
2005-04-17 02:20:36 +04:00
{
struct sockaddr_llc * uaddr = ( struct sockaddr_llc * ) msg - > msg_name ;
2005-09-22 15:29:08 +04:00
const int nonblock = flags & MSG_DONTWAIT ;
struct sk_buff * skb = NULL ;
struct sock * sk = sock - > sk ;
struct llc_sock * llc = llc_sk ( sk ) ;
2005-04-17 02:20:36 +04:00
size_t copied = 0 ;
2005-09-22 15:29:08 +04:00
u32 peek_seq = 0 ;
u32 * seq ;
unsigned long used ;
int target ; /* Read at least this many bytes */
long timeo ;
2005-04-17 02:20:36 +04:00
lock_sock ( sk ) ;
2005-09-22 15:29:08 +04:00
copied = - ENOTCONN ;
2006-05-26 02:08:59 +04:00
if ( unlikely ( sk - > sk_type = = SOCK_STREAM & & sk - > sk_state = = TCP_LISTEN ) )
2005-04-17 02:20:36 +04:00
goto out ;
2005-09-22 15:29:08 +04:00
timeo = sock_rcvtimeo ( sk , nonblock ) ;
seq = & llc - > copied_seq ;
if ( flags & MSG_PEEK ) {
peek_seq = llc - > copied_seq ;
seq = & peek_seq ;
2007-02-09 17:25:01 +03:00
}
2005-09-22 15:29:08 +04:00
target = sock_rcvlowat ( sk , flags & MSG_WAITALL , len ) ;
copied = 0 ;
do {
u32 offset ;
/*
* We need to check signals first , to get correct SIGURG
* handling . FIXME : Need to check this doesn ' t impact 1003.1 g
* and move it down to the bottom of the loop
*/
if ( signal_pending ( current ) ) {
if ( copied )
break ;
copied = timeo ? sock_intr_errno ( timeo ) : - EAGAIN ;
break ;
}
/* Next get a buffer. */
skb = skb_peek ( & sk - > sk_receive_queue ) ;
if ( skb ) {
offset = * seq ;
goto found_ok_skb ;
}
/* Well, if we have backlog, try to process it now yet. */
if ( copied > = target & & ! sk - > sk_backlog . tail )
break ;
if ( copied ) {
if ( sk - > sk_err | |
sk - > sk_state = = TCP_CLOSE | |
( sk - > sk_shutdown & RCV_SHUTDOWN ) | |
! timeo | |
( flags & MSG_PEEK ) )
break ;
} else {
if ( sock_flag ( sk , SOCK_DONE ) )
break ;
if ( sk - > sk_err ) {
copied = sock_error ( sk ) ;
break ;
}
if ( sk - > sk_shutdown & RCV_SHUTDOWN )
break ;
2006-05-26 02:08:59 +04:00
if ( sk - > sk_type = = SOCK_STREAM & & sk - > sk_state = = TCP_CLOSE ) {
2005-09-22 15:29:08 +04:00
if ( ! sock_flag ( sk , SOCK_DONE ) ) {
/*
* This occurs when user tries to read
* from never connected socket .
*/
copied = - ENOTCONN ;
break ;
}
break ;
}
if ( ! timeo ) {
copied = - EAGAIN ;
break ;
}
}
if ( copied > = target ) { /* Do not sleep, just process backlog. */
release_sock ( sk ) ;
lock_sock ( sk ) ;
} else
sk_wait_data ( sk , & timeo ) ;
if ( ( flags & MSG_PEEK ) & & peek_seq ! = llc - > copied_seq ) {
if ( net_ratelimit ( ) )
printk ( KERN_DEBUG " LLC(%s:%d): Application "
" bug, race in MSG_PEEK. \n " ,
current - > comm , current - > pid ) ;
peek_seq = llc - > copied_seq ;
}
continue ;
found_ok_skb :
/* Ok so how much can we use? */
used = skb - > len - offset ;
if ( len < used )
used = len ;
if ( ! ( flags & MSG_TRUNC ) ) {
int rc = skb_copy_datagram_iovec ( skb , offset ,
msg - > msg_iov , used ) ;
if ( rc ) {
/* Exception. Bailout! */
if ( ! copied )
copied = - EFAULT ;
break ;
}
}
* seq + = used ;
copied + = used ;
len - = used ;
if ( ! ( flags & MSG_PEEK ) ) {
2006-05-24 05:01:28 +04:00
sk_eat_skb ( sk , skb , 0 ) ;
2005-09-22 15:29:08 +04:00
* seq = 0 ;
}
2006-08-04 03:38:49 +04:00
/* For non stream protcols we get one packet per recvmsg call */
if ( sk - > sk_type ! = SOCK_STREAM )
goto copy_uaddr ;
/* Partial read */
if ( used + offset < skb - > len )
continue ;
2005-09-22 15:29:08 +04:00
} while ( len > 0 ) ;
2005-04-17 02:20:36 +04:00
out :
release_sock ( sk ) ;
2005-09-22 15:29:08 +04:00
return copied ;
copy_uaddr :
if ( uaddr ! = NULL & & skb ! = NULL ) {
memcpy ( uaddr , llc_ui_skb_cb ( skb ) , sizeof ( * uaddr ) ) ;
msg - > msg_namelen = sizeof ( * uaddr ) ;
}
goto out ;
2005-04-17 02:20:36 +04:00
}
/**
* llc_ui_sendmsg - Transmit data provided by the socket user .
* @ sock : Socket to transmit data from .
* @ msg : Various user related information .
* @ len : Length of data to transmit .
*
* Transmit data provided by the socket user .
* Returns non - negative upon success , negative otherwise .
*/
static int llc_ui_sendmsg ( struct kiocb * iocb , struct socket * sock ,
struct msghdr * msg , size_t len )
{
struct sock * sk = sock - > sk ;
struct llc_sock * llc = llc_sk ( sk ) ;
struct sockaddr_llc * addr = ( struct sockaddr_llc * ) msg - > msg_name ;
int flags = msg - > msg_flags ;
int noblock = flags & MSG_DONTWAIT ;
struct sk_buff * skb ;
size_t size = 0 ;
int rc = - EINVAL , copied = 0 , hdrlen ;
dprintk ( " %s: sending from %02X to %02X \n " , __FUNCTION__ ,
llc - > laddr . lsap , llc - > daddr . lsap ) ;
lock_sock ( sk ) ;
if ( addr ) {
if ( msg - > msg_namelen < sizeof ( * addr ) )
goto release ;
} else {
if ( llc_ui_addr_null ( & llc - > addr ) )
goto release ;
addr = & llc - > addr ;
}
/* must bind connection to sap if user hasn't done it. */
if ( sock_flag ( sk , SOCK_ZAPPED ) ) {
/* bind to sap with null dev, exclusive. */
rc = llc_ui_autobind ( sock , addr ) ;
if ( rc )
goto release ;
}
2005-09-22 10:53:35 +04:00
hdrlen = llc - > dev - > hard_header_len + llc_ui_header_len ( sk , addr ) ;
2005-04-17 02:20:36 +04:00
size = hdrlen + len ;
2005-09-22 10:53:35 +04:00
if ( size > llc - > dev - > mtu )
size = llc - > dev - > mtu ;
2005-04-17 02:20:36 +04:00
copied = size - hdrlen ;
release_sock ( sk ) ;
skb = sock_alloc_send_skb ( sk , size , noblock , & rc ) ;
lock_sock ( sk ) ;
if ( ! skb )
goto release ;
2005-09-22 10:53:35 +04:00
skb - > dev = llc - > dev ;
2005-04-17 02:20:36 +04:00
skb - > protocol = llc_proto_type ( addr - > sllc_arphrd ) ;
2007-02-09 17:25:01 +03:00
skb_reserve ( skb , hdrlen ) ;
2005-04-17 02:20:36 +04:00
rc = memcpy_fromiovec ( skb_put ( skb , copied ) , msg - > msg_iov , copied ) ;
if ( rc )
goto out ;
if ( sk - > sk_type = = SOCK_DGRAM | | addr - > sllc_ua ) {
llc_build_and_send_ui_pkt ( llc - > sap , skb , addr - > sllc_mac ,
addr - > sllc_sap ) ;
goto out ;
}
if ( addr - > sllc_test ) {
llc_build_and_send_test_pkt ( llc - > sap , skb , addr - > sllc_mac ,
addr - > sllc_sap ) ;
goto out ;
}
if ( addr - > sllc_xid ) {
llc_build_and_send_xid_pkt ( llc - > sap , skb , addr - > sllc_mac ,
addr - > sllc_sap ) ;
goto out ;
}
rc = - ENOPROTOOPT ;
if ( ! ( sk - > sk_type = = SOCK_STREAM & & ! addr - > sllc_ua ) )
goto out ;
rc = llc_ui_send_data ( sk , skb , noblock ) ;
out :
2005-09-22 11:32:10 +04:00
if ( rc ) {
2005-04-17 02:20:36 +04:00
kfree_skb ( skb ) ;
release :
dprintk ( " %s: failed sending from %02X to %02X: %d \n " ,
__FUNCTION__ , llc - > laddr . lsap , llc - > daddr . lsap , rc ) ;
2005-09-22 11:32:10 +04:00
}
2005-04-17 02:20:36 +04:00
release_sock ( sk ) ;
return rc ? : copied ;
}
/**
* llc_ui_getname - return the address info of a socket
* @ sock : Socket to get address of .
* @ uaddr : Address structure to return information .
* @ uaddrlen : Length of address structure .
* @ peer : Does user want local or remote address information .
*
* Return the address information of a socket .
*/
static int llc_ui_getname ( struct socket * sock , struct sockaddr * uaddr ,
int * uaddrlen , int peer )
{
struct sockaddr_llc sllc ;
struct sock * sk = sock - > sk ;
struct llc_sock * llc = llc_sk ( sk ) ;
int rc = 0 ;
lock_sock ( sk ) ;
if ( sock_flag ( sk , SOCK_ZAPPED ) )
goto out ;
* uaddrlen = sizeof ( sllc ) ;
memset ( uaddr , 0 , * uaddrlen ) ;
if ( peer ) {
rc = - ENOTCONN ;
if ( sk - > sk_state ! = TCP_ESTABLISHED )
goto out ;
if ( llc - > dev )
sllc . sllc_arphrd = llc - > dev - > type ;
sllc . sllc_sap = llc - > daddr . lsap ;
memcpy ( & sllc . sllc_mac , & llc - > daddr . mac , IFHWADDRLEN ) ;
} else {
rc = - EINVAL ;
if ( ! llc - > sap )
goto out ;
sllc . sllc_sap = llc - > sap - > laddr . lsap ;
if ( llc - > dev ) {
sllc . sllc_arphrd = llc - > dev - > type ;
memcpy ( & sllc . sllc_mac , & llc - > dev - > dev_addr ,
IFHWADDRLEN ) ;
}
}
rc = 0 ;
sllc . sllc_family = AF_LLC ;
memcpy ( uaddr , & sllc , sizeof ( sllc ) ) ;
out :
release_sock ( sk ) ;
return rc ;
}
/**
* llc_ui_ioctl - io controls for PF_LLC
* @ sock : Socket to get / set info
* @ cmd : command
* @ arg : optional argument for cmd
*
* get / set info on llc sockets
*/
static int llc_ui_ioctl ( struct socket * sock , unsigned int cmd ,
unsigned long arg )
{
2006-01-04 01:18:33 +03:00
return - ENOIOCTLCMD ;
2005-04-17 02:20:36 +04:00
}
/**
* llc_ui_setsockopt - set various connection specific parameters .
* @ sock : Socket to set options on .
* @ level : Socket level user is requesting operations on .
* @ optname : Operation name .
* @ optval User provided operation data .
* @ optlen : Length of optval .
*
* Set various connection specific parameters .
*/
static int llc_ui_setsockopt ( struct socket * sock , int level , int optname ,
char __user * optval , int optlen )
{
struct sock * sk = sock - > sk ;
struct llc_sock * llc = llc_sk ( sk ) ;
int rc = - EINVAL , opt ;
lock_sock ( sk ) ;
2005-09-22 10:59:22 +04:00
if ( unlikely ( level ! = SOL_LLC | | optlen ! = sizeof ( int ) ) )
2005-04-17 02:20:36 +04:00
goto out ;
rc = get_user ( opt , ( int __user * ) optval ) ;
if ( rc )
goto out ;
rc = - EINVAL ;
switch ( optname ) {
case LLC_OPT_RETRY :
if ( opt > LLC_OPT_MAX_RETRY )
goto out ;
llc - > n2 = opt ;
break ;
case LLC_OPT_SIZE :
if ( opt > LLC_OPT_MAX_SIZE )
goto out ;
llc - > n1 = opt ;
break ;
case LLC_OPT_ACK_TMR_EXP :
if ( opt > LLC_OPT_MAX_ACK_TMR_EXP )
goto out ;
2005-09-22 11:30:44 +04:00
llc - > ack_timer . expire = opt * HZ ;
2005-04-17 02:20:36 +04:00
break ;
case LLC_OPT_P_TMR_EXP :
if ( opt > LLC_OPT_MAX_P_TMR_EXP )
goto out ;
2005-09-22 11:30:44 +04:00
llc - > pf_cycle_timer . expire = opt * HZ ;
2005-04-17 02:20:36 +04:00
break ;
case LLC_OPT_REJ_TMR_EXP :
if ( opt > LLC_OPT_MAX_REJ_TMR_EXP )
goto out ;
2005-09-22 11:30:44 +04:00
llc - > rej_sent_timer . expire = opt * HZ ;
2005-04-17 02:20:36 +04:00
break ;
case LLC_OPT_BUSY_TMR_EXP :
if ( opt > LLC_OPT_MAX_BUSY_TMR_EXP )
goto out ;
2005-09-22 11:30:44 +04:00
llc - > busy_state_timer . expire = opt * HZ ;
2005-04-17 02:20:36 +04:00
break ;
case LLC_OPT_TX_WIN :
if ( opt > LLC_OPT_MAX_WIN )
goto out ;
llc - > k = opt ;
break ;
case LLC_OPT_RX_WIN :
if ( opt > LLC_OPT_MAX_WIN )
goto out ;
llc - > rw = opt ;
break ;
default :
rc = - ENOPROTOOPT ;
goto out ;
}
rc = 0 ;
out :
release_sock ( sk ) ;
return rc ;
}
/**
* llc_ui_getsockopt - get connection specific socket info
* @ sock : Socket to get information from .
* @ level : Socket level user is requesting operations on .
* @ optname : Operation name .
* @ optval : Variable to return operation data in .
* @ optlen : Length of optval .
*
* Get connection specific socket information .
*/
static int llc_ui_getsockopt ( struct socket * sock , int level , int optname ,
char __user * optval , int __user * optlen )
{
struct sock * sk = sock - > sk ;
struct llc_sock * llc = llc_sk ( sk ) ;
int val = 0 , len = 0 , rc = - EINVAL ;
lock_sock ( sk ) ;
2005-09-22 10:59:22 +04:00
if ( unlikely ( level ! = SOL_LLC ) )
2005-04-17 02:20:36 +04:00
goto out ;
rc = get_user ( len , optlen ) ;
if ( rc )
goto out ;
rc = - EINVAL ;
if ( len ! = sizeof ( int ) )
goto out ;
switch ( optname ) {
case LLC_OPT_RETRY :
2005-09-22 11:30:44 +04:00
val = llc - > n2 ; break ;
2005-04-17 02:20:36 +04:00
case LLC_OPT_SIZE :
2005-09-22 11:30:44 +04:00
val = llc - > n1 ; break ;
2005-04-17 02:20:36 +04:00
case LLC_OPT_ACK_TMR_EXP :
2005-09-22 11:30:44 +04:00
val = llc - > ack_timer . expire / HZ ; break ;
2005-04-17 02:20:36 +04:00
case LLC_OPT_P_TMR_EXP :
2005-09-22 11:30:44 +04:00
val = llc - > pf_cycle_timer . expire / HZ ; break ;
2005-04-17 02:20:36 +04:00
case LLC_OPT_REJ_TMR_EXP :
2005-09-22 11:30:44 +04:00
val = llc - > rej_sent_timer . expire / HZ ; break ;
2005-04-17 02:20:36 +04:00
case LLC_OPT_BUSY_TMR_EXP :
2005-09-22 11:30:44 +04:00
val = llc - > busy_state_timer . expire / HZ ; break ;
2005-04-17 02:20:36 +04:00
case LLC_OPT_TX_WIN :
val = llc - > k ; break ;
case LLC_OPT_RX_WIN :
val = llc - > rw ; break ;
default :
rc = - ENOPROTOOPT ;
goto out ;
}
rc = 0 ;
if ( put_user ( len , optlen ) | | copy_to_user ( optval , & val , len ) )
rc = - EFAULT ;
out :
release_sock ( sk ) ;
return rc ;
}
static struct net_proto_family llc_ui_family_ops = {
. family = PF_LLC ,
. create = llc_ui_create ,
. owner = THIS_MODULE ,
} ;
2005-12-22 23:49:22 +03:00
static const struct proto_ops llc_ui_ops = {
2005-04-17 02:20:36 +04:00
. family = PF_LLC ,
. owner = THIS_MODULE ,
. release = llc_ui_release ,
. bind = llc_ui_bind ,
. connect = llc_ui_connect ,
. socketpair = sock_no_socketpair ,
. accept = llc_ui_accept ,
. getname = llc_ui_getname ,
. poll = datagram_poll ,
. ioctl = llc_ui_ioctl ,
. listen = llc_ui_listen ,
. shutdown = llc_ui_shutdown ,
. setsockopt = llc_ui_setsockopt ,
. getsockopt = llc_ui_getsockopt ,
. sendmsg = llc_ui_sendmsg ,
. recvmsg = llc_ui_recvmsg ,
. mmap = sock_no_mmap ,
. sendpage = sock_no_sendpage ,
} ;
2005-09-22 11:30:44 +04:00
static char llc_proc_err_msg [ ] __initdata =
2007-02-09 17:25:01 +03:00
KERN_CRIT " LLC: Unable to register the proc_fs entries \n " ;
2005-09-22 11:30:44 +04:00
static char llc_sysctl_err_msg [ ] __initdata =
2007-02-09 17:25:01 +03:00
KERN_CRIT " LLC: Unable to register the sysctl entries \n " ;
2005-09-22 11:30:44 +04:00
static char llc_sock_err_msg [ ] __initdata =
2007-02-09 17:25:01 +03:00
KERN_CRIT " LLC: Unable to register the network family \n " ;
2005-09-22 11:30:44 +04:00
2005-04-17 02:20:36 +04:00
static int __init llc2_init ( void )
{
int rc = proto_register ( & llc_proto , 0 ) ;
if ( rc ! = 0 )
goto out ;
llc_build_offset_table ( ) ;
llc_station_init ( ) ;
llc_ui_sap_last_autoport = LLC_SAP_DYN_START ;
rc = llc_proc_init ( ) ;
2005-09-22 11:30:44 +04:00
if ( rc ! = 0 ) {
printk ( llc_proc_err_msg ) ;
2005-04-17 02:20:36 +04:00
goto out_unregister_llc_proto ;
2005-09-22 11:30:44 +04:00
}
rc = llc_sysctl_init ( ) ;
if ( rc ) {
printk ( llc_sysctl_err_msg ) ;
goto out_proc ;
}
rc = sock_register ( & llc_ui_family_ops ) ;
if ( rc ) {
printk ( llc_sock_err_msg ) ;
goto out_sysctl ;
}
2005-04-17 02:20:36 +04:00
llc_add_pack ( LLC_DEST_SAP , llc_sap_handler ) ;
llc_add_pack ( LLC_DEST_CONN , llc_conn_handler ) ;
out :
return rc ;
2005-09-22 11:30:44 +04:00
out_sysctl :
llc_sysctl_exit ( ) ;
out_proc :
llc_proc_exit ( ) ;
2005-04-17 02:20:36 +04:00
out_unregister_llc_proto :
proto_unregister ( & llc_proto ) ;
goto out ;
}
static void __exit llc2_exit ( void )
{
llc_station_exit ( ) ;
llc_remove_pack ( LLC_DEST_SAP ) ;
llc_remove_pack ( LLC_DEST_CONN ) ;
sock_unregister ( PF_LLC ) ;
llc_proc_exit ( ) ;
2005-09-22 11:30:44 +04:00
llc_sysctl_exit ( ) ;
2005-04-17 02:20:36 +04:00
proto_unregister ( & llc_proto ) ;
}
module_init ( llc2_init ) ;
module_exit ( llc2_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Procom 1997, Jay Schullist 2001, Arnaldo C. Melo 2001-2003 " ) ;
MODULE_DESCRIPTION ( " IEEE 802.2 PF_LLC support " ) ;
MODULE_ALIAS_NETPROTO ( PF_LLC ) ;