2005-04-17 02:20:36 +04:00
/*
* llc_input . c - Minimal input path for LLC
*
* Copyright ( c ) 1997 by Procom Technology , Inc .
* 2001 - 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 .
*/
# include <linux/netdevice.h>
2007-09-17 22:53:39 +04:00
# include <net/net_namespace.h>
2005-04-17 02:20:36 +04:00
# include <net/llc.h>
# include <net/llc_pdu.h>
# include <net/llc_sap.h>
#if 0
# define dprintk(args...) printk(KERN_DEBUG args)
# else
# define dprintk(args...)
# endif
/*
* Packet handler for the station , registerable because in the minimal
* LLC core that is taking shape only the very minimal subset of LLC that
* is needed for things like IPX , Appletalk , etc will stay , with all the
* rest in the llc1 and llc2 modules .
*/
static void ( * llc_station_handler ) ( struct sk_buff * skb ) ;
/*
* Packet handlers for LLC_DEST_SAP and LLC_DEST_CONN .
*/
static void ( * llc_type_handlers [ 2 ] ) ( struct llc_sap * sap ,
struct sk_buff * skb ) ;
void llc_add_pack ( int type , void ( * handler ) ( struct llc_sap * sap ,
struct sk_buff * skb ) )
{
if ( type = = LLC_DEST_SAP | | type = = LLC_DEST_CONN )
llc_type_handlers [ type - 1 ] = handler ;
}
void llc_remove_pack ( int type )
{
if ( type = = LLC_DEST_SAP | | type = = LLC_DEST_CONN )
llc_type_handlers [ type - 1 ] = NULL ;
}
void llc_set_station_handler ( void ( * handler ) ( struct sk_buff * skb ) )
{
llc_station_handler = handler ;
}
/**
* llc_pdu_type - returns which LLC component must handle for PDU
* @ skb : input skb
*
* This function returns which LLC component must handle this PDU .
*/
static __inline__ int llc_pdu_type ( struct sk_buff * skb )
{
int type = LLC_DEST_CONN ; /* I-PDU or S-PDU type */
struct llc_pdu_sn * pdu = llc_pdu_sn_hdr ( skb ) ;
if ( ( pdu - > ctrl_1 & LLC_PDU_TYPE_MASK ) ! = LLC_PDU_TYPE_U )
goto out ;
switch ( LLC_U_PDU_CMD ( pdu ) ) {
case LLC_1_PDU_CMD_XID :
case LLC_1_PDU_CMD_UI :
case LLC_1_PDU_CMD_TEST :
type = LLC_DEST_SAP ;
break ;
case LLC_2_PDU_CMD_SABME :
case LLC_2_PDU_CMD_DISC :
case LLC_2_PDU_RSP_UA :
case LLC_2_PDU_RSP_DM :
case LLC_2_PDU_RSP_FRMR :
break ;
default :
type = LLC_DEST_INVALID ;
break ;
}
out :
return type ;
}
/**
* llc_fixup_skb - initializes skb pointers
* @ skb : This argument points to incoming skb
*
* Initializes internal skb pointer to start of network layer by deriving
* length of LLC header ; finds length of LLC control field in LLC header
* by looking at the two lowest - order bits of the first control field
* byte ; field is either 3 or 4 bytes long .
*/
static inline int llc_fixup_skb ( struct sk_buff * skb )
{
u8 llc_len = 2 ;
2005-09-22 11:48:46 +04:00
struct llc_pdu_un * pdu ;
2005-04-17 02:20:36 +04:00
2005-09-22 10:59:22 +04:00
if ( unlikely ( ! pskb_may_pull ( skb , sizeof ( * pdu ) ) ) )
2005-04-17 02:20:36 +04:00
return 0 ;
2005-09-22 11:48:46 +04:00
pdu = ( struct llc_pdu_un * ) skb - > data ;
2005-04-17 02:20:36 +04:00
if ( ( pdu - > ctrl_1 & LLC_PDU_TYPE_MASK ) = = LLC_PDU_TYPE_U )
llc_len = 1 ;
llc_len + = 2 ;
2005-09-22 11:48:46 +04:00
if ( unlikely ( ! pskb_may_pull ( skb , llc_len ) ) )
return 0 ;
2007-04-11 08:21:55 +04:00
skb - > transport_header + = llc_len ;
2005-04-17 02:20:36 +04:00
skb_pull ( skb , llc_len ) ;
if ( skb - > protocol = = htons ( ETH_P_802_2 ) ) {
2006-11-08 11:26:05 +03:00
__be16 pdulen = eth_hdr ( skb ) - > h_proto ;
2008-03-29 02:27:33 +03:00
s32 data_size = ntohs ( pdulen ) - llc_len ;
2005-04-17 02:20:36 +04:00
2008-03-29 02:27:33 +03:00
if ( data_size < 0 | |
( ( skb_tail_pointer ( skb ) -
( u8 * ) pdu ) - llc_len ) < data_size )
return 0 ;
2006-04-20 02:37:13 +04:00
if ( unlikely ( pskb_trim_rcsum ( skb , data_size ) ) )
return 0 ;
2005-04-17 02:20:36 +04:00
}
return 1 ;
}
/**
* llc_rcv - 802.2 entry point from net lower layers
* @ skb : received pdu
* @ dev : device that receive pdu
* @ pt : packet type
*
* When the system receives a 802.2 frame this function is called . It
* checks SAP and connection of received pdu and passes frame to
* llc_ { station , sap , conn } _rcv for sending to proper state machine . If
* the frame is related to a busy connection ( a connection is sending
* data now ) , it queues this frame in the connection ' s backlog .
*/
int llc_rcv ( struct sk_buff * skb , struct net_device * dev ,
2005-08-10 06:34:12 +04:00
struct packet_type * pt , struct net_device * orig_dev )
2005-04-17 02:20:36 +04:00
{
struct llc_sap * sap ;
struct llc_pdu_sn * pdu ;
int dest ;
2006-05-26 02:09:37 +04:00
int ( * rcv ) ( struct sk_buff * , struct net_device * ,
struct packet_type * , struct net_device * ) ;
2005-04-17 02:20:36 +04:00
2008-07-20 09:34:43 +04:00
if ( ! net_eq ( dev_net ( dev ) , & init_net ) )
2007-09-17 22:53:39 +04:00
goto drop ;
2005-04-17 02:20:36 +04:00
/*
* When the interface is in promisc . mode , drop all the crap that it
* receives , do not try to analyse it .
*/
if ( unlikely ( skb - > pkt_type = = PACKET_OTHERHOST ) ) {
2008-03-06 07:47:47 +03:00
dprintk ( " %s: PACKET_OTHERHOST \n " , __func__ ) ;
2005-04-17 02:20:36 +04:00
goto drop ;
}
skb = skb_share_check ( skb , GFP_ATOMIC ) ;
if ( unlikely ( ! skb ) )
goto out ;
if ( unlikely ( ! llc_fixup_skb ( skb ) ) )
goto drop ;
pdu = llc_pdu_sn_hdr ( skb ) ;
if ( unlikely ( ! pdu - > dsap ) ) /* NULL DSAP, refer to station */
goto handle_station ;
sap = llc_sap_find ( pdu - > dsap ) ;
if ( unlikely ( ! sap ) ) { /* unknown SAP */
2008-03-06 07:47:47 +03:00
dprintk ( " %s: llc_sap_find(%02X) failed! \n " , __func__ ,
2007-02-09 17:25:01 +03:00
pdu - > dsap ) ;
2005-04-17 02:20:36 +04:00
goto drop ;
}
/*
* First the upper layer protocols that don ' t need the full
* LLC functionality
*/
2006-05-26 02:09:37 +04:00
rcv = rcu_dereference ( sap - > rcv_func ) ;
if ( rcv ) {
2007-02-09 17:25:01 +03:00
struct sk_buff * cskb = skb_clone ( skb , GFP_ATOMIC ) ;
if ( cskb )
rcv ( cskb , dev , pt , orig_dev ) ;
2005-04-17 02:20:36 +04:00
}
dest = llc_pdu_type ( skb ) ;
if ( unlikely ( ! dest | | ! llc_type_handlers [ dest - 1 ] ) )
2005-09-22 11:43:05 +04:00
goto drop_put ;
2005-04-17 02:20:36 +04:00
llc_type_handlers [ dest - 1 ] ( sap , skb ) ;
2005-09-22 11:43:05 +04:00
out_put :
llc_sap_put ( sap ) ;
2005-04-17 02:20:36 +04:00
out :
return 0 ;
drop :
kfree_skb ( skb ) ;
goto out ;
2005-09-22 11:43:05 +04:00
drop_put :
kfree_skb ( skb ) ;
goto out_put ;
2005-04-17 02:20:36 +04:00
handle_station :
if ( ! llc_station_handler )
goto drop ;
llc_station_handler ( skb ) ;
goto out ;
}
EXPORT_SYMBOL ( llc_add_pack ) ;
EXPORT_SYMBOL ( llc_remove_pack ) ;
EXPORT_SYMBOL ( llc_set_station_handler ) ;