src/switch/datapath/port_manager.c
/*
* Copyright (C) 2012-2013 NEC Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "async_event_notifier.h"
#include "ether_device.h"
#include "mutex.h"
#include "ofdp_private.h"
#include "openflow_helper.h"
#include "pipeline.h"
#include "port_manager.h"
#include "switch_port.h"
#include "table_manager.h"
typedef struct {
size_t max_send_queue_length;
size_t max_recv_queue_length;
} port_manager_config;
static port_manager_config config = { 0, 0 };
static pthread_mutex_t mutex;
static const time_t PORT_STATUS_UPDATE_INTERVAL = 1;
static void
update_switch_port_status_and_stats_walker( switch_port *port, void *user_data ) {
assert( port != NULL );
UNUSED( user_data );
bool updated = update_switch_port_status( port );
if ( updated ) {
notify_port_status( port, OFPPR_MODIFY );
}
assert( port->device != NULL );
update_device_stats( port->device );
}
static void
update_switch_port_status_and_stats( void *user_data ) {
if ( !lock_mutex( &mutex ) ) {
return;
}
foreach_switch_port( update_switch_port_status_and_stats_walker, user_data );
unlock_mutex( &mutex );
}
OFDPE
init_port_manager( const size_t max_send_queue_length, const size_t max_recv_queue_length ) {
if ( max_send_queue_length == 0 || max_recv_queue_length == 0 ) {
error( "Failed to initialize port manager. Maximum queue length must be greater than zero "
"( max_send_queue = %u, max_recv_queue = %u ).", max_send_queue_length, max_recv_queue_length );
return ERROR_INVALID_PARAMETER;
}
bool ret = init_mutex( &mutex );
if ( !ret ) {
return ERROR_INIT_MUTEX;
}
ret = lock_mutex( &mutex );
if ( !ret ) {
return ERROR_LOCK;
}
config.max_send_queue_length = max_send_queue_length;
config.max_recv_queue_length = max_recv_queue_length;
init_switch_port();
add_periodic_event_callback_safe( PORT_STATUS_UPDATE_INTERVAL, update_switch_port_status_and_stats, NULL );
ret = unlock_mutex( &mutex );
if ( !ret ) {
return ERROR_UNLOCK;
}
return OFDPE_SUCCESS;
}
OFDPE
finalize_port_manager() {
bool ret = lock_mutex( &mutex );
if ( !ret ) {
return ERROR_LOCK;
}
delete_timer_event_safe( update_switch_port_status_and_stats, NULL );
finalize_switch_port();
config.max_send_queue_length = 0;
config.max_recv_queue_length = 0;
ret = unlock_mutex( &mutex );
if ( !ret ) {
return ERROR_UNLOCK;
}
ret = finalize_mutex( &mutex );
if ( !ret ) {
return ERROR_FINALIZE_MUTEX;
}
return OFDPE_SUCCESS;
}
static void
handle_frame_received_on_switch_port( buffer *frame, void *user_data ) {
assert( frame != NULL );
assert( user_data != NULL );
switch_port *port = user_data;
if ( !lock_mutex( &mutex ) ) {
return;
}
if ( ( port->config & ( OFPPC_PORT_DOWN | OFPPC_NO_RECV ) ) != 0 ) {
unlock_mutex( &mutex );
return;
}
if ( frame->length + ETH_FCS_LENGTH < ETH_MINIMUM_LENGTH ) {
fill_ether_padding( frame );
}
handle_received_frame( port, frame );
unlock_mutex( &mutex );
}
static OFDPE
add_ether_device_as_switch_port( const char *device, uint32_t port_no ) {
assert( device != NULL );
assert( port_no <= OFPP_MAX );
if ( !lock_mutex( &mutex ) ) {
return ERROR_LOCK;
}
if ( port_no == 0 ) {
bool ret = get_free_switch_port_no( &port_no );
if ( !ret ) {
error( "No switch port number available ( device = %s ).", device );
return unlock_mutex( &mutex ) ? ERROR_OFDPE_PORT_MOD_FAILED_BAD_PORT : ERROR_UNLOCK;
}
}
bool ret = switch_port_exists( port_no );
if ( ret ) {
error( "Specified port already exists ( device = %s, port_no = %u ).", device, port_no );
return unlock_mutex( &mutex ) ? ERROR_OFDPE_PORT_MOD_FAILED_BAD_PORT : ERROR_UNLOCK;
}
info( "Adding an Ethernet device as a switch port ( device = %s, port_no = %u ).", device, port_no );
switch_port *port = add_switch_port( device, port_no, config.max_send_queue_length, config.max_recv_queue_length );
if ( port == NULL ) {
error( "Failed to add an Ethernet device as a switch port ( device = %s, port_no = %u ).", device, port_no );
return unlock_mutex( &mutex ) ? ERROR_OFDPE_PORT_MOD_FAILED_EPERM : ERROR_UNLOCK;
}
set_frame_received_handler( port->device, handle_frame_received_on_switch_port, port );
notify_port_status( port, OFPPR_ADD );
if ( !unlock_mutex( &mutex ) ) {
return ERROR_UNLOCK;
}
return OFDPE_SUCCESS;
}
static OFDPE
delete_ether_device_from_switch( const uint32_t port_no ) {
assert( port_no > 0 && port_no <= OFPP_MAX );
if ( !lock_mutex( &mutex ) ) {
return ERROR_LOCK;
}
switch_port *port = delete_switch_port( port_no );
if ( port == NULL ) {
return unlock_mutex( &mutex ) ? ERROR_INVALID_PARAMETER : ERROR_UNLOCK;
}
notify_port_status( port, OFPPR_DELETE );
if ( port->device != NULL ) {
delete_ether_device( port->device );
}
xfree( port );
if ( !unlock_mutex( &mutex ) ) {
return ERROR_UNLOCK;
}
return OFDPE_SUCCESS;
}
OFDPE
add_port( const uint32_t port_no, const char *device_name ) {
assert( port_no <= OFPP_MAX );
assert( device_name != NULL );
if ( datapath_is_running() && !lock_pipeline() ) {
return ERROR_LOCK;
}
OFDPE ret = add_ether_device_as_switch_port( device_name, port_no );
if ( ret != OFDPE_SUCCESS ) {
error( "Failed to add an Ethernet port as a switch port ( ret = %d, port_no = %u, device_name = %s ).",
ret, port_no, device_name );
}
if ( datapath_is_running() && !unlock_pipeline() ) {
return ERROR_UNLOCK;
}
return ret;
}
OFDPE
delete_port( const uint32_t port_no ) {
assert( port_no > 0 && port_no <= OFPP_MAX );
if ( datapath_is_running() && !lock_pipeline() ) {
return ERROR_LOCK;
}
OFDPE ret = delete_ether_device_from_switch( port_no );
if ( ret != OFDPE_SUCCESS ) {
error( "Failed to delete an Ethernet port from a switch ( ret = %d, port_no = %u ).",
ret, port_no );
}
if ( datapath_is_running() && !unlock_pipeline() ) {
return ERROR_UNLOCK;
}
return ret;
}
OFDPE
update_port( const uint32_t port_no, uint32_t config, uint32_t mask ) {
assert( port_no > 0 && port_no <= OFPP_MAX );
if ( datapath_is_running() && !lock_pipeline() ) {
return ERROR_LOCK;
}
switch_port *port = lookup_switch_port( port_no );
if ( port == NULL ) {
return ERROR_OFDPE_PORT_MOD_FAILED_BAD_PORT;
}
bool ret = update_switch_port_config( port, config, mask );
if ( !ret ) {
error( "Failed to update switch port config ( port_no = %u, config = %#x, mask = %#x ).",
port_no, config, mask );
}
if ( datapath_is_running() && !unlock_pipeline() ) {
return ERROR_UNLOCK;
}
return ret ? OFDPE_SUCCESS : ERROR_OFDPE_PORT_MOD_FAILED_BAD_CONFIG;
}
typedef struct {
list_element *list;
uint32_t in_port;
} switch_port_list;
static void
append_switch_port_to_list( switch_port *port, void *user_data ) {
assert( port != NULL );
assert( user_data != NULL );
switch_port_list *ports = user_data;
if ( ports->in_port != port->port_no ) {
append_to_tail( &ports->list, port );
}
}
static list_element *
get_switch_ports_to_output( const uint32_t port_no, const uint32_t in_port ) {
assert( port_no > 0 );
assert( in_port <= OFPP_MAX || in_port == OFPP_CONTROLLER );
switch_port_list ports;
create_list( &ports.list );
ports.in_port = in_port;
switch ( port_no ) {
case OFPP_IN_PORT:
{
switch_port *port = lookup_switch_port( in_port );
if ( port != NULL ) {
append_to_tail( &ports.list, port );
}
}
break;
case OFPP_FLOOD:
case OFPP_ALL:
{
foreach_switch_port( append_switch_port_to_list, &ports );
}
break;
case OFPP_TABLE:
case OFPP_NORMAL:
case OFPP_CONTROLLER:
case OFPP_LOCAL:
case OFPP_ANY:
{
warn( "Invalid port number ( port_no = %u, in_port = %u ).", port_no, in_port );
}
break;
default:
{
switch_port *port = lookup_switch_port( port_no );
if ( port != NULL ) {
append_to_tail( &ports.list, port );
}
}
break;
}
return ports.list;
}
OFDPE
send_frame_from_switch_port( const uint32_t port_no, buffer *frame ) {
assert( port_no > 0 );
assert( frame != NULL );
if ( port_no == OFPP_NORMAL || port_no == OFPP_CONTROLLER || port_no == OFPP_LOCAL || port_no == OFPP_ANY ) {
error( "Invalid output port number ( port_no = %u, frame = %p ).", port_no, frame );
return OFDPE_FAILED;
}
if ( frame->length + ETH_FCS_LENGTH < ETH_MINIMUM_LENGTH ) {
fill_ether_padding( frame );
}
uint32_t in_port = 0;
if ( port_no == OFPP_ALL || port_no == OFPP_FLOOD || port_no == OFPP_IN_PORT || port_no == OFPP_TABLE ) {
if ( frame->user_data == NULL ) {
warn( "Ethernet frame is not parsed yet. OFPP_ALL, OFPP_FLOOD, OFPP_IN_PORT, and OFPP_TABLE "
"require eth_in_port to find actual output ports ( port_no = %u, frame = %p ).", port_no, frame );
return OFDPE_FAILED;
}
in_port = ( ( packet_info * ) frame->user_data )->eth_in_port;
if ( in_port == 0 || ( in_port > OFPP_MAX && in_port != OFPP_CONTROLLER ) ) {
warn( "Invalid eth_in_port found in a parsed frame ( frame = %p, eth_in_port = %u ).", frame, in_port );
return OFDPE_FAILED;
}
}
if ( !lock_mutex( &mutex ) ) {
return ERROR_LOCK;
}
OFDPE ret = OFDPE_SUCCESS;
if ( port_no == OFPP_IN_PORT && in_port == OFPP_CONTROLLER ) {
warn( "Packet-out with in_port = OFPP_CONTROLLER is not supported." );
ret = OFDPE_FAILED;
}
else if ( port_no != OFPP_TABLE ) {
list_element *ports = get_switch_ports_to_output( port_no, in_port );
for ( list_element *e = ports; e != NULL; e = e->next ) {
assert( e->data != NULL );
switch_port *port = e->data;
if ( ( port->config & ( OFPPC_PORT_DOWN | OFPPC_NO_FWD ) ) != 0 ) {
continue;
}
assert( port->device != NULL );
send_frame( port->device, frame );
}
if ( ports != NULL ) {
delete_list( ports );
}
}
else {
if ( in_port != OFPP_CONTROLLER ) {
switch_port *port = lookup_switch_port( in_port );
if ( port != NULL ) {
handle_received_frame( port, frame );
}
else {
ret = OFDPE_FAILED;
}
}
else {
warn( "Packet-out with in_port = OFPP_CONTROLLER is not supported." );
ret = OFDPE_FAILED;
}
}
if ( !unlock_mutex( &mutex ) ) {
return ERROR_UNLOCK;
}
return ret;
}
OFDPE
get_port_stats( const uint32_t port_no, port_stats **stats, uint32_t *n_ports ) {
assert( ( port_no > 0 && port_no <= OFPP_MAX ) || port_no == OFPP_ANY );
assert( stats != NULL );
assert( n_ports != NULL );
if ( !lock_mutex( &mutex ) ) {
return ERROR_LOCK;
}
list_element *ports = NULL;
*n_ports = 0;
*stats = NULL;
if ( port_no != OFPP_ANY ) {
switch_port *port = lookup_switch_port( port_no );
if ( port == NULL ) {
if ( unlock_mutex( &mutex ) != OFDPE_SUCCESS ) {
return ERROR_UNLOCK;
}
return ERROR_OFDPE_BAD_REQUEST_BAD_PORT;
}
create_list( &ports );
append_to_tail( &ports, port );
}
else {
ports = get_all_switch_ports();
if ( ports == NULL ) {
return unlock_mutex( &mutex ) ? OFDPE_SUCCESS : ERROR_UNLOCK;
}
}
*n_ports = ( uint32_t ) list_length_of( ports );
size_t length = ( *n_ports ) * sizeof( port_stats );
*stats = xmalloc( length );
memset( *stats, 0, length );
port_stats *stat = *stats;
for ( list_element *e = ports; e != NULL; e = e->next ) {
assert( e->data != NULL );
switch_port *port = e->data;
stat->port_no = port->port_no;
assert( port->device != NULL );
stat->rx_packets = port->device->stats.rx_packets;
stat->tx_packets = port->device->stats.tx_packets;
stat->rx_bytes = port->device->stats.rx_bytes;
stat->tx_bytes = port->device->stats.tx_bytes;
stat->rx_dropped = port->device->stats.rx_dropped;
stat->tx_dropped = port->device->stats.tx_dropped;
stat->rx_errors = port->device->stats.rx_errors;
stat->tx_errors = port->device->stats.tx_errors;
stat->rx_frame_err = port->device->stats.rx_frame_err;
stat->rx_over_err = port->device->stats.rx_over_err;
stat->rx_crc_err = port->device->stats.rx_crc_err;
stat->collisions = port->device->stats.collisions;
struct timespec duration = get_switch_port_uptime( port );
stat->duration_sec = ( uint32_t ) duration.tv_sec;
stat->duration_nsec = ( uint32_t ) duration.tv_nsec;
stat++;
}
delete_list( ports );
if ( !unlock_mutex( &mutex ) ) {
return ERROR_UNLOCK;
}
return OFDPE_SUCCESS;
}
OFDPE
get_port_description( const uint32_t port_no, port_description **descriptions, uint32_t *n_ports ) {
assert( port_no > 0 || port_no == OFPP_ALL );
assert( descriptions != NULL );
assert( n_ports != NULL );
if ( !lock_mutex( &mutex ) ) {
return ERROR_LOCK;
}
*n_ports = 0;
*descriptions = NULL;
list_element *ports = NULL;
if ( port_no != OFPP_ALL ) {
switch_port *port = lookup_switch_port( port_no );
if ( ports == NULL ) {
return unlock_mutex( &mutex ) ? OFDPE_SUCCESS : ERROR_UNLOCK;
}
create_list( &ports );
append_to_tail( &ports, port );
}
else {
ports = get_all_switch_ports();
if ( ports == NULL ) {
return unlock_mutex( &mutex ) ? OFDPE_SUCCESS : ERROR_UNLOCK;
}
}
*n_ports = ( uint32_t ) list_length_of( ports );
size_t length = ( *n_ports ) * sizeof( port_description );
*descriptions = xmalloc( length );
memset( *descriptions, 0, length );
port_description *description = *descriptions;
for ( list_element *e = ports; e != NULL; e = e->next ) {
assert( e->data != NULL );
switch_port *port = e->data;
assert( port->device != NULL );
// FIXME: we assume that "port_description" is the same structure as "struct ofp_port".
switch_port_to_ofp_port( description, port );
description++;
}
delete_list( ports );
if ( !unlock_mutex( &mutex ) ) {
return ERROR_UNLOCK;
}
return OFDPE_SUCCESS;
}
void
dump_port_description( const port_description *description, void dump_function( const char *format, ... ) ) {
assert( description != NULL );
assert( dump_function != NULL );
( *dump_function )( "port_no: %u", description->port_no );
( *dump_function )( "hw_addr: %02x:%02x:%02x:%02x:%02x:%02x",
description->hw_addr[ 0 ], description->hw_addr[ 1 ], description->hw_addr[ 2 ],
description->hw_addr[ 3 ], description->hw_addr[ 4 ], description->hw_addr[ 5 ] );
( *dump_function )( "name: %s", description->name );
( *dump_function )( "config: %#x", description->config );
( *dump_function )( "state: %#x", description->state );
( *dump_function )( "curr: %#x", description->curr );
( *dump_function )( "advertised: %#x", description->advertised );
( *dump_function )( "supported: %#x", description->supported );
( *dump_function )( "peer: %#x", description->peer );
( *dump_function )( "curr_speed: %u", description->curr_speed );
( *dump_function )( "max_speed: %u", description->max_speed );
}
/*
* Local variables:
* c-basic-offset: 2
* indent-tabs-mode: nil
* End:
*/