2007-07-15 05:55:06 +04:00
/*
* Copyright ( c ) 2007 Patrick McHardy < kaber @ trash . net >
*
* 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 .
*
* The code this is based on carried the following copyright notice :
* - - -
* ( C ) Copyright 2001 - 2006
* Alex Zeffertt , Cambridge Broadband Ltd , ajz @ cambridgebroadband . com
* Re - worked by Ben Greear < greearb @ candelatech . com >
* - - -
*/
# include <linux/kernel.h>
# include <linux/types.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/errno.h>
# include <linux/slab.h>
# include <linux/string.h>
# include <linux/list.h>
# include <linux/notifier.h>
# include <linux/netdevice.h>
# include <linux/etherdevice.h>
# include <linux/ethtool.h>
# include <linux/if_arp.h>
# include <linux/if_link.h>
# include <linux/if_macvlan.h>
# include <net/rtnetlink.h>
# define MACVLAN_HASH_SIZE (1 << BITS_PER_BYTE)
struct macvlan_port {
struct net_device * dev ;
struct hlist_head vlan_hash [ MACVLAN_HASH_SIZE ] ;
struct list_head vlans ;
} ;
struct macvlan_dev {
struct net_device * dev ;
struct list_head list ;
struct hlist_node hlist ;
struct macvlan_port * port ;
struct net_device * lowerdev ;
} ;
static struct macvlan_dev * macvlan_hash_lookup ( const struct macvlan_port * port ,
const unsigned char * addr )
{
struct macvlan_dev * vlan ;
struct hlist_node * n ;
hlist_for_each_entry_rcu ( vlan , n , & port - > vlan_hash [ addr [ 5 ] ] , hlist ) {
if ( ! compare_ether_addr ( vlan - > dev - > dev_addr , addr ) )
return vlan ;
}
return NULL ;
}
static void macvlan_broadcast ( struct sk_buff * skb ,
const struct macvlan_port * port )
{
const struct ethhdr * eth = eth_hdr ( skb ) ;
const struct macvlan_dev * vlan ;
struct hlist_node * n ;
struct net_device * dev ;
struct sk_buff * nskb ;
unsigned int i ;
for ( i = 0 ; i < MACVLAN_HASH_SIZE ; i + + ) {
hlist_for_each_entry_rcu ( vlan , n , & port - > vlan_hash [ i ] , hlist ) {
dev = vlan - > dev ;
if ( unlikely ( ! ( dev - > flags & IFF_UP ) ) )
continue ;
nskb = skb_clone ( skb , GFP_ATOMIC ) ;
if ( nskb = = NULL ) {
dev - > stats . rx_errors + + ;
dev - > stats . rx_dropped + + ;
continue ;
}
dev - > stats . rx_bytes + = skb - > len + ETH_HLEN ;
dev - > stats . rx_packets + + ;
dev - > stats . multicast + + ;
dev - > last_rx = jiffies ;
nskb - > dev = dev ;
if ( ! compare_ether_addr ( eth - > h_dest , dev - > broadcast ) )
nskb - > pkt_type = PACKET_BROADCAST ;
else
nskb - > pkt_type = PACKET_MULTICAST ;
netif_rx ( nskb ) ;
}
}
}
/* called under rcu_read_lock() from netif_receive_skb */
static struct sk_buff * macvlan_handle_frame ( struct sk_buff * skb )
{
const struct ethhdr * eth = eth_hdr ( skb ) ;
const struct macvlan_port * port ;
const struct macvlan_dev * vlan ;
struct net_device * dev ;
port = rcu_dereference ( skb - > dev - > macvlan_port ) ;
if ( port = = NULL )
return skb ;
if ( is_multicast_ether_addr ( eth - > h_dest ) ) {
macvlan_broadcast ( skb , port ) ;
return skb ;
}
vlan = macvlan_hash_lookup ( port , eth - > h_dest ) ;
if ( vlan = = NULL )
return skb ;
dev = vlan - > dev ;
if ( unlikely ( ! ( dev - > flags & IFF_UP ) ) ) {
kfree_skb ( skb ) ;
return NULL ;
}
skb = skb_share_check ( skb , GFP_ATOMIC ) ;
if ( skb = = NULL ) {
dev - > stats . rx_errors + + ;
dev - > stats . rx_dropped + + ;
return NULL ;
}
dev - > stats . rx_bytes + = skb - > len + ETH_HLEN ;
dev - > stats . rx_packets + + ;
dev - > last_rx = jiffies ;
skb - > dev = dev ;
skb - > pkt_type = PACKET_HOST ;
netif_rx ( skb ) ;
return NULL ;
}
static int macvlan_hard_start_xmit ( struct sk_buff * skb , struct net_device * dev )
{
const struct macvlan_dev * vlan = netdev_priv ( dev ) ;
unsigned int len = skb - > len ;
int ret ;
skb - > dev = vlan - > lowerdev ;
ret = dev_queue_xmit ( skb ) ;
if ( likely ( ret = = NET_XMIT_SUCCESS ) ) {
dev - > stats . tx_packets + + ;
dev - > stats . tx_bytes + = len ;
} else {
dev - > stats . tx_errors + + ;
dev - > stats . tx_aborted_errors + + ;
}
return NETDEV_TX_OK ;
}
static int macvlan_hard_header ( struct sk_buff * skb , struct net_device * dev ,
2007-10-09 12:40:57 +04:00
unsigned short type , const void * daddr ,
const void * saddr , unsigned len )
2007-07-15 05:55:06 +04:00
{
const struct macvlan_dev * vlan = netdev_priv ( dev ) ;
struct net_device * lowerdev = vlan - > lowerdev ;
2007-10-09 12:36:32 +04:00
return dev_hard_header ( skb , lowerdev , type , daddr ,
saddr ? : dev - > dev_addr , len ) ;
2007-07-15 05:55:06 +04:00
}
2007-10-09 12:40:57 +04:00
static const struct header_ops macvlan_hard_header_ops = {
. create = macvlan_hard_header ,
. rebuild = eth_rebuild_header ,
. parse = eth_header_parse ,
. cache = eth_header_cache ,
. cache_update = eth_header_cache_update ,
} ;
2007-07-15 05:55:06 +04:00
static int macvlan_open ( struct net_device * dev )
{
struct macvlan_dev * vlan = netdev_priv ( dev ) ;
struct macvlan_port * port = vlan - > port ;
struct net_device * lowerdev = vlan - > lowerdev ;
int err ;
err = dev_unicast_add ( lowerdev , dev - > dev_addr , ETH_ALEN ) ;
if ( err < 0 )
return err ;
if ( dev - > flags & IFF_ALLMULTI )
dev_set_allmulti ( lowerdev , 1 ) ;
hlist_add_head_rcu ( & vlan - > hlist , & port - > vlan_hash [ dev - > dev_addr [ 5 ] ] ) ;
return 0 ;
}
static int macvlan_stop ( struct net_device * dev )
{
struct macvlan_dev * vlan = netdev_priv ( dev ) ;
struct net_device * lowerdev = vlan - > lowerdev ;
dev_mc_unsync ( lowerdev , dev ) ;
if ( dev - > flags & IFF_ALLMULTI )
dev_set_allmulti ( lowerdev , - 1 ) ;
dev_unicast_delete ( lowerdev , dev - > dev_addr , ETH_ALEN ) ;
hlist_del_rcu ( & vlan - > hlist ) ;
synchronize_rcu ( ) ;
return 0 ;
}
static void macvlan_change_rx_flags ( struct net_device * dev , int change )
{
struct macvlan_dev * vlan = netdev_priv ( dev ) ;
struct net_device * lowerdev = vlan - > lowerdev ;
if ( change & IFF_ALLMULTI )
dev_set_allmulti ( lowerdev , dev - > flags & IFF_ALLMULTI ? 1 : - 1 ) ;
}
static void macvlan_set_multicast_list ( struct net_device * dev )
{
struct macvlan_dev * vlan = netdev_priv ( dev ) ;
dev_mc_sync ( vlan - > lowerdev , dev ) ;
}
static int macvlan_change_mtu ( struct net_device * dev , int new_mtu )
{
struct macvlan_dev * vlan = netdev_priv ( dev ) ;
if ( new_mtu < 68 | | vlan - > lowerdev - > mtu < new_mtu )
return - EINVAL ;
dev - > mtu = new_mtu ;
return 0 ;
}
/*
* macvlan network devices have devices nesting below it and are a special
* " super class " of normal network devices ; split their locks off into a
* separate class since they always nest .
*/
static struct lock_class_key macvlan_netdev_xmit_lock_key ;
# define MACVLAN_FEATURES \
( NETIF_F_SG | NETIF_F_ALL_CSUM | NETIF_F_HIGHDMA | NETIF_F_FRAGLIST | \
NETIF_F_GSO | NETIF_F_TSO | NETIF_F_UFO | NETIF_F_GSO_ROBUST | \
NETIF_F_TSO_ECN | NETIF_F_TSO6 )
# define MACVLAN_STATE_MASK \
( ( 1 < < __LINK_STATE_NOCARRIER ) | ( 1 < < __LINK_STATE_DORMANT ) )
static int macvlan_init ( struct net_device * dev )
{
struct macvlan_dev * vlan = netdev_priv ( dev ) ;
const struct net_device * lowerdev = vlan - > lowerdev ;
dev - > state = ( dev - > state & ~ MACVLAN_STATE_MASK ) |
( lowerdev - > state & MACVLAN_STATE_MASK ) ;
dev - > features = lowerdev - > features & MACVLAN_FEATURES ;
dev - > iflink = lowerdev - > ifindex ;
lockdep_set_class ( & dev - > _xmit_lock , & macvlan_netdev_xmit_lock_key ) ;
return 0 ;
}
static void macvlan_ethtool_get_drvinfo ( struct net_device * dev ,
struct ethtool_drvinfo * drvinfo )
{
snprintf ( drvinfo - > driver , 32 , " macvlan " ) ;
snprintf ( drvinfo - > version , 32 , " 0.1 " ) ;
}
static u32 macvlan_ethtool_get_rx_csum ( struct net_device * dev )
{
const struct macvlan_dev * vlan = netdev_priv ( dev ) ;
struct net_device * lowerdev = vlan - > lowerdev ;
if ( lowerdev - > ethtool_ops - > get_rx_csum = = NULL )
return 0 ;
return lowerdev - > ethtool_ops - > get_rx_csum ( lowerdev ) ;
}
static const struct ethtool_ops macvlan_ethtool_ops = {
. get_link = ethtool_op_get_link ,
. get_rx_csum = macvlan_ethtool_get_rx_csum ,
. get_drvinfo = macvlan_ethtool_get_drvinfo ,
} ;
static void macvlan_setup ( struct net_device * dev )
{
ether_setup ( dev ) ;
dev - > init = macvlan_init ;
dev - > open = macvlan_open ;
dev - > stop = macvlan_stop ;
dev - > change_mtu = macvlan_change_mtu ;
dev - > change_rx_flags = macvlan_change_rx_flags ;
dev - > set_multicast_list = macvlan_set_multicast_list ;
dev - > hard_start_xmit = macvlan_hard_start_xmit ;
dev - > destructor = free_netdev ;
2007-10-09 12:40:57 +04:00
dev - > header_ops = & macvlan_hard_header_ops ,
2007-07-15 05:55:06 +04:00
dev - > ethtool_ops = & macvlan_ethtool_ops ;
dev - > tx_queue_len = 0 ;
}
static int macvlan_port_create ( struct net_device * dev )
{
struct macvlan_port * port ;
unsigned int i ;
if ( dev - > type ! = ARPHRD_ETHER | | dev - > flags & IFF_LOOPBACK )
return - EINVAL ;
port = kzalloc ( sizeof ( * port ) , GFP_KERNEL ) ;
if ( port = = NULL )
return - ENOMEM ;
port - > dev = dev ;
INIT_LIST_HEAD ( & port - > vlans ) ;
for ( i = 0 ; i < MACVLAN_HASH_SIZE ; i + + )
INIT_HLIST_HEAD ( & port - > vlan_hash [ i ] ) ;
rcu_assign_pointer ( dev - > macvlan_port , port ) ;
return 0 ;
}
static void macvlan_port_destroy ( struct net_device * dev )
{
struct macvlan_port * port = dev - > macvlan_port ;
rcu_assign_pointer ( dev - > macvlan_port , NULL ) ;
synchronize_rcu ( ) ;
kfree ( port ) ;
}
static void macvlan_transfer_operstate ( struct net_device * dev )
{
struct macvlan_dev * vlan = netdev_priv ( dev ) ;
const struct net_device * lowerdev = vlan - > lowerdev ;
if ( lowerdev - > operstate = = IF_OPER_DORMANT )
netif_dormant_on ( dev ) ;
else
netif_dormant_off ( dev ) ;
if ( netif_carrier_ok ( lowerdev ) ) {
if ( ! netif_carrier_ok ( dev ) )
netif_carrier_on ( dev ) ;
} else {
if ( netif_carrier_ok ( lowerdev ) )
netif_carrier_off ( dev ) ;
}
}
static int macvlan_validate ( struct nlattr * tb [ ] , struct nlattr * data [ ] )
{
if ( tb [ IFLA_ADDRESS ] ) {
if ( nla_len ( tb [ IFLA_ADDRESS ] ) ! = ETH_ALEN )
return - EINVAL ;
if ( ! is_valid_ether_addr ( nla_data ( tb [ IFLA_ADDRESS ] ) ) )
return - EADDRNOTAVAIL ;
}
return 0 ;
}
static int macvlan_newlink ( struct net_device * dev ,
struct nlattr * tb [ ] , struct nlattr * data [ ] )
{
struct macvlan_dev * vlan = netdev_priv ( dev ) ;
struct macvlan_port * port ;
struct net_device * lowerdev ;
int err ;
if ( ! tb [ IFLA_LINK ] )
return - EINVAL ;
2007-09-17 22:56:21 +04:00
lowerdev = __dev_get_by_index ( dev - > nd_net , nla_get_u32 ( tb [ IFLA_LINK ] ) ) ;
2007-07-15 05:55:06 +04:00
if ( lowerdev = = NULL )
return - ENODEV ;
if ( ! tb [ IFLA_MTU ] )
dev - > mtu = lowerdev - > mtu ;
else if ( dev - > mtu > lowerdev - > mtu )
return - EINVAL ;
if ( ! tb [ IFLA_ADDRESS ] )
random_ether_addr ( dev - > dev_addr ) ;
if ( lowerdev - > macvlan_port = = NULL ) {
err = macvlan_port_create ( lowerdev ) ;
if ( err < 0 )
return err ;
}
port = lowerdev - > macvlan_port ;
vlan - > lowerdev = lowerdev ;
vlan - > dev = dev ;
vlan - > port = port ;
err = register_netdevice ( dev ) ;
if ( err < 0 )
return err ;
list_add_tail ( & vlan - > list , & port - > vlans ) ;
macvlan_transfer_operstate ( dev ) ;
return 0 ;
}
static void macvlan_dellink ( struct net_device * dev )
{
struct macvlan_dev * vlan = netdev_priv ( dev ) ;
struct macvlan_port * port = vlan - > port ;
list_del ( & vlan - > list ) ;
unregister_netdevice ( dev ) ;
if ( list_empty ( & port - > vlans ) )
macvlan_port_destroy ( dev ) ;
}
static struct rtnl_link_ops macvlan_link_ops __read_mostly = {
. kind = " macvlan " ,
. priv_size = sizeof ( struct macvlan_dev ) ,
. setup = macvlan_setup ,
. validate = macvlan_validate ,
. newlink = macvlan_newlink ,
. dellink = macvlan_dellink ,
} ;
static int macvlan_device_event ( struct notifier_block * unused ,
unsigned long event , void * ptr )
{
struct net_device * dev = ptr ;
struct macvlan_dev * vlan , * next ;
struct macvlan_port * port ;
port = dev - > macvlan_port ;
if ( port = = NULL )
return NOTIFY_DONE ;
switch ( event ) {
case NETDEV_CHANGE :
list_for_each_entry ( vlan , & port - > vlans , list )
macvlan_transfer_operstate ( vlan - > dev ) ;
break ;
case NETDEV_FEAT_CHANGE :
list_for_each_entry ( vlan , & port - > vlans , list ) {
vlan - > dev - > features = dev - > features & MACVLAN_FEATURES ;
netdev_features_change ( vlan - > dev ) ;
}
break ;
case NETDEV_UNREGISTER :
list_for_each_entry_safe ( vlan , next , & port - > vlans , list )
macvlan_dellink ( vlan - > dev ) ;
break ;
}
return NOTIFY_DONE ;
}
static struct notifier_block macvlan_notifier_block __read_mostly = {
. notifier_call = macvlan_device_event ,
} ;
static int __init macvlan_init_module ( void )
{
int err ;
register_netdevice_notifier ( & macvlan_notifier_block ) ;
macvlan_handle_frame_hook = macvlan_handle_frame ;
err = rtnl_link_register ( & macvlan_link_ops ) ;
if ( err < 0 )
goto err1 ;
return 0 ;
err1 :
macvlan_handle_frame_hook = macvlan_handle_frame ;
unregister_netdevice_notifier ( & macvlan_notifier_block ) ;
return err ;
}
static void __exit macvlan_cleanup_module ( void )
{
rtnl_link_unregister ( & macvlan_link_ops ) ;
macvlan_handle_frame_hook = NULL ;
unregister_netdevice_notifier ( & macvlan_notifier_block ) ;
}
module_init ( macvlan_init_module ) ;
module_exit ( macvlan_cleanup_module ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Patrick McHardy <kaber@trash.net> " ) ;
MODULE_DESCRIPTION ( " Driver for MAC address based VLANs " ) ;
MODULE_ALIAS_RTNL_LINK ( " macvlan " ) ;