Exemple #1
0
OFDPE
get_table_stats( table_stats **stats, uint8_t *n_tables ) {
  assert( stats != NULL );
  assert( n_tables != NULL );

  if ( !lock_pipeline() ) {
    return ERROR_LOCK;
  }

  *stats = xmalloc( sizeof( table_stats ) * N_FLOW_TABLES );
  memset( *stats, 0, sizeof( table_stats ) * N_FLOW_TABLES );
  *n_tables = 0;

  table_stats *stat = *stats;
  for ( uint8_t i = 0; i <= FLOW_TABLE_ID_MAX; i++ ) {
    stat->table_id = i;
    stat->active_count = get_active_count( i );
    stat->lookup_count = get_lookup_count( i );
    stat->matched_count = get_matched_count( i );
    stat++;
    ( *n_tables )++;
  }

  if ( !unlock_pipeline() ) {
    return ERROR_UNLOCK;
  }

  return OFDPE_SUCCESS;
}
Exemple #2
0
OFDPE
delete_flow_entries_by_group_id( const uint32_t group_id ) {
  assert( valid_group_id( group_id ) );

  if ( !lock_pipeline() ) {
    return ERROR_LOCK;
  }

  list_element *delete_us = NULL;
  create_list( &delete_us );

  for ( uint8_t table_id = 0; table_id <= FLOW_TABLE_ID_MAX; table_id++ ) {
    flow_table *table = get_flow_table( table_id );
    assert( table != NULL );
    for ( list_element *e = table->entries; e != NULL; e = e->next ) {
      assert( e->data != NULL );
      flow_entry *entry = e->data;
      if ( instructions_have_output_group( entry->instructions, group_id ) ) {
        append_to_tail( &delete_us, e->data );
      }
    }
  }

  delete_flow_entries_in_list( delete_us, 0, 0, OFPP_ANY, OFPG_ANY, OFPRR_GROUP_DELETE );

  if ( delete_us != NULL ) {
    delete_list( delete_us );
  }

  if ( !unlock_pipeline() ) {
    return ERROR_UNLOCK;
  }

  return OFDPE_SUCCESS;
}
Exemple #3
0
void
dump_flow_table( const uint8_t table_id, void dump_function( const char *format, ... ) ) {
  assert( valid_table_id( table_id ) );
  assert( dump_function != NULL );

  if ( !lock_pipeline() ) {
    return;
  }

  flow_table *table = get_flow_table( table_id );
  assert( table != NULL );

  ( *dump_function )( "#### TABLE %#x (%s) ####", table_id, table->initialized ? "initialized" : "not initialized yet" );
  ( *dump_function )( "[Features]" );
  ( *dump_function )( "table_id: %#x", table->features.table_id );
  ( *dump_function )( "name: %s", table->features.name );
  ( *dump_function )( "metadata_match: %#" PRIx64, table->features.metadata_match );
  ( *dump_function )( "metadata_write: %#" PRIx64, table->features.metadata_write );
  ( *dump_function )( "config: %#x", table->features.config );
  ( *dump_function )( "max_entries: %u", table->features.max_entries );
  ( *dump_function )( "instructions: %#" PRIx64, table->features.instructions );
  ( *dump_function )( "instructions_miss: %#" PRIx64, table->features.instructions_miss );
  ( *dump_function )( "write_actions: %#" PRIx64, table->features.write_actions );
  ( *dump_function )( "write_actions_miss: %#" PRIx64, table->features.write_actions_miss );
  ( *dump_function )( "apply_actions: %#" PRIx64, table->features.apply_actions );
  ( *dump_function )( "apply_actions_miss: %#" PRIx64, table->features.apply_actions_miss );
  ( *dump_function )( "matches: %#" PRIx64, table->features.matches );
  ( *dump_function )( "wildcards: %#" PRIx64, table->features.wildcards );
  ( *dump_function )( "write_setfield: %#" PRIx64, table->features.write_setfield );
  ( *dump_function )( "write_setfield_miss: %#" PRIx64, table->features.write_setfield_miss );
  ( *dump_function )( "apply_setfield: %#" PRIx64, table->features.apply_setfield );
  ( *dump_function )( "apply_setfield_miss: %#" PRIx64, table->features.apply_setfield_miss );
  for ( uint8_t i = 0; i < N_FLOW_TABLES; i++ ) {
    if ( !table->features.next_table_ids[ i ] ) {
      ( *dump_function )( "next_table_id: %#x - %s", i, table->features.next_table_ids[ i ] ? "true" : "false" ); 
    }
  }
  for ( uint8_t i = 0; i < N_FLOW_TABLES; i++ ) {
    if ( !table->features.next_table_ids_miss[ i ] ) {
      ( *dump_function )( "next_table_id_miss: %#x - %s", i, table->features.next_table_ids_miss[ i ] ? "true" : "false" ); 
    }
  }

  ( *dump_function )( "[Stats]" );
  ( *dump_function )( "active_count: %u", table->counters.active_count );
  ( *dump_function )( "lookup_count: %" PRIu64, table->counters.lookup_count );
  ( *dump_function )( "matched_count: %" PRIu64, table->counters.matched_count );

  ( *dump_function )( "[Entries]" );

  for ( list_element *e = table->entries; e != NULL; e = e->next ) {
    flow_entry *entry = e->data;
    assert( entry != NULL );
    dump_flow_entry( entry, dump_function );
  }

  unlock_pipeline();
}
Exemple #4
0
OFDPE
update_flow_entry_strict( const uint8_t table_id, const match *match, const uint64_t cookie, const uint64_t cookie_mask,
                          const uint16_t priority, const uint16_t flags, instruction_set *instructions ) {
  /*
   * FLOW_TABLE_ALL (=OFPTT_ALL) is not allowed in the protocol specification.
   * But we allow it for internal use.
   */
  if ( !valid_table_id( table_id ) && table_id != FLOW_TABLE_ALL ) {
    return ERROR_OFDPE_FLOW_MOD_FAILED_BAD_TABLE_ID;
  }

  if ( match == NULL ) {
    error( "Invalid match ( %p ).", match );
    return ERROR_INVALID_PARAMETER;
  }
  if ( instructions == NULL ) {
    error( "Invalid instruction set ( %p ).", instructions );
    return ERROR_INVALID_PARAMETER;
  }

  if ( !lock_pipeline() ) {
    return ERROR_LOCK;
  }

  flow_table *table = get_flow_table( table_id );
  assert( table != NULL );

  OFDPE ret = validate_instruction_set( instructions, table->features.metadata_write );
  if ( ret != OFDPE_SUCCESS ) {
    error( "Invalid instruction set ( ret = %#x, instructions = %p ).", ret, instructions );
    return ret;
  }

  list_element *list = NULL;
  if ( table_id != FLOW_TABLE_ALL ) {
    list = lookup_flow_entries_with_table_id( table_id, match, priority, true, false );
  }
  else {
    list = lookup_flow_entries_from_all_tables( match, priority, true, false );
  }

  update_flow_entries_in_list( list, cookie, cookie_mask, flags, instructions );

  if ( list != NULL ) {
    delete_list( list );
  }

  if ( !unlock_pipeline() ) {
    return ERROR_UNLOCK;
  }

  return OFDPE_SUCCESS;
}
OFDPE
unref_meter_id( const uint32_t meter_id ) {
  if ( meter_id == 0 || meter_id > OFPM_MAX ) {
    return ERROR_OFDPE_METER_MOD_FAILED_INVALID_METER;
  }
  if ( !lock_pipeline() ) {
    return ERROR_UNLOCK;
  }
  meter_entry *entry = lookup_meter_entry( meter_id );
  if ( entry == NULL ) {
    return ERROR_OFDPE_METER_MOD_FAILED_UNKNOWN_METER;
  }
  entry->ref_count--;
  if ( !unlock_pipeline() ) {
    return ERROR_UNLOCK;
  }
  return OFDPE_SUCCESS;
}
void
decrement_reference_count( const uint32_t group_id ) {
  assert( table != NULL );
  assert( valid_group_id( group_id ) );

  if ( !lock_pipeline() ) {
    error( "Failed to lock pipeline." );
    return;
  }

  group_entry *entry = lookup_group_entry( group_id );
  if ( entry != NULL ) {
    entry->ref_count--;
  }

  if ( !unlock_pipeline() ) {
    error( "Failed to unlock pipeline." );
  }
}
Exemple #7
0
static void
age_flow_entries( void *user_data ) {
  const uint8_t table_id = *( uint8_t * ) user_data;

  assert( valid_table_id( table_id ) );

  if ( !lock_pipeline() ) {
    return;
  }

  struct timespec now = { 0, 0 };
  time_now( &now );

  foreach_flow_entry( table_id, age_flow_entries_walker, &now );

  if ( !unlock_pipeline() ) {
    return;
  }
}
OFDPE
get_group_desc( group_desc **stats, uint16_t *n_groups ) {
  assert( table != NULL );
  assert( n_groups != NULL );

  if ( !lock_pipeline() ) {
    return ERROR_LOCK;
  }

  *n_groups = 0;
  for ( list_element *element = table->entries; element != NULL; element = element->next ) {
    if ( element->data == NULL ) {
      continue;
    }
    ( *n_groups )++;
  }

  size_t length = sizeof( group_desc ) * ( *n_groups );
  *stats = NULL;
  if ( *n_groups > 0 ) {
    *stats = xmalloc( length );
    memset( *stats, 0, length );
  }

  group_desc *stat = *stats;
  for ( list_element *element = table->entries; element != NULL; element = element->next ) {
    if ( element->data == NULL ) {
      continue;
    }
    group_entry *entry = element->data;
    stat->type = entry->type;
    stat->group_id = entry->group_id;
    stat->buckets = duplicate_buckets( entry->buckets );
    stat++;
  }

  if ( !unlock_pipeline() ) {
    return ERROR_UNLOCK;
  }

  return OFDPE_SUCCESS;
}
Exemple #9
0
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;
}
Exemple #10
0
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_meter_entry( const uint32_t meter_id ) {
  assert( table != NULL );

  if ( meter_id == 0 && meter_id <= OFPM_MAX && meter_id != OFPM_CONTROLLER && meter_id != OFPM_ALL ) {
    return ERROR_OFDPE_METER_MOD_FAILED_INVALID_METER;
  }

  OFDPE ret = OFDPE_SUCCESS;
  if ( !lock_pipeline() ) {
    return ERROR_LOCK;
  }
  if ( meter_id == OFPM_ALL ) {
    delete_flow_entries_by_meter_id( meter_id );
    for ( list_element *e = table->entries; e != NULL; ) {
      list_element *next = e->next;
      meter_entry *entry = e->data;
      if ( entry->meter_id > 0 && entry->meter_id <= OFPM_MAX ) { // virtual meters won't be deleted by OFPM_ALL
        delete_element( &table->entries, entry );
        free_meter_entry( entry );
      }
      e = next;
    }
  } else {
    meter_entry *old_entry = lookup_meter_entry( meter_id );
    if ( NULL == old_entry ) {
      ret = ERROR_OFDPE_METER_MOD_FAILED_UNKNOWN_METER;
    } else {
      if ( old_entry->ref_count > 0 ) {
        delete_flow_entries_by_meter_id( meter_id );
      }
      delete_element( &table->entries, old_entry );
      free_meter_entry( old_entry );
    }
  }
  if ( !unlock_pipeline() ) {
    return ERROR_UNLOCK;
  }
  return ret;
}
OFDPE
replace_meter_entry( const uint16_t flags, const uint32_t meter_id, const list_element *bands ) {
  assert( table != NULL );

  if ( meter_id == 0 || ( meter_id > OFPM_MAX && meter_id != OFPM_CONTROLLER )) {
    return ERROR_OFDPE_METER_MOD_FAILED_INVALID_METER;
  }

  meter_entry *entry = alloc_meter_entry( flags, meter_id, bands );
  if ( entry == NULL ) {
    return ERROR_INVALID_PARAMETER;
  }

  OFDPE ret = OFDPE_SUCCESS;
  if ( !lock_pipeline() ) {
    free_meter_entry( entry );
    return ERROR_LOCK;
  }
  meter_entry *old_entry = lookup_meter_entry( entry->meter_id );
  if ( NULL == old_entry ) {
    ret = ERROR_OFDPE_METER_MOD_FAILED_UNKNOWN_METER;
    free_meter_entry( entry );
  } else {
    entry->ref_count = old_entry->ref_count;
    entry->packet_count = old_entry->packet_count;
    entry->byte_count = old_entry->byte_count;
    entry->estimated_rate = old_entry->estimated_rate;
    entry->created_at = old_entry->created_at;
    entry->meter_at = old_entry->meter_at;
    
    delete_element( &table->entries, old_entry );
    append_to_tail( &table->entries, entry );
    free_meter_entry( old_entry );
  }
  if ( !unlock_pipeline() ) {
    free_meter_entry( entry );
    return ERROR_UNLOCK;
  }
  return ret;
}
OFDPE
update_group_entry( const uint32_t group_id, const uint8_t type, bucket_list *buckets ) {
  assert( table != NULL );

  if ( !valid_group_id( group_id ) ) {
    return ERROR_OFDPE_GROUP_MOD_FAILED_INVALID_GROUP;
  }

  if ( !valid_group_type( type ) ) {
    return ERROR_OFDPE_GROUP_MOD_FAILED_INVALID_GROUP;
  }

  if ( !lock_pipeline() ) {
    return ERROR_LOCK;
  }

  OFDPE ret = OFDPE_SUCCESS;
  group_entry *entry = lookup_group_entry( group_id );
  if ( entry == NULL ) {
    ret = ERROR_OFDPE_GROUP_MOD_FAILED_UNKNOWN_GROUP;
  }

  if ( ret == OFDPE_SUCCESS ) {
    ret = validate_buckets( entry->type, buckets );
  }

  if ( ret == OFDPE_SUCCESS ) {
    if ( entry->buckets != NULL ) {
      delete_action_bucket_list( entry->buckets );
    }
    entry->type = type;
    entry->buckets = buckets;
  }

  if ( !unlock_pipeline() ) {
    return ERROR_UNLOCK;
  }

  return ret;
}
OFDPE
delete_group_entry( const uint32_t group_id ) {
  assert( table != NULL );

  if ( !valid_group_id( group_id ) ) {
    return ERROR_OFDPE_GROUP_MOD_FAILED_INVALID_GROUP;
  }

  if ( !lock_pipeline() ) {
    return ERROR_LOCK;
  }

  if ( group_id != OFPG_ALL ) {
    group_entry *entry = lookup_group_entry( group_id );
    if ( entry != NULL ) {
      delete_element( &table->entries, entry );
      delete_flow_entries_by_group_id( entry->group_id );
      free_group_entry( entry );
    }
  }
  else {
    for ( list_element *element = table->entries; element != NULL; element = element->next ) {
      if ( element->data == NULL ) {
        continue;
      }
      group_entry *entry = element->data;
      delete_flow_entries_by_group_id( entry->group_id );
      free_group_entry( entry );
    }
    delete_list( table->entries );
    create_list( &table->entries );
  }

  if ( !unlock_pipeline() ) {
    return ERROR_UNLOCK;
  }

  return OFDPE_SUCCESS;
}
Exemple #15
0
list_element *
lookup_flow_entries( const uint8_t table_id, const match *match ) {
  assert( valid_table_id( table_id ) || table_id == FLOW_TABLE_ALL );

  if ( !lock_pipeline() ) {
    return NULL;
  }

  list_element *list = NULL;
  if ( table_id != FLOW_TABLE_ALL ) {
    list = lookup_flow_entries_with_table_id( table_id, match, 0, false, true );
  }
  else {
    list = lookup_flow_entries_from_all_tables( match, 0, false, true );
  }

  if ( !unlock_pipeline() ) {
    delete_list( list );
    return NULL;
  }

  return list;
}
Exemple #16
0
OFDPE
add_flow_entry( const uint8_t table_id, flow_entry *entry, const uint16_t flags ) {
  if ( !valid_table_id( table_id ) ) {
    return ERROR_OFDPE_FLOW_MOD_FAILED_BAD_TABLE_ID;
  }
  if ( entry == NULL ) {
    return ERROR_INVALID_PARAMETER;
  }

  if ( !lock_pipeline() ) {
    return ERROR_LOCK;
  }

  flow_table *table = get_flow_table( table_id );
  if ( table == NULL ) {
    return ERROR_OFDPE_FLOW_MOD_FAILED_BAD_TABLE_ID;
  }

  if ( table->features.max_entries <= get_active_count( table_id ) ) {
    return ERROR_OFDPE_FLOW_MOD_FAILED_TABLE_FULL;
  }

  OFDPE ret = validate_instruction_set( entry->instructions, table->features.metadata_write );
  if ( ret == OFDPE_SUCCESS ) {
    entry->table_id = table_id;
    ret = insert_flow_entry( table, entry, flags );
    if ( ret == OFDPE_SUCCESS ) {
      increment_reference_counters_in_groups( entry->instructions );
    }
  }

  if ( !unlock_pipeline() ) {
    return ERROR_UNLOCK;
  }

  return ret;
}
Exemple #17
0
OFDPE
update_or_add_flow_entry( const uint8_t table_id, const match *key,
                          const uint64_t cookie, const uint64_t cookie_mask,
                          const uint16_t priority, const uint16_t idle_timeout, const uint16_t hard_timeout,
                          const uint16_t flags, const bool strict, instruction_set *instructions ) {
  if ( !valid_table_id( table_id ) ) {
    return ERROR_OFDPE_FLOW_MOD_FAILED_BAD_TABLE_ID;
  }

  if ( key == NULL ) {
    error( "Invalid match ( %p ).", key );
    return ERROR_INVALID_PARAMETER;
  }
  if ( instructions == NULL ) {
    error( "Invalid instruction set ( %p ).", instructions );
    return ERROR_INVALID_PARAMETER;
  }

  if ( !lock_pipeline() ) {
    return ERROR_LOCK;
  }

  flow_table *table = get_flow_table( table_id );
  assert( table != NULL );

  OFDPE ret = validate_instruction_set( instructions, table->features.metadata_write );
  if ( ret != OFDPE_SUCCESS ) {
    error( "Invalid instruction set ( ret = %#x, instructions = %p ).", ret, instructions );
    unlock_pipeline();
    return ret;
  }

  list_element *list = lookup_flow_entries_with_table_id( table_id, key, priority, strict, false );
  if ( list != NULL ) {
    update_flow_entries_in_list( list, cookie, cookie_mask, flags, instructions );
    delete_list( list );
  }
  else {
    match *duplicated_match = duplicate_match( key );
    instruction_set *duplicated_instructions = duplicate_instructions( instructions );
    flow_entry *entry = alloc_flow_entry( duplicated_match, duplicated_instructions,
                                          priority, idle_timeout, hard_timeout, flags, cookie );
    if ( entry != NULL ) {
      ret = add_flow_entry( table_id, entry, flags );
      if ( ret != OFDPE_SUCCESS ) {
        error( "Failed to add flow entry ( table_id = %#x, entry = %p, flags = %#x ).", table_id, entry, flags );
      }
    }
    else { 
      delete_match( duplicated_match );
      delete_instructions( duplicated_instructions );
      ret = OFDPE_FAILED;
    }
  }

  if ( !unlock_pipeline() ) {
    return ERROR_UNLOCK;
  }

  return ret;
}
Exemple #18
0
OFDPE
get_flow_stats( const uint8_t table_id, const match *match, const uint64_t cookie, const uint64_t cookie_mask,
                const uint32_t out_port, const uint32_t out_group, flow_stats **stats, uint32_t *n_entries ) {
  assert( valid_table_id( table_id ) || table_id == FLOW_TABLE_ALL );
  assert( stats != NULL );
  assert( n_entries != NULL );

  if ( !lock_pipeline() ) {
    return ERROR_LOCK;
  }

  list_element *list = NULL;
  if ( table_id != FLOW_TABLE_ALL ) {
    list = lookup_flow_entries_with_table_id( table_id, match, 0, false, false );
  }
  else {
    list = lookup_flow_entries_from_all_tables( match, 0, false, false );
  }

  *n_entries = 0;
  list_element *entries = NULL;
  create_list( &entries );

  for ( list_element *e = list; e != NULL; e = e->next ) {
    flow_entry *entry = e->data;
    assert( entry != NULL );

    bool matched = true;

    if ( out_port != OFPP_ANY ) {
      if ( !instructions_have_output_port( entry->instructions, out_port ) ) {
        matched = false;
      }
    }
    if ( out_group != OFPG_ANY ) {
      if ( !instructions_have_output_group( entry->instructions, out_group ) ) {
        matched = false;
      }
    }

    if ( matched && cookie_mask != 0 ) {
      if ( ( entry->cookie & cookie_mask ) != ( cookie & cookie_mask ) ) {
        matched = false;
      }
    }

    if ( matched ) {
      ( *n_entries )++;
      append_to_tail( &entries, entry );
    }
  }

  if ( list != NULL ) {
    delete_list( list );
  }

  *stats = NULL;
  if ( *n_entries > 0 ) {
    *stats = xmalloc( sizeof( flow_stats ) * ( *n_entries ) );
    memset( *stats, 0, sizeof( flow_stats ) * ( *n_entries ) );
  }

  struct timespec now = { 0, 0 };
  time_now( &now );
  struct timespec diff = { 0, 0 };
  flow_stats *stat = *stats;

  for ( list_element *e = entries; e != NULL; e = e->next ) {
    flow_entry *entry = e->data;
    stat->table_id = entry->table_id;
    timespec_diff( entry->created_at, now, &diff );
    stat->duration_sec = ( uint32_t ) diff.tv_sec;
    stat->duration_nsec = ( uint32_t ) diff.tv_nsec;
    stat->priority = entry->priority;
    stat->idle_timeout = entry->idle_timeout;
    stat->hard_timeout = entry->hard_timeout;
    stat->flags = entry->flags;
    stat->cookie = entry->cookie;
    stat->packet_count = entry->packet_count;
    stat->byte_count = entry->byte_count;
    stat->match = *entry->match;
    stat->instructions = *entry->instructions;

    stat++;
  }

  if ( entries != NULL ) {
    delete_list( entries );
  }

  if ( !unlock_pipeline() ) {
    return ERROR_UNLOCK;
  }

  return OFDPE_SUCCESS;
}
OFDPE
get_group_stats( const uint32_t group_id, group_stats **stats, uint32_t *n_groups ) {
  assert( table != NULL );
  assert( stats != NULL );
  assert( n_groups != NULL );

  if ( !valid_group_id( group_id ) && group_id != OFPG_ALL ) {
    return ERROR_OFDPE_BAD_REQUEST_BAD_TABLE_ID;
  }

  if ( !lock_pipeline() ) {
    return ERROR_LOCK;
  }

  OFDPE ret = OFDPE_SUCCESS;

  list_element *groups = NULL;
  create_list( &groups );
  *n_groups = 0;
  if ( group_id != OFPG_ALL ) {
    group_entry *entry = lookup_group_entry( group_id );
    if ( entry == NULL ) {
      if ( !unlock_pipeline() ) {
        return ERROR_UNLOCK;
      }
      return ERROR_OFDPE_BAD_REQUEST_BAD_TABLE_ID;
    }
    append_to_tail( &groups, entry );
    ( *n_groups )++;
  }
  else {
    for ( list_element *e = table->entries; e != NULL; e = e->next ) {
      if ( e->data == NULL ) {
        continue;
      }
      group_entry *entry = e->data;
      append_to_tail( &groups, entry );
      ( *n_groups )++;
    }
  }

  *stats = NULL;
  if ( *n_groups > 0 ) {
    *stats = xmalloc( sizeof( group_stats ) * ( *n_groups ) );
    memset( *stats, 0, sizeof( group_stats ) * ( *n_groups ) );
  }

  group_stats *stat = *stats;
  for ( list_element *e = groups; e != NULL; e = e->next ) {
    assert( e->data != NULL );
    group_entry *entry = e->data;
    stat->group_id = entry->group_id;
    stat->ref_count = entry->ref_count;
    stat->packet_count = entry->packet_count;
    stat->byte_count = entry->byte_count;
    struct timespec now = { 0, 0 };
    time_now( &now );
    struct timespec diff = { 0, 0 };
    timespec_diff( entry->created_at, now, &diff );
    stat->duration_sec = ( uint32_t ) diff.tv_sec;
    stat->duration_nsec = ( uint32_t ) diff.tv_nsec;
    create_list( &stat->bucket_stats );
    for ( dlist_element *b = get_first_element( entry->buckets ); b != NULL; b = b->next ) {
      if ( b->data == NULL ) {
        continue;
      }
      bucket *bucket = b->data;
      bucket_counter *counter = xmalloc( sizeof( bucket_counter ) );
      memset( counter, 0, sizeof( bucket_counter ) );
      counter->packet_count = bucket->packet_count;
      counter->byte_count = bucket->byte_count;
      append_to_tail( &stat->bucket_stats, counter );
    }
    stat++;
  }

  if ( groups != NULL ) {
    delete_list( groups );
  }

  if ( !unlock_pipeline() ) {
    return ERROR_UNLOCK;
  }

  return ret;
}