2005-04-17 02:20:36 +04:00
/*
* Ioctl handler
* Linux ethernet bridge
*
* Authors :
* Lennert Buytenhek < buytenh @ gnu . org >
*
* $ Id : br_ioctl . c , v 1.4 2000 / 11 / 08 05 : 16 : 40 davem Exp $
*
* 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 .
*/
2006-01-11 23:17:47 +03:00
# include <linux/capability.h>
2005-04-17 02:20:36 +04:00
# include <linux/kernel.h>
# include <linux/if_bridge.h>
# include <linux/netdevice.h>
# include <linux/times.h>
# include <asm/uaccess.h>
# include "br_private.h"
/* called with RTNL */
static int get_bridge_ifindices ( int * indices , int num )
{
struct net_device * dev ;
int i = 0 ;
for ( dev = dev_base ; dev & & i < num ; dev = dev - > next ) {
2007-02-09 17:24:35 +03:00
if ( dev - > priv_flags & IFF_EBRIDGE )
2005-04-17 02:20:36 +04:00
indices [ i + + ] = dev - > ifindex ;
}
return i ;
}
/* called with RTNL */
static void get_port_ifindices ( struct net_bridge * br , int * ifindices , int num )
{
struct net_bridge_port * p ;
list_for_each_entry ( p , & br - > port_list , list ) {
if ( p - > port_no < num )
ifindices [ p - > port_no ] = p - > dev - > ifindex ;
}
}
/*
* Format up to a page worth of forwarding table entries
* userbuf - - where to copy result
* maxnum - - maximum number of entries desired
* ( limited to a page for sanity )
* offset - - number of records to skip
*/
2007-02-09 17:24:35 +03:00
static int get_fdb_entries ( struct net_bridge * br , void __user * userbuf ,
2005-04-17 02:20:36 +04:00
unsigned long maxnum , unsigned long offset )
{
int num ;
void * buf ;
2006-11-21 02:02:49 +03:00
size_t size ;
2005-04-17 02:20:36 +04:00
2006-11-21 02:02:49 +03:00
/* Clamp size to PAGE_SIZE, test maxnum to avoid overflow */
if ( maxnum > PAGE_SIZE / sizeof ( struct __fdb_entry ) )
2005-04-17 02:20:36 +04:00
maxnum = PAGE_SIZE / sizeof ( struct __fdb_entry ) ;
2006-11-21 02:02:49 +03:00
size = maxnum * sizeof ( struct __fdb_entry ) ;
2005-04-17 02:20:36 +04:00
buf = kmalloc ( size , GFP_USER ) ;
if ( ! buf )
return - ENOMEM ;
2007-02-09 17:24:35 +03:00
2005-04-17 02:20:36 +04:00
num = br_fdb_fillbuf ( br , buf , maxnum , offset ) ;
if ( num > 0 ) {
if ( copy_to_user ( userbuf , buf , num * sizeof ( struct __fdb_entry ) ) )
num = - EFAULT ;
}
kfree ( buf ) ;
return num ;
}
static int add_del_if ( struct net_bridge * br , int ifindex , int isadd )
{
struct net_device * dev ;
int ret ;
if ( ! capable ( CAP_NET_ADMIN ) )
return - EPERM ;
dev = dev_get_by_index ( ifindex ) ;
if ( dev = = NULL )
return - EINVAL ;
2007-02-09 17:24:35 +03:00
2005-04-17 02:20:36 +04:00
if ( isadd )
ret = br_add_if ( br , dev ) ;
else
ret = br_del_if ( br , dev ) ;
dev_put ( dev ) ;
return ret ;
}
/*
* Legacy ioctl ' s through SIOCDEVPRIVATE
* This interface is deprecated because it was too difficult to
* to do the translation for 32 / 64 bit ioctl compatability .
*/
static int old_dev_ioctl ( struct net_device * dev , struct ifreq * rq , int cmd )
{
struct net_bridge * br = netdev_priv ( dev ) ;
unsigned long args [ 4 ] ;
2007-02-09 17:24:35 +03:00
2005-04-17 02:20:36 +04:00
if ( copy_from_user ( args , rq - > ifr_data , sizeof ( args ) ) )
return - EFAULT ;
switch ( args [ 0 ] ) {
case BRCTL_ADD_IF :
case BRCTL_DEL_IF :
return add_del_if ( br , args [ 1 ] , args [ 0 ] = = BRCTL_ADD_IF ) ;
case BRCTL_GET_BRIDGE_INFO :
{
struct __bridge_info b ;
memset ( & b , 0 , sizeof ( struct __bridge_info ) ) ;
rcu_read_lock ( ) ;
memcpy ( & b . designated_root , & br - > designated_root , 8 ) ;
memcpy ( & b . bridge_id , & br - > bridge_id , 8 ) ;
b . root_path_cost = br - > root_path_cost ;
b . max_age = jiffies_to_clock_t ( br - > max_age ) ;
b . hello_time = jiffies_to_clock_t ( br - > hello_time ) ;
b . forward_delay = br - > forward_delay ;
b . bridge_max_age = br - > bridge_max_age ;
b . bridge_hello_time = br - > bridge_hello_time ;
b . bridge_forward_delay = jiffies_to_clock_t ( br - > bridge_forward_delay ) ;
b . topology_change = br - > topology_change ;
b . topology_change_detected = br - > topology_change_detected ;
b . root_port = br - > root_port ;
b . stp_enabled = br - > stp_enabled ;
b . ageing_time = jiffies_to_clock_t ( br - > ageing_time ) ;
b . hello_timer_value = br_timer_value ( & br - > hello_timer ) ;
b . tcn_timer_value = br_timer_value ( & br - > tcn_timer ) ;
b . topology_change_timer_value = br_timer_value ( & br - > topology_change_timer ) ;
b . gc_timer_value = br_timer_value ( & br - > gc_timer ) ;
2007-02-09 17:24:35 +03:00
rcu_read_unlock ( ) ;
2005-04-17 02:20:36 +04:00
if ( copy_to_user ( ( void __user * ) args [ 1 ] , & b , sizeof ( b ) ) )
return - EFAULT ;
return 0 ;
}
case BRCTL_GET_PORT_LIST :
{
int num , * indices ;
num = args [ 2 ] ;
if ( num < 0 )
return - EINVAL ;
if ( num = = 0 )
num = 256 ;
if ( num > BR_MAX_PORTS )
num = BR_MAX_PORTS ;
2006-07-22 01:51:30 +04:00
indices = kcalloc ( num , sizeof ( int ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( indices = = NULL )
return - ENOMEM ;
get_port_ifindices ( br , indices , num ) ;
if ( copy_to_user ( ( void __user * ) args [ 1 ] , indices , num * sizeof ( int ) ) )
num = - EFAULT ;
kfree ( indices ) ;
return num ;
}
case BRCTL_SET_BRIDGE_FORWARD_DELAY :
if ( ! capable ( CAP_NET_ADMIN ) )
return - EPERM ;
spin_lock_bh ( & br - > lock ) ;
br - > bridge_forward_delay = clock_t_to_jiffies ( args [ 1 ] ) ;
if ( br_is_root_bridge ( br ) )
br - > forward_delay = br - > bridge_forward_delay ;
spin_unlock_bh ( & br - > lock ) ;
return 0 ;
case BRCTL_SET_BRIDGE_HELLO_TIME :
if ( ! capable ( CAP_NET_ADMIN ) )
return - EPERM ;
spin_lock_bh ( & br - > lock ) ;
br - > bridge_hello_time = clock_t_to_jiffies ( args [ 1 ] ) ;
if ( br_is_root_bridge ( br ) )
br - > hello_time = br - > bridge_hello_time ;
spin_unlock_bh ( & br - > lock ) ;
return 0 ;
case BRCTL_SET_BRIDGE_MAX_AGE :
if ( ! capable ( CAP_NET_ADMIN ) )
return - EPERM ;
spin_lock_bh ( & br - > lock ) ;
br - > bridge_max_age = clock_t_to_jiffies ( args [ 1 ] ) ;
if ( br_is_root_bridge ( br ) )
br - > max_age = br - > bridge_max_age ;
spin_unlock_bh ( & br - > lock ) ;
return 0 ;
case BRCTL_SET_AGEING_TIME :
if ( ! capable ( CAP_NET_ADMIN ) )
return - EPERM ;
br - > ageing_time = clock_t_to_jiffies ( args [ 1 ] ) ;
return 0 ;
case BRCTL_GET_PORT_INFO :
{
struct __port_info p ;
struct net_bridge_port * pt ;
rcu_read_lock ( ) ;
if ( ( pt = br_get_port ( br , args [ 2 ] ) ) = = NULL ) {
rcu_read_unlock ( ) ;
return - EINVAL ;
}
memset ( & p , 0 , sizeof ( struct __port_info ) ) ;
memcpy ( & p . designated_root , & pt - > designated_root , 8 ) ;
memcpy ( & p . designated_bridge , & pt - > designated_bridge , 8 ) ;
p . port_id = pt - > port_id ;
p . designated_port = pt - > designated_port ;
p . path_cost = pt - > path_cost ;
p . designated_cost = pt - > designated_cost ;
p . state = pt - > state ;
p . top_change_ack = pt - > topology_change_ack ;
p . config_pending = pt - > config_pending ;
p . message_age_timer_value = br_timer_value ( & pt - > message_age_timer ) ;
p . forward_delay_timer_value = br_timer_value ( & pt - > forward_delay_timer ) ;
p . hold_timer_value = br_timer_value ( & pt - > hold_timer ) ;
rcu_read_unlock ( ) ;
if ( copy_to_user ( ( void __user * ) args [ 1 ] , & p , sizeof ( p ) ) )
return - EFAULT ;
return 0 ;
}
case BRCTL_SET_BRIDGE_STP_STATE :
if ( ! capable ( CAP_NET_ADMIN ) )
return - EPERM ;
br - > stp_enabled = args [ 1 ] ? 1 : 0 ;
return 0 ;
case BRCTL_SET_BRIDGE_PRIORITY :
if ( ! capable ( CAP_NET_ADMIN ) )
return - EPERM ;
spin_lock_bh ( & br - > lock ) ;
br_stp_set_bridge_priority ( br , args [ 1 ] ) ;
spin_unlock_bh ( & br - > lock ) ;
return 0 ;
case BRCTL_SET_PORT_PRIORITY :
{
struct net_bridge_port * p ;
int ret = 0 ;
if ( ! capable ( CAP_NET_ADMIN ) )
return - EPERM ;
if ( args [ 2 ] > = ( 1 < < ( 16 - BR_PORT_BITS ) ) )
return - ERANGE ;
spin_lock_bh ( & br - > lock ) ;
2007-02-09 17:24:35 +03:00
if ( ( p = br_get_port ( br , args [ 1 ] ) ) = = NULL )
2005-04-17 02:20:36 +04:00
ret = - EINVAL ;
else
br_stp_set_port_priority ( p , args [ 2 ] ) ;
spin_unlock_bh ( & br - > lock ) ;
return ret ;
}
case BRCTL_SET_PATH_COST :
{
struct net_bridge_port * p ;
int ret = 0 ;
if ( ! capable ( CAP_NET_ADMIN ) )
return - EPERM ;
spin_lock_bh ( & br - > lock ) ;
if ( ( p = br_get_port ( br , args [ 1 ] ) ) = = NULL )
ret = - EINVAL ;
else
br_stp_set_path_cost ( p , args [ 2 ] ) ;
spin_unlock_bh ( & br - > lock ) ;
return ret ;
}
case BRCTL_GET_FDB_ENTRIES :
2007-02-09 17:24:35 +03:00
return get_fdb_entries ( br , ( void __user * ) args [ 1 ] ,
2005-04-17 02:20:36 +04:00
args [ 2 ] , args [ 3 ] ) ;
}
return - EOPNOTSUPP ;
}
static int old_deviceless ( void __user * uarg )
{
unsigned long args [ 3 ] ;
if ( copy_from_user ( args , uarg , sizeof ( args ) ) )
return - EFAULT ;
switch ( args [ 0 ] ) {
case BRCTL_GET_VERSION :
return BRCTL_VERSION ;
case BRCTL_GET_BRIDGES :
{
int * indices ;
int ret = 0 ;
if ( args [ 2 ] > = 2048 )
return - ENOMEM ;
2006-07-22 01:51:30 +04:00
indices = kcalloc ( args [ 2 ] , sizeof ( int ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( indices = = NULL )
return - ENOMEM ;
args [ 2 ] = get_bridge_ifindices ( indices , args [ 2 ] ) ;
ret = copy_to_user ( ( void __user * ) args [ 1 ] , indices , args [ 2 ] * sizeof ( int ) )
? - EFAULT : args [ 2 ] ;
kfree ( indices ) ;
return ret ;
}
case BRCTL_ADD_BRIDGE :
case BRCTL_DEL_BRIDGE :
{
char buf [ IFNAMSIZ ] ;
if ( ! capable ( CAP_NET_ADMIN ) )
return - EPERM ;
if ( copy_from_user ( buf , ( void __user * ) args [ 1 ] , IFNAMSIZ ) )
return - EFAULT ;
buf [ IFNAMSIZ - 1 ] = 0 ;
if ( args [ 0 ] = = BRCTL_ADD_BRIDGE )
return br_add_bridge ( buf ) ;
return br_del_bridge ( buf ) ;
}
}
return - EOPNOTSUPP ;
}
int br_ioctl_deviceless_stub ( unsigned int cmd , void __user * uarg )
{
switch ( cmd ) {
case SIOCGIFBR :
case SIOCSIFBR :
return old_deviceless ( uarg ) ;
2007-02-09 17:24:35 +03:00
2005-04-17 02:20:36 +04:00
case SIOCBRADDBR :
case SIOCBRDELBR :
{
char buf [ IFNAMSIZ ] ;
if ( ! capable ( CAP_NET_ADMIN ) )
return - EPERM ;
if ( copy_from_user ( buf , uarg , IFNAMSIZ ) )
return - EFAULT ;
buf [ IFNAMSIZ - 1 ] = 0 ;
if ( cmd = = SIOCBRADDBR )
return br_add_bridge ( buf ) ;
return br_del_bridge ( buf ) ;
}
}
return - EOPNOTSUPP ;
}
int br_dev_ioctl ( struct net_device * dev , struct ifreq * rq , int cmd )
{
struct net_bridge * br = netdev_priv ( dev ) ;
switch ( cmd ) {
case SIOCDEVPRIVATE :
return old_dev_ioctl ( dev , rq , cmd ) ;
case SIOCBRADDIF :
case SIOCBRDELIF :
return add_del_if ( br , rq - > ifr_ifindex , cmd = = SIOCBRADDIF ) ;
}
pr_debug ( " Bridge does not support ioctl 0x%x \n " , cmd ) ;
return - EOPNOTSUPP ;
}