2005-04-17 02:20:36 +04:00
/*
* DECnet An implementation of the DECnet protocol suite for the LINUX
* operating system . DECnet is implemented using the BSD Socket
* interface as the means of communication with the user level .
*
* DECnet Routing Forwarding Information Base ( Routing Tables )
*
* Author : Steve Whitehouse < SteveW @ ACM . org >
* Mostly copied from the IPv4 routing code
*
*
* Changes :
*
*/
# include <linux/string.h>
# include <linux/net.h>
# include <linux/socket.h>
# include <linux/sockios.h>
# include <linux/init.h>
# include <linux/skbuff.h>
# include <linux/netlink.h>
# include <linux/rtnetlink.h>
# include <linux/proc_fs.h>
# include <linux/netdevice.h>
# include <linux/timer.h>
# include <linux/spinlock.h>
# include <asm/atomic.h>
# include <asm/uaccess.h>
# include <linux/route.h> /* RTF_xxx */
# include <net/neighbour.h>
2007-03-26 10:06:12 +04:00
# include <net/netlink.h>
2005-04-17 02:20:36 +04:00
# include <net/dst.h>
# include <net/flow.h>
2006-08-10 02:56:46 +04:00
# include <net/fib_rules.h>
2005-04-17 02:20:36 +04:00
# include <net/dn.h>
# include <net/dn_route.h>
# include <net/dn_fib.h>
# include <net/dn_neigh.h>
# include <net/dn_dev.h>
struct dn_zone
{
struct dn_zone * dz_next ;
struct dn_fib_node * * dz_hash ;
int dz_nent ;
int dz_divisor ;
u32 dz_hashmask ;
# define DZ_HASHMASK(dz) ((dz)->dz_hashmask)
int dz_order ;
2006-03-21 09:42:39 +03:00
__le16 dz_mask ;
2005-04-17 02:20:36 +04:00
# define DZ_MASK(dz) ((dz)->dz_mask)
} ;
struct dn_hash
{
struct dn_zone * dh_zones [ 17 ] ;
struct dn_zone * dh_zone_list ;
} ;
# define dz_key_0(key) ((key).datum = 0)
# define dz_prefix(key,dz) ((key).datum)
# define for_nexthops(fi) { int nhsel; const struct dn_fib_nh *nh;\
2007-02-09 17:24:40 +03:00
for ( nhsel = 0 , nh = ( fi ) - > fib_nh ; nhsel < ( fi ) - > fib_nhs ; nh + + , nhsel + + )
2005-04-17 02:20:36 +04:00
# define endfor_nexthops(fi) }
# define DN_MAX_DIVISOR 1024
# define DN_S_ZOMBIE 1
# define DN_S_ACCESSED 2
# define DN_FIB_SCAN(f, fp) \
for ( ; ( ( f ) = * ( fp ) ) ! = NULL ; ( fp ) = & ( f ) - > fn_next )
# define DN_FIB_SCAN_KEY(f, fp, key) \
for ( ; ( ( f ) = * ( fp ) ) ! = NULL & & dn_key_eq ( ( f ) - > fn_key , ( key ) ) ; ( fp ) = & ( f ) - > fn_next )
# define RT_TABLE_MIN 1
2006-08-11 10:11:47 +04:00
# define DN_FIB_TABLE_HASHSZ 256
static struct hlist_head dn_fib_table_hash [ DN_FIB_TABLE_HASHSZ ] ;
2005-04-17 02:20:36 +04:00
static DEFINE_RWLOCK ( dn_fib_tables_lock ) ;
2006-12-07 07:33:20 +03:00
static struct kmem_cache * dn_hash_kmem __read_mostly ;
2005-04-17 02:20:36 +04:00
static int dn_fib_hash_zombies ;
static inline dn_fib_idx_t dn_hash ( dn_fib_key_t key , struct dn_zone * dz )
{
2008-11-27 11:12:47 +03:00
u16 h = le16_to_cpu ( key . datum ) > > ( 16 - dz - > dz_order ) ;
2005-04-17 02:20:36 +04:00
h ^ = ( h > > 10 ) ;
h ^ = ( h > > 6 ) ;
h & = DZ_HASHMASK ( dz ) ;
return * ( dn_fib_idx_t * ) & h ;
}
2006-03-21 09:42:39 +03:00
static inline dn_fib_key_t dz_key ( __le16 dst , struct dn_zone * dz )
2005-04-17 02:20:36 +04:00
{
dn_fib_key_t k ;
k . datum = dst & DZ_MASK ( dz ) ;
return k ;
}
static inline struct dn_fib_node * * dn_chain_p ( dn_fib_key_t key , struct dn_zone * dz )
{
return & dz - > dz_hash [ dn_hash ( key , dz ) . datum ] ;
}
static inline struct dn_fib_node * dz_chain ( dn_fib_key_t key , struct dn_zone * dz )
{
return dz - > dz_hash [ dn_hash ( key , dz ) . datum ] ;
}
static inline int dn_key_eq ( dn_fib_key_t a , dn_fib_key_t b )
{
return a . datum = = b . datum ;
}
static inline int dn_key_leq ( dn_fib_key_t a , dn_fib_key_t b )
{
return a . datum < = b . datum ;
}
static inline void dn_rebuild_zone ( struct dn_zone * dz ,
struct dn_fib_node * * old_ht ,
int old_divisor )
{
int i ;
struct dn_fib_node * f , * * fp , * next ;
for ( i = 0 ; i < old_divisor ; i + + ) {
for ( f = old_ht [ i ] ; f ; f = f - > fn_next ) {
next = f - > fn_next ;
for ( fp = dn_chain_p ( f - > fn_key , dz ) ;
* fp & & dn_key_leq ( ( * fp ) - > fn_key , f - > fn_key ) ;
fp = & ( * fp ) - > fn_next )
/* NOTHING */ ;
f - > fn_next = * fp ;
* fp = f ;
}
}
}
static void dn_rehash_zone ( struct dn_zone * dz )
{
struct dn_fib_node * * ht , * * old_ht ;
int old_divisor , new_divisor ;
u32 new_hashmask ;
old_divisor = dz - > dz_divisor ;
switch ( old_divisor ) {
case 16 :
new_divisor = 256 ;
new_hashmask = 0xFF ;
break ;
default :
printk ( KERN_DEBUG " DECnet: dn_rehash_zone: BUG! %d \n " , old_divisor ) ;
case 256 :
new_divisor = 1024 ;
new_hashmask = 0x3FF ;
break ;
}
2006-07-22 01:51:30 +04:00
ht = kcalloc ( new_divisor , sizeof ( struct dn_fib_node * ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ht = = NULL )
return ;
write_lock_bh ( & dn_fib_tables_lock ) ;
old_ht = dz - > dz_hash ;
dz - > dz_hash = ht ;
dz - > dz_hashmask = new_hashmask ;
dz - > dz_divisor = new_divisor ;
dn_rebuild_zone ( dz , old_ht , old_divisor ) ;
write_unlock_bh ( & dn_fib_tables_lock ) ;
kfree ( old_ht ) ;
}
static void dn_free_node ( struct dn_fib_node * f )
{
dn_fib_release_info ( DN_FIB_INFO ( f ) ) ;
kmem_cache_free ( dn_hash_kmem , f ) ;
}
static struct dn_zone * dn_new_zone ( struct dn_hash * table , int z )
{
int i ;
2006-07-22 01:51:30 +04:00
struct dn_zone * dz = kzalloc ( sizeof ( struct dn_zone ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! dz )
return NULL ;
if ( z ) {
dz - > dz_divisor = 16 ;
dz - > dz_hashmask = 0x0F ;
} else {
dz - > dz_divisor = 1 ;
dz - > dz_hashmask = 0 ;
}
2006-07-22 01:51:30 +04:00
dz - > dz_hash = kcalloc ( dz - > dz_divisor , sizeof ( struct dn_fib_node * ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! dz - > dz_hash ) {
kfree ( dz ) ;
return NULL ;
}
dz - > dz_order = z ;
dz - > dz_mask = dnet_make_mask ( z ) ;
for ( i = z + 1 ; i < = 16 ; i + + )
if ( table - > dh_zones [ i ] )
break ;
write_lock_bh ( & dn_fib_tables_lock ) ;
if ( i > 16 ) {
dz - > dz_next = table - > dh_zone_list ;
table - > dh_zone_list = dz ;
} else {
dz - > dz_next = table - > dh_zones [ i ] - > dz_next ;
table - > dh_zones [ i ] - > dz_next = dz ;
}
table - > dh_zones [ z ] = dz ;
write_unlock_bh ( & dn_fib_tables_lock ) ;
return dz ;
}
static int dn_fib_nh_match ( struct rtmsg * r , struct nlmsghdr * nlh , struct dn_kern_rta * rta , struct dn_fib_info * fi )
{
struct rtnexthop * nhp ;
int nhlen ;
if ( rta - > rta_priority & & * rta - > rta_priority ! = fi - > fib_priority )
return 1 ;
if ( rta - > rta_oif | | rta - > rta_gw ) {
if ( ( ! rta - > rta_oif | | * rta - > rta_oif = = fi - > fib_nh - > nh_oif ) & &
( ! rta - > rta_gw | | memcmp ( rta - > rta_gw , & fi - > fib_nh - > nh_gw , 2 ) = = 0 ) )
return 0 ;
return 1 ;
}
if ( rta - > rta_mp = = NULL )
return 0 ;
nhp = RTA_DATA ( rta - > rta_mp ) ;
nhlen = RTA_PAYLOAD ( rta - > rta_mp ) ;
for_nexthops ( fi ) {
int attrlen = nhlen - sizeof ( struct rtnexthop ) ;
2006-03-21 09:42:39 +03:00
__le16 gw ;
2005-04-17 02:20:36 +04:00
if ( attrlen < 0 | | ( nhlen - = nhp - > rtnh_len ) < 0 )
return - EINVAL ;
if ( nhp - > rtnh_ifindex & & nhp - > rtnh_ifindex ! = nh - > nh_oif )
return 1 ;
if ( attrlen ) {
gw = dn_fib_get_attr16 ( RTNH_DATA ( nhp ) , attrlen , RTA_GATEWAY ) ;
if ( gw & & gw ! = nh - > nh_gw )
return 1 ;
}
nhp = RTNH_NEXT ( nhp ) ;
} endfor_nexthops ( fi ) ;
return 0 ;
}
2006-11-11 01:10:15 +03:00
static inline size_t dn_fib_nlmsg_size ( struct dn_fib_info * fi )
{
2006-11-13 10:02:01 +03:00
size_t payload = NLMSG_ALIGN ( sizeof ( struct rtmsg ) )
2006-11-11 01:10:15 +03:00
+ nla_total_size ( 4 ) /* RTA_TABLE */
+ nla_total_size ( 2 ) /* RTA_DST */
+ nla_total_size ( 4 ) ; /* RTA_PRIORITY */
/* space for nested metrics */
payload + = nla_total_size ( ( RTAX_MAX * nla_total_size ( 4 ) ) ) ;
if ( fi - > fib_nhs ) {
/* Also handles the special case fib_nhs == 1 */
/* each nexthop is packed in an attribute */
size_t nhsize = nla_total_size ( sizeof ( struct rtnexthop ) ) ;
/* may contain a gateway attribute */
nhsize + = nla_total_size ( 4 ) ;
/* all nexthops are packed in a nested attribute */
payload + = nla_total_size ( fi - > fib_nhs * nhsize ) ;
}
return payload ;
}
2005-04-17 02:20:36 +04:00
static int dn_fib_dump_info ( struct sk_buff * skb , u32 pid , u32 seq , int event ,
2007-02-09 17:24:40 +03:00
u32 tb_id , u8 type , u8 scope , void * dst , int dst_len ,
struct dn_fib_info * fi , unsigned int flags )
2005-04-17 02:20:36 +04:00
{
2007-02-09 17:24:40 +03:00
struct rtmsg * rtm ;
struct nlmsghdr * nlh ;
2007-04-20 07:29:13 +04:00
unsigned char * b = skb_tail_pointer ( skb ) ;
2007-02-09 17:24:40 +03:00
nlh = NLMSG_NEW ( skb , pid , seq , event , sizeof ( * rtm ) , flags ) ;
rtm = NLMSG_DATA ( nlh ) ;
rtm - > rtm_family = AF_DECnet ;
rtm - > rtm_dst_len = dst_len ;
rtm - > rtm_src_len = 0 ;
rtm - > rtm_tos = 0 ;
rtm - > rtm_table = tb_id ;
2006-08-11 10:09:48 +04:00
RTA_PUT_U32 ( skb , RTA_TABLE , tb_id ) ;
2007-02-09 17:24:40 +03:00
rtm - > rtm_flags = fi - > fib_flags ;
rtm - > rtm_scope = scope ;
2005-04-17 02:20:36 +04:00
rtm - > rtm_type = type ;
2007-02-09 17:24:40 +03:00
if ( rtm - > rtm_dst_len )
RTA_PUT ( skb , RTA_DST , 2 , dst ) ;
rtm - > rtm_protocol = fi - > fib_protocol ;
if ( fi - > fib_priority )
RTA_PUT ( skb , RTA_PRIORITY , 4 , & fi - > fib_priority ) ;
2005-04-17 02:20:36 +04:00
if ( rtnetlink_put_metrics ( skb , fi - > fib_metrics ) < 0 )
goto rtattr_failure ;
2007-02-09 17:24:40 +03:00
if ( fi - > fib_nhs = = 1 ) {
if ( fi - > fib_nh - > nh_gw )
RTA_PUT ( skb , RTA_GATEWAY , 2 , & fi - > fib_nh - > nh_gw ) ;
if ( fi - > fib_nh - > nh_oif )
RTA_PUT ( skb , RTA_OIF , sizeof ( int ) , & fi - > fib_nh - > nh_oif ) ;
}
if ( fi - > fib_nhs > 1 ) {
struct rtnexthop * nhp ;
struct rtattr * mp_head ;
if ( skb_tailroom ( skb ) < = RTA_SPACE ( 0 ) )
goto rtattr_failure ;
mp_head = ( struct rtattr * ) skb_put ( skb , RTA_SPACE ( 0 ) ) ;
for_nexthops ( fi ) {
if ( skb_tailroom ( skb ) < RTA_ALIGN ( RTA_ALIGN ( sizeof ( * nhp ) ) + 4 ) )
goto rtattr_failure ;
nhp = ( struct rtnexthop * ) skb_put ( skb , RTA_ALIGN ( sizeof ( * nhp ) ) ) ;
nhp - > rtnh_flags = nh - > nh_flags & 0xFF ;
nhp - > rtnh_hops = nh - > nh_weight - 1 ;
nhp - > rtnh_ifindex = nh - > nh_oif ;
if ( nh - > nh_gw )
RTA_PUT ( skb , RTA_GATEWAY , 2 , & nh - > nh_gw ) ;
2007-04-20 07:29:13 +04:00
nhp - > rtnh_len = skb_tail_pointer ( skb ) - ( unsigned char * ) nhp ;
2007-02-09 17:24:40 +03:00
} endfor_nexthops ( fi ) ;
mp_head - > rta_type = RTA_MULTIPATH ;
2007-04-20 07:29:13 +04:00
mp_head - > rta_len = skb_tail_pointer ( skb ) - ( u8 * ) mp_head ;
2007-02-09 17:24:40 +03:00
}
2007-04-20 07:29:13 +04:00
nlh - > nlmsg_len = skb_tail_pointer ( skb ) - b ;
2007-02-09 17:24:40 +03:00
return skb - > len ;
2005-04-17 02:20:36 +04:00
nlmsg_failure :
rtattr_failure :
2007-03-26 10:06:12 +04:00
nlmsg_trim ( skb , b ) ;
2007-02-09 17:24:40 +03:00
return - EMSGSIZE ;
2005-04-17 02:20:36 +04:00
}
2006-08-11 10:08:33 +04:00
static void dn_rtmsg_fib ( int event , struct dn_fib_node * f , int z , u32 tb_id ,
2007-02-09 17:24:40 +03:00
struct nlmsghdr * nlh , struct netlink_skb_parms * req )
2005-04-17 02:20:36 +04:00
{
2007-02-09 17:24:40 +03:00
struct sk_buff * skb ;
u32 pid = req ? req - > pid : 0 ;
2006-08-15 11:33:35 +04:00
int err = - ENOBUFS ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:40 +03:00
skb = nlmsg_new ( dn_fib_nlmsg_size ( DN_FIB_INFO ( f ) ) , GFP_KERNEL ) ;
if ( skb = = NULL )
2006-08-15 11:33:35 +04:00
goto errout ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:40 +03:00
err = dn_fib_dump_info ( skb , pid , nlh - > nlmsg_seq , event , tb_id ,
2006-08-15 11:33:35 +04:00
f - > fn_type , f - > fn_scope , & f - > fn_key , z ,
DN_FIB_INFO ( f ) , 0 ) ;
2007-02-01 10:16:40 +03:00
if ( err < 0 ) {
/* -EMSGSIZE implies BUG in dn_fib_nlmsg_size() */
WARN_ON ( err = = - EMSGSIZE ) ;
kfree_skb ( skb ) ;
goto errout ;
}
2009-02-25 10:18:28 +03:00
rtnl_notify ( skb , & init_net , pid , RTNLGRP_DECnet_ROUTE , nlh , GFP_KERNEL ) ;
return ;
2006-08-15 11:33:35 +04:00
errout :
if ( err < 0 )
2007-11-20 09:26:51 +03:00
rtnl_set_sk_err ( & init_net , RTNLGRP_DECnet_ROUTE , err ) ;
2005-04-17 02:20:36 +04:00
}
2007-02-09 17:24:40 +03:00
static __inline__ int dn_hash_dump_bucket ( struct sk_buff * skb ,
2005-04-17 02:20:36 +04:00
struct netlink_callback * cb ,
struct dn_fib_table * tb ,
struct dn_zone * dz ,
struct dn_fib_node * f )
{
int i , s_i ;
2006-08-11 10:11:47 +04:00
s_i = cb - > args [ 4 ] ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; f ; i + + , f = f - > fn_next ) {
if ( i < s_i )
continue ;
if ( f - > fn_state & DN_S_ZOMBIE )
continue ;
2007-02-09 17:24:40 +03:00
if ( dn_fib_dump_info ( skb , NETLINK_CB ( cb - > skb ) . pid ,
2005-04-17 02:20:36 +04:00
cb - > nlh - > nlmsg_seq ,
RTM_NEWROUTE ,
2007-02-09 17:24:40 +03:00
tb - > n ,
2005-04-17 02:20:36 +04:00
( f - > fn_state & DN_S_ZOMBIE ) ? 0 : f - > fn_type ,
2007-02-09 17:24:40 +03:00
f - > fn_scope , & f - > fn_key , dz - > dz_order ,
2005-06-19 09:54:12 +04:00
f - > fn_info , NLM_F_MULTI ) < 0 ) {
2006-08-11 10:11:47 +04:00
cb - > args [ 4 ] = i ;
2005-04-17 02:20:36 +04:00
return - 1 ;
}
}
2006-08-11 10:11:47 +04:00
cb - > args [ 4 ] = i ;
2005-04-17 02:20:36 +04:00
return skb - > len ;
}
2007-02-09 17:24:40 +03:00
static __inline__ int dn_hash_dump_zone ( struct sk_buff * skb ,
2005-04-17 02:20:36 +04:00
struct netlink_callback * cb ,
struct dn_fib_table * tb ,
struct dn_zone * dz )
{
int h , s_h ;
2006-08-11 10:11:47 +04:00
s_h = cb - > args [ 3 ] ;
2005-04-17 02:20:36 +04:00
for ( h = 0 ; h < dz - > dz_divisor ; h + + ) {
if ( h < s_h )
continue ;
if ( h > s_h )
2006-08-11 10:11:47 +04:00
memset ( & cb - > args [ 4 ] , 0 , sizeof ( cb - > args ) - 4 * sizeof ( cb - > args [ 0 ] ) ) ;
2005-04-17 02:20:36 +04:00
if ( dz - > dz_hash = = NULL | | dz - > dz_hash [ h ] = = NULL )
continue ;
if ( dn_hash_dump_bucket ( skb , cb , tb , dz , dz - > dz_hash [ h ] ) < 0 ) {
2006-08-11 10:11:47 +04:00
cb - > args [ 3 ] = h ;
2005-04-17 02:20:36 +04:00
return - 1 ;
}
}
2006-08-11 10:11:47 +04:00
cb - > args [ 3 ] = h ;
2005-04-17 02:20:36 +04:00
return skb - > len ;
}
2007-02-09 17:24:40 +03:00
static int dn_fib_table_dump ( struct dn_fib_table * tb , struct sk_buff * skb ,
struct netlink_callback * cb )
2005-04-17 02:20:36 +04:00
{
2007-02-09 17:24:40 +03:00
int m , s_m ;
2005-04-17 02:20:36 +04:00
struct dn_zone * dz ;
struct dn_hash * table = ( struct dn_hash * ) tb - > data ;
2006-08-11 10:11:47 +04:00
s_m = cb - > args [ 2 ] ;
2005-04-17 02:20:36 +04:00
read_lock ( & dn_fib_tables_lock ) ;
for ( dz = table - > dh_zone_list , m = 0 ; dz ; dz = dz - > dz_next , m + + ) {
if ( m < s_m )
continue ;
if ( m > s_m )
2006-08-11 10:11:47 +04:00
memset ( & cb - > args [ 3 ] , 0 , sizeof ( cb - > args ) - 3 * sizeof ( cb - > args [ 0 ] ) ) ;
2005-04-17 02:20:36 +04:00
if ( dn_hash_dump_zone ( skb , cb , tb , dz ) < 0 ) {
2006-08-11 10:11:47 +04:00
cb - > args [ 2 ] = m ;
2005-04-17 02:20:36 +04:00
read_unlock ( & dn_fib_tables_lock ) ;
return - 1 ;
}
}
read_unlock ( & dn_fib_tables_lock ) ;
2006-08-11 10:11:47 +04:00
cb - > args [ 2 ] = m ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:40 +03:00
return skb - > len ;
2005-04-17 02:20:36 +04:00
}
2006-08-11 10:11:47 +04:00
int dn_fib_dump ( struct sk_buff * skb , struct netlink_callback * cb )
{
2008-03-25 20:26:21 +03:00
struct net * net = sock_net ( skb - > sk ) ;
2006-08-11 10:11:47 +04:00
unsigned int h , s_h ;
unsigned int e = 0 , s_e ;
struct dn_fib_table * tb ;
struct hlist_node * node ;
int dumped = 0 ;
2009-11-26 02:14:13 +03:00
if ( ! net_eq ( net , & init_net ) )
2007-11-30 16:21:31 +03:00
return 0 ;
2006-08-11 10:11:47 +04:00
if ( NLMSG_PAYLOAD ( cb - > nlh , 0 ) > = sizeof ( struct rtmsg ) & &
( ( struct rtmsg * ) NLMSG_DATA ( cb - > nlh ) ) - > rtm_flags & RTM_F_CLONED )
return dn_cache_dump ( skb , cb ) ;
s_h = cb - > args [ 0 ] ;
s_e = cb - > args [ 1 ] ;
for ( h = s_h ; h < DN_FIB_TABLE_HASHSZ ; h + + , s_h = 0 ) {
e = 0 ;
hlist_for_each_entry ( tb , node , & dn_fib_table_hash [ h ] , hlist ) {
if ( e < s_e )
goto next ;
if ( dumped )
memset ( & cb - > args [ 2 ] , 0 , sizeof ( cb - > args ) -
2007-02-09 17:24:40 +03:00
2 * sizeof ( cb - > args [ 0 ] ) ) ;
2006-08-11 10:11:47 +04:00
if ( tb - > dump ( tb , skb , cb ) < 0 )
goto out ;
dumped = 1 ;
next :
e + + ;
}
}
out :
cb - > args [ 1 ] = e ;
cb - > args [ 0 ] = h ;
return skb - > len ;
}
2005-04-17 02:20:36 +04:00
static int dn_fib_table_insert ( struct dn_fib_table * tb , struct rtmsg * r , struct dn_kern_rta * rta , struct nlmsghdr * n , struct netlink_skb_parms * req )
{
struct dn_hash * table = ( struct dn_hash * ) tb - > data ;
struct dn_fib_node * new_f , * f , * * fp , * * del_fp ;
struct dn_zone * dz ;
struct dn_fib_info * fi ;
2007-02-09 17:24:40 +03:00
int z = r - > rtm_dst_len ;
2005-04-17 02:20:36 +04:00
int type = r - > rtm_type ;
dn_fib_key_t key ;
2007-02-09 17:24:40 +03:00
int err ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:40 +03:00
if ( z > 16 )
return - EINVAL ;
2005-04-17 02:20:36 +04:00
dz = table - > dh_zones [ z ] ;
if ( ! dz & & ! ( dz = dn_new_zone ( table , z ) ) )
return - ENOBUFS ;
dz_key_0 ( key ) ;
if ( rta - > rta_dst ) {
2006-03-21 09:42:39 +03:00
__le16 dst ;
2005-04-17 02:20:36 +04:00
memcpy ( & dst , rta - > rta_dst , 2 ) ;
if ( dst & ~ DZ_MASK ( dz ) )
return - EINVAL ;
key = dz_key ( dst , dz ) ;
}
2007-02-09 17:24:40 +03:00
if ( ( fi = dn_fib_create_info ( r , rta , n , & err ) ) = = NULL )
return err ;
2005-04-17 02:20:36 +04:00
if ( dz - > dz_nent > ( dz - > dz_divisor < < 2 ) & &
dz - > dz_divisor > DN_MAX_DIVISOR & &
( z = = 16 | | ( 1 < < z ) > dz - > dz_divisor ) )
dn_rehash_zone ( dz ) ;
fp = dn_chain_p ( key , dz ) ;
DN_FIB_SCAN ( f , fp ) {
if ( dn_key_leq ( key , f - > fn_key ) )
break ;
}
del_fp = NULL ;
if ( f & & ( f - > fn_state & DN_S_ZOMBIE ) & &
dn_key_eq ( f - > fn_key , key ) ) {
del_fp = fp ;
fp = & f - > fn_next ;
f = * fp ;
goto create ;
}
DN_FIB_SCAN_KEY ( f , fp , key ) {
if ( fi - > fib_priority < = DN_FIB_INFO ( f ) - > fib_priority )
break ;
}
if ( f & & dn_key_eq ( f - > fn_key , key ) & &
fi - > fib_priority = = DN_FIB_INFO ( f ) - > fib_priority ) {
struct dn_fib_node * * ins_fp ;
err = - EEXIST ;
if ( n - > nlmsg_flags & NLM_F_EXCL )
goto out ;
if ( n - > nlmsg_flags & NLM_F_REPLACE ) {
del_fp = fp ;
fp = & f - > fn_next ;
f = * fp ;
goto replace ;
}
ins_fp = fp ;
err = - EEXIST ;
DN_FIB_SCAN_KEY ( f , fp , key ) {
if ( fi - > fib_priority ! = DN_FIB_INFO ( f ) - > fib_priority )
break ;
if ( f - > fn_type = = type & & f - > fn_scope = = r - > rtm_scope
& & DN_FIB_INFO ( f ) = = fi )
goto out ;
}
if ( ! ( n - > nlmsg_flags & NLM_F_APPEND ) ) {
fp = ins_fp ;
f = * fp ;
}
}
create :
err = - ENOENT ;
if ( ! ( n - > nlmsg_flags & NLM_F_CREATE ) )
goto out ;
replace :
err = - ENOBUFS ;
2007-02-10 12:45:03 +03:00
new_f = kmem_cache_zalloc ( dn_hash_kmem , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( new_f = = NULL )
goto out ;
new_f - > fn_key = key ;
new_f - > fn_type = type ;
new_f - > fn_scope = r - > rtm_scope ;
DN_FIB_INFO ( new_f ) = fi ;
new_f - > fn_next = f ;
write_lock_bh ( & dn_fib_tables_lock ) ;
* fp = new_f ;
write_unlock_bh ( & dn_fib_tables_lock ) ;
dz - > dz_nent + + ;
if ( del_fp ) {
f = * del_fp ;
write_lock_bh ( & dn_fib_tables_lock ) ;
* del_fp = f - > fn_next ;
write_unlock_bh ( & dn_fib_tables_lock ) ;
if ( ! ( f - > fn_state & DN_S_ZOMBIE ) )
dn_rtmsg_fib ( RTM_DELROUTE , f , z , tb - > n , n , req ) ;
if ( f - > fn_state & DN_S_ACCESSED )
dn_rt_cache_flush ( - 1 ) ;
dn_free_node ( f ) ;
dz - > dz_nent - - ;
} else {
dn_rt_cache_flush ( - 1 ) ;
}
2007-02-09 17:24:40 +03:00
dn_rtmsg_fib ( RTM_NEWROUTE , new_f , z , tb - > n , n , req ) ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:40 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
out :
dn_fib_release_info ( fi ) ;
return err ;
}
static int dn_fib_table_delete ( struct dn_fib_table * tb , struct rtmsg * r , struct dn_kern_rta * rta , struct nlmsghdr * n , struct netlink_skb_parms * req )
{
struct dn_hash * table = ( struct dn_hash * ) tb - > data ;
struct dn_fib_node * * fp , * * del_fp , * f ;
2007-02-09 17:24:40 +03:00
int z = r - > rtm_dst_len ;
2005-04-17 02:20:36 +04:00
struct dn_zone * dz ;
dn_fib_key_t key ;
int matched ;
2007-02-09 17:24:40 +03:00
if ( z > 16 )
return - EINVAL ;
2005-04-17 02:20:36 +04:00
if ( ( dz = table - > dh_zones [ z ] ) = = NULL )
return - ESRCH ;
dz_key_0 ( key ) ;
if ( rta - > rta_dst ) {
2006-03-21 09:42:39 +03:00
__le16 dst ;
2005-04-17 02:20:36 +04:00
memcpy ( & dst , rta - > rta_dst , 2 ) ;
if ( dst & ~ DZ_MASK ( dz ) )
return - EINVAL ;
key = dz_key ( dst , dz ) ;
}
fp = dn_chain_p ( key , dz ) ;
DN_FIB_SCAN ( f , fp ) {
if ( dn_key_eq ( f - > fn_key , key ) )
break ;
if ( dn_key_leq ( key , f - > fn_key ) )
return - ESRCH ;
}
matched = 0 ;
del_fp = NULL ;
DN_FIB_SCAN_KEY ( f , fp , key ) {
struct dn_fib_info * fi = DN_FIB_INFO ( f ) ;
if ( f - > fn_state & DN_S_ZOMBIE )
return - ESRCH ;
matched + + ;
if ( del_fp = = NULL & &
( ! r - > rtm_type | | f - > fn_type = = r - > rtm_type ) & &
( r - > rtm_scope = = RT_SCOPE_NOWHERE | | f - > fn_scope = = r - > rtm_scope ) & &
2007-02-09 17:24:40 +03:00
( ! r - > rtm_protocol | |
2005-04-17 02:20:36 +04:00
fi - > fib_protocol = = r - > rtm_protocol ) & &
dn_fib_nh_match ( r , n , rta , fi ) = = 0 )
del_fp = fp ;
}
if ( del_fp ) {
f = * del_fp ;
2007-02-09 17:24:40 +03:00
dn_rtmsg_fib ( RTM_DELROUTE , f , z , tb - > n , n , req ) ;
2005-04-17 02:20:36 +04:00
if ( matched ! = 1 ) {
write_lock_bh ( & dn_fib_tables_lock ) ;
* del_fp = f - > fn_next ;
write_unlock_bh ( & dn_fib_tables_lock ) ;
if ( f - > fn_state & DN_S_ACCESSED )
dn_rt_cache_flush ( - 1 ) ;
dn_free_node ( f ) ;
dz - > dz_nent - - ;
} else {
f - > fn_state | = DN_S_ZOMBIE ;
if ( f - > fn_state & DN_S_ACCESSED ) {
f - > fn_state & = ~ DN_S_ACCESSED ;
dn_rt_cache_flush ( - 1 ) ;
}
if ( + + dn_fib_hash_zombies > 128 )
dn_fib_flush ( ) ;
}
return 0 ;
}
2007-02-09 17:24:40 +03:00
return - ESRCH ;
2005-04-17 02:20:36 +04:00
}
static inline int dn_flush_list ( struct dn_fib_node * * fp , int z , struct dn_hash * table )
{
int found = 0 ;
struct dn_fib_node * f ;
while ( ( f = * fp ) ! = NULL ) {
struct dn_fib_info * fi = DN_FIB_INFO ( f ) ;
if ( fi & & ( ( f - > fn_state & DN_S_ZOMBIE ) | | ( fi - > fib_flags & RTNH_F_DEAD ) ) ) {
write_lock_bh ( & dn_fib_tables_lock ) ;
* fp = f - > fn_next ;
write_unlock_bh ( & dn_fib_tables_lock ) ;
dn_free_node ( f ) ;
found + + ;
continue ;
}
fp = & f - > fn_next ;
}
return found ;
}
static int dn_fib_table_flush ( struct dn_fib_table * tb )
{
struct dn_hash * table = ( struct dn_hash * ) tb - > data ;
struct dn_zone * dz ;
int found = 0 ;
dn_fib_hash_zombies = 0 ;
for ( dz = table - > dh_zone_list ; dz ; dz = dz - > dz_next ) {
int i ;
int tmp = 0 ;
for ( i = dz - > dz_divisor - 1 ; i > = 0 ; i - - )
tmp + = dn_flush_list ( & dz - > dz_hash [ i ] , dz - > dz_order , table ) ;
dz - > dz_nent - = tmp ;
found + = tmp ;
}
return found ;
}
static int dn_fib_table_lookup ( struct dn_fib_table * tb , const struct flowi * flp , struct dn_fib_res * res )
{
2007-02-09 17:24:40 +03:00
int err ;
2005-04-17 02:20:36 +04:00
struct dn_zone * dz ;
struct dn_hash * t = ( struct dn_hash * ) tb - > data ;
read_lock ( & dn_fib_tables_lock ) ;
for ( dz = t - > dh_zone_list ; dz ; dz = dz - > dz_next ) {
struct dn_fib_node * f ;
dn_fib_key_t k = dz_key ( flp - > fld_dst , dz ) ;
for ( f = dz_chain ( k , dz ) ; f ; f = f - > fn_next ) {
if ( ! dn_key_eq ( k , f - > fn_key ) ) {
if ( dn_key_leq ( k , f - > fn_key ) )
break ;
else
continue ;
}
f - > fn_state | = DN_S_ACCESSED ;
if ( f - > fn_state & DN_S_ZOMBIE )
continue ;
if ( f - > fn_scope < flp - > fld_scope )
continue ;
err = dn_fib_semantic_match ( f - > fn_type , DN_FIB_INFO ( f ) , flp , res ) ;
if ( err = = 0 ) {
res - > type = f - > fn_type ;
2007-02-09 17:24:40 +03:00
res - > scope = f - > fn_scope ;
2005-04-17 02:20:36 +04:00
res - > prefixlen = dz - > dz_order ;
goto out ;
}
if ( err < 0 )
goto out ;
}
}
err = 1 ;
out :
read_unlock ( & dn_fib_tables_lock ) ;
2007-02-09 17:24:40 +03:00
return err ;
2005-04-17 02:20:36 +04:00
}
2006-08-11 10:08:33 +04:00
struct dn_fib_table * dn_fib_get_table ( u32 n , int create )
2005-04-17 02:20:36 +04:00
{
2007-02-09 17:24:40 +03:00
struct dn_fib_table * t ;
2006-08-11 10:11:47 +04:00
struct hlist_node * node ;
unsigned int h ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:40 +03:00
if ( n < RT_TABLE_MIN )
return NULL ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:40 +03:00
if ( n > RT_TABLE_MAX )
return NULL ;
2005-04-17 02:20:36 +04:00
2006-08-11 10:11:47 +04:00
h = n & ( DN_FIB_TABLE_HASHSZ - 1 ) ;
rcu_read_lock ( ) ;
hlist_for_each_entry_rcu ( t , node , & dn_fib_table_hash [ h ] , hlist ) {
if ( t - > n = = n ) {
rcu_read_unlock ( ) ;
return t ;
}
}
rcu_read_unlock ( ) ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:40 +03:00
if ( ! create )
return NULL ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:40 +03:00
if ( in_interrupt ( ) & & net_ratelimit ( ) ) {
printk ( KERN_DEBUG " DECnet: BUG! Attempt to create routing table from interrupt \n " ) ;
return NULL ;
}
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:40 +03:00
t = kzalloc ( sizeof ( struct dn_fib_table ) + sizeof ( struct dn_hash ) ,
2006-11-21 06:16:24 +03:00
GFP_KERNEL ) ;
2007-02-09 17:24:40 +03:00
if ( t = = NULL )
return NULL ;
t - > n = n ;
t - > insert = dn_fib_table_insert ;
t - > delete = dn_fib_table_delete ;
t - > lookup = dn_fib_table_lookup ;
t - > flush = dn_fib_table_flush ;
t - > dump = dn_fib_table_dump ;
2006-08-11 10:11:47 +04:00
hlist_add_head_rcu ( & t - > hlist , & dn_fib_table_hash [ h ] ) ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:40 +03:00
return t ;
2005-04-17 02:20:36 +04:00
}
struct dn_fib_table * dn_fib_empty_table ( void )
{
2007-02-09 17:24:40 +03:00
u32 id ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:40 +03:00
for ( id = RT_TABLE_MIN ; id < = RT_TABLE_MAX ; id + + )
2006-08-11 10:11:47 +04:00
if ( dn_fib_get_table ( id , 0 ) = = NULL )
2007-02-09 17:24:40 +03:00
return dn_fib_get_table ( id , 1 ) ;
return NULL ;
2005-04-17 02:20:36 +04:00
}
2006-08-11 10:11:47 +04:00
void dn_fib_flush ( void )
{
2007-02-09 17:24:40 +03:00
int flushed = 0 ;
struct dn_fib_table * tb ;
2006-08-11 10:11:47 +04:00
struct hlist_node * node ;
unsigned int h ;
for ( h = 0 ; h < DN_FIB_TABLE_HASHSZ ; h + + ) {
hlist_for_each_entry ( tb , node , & dn_fib_table_hash [ h ] , hlist )
2007-02-09 17:24:40 +03:00
flushed + = tb - > flush ( tb ) ;
}
2006-08-11 10:11:47 +04:00
2007-02-09 17:24:40 +03:00
if ( flushed )
dn_rt_cache_flush ( - 1 ) ;
2006-08-11 10:11:47 +04:00
}
2005-04-17 02:20:36 +04:00
void __init dn_fib_table_init ( void )
{
dn_hash_kmem = kmem_cache_create ( " dn_fib_info_cache " ,
sizeof ( struct dn_fib_info ) ,
0 , SLAB_HWCACHE_ALIGN ,
2007-07-20 05:11:58 +04:00
NULL ) ;
2005-04-17 02:20:36 +04:00
}
void __exit dn_fib_table_cleanup ( void )
{
2006-08-11 10:11:47 +04:00
struct dn_fib_table * t ;
struct hlist_node * node , * next ;
unsigned int h ;
2005-04-17 02:20:36 +04:00
2006-08-11 10:11:47 +04:00
write_lock ( & dn_fib_tables_lock ) ;
for ( h = 0 ; h < DN_FIB_TABLE_HASHSZ ; h + + ) {
hlist_for_each_entry_safe ( t , node , next , & dn_fib_table_hash [ h ] ,
2007-02-09 17:24:40 +03:00
hlist ) {
2006-08-11 10:11:47 +04:00
hlist_del ( & t - > hlist ) ;
kfree ( t ) ;
}
}
write_unlock ( & dn_fib_tables_lock ) ;
2005-04-17 02:20:36 +04:00
}