2005-04-17 02:20:36 +04:00
/*
* llc_sap . c - driver routines for SAP component .
*
* 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 <net/llc.h>
# include <net/llc_if.h>
# include <net/llc_conn.h>
# include <net/llc_pdu.h>
# include <net/llc_sap.h>
# include <net/llc_s_ac.h>
# include <net/llc_s_ev.h>
# include <net/llc_s_st.h>
# include <net/sock.h>
2005-08-10 07:08:28 +04:00
# include <net/tcp_states.h>
2005-04-17 02:20:36 +04:00
# include <linux/llc.h>
2008-04-01 08:02:47 +04:00
static int llc_mac_header_len ( unsigned short devtype )
{
switch ( devtype ) {
case ARPHRD_ETHER :
case ARPHRD_LOOPBACK :
return sizeof ( struct ethhdr ) ;
# ifdef CONFIG_TR
case ARPHRD_IEEE802_TR :
return sizeof ( struct trh_hdr ) ;
# endif
}
return 0 ;
}
2005-04-17 02:20:36 +04:00
/**
* llc_alloc_frame - allocates sk_buff for frame
2005-09-22 10:27:56 +04:00
* @ dev : network device this skb will be sent over
2008-04-01 08:02:47 +04:00
* @ type : pdu type to allocate
* @ data_size : data size to allocate
2005-04-17 02:20:36 +04:00
*
* Allocates an sk_buff for frame and initializes sk_buff fields .
* Returns allocated skb or % NULL when out of memory .
*/
2008-04-01 08:02:47 +04:00
struct sk_buff * llc_alloc_frame ( struct sock * sk , struct net_device * dev ,
u8 type , u32 data_size )
2005-04-17 02:20:36 +04:00
{
2008-04-01 08:02:47 +04:00
int hlen = type = = LLC_PDU_TYPE_U ? 3 : 4 ;
struct sk_buff * skb ;
hlen + = llc_mac_header_len ( dev - > type ) ;
skb = alloc_skb ( hlen + data_size , GFP_ATOMIC ) ;
2005-04-17 02:20:36 +04:00
if ( skb ) {
2007-03-10 18:14:56 +03:00
skb_reset_mac_header ( skb ) ;
2008-04-01 08:02:47 +04:00
skb_reserve ( skb , hlen ) ;
2007-04-11 07:45:18 +04:00
skb_reset_network_header ( skb ) ;
2007-03-13 19:06:52 +03:00
skb_reset_transport_header ( skb ) ;
2005-04-17 02:20:36 +04:00
skb - > protocol = htons ( ETH_P_802_2 ) ;
2005-09-22 10:27:56 +04:00
skb - > dev = dev ;
2005-09-22 14:57:21 +04:00
if ( sk ! = NULL )
skb_set_owner_w ( skb , sk ) ;
2005-04-17 02:20:36 +04:00
}
return skb ;
}
2005-09-22 11:40:59 +04:00
void llc_save_primitive ( struct sock * sk , struct sk_buff * skb , u8 prim )
2005-04-17 02:20:36 +04:00
{
2005-09-22 15:29:08 +04:00
struct sockaddr_llc * addr ;
2005-04-17 02:20:36 +04:00
/* save primitive for use by the user. */
2005-09-22 15:29:08 +04:00
addr = llc_ui_skb_cb ( skb ) ;
2006-08-04 03:38:49 +04:00
memset ( addr , 0 , sizeof ( * addr ) ) ;
2005-09-22 11:40:59 +04:00
addr - > sllc_family = sk - > sk_family ;
2005-04-17 02:20:36 +04:00
addr - > sllc_arphrd = skb - > dev - > type ;
addr - > sllc_test = prim = = LLC_TEST_PRIM ;
addr - > sllc_xid = prim = = LLC_XID_PRIM ;
addr - > sllc_ua = prim = = LLC_DATAUNIT_PRIM ;
llc_pdu_decode_sa ( skb , addr - > sllc_mac ) ;
llc_pdu_decode_ssap ( skb , & addr - > sllc_sap ) ;
}
/**
* llc_sap_rtn_pdu - Informs upper layer on rx of an UI , XID or TEST pdu .
* @ sap : pointer to SAP
* @ skb : received pdu
*/
void llc_sap_rtn_pdu ( struct llc_sap * sap , struct sk_buff * skb )
{
struct llc_sap_state_ev * ev = llc_sap_ev ( skb ) ;
struct llc_pdu_un * pdu = llc_pdu_un_hdr ( skb ) ;
switch ( LLC_U_PDU_RSP ( pdu ) ) {
case LLC_1_PDU_CMD_TEST :
ev - > prim = LLC_TEST_PRIM ; break ;
case LLC_1_PDU_CMD_XID :
ev - > prim = LLC_XID_PRIM ; break ;
case LLC_1_PDU_CMD_UI :
ev - > prim = LLC_DATAUNIT_PRIM ; break ;
}
ev - > ind_cfm_flag = LLC_IND ;
}
/**
* llc_find_sap_trans - finds transition for event
* @ sap : pointer to SAP
* @ skb : happened event
*
* This function finds transition that matches with happened event .
* Returns the pointer to found transition on success or % NULL for
* failure .
*/
static struct llc_sap_state_trans * llc_find_sap_trans ( struct llc_sap * sap ,
struct sk_buff * skb )
{
int i = 0 ;
struct llc_sap_state_trans * rc = NULL ;
struct llc_sap_state_trans * * next_trans ;
struct llc_sap_state * curr_state = & llc_sap_state_table [ sap - > state - 1 ] ;
/*
* Search thru events for this state until list exhausted or until
* its obvious the event is not valid for the current state
*/
for ( next_trans = curr_state - > transitions ; next_trans [ i ] - > ev ; i + + )
if ( ! next_trans [ i ] - > ev ( sap , skb ) ) {
rc = next_trans [ i ] ; /* got event match; return it */
break ;
}
return rc ;
}
/**
* llc_exec_sap_trans_actions - execute actions related to event
* @ sap : pointer to SAP
* @ trans : pointer to transition that it ' s actions must be performed
* @ skb : happened event .
*
* This function executes actions that is related to happened event .
* Returns 0 for success and 1 for failure of at least one action .
*/
static int llc_exec_sap_trans_actions ( struct llc_sap * sap ,
struct llc_sap_state_trans * trans ,
struct sk_buff * skb )
{
int rc = 0 ;
llc_sap_action_t * next_action = trans - > ev_actions ;
for ( ; next_action & & * next_action ; next_action + + )
if ( ( * next_action ) ( sap , skb ) )
rc = 1 ;
return rc ;
}
/**
* llc_sap_next_state - finds transition , execs actions & change SAP state
* @ sap : pointer to SAP
* @ skb : happened event
*
* This function finds transition that matches with happened event , then
* executes related actions and finally changes state of SAP . It returns
* 0 on success and 1 for failure .
*/
static int llc_sap_next_state ( struct llc_sap * sap , struct sk_buff * skb )
{
int rc = 1 ;
struct llc_sap_state_trans * trans ;
if ( sap - > state > LLC_NR_SAP_STATES )
goto out ;
trans = llc_find_sap_trans ( sap , skb ) ;
if ( ! trans )
goto out ;
/*
* Got the state to which we next transition ; perform the actions
* associated with this transition before actually transitioning to the
* next state
*/
rc = llc_exec_sap_trans_actions ( sap , trans , skb ) ;
if ( rc )
goto out ;
/*
* Transition SAP to next state if all actions execute successfully
*/
sap - > state = trans - > next_state ;
out :
return rc ;
}
/**
* llc_sap_state_process - sends event to SAP state machine
* @ sap : sap to use
* @ skb : pointer to occurred event
*
* After executing actions of the event , upper layer will be indicated
* if needed ( on receiving an UI frame ) . sk can be null for the
* datalink_proto case .
*/
static void llc_sap_state_process ( struct llc_sap * sap , struct sk_buff * skb )
{
struct llc_sap_state_ev * ev = llc_sap_ev ( skb ) ;
/*
* We have to hold the skb , because llc_sap_next_state
* will kfree it in the sending path and we need to
* look at the skb - > cb , where we encode llc_sap_state_ev .
*/
skb_get ( skb ) ;
ev - > ind_cfm_flag = 0 ;
llc_sap_next_state ( sap , skb ) ;
if ( ev - > ind_cfm_flag = = LLC_IND ) {
if ( skb - > sk - > sk_state = = TCP_LISTEN )
kfree_skb ( skb ) ;
else {
2005-09-22 11:40:59 +04:00
llc_save_primitive ( skb - > sk , skb , ev - > prim ) ;
2005-04-17 02:20:36 +04:00
/* queue skb to the user. */
if ( sock_queue_rcv_skb ( skb - > sk , skb ) )
kfree_skb ( skb ) ;
}
2007-02-09 17:25:01 +03:00
}
2005-04-17 02:20:36 +04:00
kfree_skb ( skb ) ;
}
/**
* llc_build_and_send_test_pkt - TEST interface for upper layers .
* @ sap : sap to use
* @ skb : packet to send
* @ dmac : destination mac address
* @ dsap : destination sap
*
* This function is called when upper layer wants to send a TEST pdu .
* Returns 0 for success , 1 otherwise .
*/
2007-02-09 17:25:01 +03:00
void llc_build_and_send_test_pkt ( struct llc_sap * sap ,
2005-04-17 02:20:36 +04:00
struct sk_buff * skb , u8 * dmac , u8 dsap )
{
struct llc_sap_state_ev * ev = llc_sap_ev ( skb ) ;
ev - > saddr . lsap = sap - > laddr . lsap ;
ev - > daddr . lsap = dsap ;
memcpy ( ev - > saddr . mac , skb - > dev - > dev_addr , IFHWADDRLEN ) ;
memcpy ( ev - > daddr . mac , dmac , IFHWADDRLEN ) ;
2007-02-09 17:25:01 +03:00
2005-04-17 02:20:36 +04:00
ev - > type = LLC_SAP_EV_TYPE_PRIM ;
ev - > prim = LLC_TEST_PRIM ;
ev - > prim_type = LLC_PRIM_TYPE_REQ ;
llc_sap_state_process ( sap , skb ) ;
}
/**
* llc_build_and_send_xid_pkt - XID interface for upper layers
* @ sap : sap to use
* @ skb : packet to send
* @ dmac : destination mac address
* @ dsap : destination sap
*
* This function is called when upper layer wants to send a XID pdu .
* Returns 0 for success , 1 otherwise .
*/
void llc_build_and_send_xid_pkt ( struct llc_sap * sap , struct sk_buff * skb ,
u8 * dmac , u8 dsap )
{
struct llc_sap_state_ev * ev = llc_sap_ev ( skb ) ;
ev - > saddr . lsap = sap - > laddr . lsap ;
ev - > daddr . lsap = dsap ;
memcpy ( ev - > saddr . mac , skb - > dev - > dev_addr , IFHWADDRLEN ) ;
memcpy ( ev - > daddr . mac , dmac , IFHWADDRLEN ) ;
ev - > type = LLC_SAP_EV_TYPE_PRIM ;
ev - > prim = LLC_XID_PRIM ;
ev - > prim_type = LLC_PRIM_TYPE_REQ ;
llc_sap_state_process ( sap , skb ) ;
}
/**
* llc_sap_rcv - sends received pdus to the sap state machine
* @ sap : current sap component structure .
* @ skb : received frame .
*
* Sends received pdus to the sap state machine .
*/
2008-05-30 13:57:29 +04:00
static void llc_sap_rcv ( struct llc_sap * sap , struct sk_buff * skb ,
struct sock * sk )
2005-04-17 02:20:36 +04:00
{
struct llc_sap_state_ev * ev = llc_sap_ev ( skb ) ;
ev - > type = LLC_SAP_EV_TYPE_PDU ;
ev - > reason = 0 ;
2008-05-30 13:57:29 +04:00
skb - > sk = sk ;
2005-04-17 02:20:36 +04:00
llc_sap_state_process ( sap , skb ) ;
}
2009-12-26 14:51:02 +03:00
static inline bool llc_dgram_match ( const struct llc_sap * sap ,
const struct llc_addr * laddr ,
const struct sock * sk )
{
struct llc_sock * llc = llc_sk ( sk ) ;
return sk - > sk_type = = SOCK_DGRAM & &
llc - > laddr . lsap = = laddr - > lsap & &
llc_mac_match ( llc - > laddr . mac , laddr - > mac ) ;
}
2005-04-17 02:20:36 +04:00
/**
* llc_lookup_dgram - Finds dgram socket for the local sap / mac
* @ sap : SAP
* @ laddr : address of local LLC ( MAC + SAP )
*
* Search socket list of the SAP and finds connection using the local
* mac , and local sap . Returns pointer for socket found , % NULL otherwise .
*/
static struct sock * llc_lookup_dgram ( struct llc_sap * sap ,
2006-05-26 02:10:37 +04:00
const struct llc_addr * laddr )
2005-04-17 02:20:36 +04:00
{
struct sock * rc ;
2009-12-26 14:51:02 +03:00
struct hlist_nulls_node * node ;
rcu_read_lock_bh ( ) ;
again :
sk_nulls_for_each_rcu ( rc , node , & sap - > sk_list ) {
if ( llc_dgram_match ( sap , laddr , rc ) ) {
/* Extra checks required by SLAB_DESTROY_BY_RCU */
if ( unlikely ( ! atomic_inc_not_zero ( & rc - > sk_refcnt ) ) )
goto again ;
if ( unlikely ( llc_sk ( rc ) - > sap ! = sap | |
! llc_dgram_match ( sap , laddr , rc ) ) ) {
sock_put ( rc ) ;
continue ;
}
2005-04-17 02:20:36 +04:00
goto found ;
}
}
rc = NULL ;
found :
2009-12-26 14:51:02 +03:00
rcu_read_unlock_bh ( ) ;
2005-04-17 02:20:36 +04:00
return rc ;
}
2009-12-26 14:51:02 +03:00
static inline bool llc_mcast_match ( const struct llc_sap * sap ,
const struct llc_addr * laddr ,
const struct sk_buff * skb ,
const struct sock * sk )
{
struct llc_sock * llc = llc_sk ( sk ) ;
return sk - > sk_type = = SOCK_DGRAM & &
llc - > laddr . lsap = = laddr - > lsap & &
llc - > dev = = skb - > dev ;
}
2006-05-26 02:10:37 +04:00
/**
* llc_sap_mcast - Deliver multicast PDU ' s to all matching datagram sockets .
* @ sap : SAP
* @ laddr : address of local LLC ( MAC + SAP )
*
* Search socket list of the SAP and finds connections with same sap .
* Deliver clone to each .
*/
static void llc_sap_mcast ( struct llc_sap * sap ,
const struct llc_addr * laddr ,
struct sk_buff * skb )
{
struct sock * sk ;
2009-12-26 14:51:02 +03:00
struct hlist_nulls_node * node ;
2006-05-26 02:10:37 +04:00
2009-12-26 14:51:02 +03:00
spin_lock_bh ( & sap - > sk_lock ) ;
sk_nulls_for_each_rcu ( sk , node , & sap - > sk_list ) {
2006-05-26 02:10:37 +04:00
struct sk_buff * skb1 ;
2009-12-26 14:51:02 +03:00
if ( ! llc_mcast_match ( sap , laddr , skb , sk ) )
2006-08-14 05:56:26 +04:00
continue ;
2006-05-26 02:10:37 +04:00
skb1 = skb_clone ( skb , GFP_ATOMIC ) ;
if ( ! skb1 )
break ;
sock_hold ( sk ) ;
2008-05-30 13:57:29 +04:00
llc_sap_rcv ( sap , skb1 , sk ) ;
2006-05-26 02:10:37 +04:00
sock_put ( sk ) ;
}
2009-12-26 14:51:02 +03:00
spin_unlock_bh ( & sap - > sk_lock ) ;
2006-05-26 02:10:37 +04:00
}
2005-04-17 02:20:36 +04:00
void llc_sap_handler ( struct llc_sap * sap , struct sk_buff * skb )
{
struct llc_addr laddr ;
llc_pdu_decode_da ( skb , laddr . mac ) ;
llc_pdu_decode_dsap ( skb , & laddr . lsap ) ;
2006-05-26 02:10:37 +04:00
if ( llc_mac_multicast ( laddr . mac ) ) {
llc_sap_mcast ( sap , & laddr , skb ) ;
2005-04-17 02:20:36 +04:00
kfree_skb ( skb ) ;
2006-05-26 02:10:37 +04:00
} else {
struct sock * sk = llc_lookup_dgram ( sap , & laddr ) ;
if ( sk ) {
2008-05-30 13:57:29 +04:00
llc_sap_rcv ( sap , skb , sk ) ;
2006-05-26 02:10:37 +04:00
sock_put ( sk ) ;
} else
kfree_skb ( skb ) ;
}
2005-04-17 02:20:36 +04:00
}