2005-04-17 02:20:36 +04:00
/*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* Copyright Jonathan Naylor G4KLX ( g4klx @ g4klx . demon . co . uk )
* Copyright Alan Cox GW4PTS ( alan @ lxorguk . ukuu . org . uk )
* Copyright Tomi Manninen OH2BNS ( oh2bns @ sral . fi )
*/
# include <linux/errno.h>
# include <linux/types.h>
# include <linux/socket.h>
# include <linux/in.h>
# include <linux/kernel.h>
# include <linux/timer.h>
# include <linux/string.h>
# include <linux/sockios.h>
# include <linux/net.h>
# include <net/ax25.h>
# include <linux/inet.h>
# include <linux/netdevice.h>
# include <net/arp.h>
# include <linux/if_arp.h>
# include <linux/skbuff.h>
# include <net/sock.h>
# include <asm/uaccess.h>
# include <asm/system.h>
# include <linux/fcntl.h>
# include <linux/termios.h> /* For TIOCINQ/OUTQ */
# include <linux/mm.h>
# include <linux/interrupt.h>
# include <linux/notifier.h>
# include <linux/netfilter.h>
# include <linux/init.h>
# include <linux/spinlock.h>
# include <net/netrom.h>
# include <linux/seq_file.h>
static unsigned int nr_neigh_no = 1 ;
static HLIST_HEAD ( nr_node_list ) ;
static DEFINE_SPINLOCK ( nr_node_list_lock ) ;
static HLIST_HEAD ( nr_neigh_list ) ;
static DEFINE_SPINLOCK ( nr_neigh_list_lock ) ;
static struct nr_node * nr_node_get ( ax25_address * callsign )
{
struct nr_node * found = NULL ;
struct nr_node * nr_node ;
struct hlist_node * node ;
spin_lock_bh ( & nr_node_list_lock ) ;
nr_node_for_each ( nr_node , node , & nr_node_list )
if ( ax25cmp ( callsign , & nr_node - > callsign ) = = 0 ) {
nr_node_hold ( nr_node ) ;
found = nr_node ;
break ;
}
spin_unlock_bh ( & nr_node_list_lock ) ;
return found ;
}
static struct nr_neigh * nr_neigh_get_dev ( ax25_address * callsign ,
struct net_device * dev )
{
struct nr_neigh * found = NULL ;
struct nr_neigh * nr_neigh ;
struct hlist_node * node ;
spin_lock_bh ( & nr_neigh_list_lock ) ;
nr_neigh_for_each ( nr_neigh , node , & nr_neigh_list )
if ( ax25cmp ( callsign , & nr_neigh - > callsign ) = = 0 & &
nr_neigh - > dev = = dev ) {
nr_neigh_hold ( nr_neigh ) ;
found = nr_neigh ;
break ;
}
spin_unlock_bh ( & nr_neigh_list_lock ) ;
return found ;
}
static void nr_remove_neigh ( struct nr_neigh * ) ;
/*
* Add a new route to a node , and in the process add the node and the
* neighbour if it is new .
*/
2006-12-15 02:49:28 +03:00
static int __must_check nr_add_node ( ax25_address * nr , const char * mnemonic ,
ax25_address * ax25 , ax25_digi * ax25_digi , struct net_device * dev ,
int quality , int obs_count )
2005-04-17 02:20:36 +04:00
{
struct nr_node * nr_node ;
struct nr_neigh * nr_neigh ;
struct nr_route nr_route ;
int i , found ;
struct net_device * odev ;
if ( ( odev = nr_dev_get ( nr ) ) ! = NULL ) { /* Can't add routes to ourself */
dev_put ( odev ) ;
return - EINVAL ;
}
nr_node = nr_node_get ( nr ) ;
nr_neigh = nr_neigh_get_dev ( ax25 , dev ) ;
/*
* The L2 link to a neighbour has failed in the past
* and now a frame comes from this neighbour . We assume
* it was a temporary trouble with the link and reset the
* routes now ( and not wait for a node broadcast ) .
*/
if ( nr_neigh ! = NULL & & nr_neigh - > failed ! = 0 & & quality = = 0 ) {
struct nr_node * nr_nodet ;
struct hlist_node * node ;
spin_lock_bh ( & nr_node_list_lock ) ;
nr_node_for_each ( nr_nodet , node , & nr_node_list ) {
nr_node_lock ( nr_nodet ) ;
for ( i = 0 ; i < nr_nodet - > count ; i + + )
if ( nr_nodet - > routes [ i ] . neighbour = = nr_neigh )
if ( i < nr_nodet - > which )
nr_nodet - > which = i ;
nr_node_unlock ( nr_nodet ) ;
}
spin_unlock_bh ( & nr_node_list_lock ) ;
}
if ( nr_neigh ! = NULL )
nr_neigh - > failed = 0 ;
if ( quality = = 0 & & nr_neigh ! = NULL & & nr_node ! = NULL ) {
nr_neigh_put ( nr_neigh ) ;
nr_node_put ( nr_node ) ;
return 0 ;
}
if ( nr_neigh = = NULL ) {
if ( ( nr_neigh = kmalloc ( sizeof ( * nr_neigh ) , GFP_ATOMIC ) ) = = NULL ) {
if ( nr_node )
nr_node_put ( nr_node ) ;
return - ENOMEM ;
}
nr_neigh - > callsign = * ax25 ;
nr_neigh - > digipeat = NULL ;
nr_neigh - > ax25 = NULL ;
nr_neigh - > dev = dev ;
nr_neigh - > quality = sysctl_netrom_default_path_quality ;
nr_neigh - > locked = 0 ;
nr_neigh - > count = 0 ;
nr_neigh - > number = nr_neigh_no + + ;
nr_neigh - > failed = 0 ;
atomic_set ( & nr_neigh - > refcount , 1 ) ;
if ( ax25_digi ! = NULL & & ax25_digi - > ndigi > 0 ) {
2006-11-17 18:05:04 +03:00
nr_neigh - > digipeat = kmemdup ( ax25_digi ,
sizeof ( * ax25_digi ) ,
GFP_KERNEL ) ;
if ( nr_neigh - > digipeat = = NULL ) {
2005-04-17 02:20:36 +04:00
kfree ( nr_neigh ) ;
if ( nr_node )
nr_node_put ( nr_node ) ;
return - ENOMEM ;
}
}
spin_lock_bh ( & nr_neigh_list_lock ) ;
hlist_add_head ( & nr_neigh - > neigh_node , & nr_neigh_list ) ;
nr_neigh_hold ( nr_neigh ) ;
spin_unlock_bh ( & nr_neigh_list_lock ) ;
}
if ( quality ! = 0 & & ax25cmp ( nr , ax25 ) = = 0 & & ! nr_neigh - > locked )
nr_neigh - > quality = quality ;
if ( nr_node = = NULL ) {
if ( ( nr_node = kmalloc ( sizeof ( * nr_node ) , GFP_ATOMIC ) ) = = NULL ) {
if ( nr_neigh )
nr_neigh_put ( nr_neigh ) ;
return - ENOMEM ;
}
nr_node - > callsign = * nr ;
strcpy ( nr_node - > mnemonic , mnemonic ) ;
nr_node - > which = 0 ;
nr_node - > count = 1 ;
atomic_set ( & nr_node - > refcount , 1 ) ;
spin_lock_init ( & nr_node - > node_lock ) ;
nr_node - > routes [ 0 ] . quality = quality ;
nr_node - > routes [ 0 ] . obs_count = obs_count ;
nr_node - > routes [ 0 ] . neighbour = nr_neigh ;
nr_neigh_hold ( nr_neigh ) ;
nr_neigh - > count + + ;
spin_lock_bh ( & nr_node_list_lock ) ;
hlist_add_head ( & nr_node - > node_node , & nr_node_list ) ;
/* refcount initialized at 1 */
spin_unlock_bh ( & nr_node_list_lock ) ;
return 0 ;
}
nr_node_lock ( nr_node ) ;
if ( quality ! = 0 )
strcpy ( nr_node - > mnemonic , mnemonic ) ;
for ( found = 0 , i = 0 ; i < nr_node - > count ; i + + ) {
if ( nr_node - > routes [ i ] . neighbour = = nr_neigh ) {
nr_node - > routes [ i ] . quality = quality ;
nr_node - > routes [ i ] . obs_count = obs_count ;
found = 1 ;
break ;
}
}
if ( ! found ) {
/* We have space at the bottom, slot it in */
if ( nr_node - > count < 3 ) {
nr_node - > routes [ 2 ] = nr_node - > routes [ 1 ] ;
nr_node - > routes [ 1 ] = nr_node - > routes [ 0 ] ;
nr_node - > routes [ 0 ] . quality = quality ;
nr_node - > routes [ 0 ] . obs_count = obs_count ;
nr_node - > routes [ 0 ] . neighbour = nr_neigh ;
nr_node - > which + + ;
nr_node - > count + + ;
nr_neigh_hold ( nr_neigh ) ;
nr_neigh - > count + + ;
} else {
/* It must be better than the worst */
if ( quality > nr_node - > routes [ 2 ] . quality ) {
nr_node - > routes [ 2 ] . neighbour - > count - - ;
nr_neigh_put ( nr_node - > routes [ 2 ] . neighbour ) ;
if ( nr_node - > routes [ 2 ] . neighbour - > count = = 0 & & ! nr_node - > routes [ 2 ] . neighbour - > locked )
nr_remove_neigh ( nr_node - > routes [ 2 ] . neighbour ) ;
nr_node - > routes [ 2 ] . quality = quality ;
nr_node - > routes [ 2 ] . obs_count = obs_count ;
nr_node - > routes [ 2 ] . neighbour = nr_neigh ;
nr_neigh_hold ( nr_neigh ) ;
nr_neigh - > count + + ;
}
}
}
/* Now re-sort the routes in quality order */
switch ( nr_node - > count ) {
case 3 :
if ( nr_node - > routes [ 1 ] . quality > nr_node - > routes [ 0 ] . quality ) {
switch ( nr_node - > which ) {
case 0 : nr_node - > which = 1 ; break ;
case 1 : nr_node - > which = 0 ; break ;
default : break ;
}
nr_route = nr_node - > routes [ 0 ] ;
nr_node - > routes [ 0 ] = nr_node - > routes [ 1 ] ;
nr_node - > routes [ 1 ] = nr_route ;
}
if ( nr_node - > routes [ 2 ] . quality > nr_node - > routes [ 1 ] . quality ) {
switch ( nr_node - > which ) {
case 1 : nr_node - > which = 2 ;
break ;
case 2 : nr_node - > which = 1 ;
break ;
default :
break ;
}
nr_route = nr_node - > routes [ 1 ] ;
nr_node - > routes [ 1 ] = nr_node - > routes [ 2 ] ;
nr_node - > routes [ 2 ] = nr_route ;
}
case 2 :
if ( nr_node - > routes [ 1 ] . quality > nr_node - > routes [ 0 ] . quality ) {
switch ( nr_node - > which ) {
case 0 : nr_node - > which = 1 ;
break ;
case 1 : nr_node - > which = 0 ;
break ;
default : break ;
}
nr_route = nr_node - > routes [ 0 ] ;
nr_node - > routes [ 0 ] = nr_node - > routes [ 1 ] ;
nr_node - > routes [ 1 ] = nr_route ;
}
case 1 :
break ;
}
for ( i = 0 ; i < nr_node - > count ; i + + ) {
if ( nr_node - > routes [ i ] . neighbour = = nr_neigh ) {
if ( i < nr_node - > which )
nr_node - > which = i ;
break ;
}
}
nr_neigh_put ( nr_neigh ) ;
nr_node_unlock ( nr_node ) ;
nr_node_put ( nr_node ) ;
return 0 ;
}
static inline void __nr_remove_node ( struct nr_node * nr_node )
{
hlist_del_init ( & nr_node - > node_node ) ;
nr_node_put ( nr_node ) ;
}
# define nr_remove_node_locked(__node) \
__nr_remove_node ( __node )
static void nr_remove_node ( struct nr_node * nr_node )
{
spin_lock_bh ( & nr_node_list_lock ) ;
__nr_remove_node ( nr_node ) ;
spin_unlock_bh ( & nr_node_list_lock ) ;
}
static inline void __nr_remove_neigh ( struct nr_neigh * nr_neigh )
{
hlist_del_init ( & nr_neigh - > neigh_node ) ;
nr_neigh_put ( nr_neigh ) ;
}
# define nr_remove_neigh_locked(__neigh) \
__nr_remove_neigh ( __neigh )
static void nr_remove_neigh ( struct nr_neigh * nr_neigh )
{
spin_lock_bh ( & nr_neigh_list_lock ) ;
__nr_remove_neigh ( nr_neigh ) ;
spin_unlock_bh ( & nr_neigh_list_lock ) ;
}
/*
* " Delete " a node . Strictly speaking remove a route to a node . The node
* is only deleted if no routes are left to it .
*/
static int nr_del_node ( ax25_address * callsign , ax25_address * neighbour , struct net_device * dev )
{
struct nr_node * nr_node ;
struct nr_neigh * nr_neigh ;
int i ;
nr_node = nr_node_get ( callsign ) ;
if ( nr_node = = NULL )
return - EINVAL ;
nr_neigh = nr_neigh_get_dev ( neighbour , dev ) ;
if ( nr_neigh = = NULL ) {
nr_node_put ( nr_node ) ;
return - EINVAL ;
}
nr_node_lock ( nr_node ) ;
for ( i = 0 ; i < nr_node - > count ; i + + ) {
if ( nr_node - > routes [ i ] . neighbour = = nr_neigh ) {
nr_neigh - > count - - ;
nr_neigh_put ( nr_neigh ) ;
if ( nr_neigh - > count = = 0 & & ! nr_neigh - > locked )
nr_remove_neigh ( nr_neigh ) ;
nr_neigh_put ( nr_neigh ) ;
nr_node - > count - - ;
if ( nr_node - > count = = 0 ) {
nr_remove_node ( nr_node ) ;
} else {
switch ( i ) {
case 0 :
nr_node - > routes [ 0 ] = nr_node - > routes [ 1 ] ;
case 1 :
nr_node - > routes [ 1 ] = nr_node - > routes [ 2 ] ;
case 2 :
break ;
}
nr_node_put ( nr_node ) ;
}
nr_node_unlock ( nr_node ) ;
return 0 ;
}
}
nr_neigh_put ( nr_neigh ) ;
nr_node_unlock ( nr_node ) ;
nr_node_put ( nr_node ) ;
return - EINVAL ;
}
/*
* Lock a neighbour with a quality .
*/
2006-12-15 02:49:28 +03:00
static int __must_check nr_add_neigh ( ax25_address * callsign ,
ax25_digi * ax25_digi , struct net_device * dev , unsigned int quality )
2005-04-17 02:20:36 +04:00
{
struct nr_neigh * nr_neigh ;
nr_neigh = nr_neigh_get_dev ( callsign , dev ) ;
if ( nr_neigh ) {
nr_neigh - > quality = quality ;
nr_neigh - > locked = 1 ;
nr_neigh_put ( nr_neigh ) ;
return 0 ;
}
if ( ( nr_neigh = kmalloc ( sizeof ( * nr_neigh ) , GFP_ATOMIC ) ) = = NULL )
return - ENOMEM ;
nr_neigh - > callsign = * callsign ;
nr_neigh - > digipeat = NULL ;
nr_neigh - > ax25 = NULL ;
nr_neigh - > dev = dev ;
nr_neigh - > quality = quality ;
nr_neigh - > locked = 1 ;
nr_neigh - > count = 0 ;
nr_neigh - > number = nr_neigh_no + + ;
nr_neigh - > failed = 0 ;
atomic_set ( & nr_neigh - > refcount , 1 ) ;
if ( ax25_digi ! = NULL & & ax25_digi - > ndigi > 0 ) {
2006-11-17 18:05:04 +03:00
nr_neigh - > digipeat = kmemdup ( ax25_digi , sizeof ( * ax25_digi ) ,
GFP_KERNEL ) ;
if ( nr_neigh - > digipeat = = NULL ) {
2005-04-17 02:20:36 +04:00
kfree ( nr_neigh ) ;
return - ENOMEM ;
}
}
spin_lock_bh ( & nr_neigh_list_lock ) ;
hlist_add_head ( & nr_neigh - > neigh_node , & nr_neigh_list ) ;
/* refcount is initialized at 1 */
spin_unlock_bh ( & nr_neigh_list_lock ) ;
return 0 ;
}
/*
* " Delete " a neighbour . The neighbour is only removed if the number
* of nodes that may use it is zero .
*/
static int nr_del_neigh ( ax25_address * callsign , struct net_device * dev , unsigned int quality )
{
struct nr_neigh * nr_neigh ;
nr_neigh = nr_neigh_get_dev ( callsign , dev ) ;
if ( nr_neigh = = NULL ) return - EINVAL ;
nr_neigh - > quality = quality ;
nr_neigh - > locked = 0 ;
if ( nr_neigh - > count = = 0 )
nr_remove_neigh ( nr_neigh ) ;
nr_neigh_put ( nr_neigh ) ;
return 0 ;
}
/*
* Decrement the obsolescence count by one . If a route is reduced to a
* count of zero , remove it . Also remove any unlocked neighbours with
* zero nodes routing via it .
*/
static int nr_dec_obs ( void )
{
struct nr_neigh * nr_neigh ;
struct nr_node * s ;
struct hlist_node * node , * nodet ;
int i ;
spin_lock_bh ( & nr_node_list_lock ) ;
nr_node_for_each_safe ( s , node , nodet , & nr_node_list ) {
nr_node_lock ( s ) ;
for ( i = 0 ; i < s - > count ; i + + ) {
switch ( s - > routes [ i ] . obs_count ) {
case 0 : /* A locked entry */
break ;
case 1 : /* From 1 -> 0 */
nr_neigh = s - > routes [ i ] . neighbour ;
nr_neigh - > count - - ;
nr_neigh_put ( nr_neigh ) ;
if ( nr_neigh - > count = = 0 & & ! nr_neigh - > locked )
nr_remove_neigh ( nr_neigh ) ;
s - > count - - ;
switch ( i ) {
case 0 :
s - > routes [ 0 ] = s - > routes [ 1 ] ;
case 1 :
s - > routes [ 1 ] = s - > routes [ 2 ] ;
case 2 :
break ;
}
break ;
default :
s - > routes [ i ] . obs_count - - ;
break ;
}
}
if ( s - > count < = 0 )
nr_remove_node_locked ( s ) ;
nr_node_unlock ( s ) ;
}
spin_unlock_bh ( & nr_node_list_lock ) ;
return 0 ;
}
/*
* A device has been removed . Remove its routes and neighbours .
*/
void nr_rt_device_down ( struct net_device * dev )
{
struct nr_neigh * s ;
struct hlist_node * node , * nodet , * node2 , * node2t ;
struct nr_node * t ;
int i ;
spin_lock_bh ( & nr_neigh_list_lock ) ;
nr_neigh_for_each_safe ( s , node , nodet , & nr_neigh_list ) {
if ( s - > dev = = dev ) {
spin_lock_bh ( & nr_node_list_lock ) ;
nr_node_for_each_safe ( t , node2 , node2t , & nr_node_list ) {
nr_node_lock ( t ) ;
for ( i = 0 ; i < t - > count ; i + + ) {
if ( t - > routes [ i ] . neighbour = = s ) {
t - > count - - ;
switch ( i ) {
case 0 :
t - > routes [ 0 ] = t - > routes [ 1 ] ;
case 1 :
t - > routes [ 1 ] = t - > routes [ 2 ] ;
case 2 :
break ;
}
}
}
if ( t - > count < = 0 )
nr_remove_node_locked ( t ) ;
nr_node_unlock ( t ) ;
}
spin_unlock_bh ( & nr_node_list_lock ) ;
nr_remove_neigh_locked ( s ) ;
}
}
spin_unlock_bh ( & nr_neigh_list_lock ) ;
}
/*
* Check that the device given is a valid AX .25 interface that is " up " .
* Or a valid ethernet interface with an AX .25 callsign binding .
*/
static struct net_device * nr_ax25_dev_get ( char * devname )
{
struct net_device * dev ;
2007-09-17 22:56:21 +04:00
if ( ( dev = dev_get_by_name ( & init_net , devname ) ) = = NULL )
2005-04-17 02:20:36 +04:00
return NULL ;
if ( ( dev - > flags & IFF_UP ) & & dev - > type = = ARPHRD_AX25 )
return dev ;
dev_put ( dev ) ;
return NULL ;
}
/*
* Find the first active NET / ROM device , usually " nr0 " .
*/
struct net_device * nr_dev_first ( void )
{
struct net_device * dev , * first = NULL ;
read_lock ( & dev_base_lock ) ;
2007-09-17 22:56:21 +04:00
for_each_netdev ( & init_net , dev ) {
2005-04-17 02:20:36 +04:00
if ( ( dev - > flags & IFF_UP ) & & dev - > type = = ARPHRD_NETROM )
if ( first = = NULL | | strncmp ( dev - > name , first - > name , 3 ) < 0 )
first = dev ;
}
if ( first )
dev_hold ( first ) ;
read_unlock ( & dev_base_lock ) ;
return first ;
}
/*
* Find the NET / ROM device for the given callsign .
*/
struct net_device * nr_dev_get ( ax25_address * addr )
{
struct net_device * dev ;
read_lock ( & dev_base_lock ) ;
2007-09-17 22:56:21 +04:00
for_each_netdev ( & init_net , dev ) {
2005-04-17 02:20:36 +04:00
if ( ( dev - > flags & IFF_UP ) & & dev - > type = = ARPHRD_NETROM & & ax25cmp ( addr , ( ax25_address * ) dev - > dev_addr ) = = 0 ) {
dev_hold ( dev ) ;
goto out ;
}
}
2007-05-04 02:13:45 +04:00
dev = NULL ;
2005-04-17 02:20:36 +04:00
out :
read_unlock ( & dev_base_lock ) ;
return dev ;
}
static ax25_digi * nr_call_to_digi ( int ndigis , ax25_address * digipeaters )
{
static ax25_digi ax25_digi ;
int i ;
if ( ndigis = = 0 )
return NULL ;
for ( i = 0 ; i < ndigis ; i + + ) {
ax25_digi . calls [ i ] = digipeaters [ i ] ;
ax25_digi . repeated [ i ] = 0 ;
}
ax25_digi . ndigi = ndigis ;
ax25_digi . lastrepeat = - 1 ;
return & ax25_digi ;
}
/*
* Handle the ioctls that control the routing functions .
*/
int nr_rt_ioctl ( unsigned int cmd , void __user * arg )
{
struct nr_route_struct nr_route ;
struct net_device * dev ;
int ret ;
switch ( cmd ) {
case SIOCADDRT :
if ( copy_from_user ( & nr_route , arg , sizeof ( struct nr_route_struct ) ) )
return - EFAULT ;
if ( ( dev = nr_ax25_dev_get ( nr_route . device ) ) = = NULL )
return - EINVAL ;
if ( nr_route . ndigis < 0 | | nr_route . ndigis > AX25_MAX_DIGIS ) {
dev_put ( dev ) ;
return - EINVAL ;
}
switch ( nr_route . type ) {
case NETROM_NODE :
ret = nr_add_node ( & nr_route . callsign ,
nr_route . mnemonic ,
& nr_route . neighbour ,
nr_call_to_digi ( nr_route . ndigis , nr_route . digipeaters ) ,
dev , nr_route . quality ,
nr_route . obs_count ) ;
break ;
case NETROM_NEIGH :
ret = nr_add_neigh ( & nr_route . callsign ,
nr_call_to_digi ( nr_route . ndigis , nr_route . digipeaters ) ,
dev , nr_route . quality ) ;
break ;
default :
ret = - EINVAL ;
}
dev_put ( dev ) ;
return ret ;
case SIOCDELRT :
if ( copy_from_user ( & nr_route , arg , sizeof ( struct nr_route_struct ) ) )
return - EFAULT ;
if ( ( dev = nr_ax25_dev_get ( nr_route . device ) ) = = NULL )
return - EINVAL ;
switch ( nr_route . type ) {
case NETROM_NODE :
ret = nr_del_node ( & nr_route . callsign ,
& nr_route . neighbour , dev ) ;
break ;
case NETROM_NEIGH :
ret = nr_del_neigh ( & nr_route . callsign ,
dev , nr_route . quality ) ;
break ;
default :
ret = - EINVAL ;
}
dev_put ( dev ) ;
return ret ;
case SIOCNRDECOBS :
return nr_dec_obs ( ) ;
default :
return - EINVAL ;
}
return 0 ;
}
/*
* A level 2 link has timed out , therefore it appears to be a poor link ,
* then don ' t use that neighbour until it is reset .
*/
void nr_link_failed ( ax25_cb * ax25 , int reason )
{
struct nr_neigh * s , * nr_neigh = NULL ;
struct hlist_node * node ;
struct nr_node * nr_node = NULL ;
spin_lock_bh ( & nr_neigh_list_lock ) ;
2006-06-26 11:05:23 +04:00
nr_neigh_for_each ( s , node , & nr_neigh_list ) {
2005-04-17 02:20:36 +04:00
if ( s - > ax25 = = ax25 ) {
nr_neigh_hold ( s ) ;
nr_neigh = s ;
break ;
}
2006-06-26 11:05:23 +04:00
}
2005-04-17 02:20:36 +04:00
spin_unlock_bh ( & nr_neigh_list_lock ) ;
2006-06-26 11:05:23 +04:00
if ( nr_neigh = = NULL )
return ;
2005-04-17 02:20:36 +04:00
nr_neigh - > ax25 = NULL ;
ax25_cb_put ( ax25 ) ;
if ( + + nr_neigh - > failed < sysctl_netrom_link_fails_count ) {
nr_neigh_put ( nr_neigh ) ;
return ;
}
spin_lock_bh ( & nr_node_list_lock ) ;
2006-06-26 11:05:23 +04:00
nr_node_for_each ( nr_node , node , & nr_node_list ) {
2005-04-17 02:20:36 +04:00
nr_node_lock ( nr_node ) ;
2006-06-26 11:05:23 +04:00
if ( nr_node - > which < nr_node - > count & &
nr_node - > routes [ nr_node - > which ] . neighbour = = nr_neigh )
2005-04-17 02:20:36 +04:00
nr_node - > which + + ;
nr_node_unlock ( nr_node ) ;
2006-06-26 11:05:23 +04:00
}
2005-04-17 02:20:36 +04:00
spin_unlock_bh ( & nr_node_list_lock ) ;
nr_neigh_put ( nr_neigh ) ;
}
/*
* Route a frame to an appropriate AX .25 connection . A NULL ax25_cb
* indicates an internally generated frame .
*/
int nr_route_frame ( struct sk_buff * skb , ax25_cb * ax25 )
{
ax25_address * nr_src , * nr_dest ;
struct nr_neigh * nr_neigh ;
struct nr_node * nr_node ;
struct net_device * dev ;
unsigned char * dptr ;
ax25_cb * ax25s ;
int ret ;
struct sk_buff * skbn ;
nr_src = ( ax25_address * ) ( skb - > data + 0 ) ;
nr_dest = ( ax25_address * ) ( skb - > data + 7 ) ;
2006-12-15 02:50:58 +03:00
if ( ax25 ! = NULL ) {
ret = nr_add_node ( nr_src , " " , & ax25 - > dest_addr , ax25 - > digipeat ,
2007-02-09 17:25:09 +03:00
ax25 - > ax25_dev - > dev , 0 ,
sysctl_netrom_obsolescence_count_initialiser ) ;
2006-12-15 02:50:58 +03:00
if ( ret )
return ret ;
}
2005-04-17 02:20:36 +04:00
if ( ( dev = nr_dev_get ( nr_dest ) ) ! = NULL ) { /* Its for me */
if ( ax25 = = NULL ) /* Its from me */
ret = nr_loopback_queue ( skb ) ;
else
ret = nr_rx_frame ( skb , dev ) ;
dev_put ( dev ) ;
return ret ;
}
if ( ! sysctl_netrom_routing_control & & ax25 ! = NULL )
return 0 ;
/* Its Time-To-Live has expired */
if ( skb - > data [ 14 ] = = 1 ) {
return 0 ;
}
nr_node = nr_node_get ( nr_dest ) ;
if ( nr_node = = NULL )
return 0 ;
nr_node_lock ( nr_node ) ;
if ( nr_node - > which > = nr_node - > count ) {
nr_node_unlock ( nr_node ) ;
nr_node_put ( nr_node ) ;
return 0 ;
}
nr_neigh = nr_node - > routes [ nr_node - > which ] . neighbour ;
if ( ( dev = nr_dev_first ( ) ) = = NULL ) {
nr_node_unlock ( nr_node ) ;
nr_node_put ( nr_node ) ;
return 0 ;
}
/* We are going to change the netrom headers so we should get our
own skb , we also did not know until now how much header space
we had to reserve . . . - RXQ */
if ( ( skbn = skb_copy_expand ( skb , dev - > hard_header_len , 0 , GFP_ATOMIC ) ) = = NULL ) {
nr_node_unlock ( nr_node ) ;
nr_node_put ( nr_node ) ;
dev_put ( dev ) ;
return 0 ;
}
kfree_skb ( skb ) ;
skb = skbn ;
skb - > data [ 14 ] - - ;
dptr = skb_push ( skb , 1 ) ;
* dptr = AX25_P_NETROM ;
ax25s = ax25_send_frame ( skb , 256 , ( ax25_address * ) dev - > dev_addr , & nr_neigh - > callsign , nr_neigh - > digipeat , nr_neigh - > dev ) ;
if ( nr_neigh - > ax25 & & ax25s ) {
/* We were already holding this ax25_cb */
ax25_cb_put ( ax25s ) ;
}
nr_neigh - > ax25 = ax25s ;
dev_put ( dev ) ;
ret = ( nr_neigh - > ax25 ! = NULL ) ;
nr_node_unlock ( nr_node ) ;
nr_node_put ( nr_node ) ;
2006-12-15 02:50:58 +03:00
2005-04-17 02:20:36 +04:00
return ret ;
}
# ifdef CONFIG_PROC_FS
static void * nr_node_start ( struct seq_file * seq , loff_t * pos )
{
struct nr_node * nr_node ;
struct hlist_node * node ;
int i = 1 ;
2007-02-09 17:25:09 +03:00
spin_lock_bh ( & nr_node_list_lock ) ;
2005-04-17 02:20:36 +04:00
if ( * pos = = 0 )
return SEQ_START_TOKEN ;
nr_node_for_each ( nr_node , node , & nr_node_list ) {
if ( i = = * pos )
return nr_node ;
+ + i ;
}
return NULL ;
}
static void * nr_node_next ( struct seq_file * seq , void * v , loff_t * pos )
{
struct hlist_node * node ;
+ + * pos ;
2007-02-09 17:25:09 +03:00
node = ( v = = SEQ_START_TOKEN )
2005-04-17 02:20:36 +04:00
? nr_node_list . first
: ( ( struct nr_node * ) v ) - > node_node . next ;
return hlist_entry ( node , struct nr_node , node_node ) ;
}
static void nr_node_stop ( struct seq_file * seq , void * v )
{
spin_unlock_bh ( & nr_node_list_lock ) ;
}
static int nr_node_show ( struct seq_file * seq , void * v )
{
2005-09-07 02:49:39 +04:00
char buf [ 11 ] ;
2005-04-17 02:20:36 +04:00
int i ;
if ( v = = SEQ_START_TOKEN )
seq_puts ( seq ,
" callsign mnemonic w n qual obs neigh qual obs neigh qual obs neigh \n " ) ;
else {
struct nr_node * nr_node = v ;
nr_node_lock ( nr_node ) ;
seq_printf ( seq , " %-9s %-7s %d %d " ,
2005-09-07 02:49:39 +04:00
ax2asc ( buf , & nr_node - > callsign ) ,
2005-04-17 02:20:36 +04:00
( nr_node - > mnemonic [ 0 ] = = ' \0 ' ) ? " * " : nr_node - > mnemonic ,
nr_node - > which + 1 ,
nr_node - > count ) ;
for ( i = 0 ; i < nr_node - > count ; i + + ) {
seq_printf ( seq , " %3d %d %05d " ,
nr_node - > routes [ i ] . quality ,
nr_node - > routes [ i ] . obs_count ,
nr_node - > routes [ i ] . neighbour - > number ) ;
}
nr_node_unlock ( nr_node ) ;
seq_puts ( seq , " \n " ) ;
}
return 0 ;
}
2007-07-11 10:07:31 +04:00
static const struct seq_operations nr_node_seqops = {
2005-04-17 02:20:36 +04:00
. start = nr_node_start ,
. next = nr_node_next ,
. stop = nr_node_stop ,
. show = nr_node_show ,
} ;
static int nr_node_info_open ( struct inode * inode , struct file * file )
{
return seq_open ( file , & nr_node_seqops ) ;
}
2007-02-12 11:55:36 +03:00
const struct file_operations nr_nodes_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. open = nr_node_info_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = seq_release ,
} ;
static void * nr_neigh_start ( struct seq_file * seq , loff_t * pos )
{
struct nr_neigh * nr_neigh ;
struct hlist_node * node ;
int i = 1 ;
spin_lock_bh ( & nr_neigh_list_lock ) ;
if ( * pos = = 0 )
return SEQ_START_TOKEN ;
nr_neigh_for_each ( nr_neigh , node , & nr_neigh_list ) {
if ( i = = * pos )
return nr_neigh ;
}
return NULL ;
}
static void * nr_neigh_next ( struct seq_file * seq , void * v , loff_t * pos )
{
struct hlist_node * node ;
+ + * pos ;
2007-02-09 17:25:09 +03:00
node = ( v = = SEQ_START_TOKEN )
2005-04-17 02:20:36 +04:00
? nr_neigh_list . first
: ( ( struct nr_neigh * ) v ) - > neigh_node . next ;
return hlist_entry ( node , struct nr_neigh , neigh_node ) ;
}
static void nr_neigh_stop ( struct seq_file * seq , void * v )
{
spin_unlock_bh ( & nr_neigh_list_lock ) ;
}
static int nr_neigh_show ( struct seq_file * seq , void * v )
{
2005-09-07 02:49:39 +04:00
char buf [ 11 ] ;
2005-04-17 02:20:36 +04:00
int i ;
if ( v = = SEQ_START_TOKEN )
seq_puts ( seq , " addr callsign dev qual lock count failed digipeaters \n " ) ;
else {
struct nr_neigh * nr_neigh = v ;
seq_printf ( seq , " %05d %-9s %-4s %3d %d %3d %3d " ,
nr_neigh - > number ,
2005-09-07 02:49:39 +04:00
ax2asc ( buf , & nr_neigh - > callsign ) ,
2005-04-17 02:20:36 +04:00
nr_neigh - > dev ? nr_neigh - > dev - > name : " ??? " ,
nr_neigh - > quality ,
nr_neigh - > locked ,
nr_neigh - > count ,
nr_neigh - > failed ) ;
if ( nr_neigh - > digipeat ! = NULL ) {
for ( i = 0 ; i < nr_neigh - > digipeat - > ndigi ; i + + )
2007-02-09 17:25:09 +03:00
seq_printf ( seq , " %s " ,
2005-09-07 02:49:39 +04:00
ax2asc ( buf , & nr_neigh - > digipeat - > calls [ i ] ) ) ;
2005-04-17 02:20:36 +04:00
}
seq_puts ( seq , " \n " ) ;
}
return 0 ;
}
2007-07-11 10:07:31 +04:00
static const struct seq_operations nr_neigh_seqops = {
2005-04-17 02:20:36 +04:00
. start = nr_neigh_start ,
. next = nr_neigh_next ,
. stop = nr_neigh_stop ,
. show = nr_neigh_show ,
} ;
static int nr_neigh_info_open ( struct inode * inode , struct file * file )
{
return seq_open ( file , & nr_neigh_seqops ) ;
}
2007-02-12 11:55:36 +03:00
const struct file_operations nr_neigh_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. open = nr_neigh_info_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = seq_release ,
} ;
# endif
/*
* Free all memory associated with the nodes and routes lists .
*/
void __exit nr_rt_free ( void )
{
struct nr_neigh * s = NULL ;
struct nr_node * t = NULL ;
struct hlist_node * node , * nodet ;
spin_lock_bh ( & nr_neigh_list_lock ) ;
spin_lock_bh ( & nr_node_list_lock ) ;
nr_node_for_each_safe ( t , node , nodet , & nr_node_list ) {
nr_node_lock ( t ) ;
nr_remove_node_locked ( t ) ;
nr_node_unlock ( t ) ;
}
nr_neigh_for_each_safe ( s , node , nodet , & nr_neigh_list ) {
while ( s - > count ) {
s - > count - - ;
nr_neigh_put ( s ) ;
}
nr_remove_neigh_locked ( s ) ;
}
spin_unlock_bh ( & nr_node_list_lock ) ;
spin_unlock_bh ( & nr_neigh_list_lock ) ;
}