#include "flow.h"
#include "flow_key.h"
#include "ha_module.h"
+#include "log/messages.h"
#include "main/snort_debug.h"
#include "packet_io/sfdaq.h"
#include "profiler/profiler.h"
KEY_TYPE_IP4 = 2
};
-typedef std::unordered_map<FlowHAClientHandle, FlowHAClient*> ClientMap;
+typedef std::array<FlowHAClient*, MAX_CLIENTS> ClientMap;
THREAD_LOCAL SimpleStats ha_stats;
THREAD_LOCAL ProfileStats ha_perf_stats;
PortBitSet* HighAvailabilityManager::ports = nullptr;
bool HighAvailabilityManager::use_daq_channel = false;
struct timeval FlowHAState::min_session_lifetime;
-uint8_t FlowHAClient::s_handle_counter = 0;
+uint8_t s_handle_counter = 1; // stream client (index == 0) always exists
-static THREAD_LOCAL ClientMap* client_map;
-static THREAD_LOCAL FlowHAClient* s_session_client;
+// The [0] entry contains the stream client (always present)
+// Entries [1] to [MAX_CLIENTS-1] contain the optional clients
+static THREAD_LOCAL ClientMap* s_client_map;
static inline bool is_ip6_key(const FlowKey* key)
{
pending = NONE_PENDING;
}
-FlowHAClient::FlowHAClient(bool session_client)
+FlowHAClient::FlowHAClient(uint8_t length, bool session_client)
{
DebugMessage(DEBUG_HA,"FlowHAClient::FlowHAClient()\n");
+ if ( !s_client_map )
+ return;
+
+ header.length = length;
+
if ( session_client )
{
handle = SESSION_HA_CLIENT;
- s_session_client = this;
+ header.client = SESSION_HA_CLIENT_INDEX;
+ (*s_client_map)[0] = this;
}
else
{
- assert(s_handle_counter != MAX_CLIENTS);
- handle = (1 << s_handle_counter);
+ if ( s_handle_counter >= MAX_CLIENTS )
+ {
+ ErrorMessage("Attempting to register too many FlowHAClients\n");
+ return;
+ }
+
+ header.client = s_handle_counter;
+ handle = (1 << (s_handle_counter-1));
+ (*s_client_map)[s_handle_counter] = this;
s_handle_counter += 1;
}
}
+bool FlowHAClient::fit(HAMessage* msg, uint8_t size)
+{
+ return ( (int)(msg->cursor - msg->content()) < (int)(msg->content_length() - size) );
+}
+
+bool FlowHAClient::place(HAMessage* msg, uint8_t* data, uint8_t length)
+{
+ if ( fit(msg, length) )
+ {
+ memcpy(msg->cursor,data,(size_t)length);
+ msg->cursor += length;
+ return true;
+ }
+ else
+ return false;
+}
+
// Write the key type, key length, and key into the message.
// Does not use the message cursor coming in.
// Leave the message cursor just after the key. Return
{
hdr->key_type = KEY_TYPE_IP6;
memcpy(msg->cursor, key, KEY_SIZE_IP6);
- msg->cursor = (uint8_t*)hdr + KEY_SIZE_IP6;
+ msg->cursor += KEY_SIZE_IP6;
return KEY_SIZE_IP6;
}
else
// set of active clients. The Session client is always present.
static uint16_t calculate_update_msg_content_length(Flow* flow)
{
- uint16_t length = s_session_client->get_message_size();
+ assert(s_client_map);
+ assert((*s_client_map)[0]);
+ uint16_t length = ((*s_client_map)[0]->get_message_size() + sizeof(HAClientHeader));
DebugFormat(DEBUG_HA,"HighAvailability::calculate_update_msg_content_length(): length: %d\n",
length);
- // Iterating through the hash map is OK to determine length.
- for (auto& iter : * client_map )
- if ( flow->ha_state->check_pending(iter.first) )
+ for (int i=1; i<s_handle_counter; i++)
+ if ( flow->ha_state->check_pending(1<<i) )
{
- length += iter.second->get_message_size();
+ assert((*s_client_map)[i]);
+ length += ((*s_client_map)[i]->get_message_size() + sizeof(HAClientHeader));
DebugFormat(DEBUG_HA,
"HighAvailability::calculate_update_msg_content_length(): length: %d\n", length);
}
write_flow_key(flow, msg); // set cursor to just beyond key
}
-static void write_update_msg_content(Flow* flow, HAMessage* msg)
+static void write_update_msg_client( FlowHAClient* client, Flow* flow, HAMessage* msg)
{
- // Always have the session portion
- s_session_client->produce(flow,msg);
+ assert(client);
+ assert(msg);
- // Since I'm not sure that the hash map is deterministic, I'll
- // step through the clients in order
- for ( int i=0; i<FlowHAClient::s_handle_counter; i++ )
- {
- FlowHAClientHandle handle = 1<<i;
- if ( flow->ha_state->check_pending(handle) )
- client_map->find(handle)->second->produce(flow,msg);
- }
+ client->place(msg,(uint8_t*)&(client->header),(uint8_t)sizeof(client->header));
+ client->produce(flow, msg);
+}
+
+static void write_update_msg_content(Flow* flow, HAMessage* msg)
+{
+ assert(s_client_map);
+
+ for ( int i=0; i<s_handle_counter; i++ )
+ if ( (i==SESSION_HA_CLIENT_INDEX) || flow->ha_state->check_pending(1<<i) )
+ write_update_msg_client((*s_client_map)[i],flow, msg);
}
static void consume_receive_delete_message(HAMessage* msg)
// flow will be nullptr if/when the session does not exist in the caches
Flow* flow = stream.get_session(&key);
- assert(s_session_client);
+ assert(s_client_map);
- // Update messages MUST include the session client component
- if ( !s_session_client->consume(flow,msg) )
- return;
+ // pointer to the last byte in the message
+ uint8_t* content_end = msg->content() + msg->content_length() - 1;
+
+ while( msg->cursor <= content_end )
+ {
+ // do we have sufficient message left to be able to have an HAClientHeader?
+ if ( (int)(content_end - msg->cursor) < (int)sizeof( HAClientHeader ) )
+ {
+ ErrorMessage("Consuming HA Update message - no HAClientHeader\n");
+ break;
+ }
+
+ HAClientHeader* header = (HAClientHeader*)msg->cursor;
+ msg->cursor += sizeof( HAClientHeader ); // step to the client content
+
+ if ( (header->client >= s_handle_counter) ||
+ ((*s_client_map)[header->client] == nullptr) )
+ {
+ ErrorMessage("Consuming HA Update message - invalid client index\n");
+ break;
+ }
+
+ if ( (content_end - msg->cursor) < header->length )
+ {
+ ErrorMessage("Consuming HA Update memssage - message too short\n");
+ break;
+ }
+
+ if ( !(*s_client_map)[header->client]->consume(flow,msg) )
+ {
+ ErrorMessage("Consuming HA Update message - error from client consume()\n");
+ break;
+ }
+ }
}
static void consume_receive_message(HAMessage* msg)
break;
}
- client_map = new ClientMap;
+ s_client_map = new ClientMap;
+ for ( int i=0; i<MAX_CLIENTS; i++ )
+ (*s_client_map)[i] = nullptr;
// Only looking for side channel processing - FIXIT-H
}
sc->unregister_receive_handler();
}
- delete client_map;
+ delete s_client_map;
}
void HighAvailability::receive_handler(SCMessage* sc_msg)
// NOTE: The type, masks, and count values must be in sync,
typedef uint16_t FlowHAClientHandle;
const FlowHAClientHandle SESSION_HA_CLIENT = 0x0000;
+const uint8_t SESSION_HA_CLIENT_INDEX = 0;
const FlowHAClientHandle ALL_CLIENTS = 0xffff;
-const uint8_t MAX_CLIENTS = 16;
+// One client for each mask bit plus one 'automatic' session client
+// client handle = (1<<(client_index-1)
+// session client has handle of 0 and index of 0
+const uint8_t MAX_CLIENTS = 17;
enum HAEvent
{
virtual ~FlowHAClient() { }
virtual bool consume(Flow*, HAMessage*) { return false; }
virtual bool produce(Flow*, HAMessage*) { return false; }
- virtual size_t get_message_size() { return 0; }
+ uint8_t get_message_size() { return header.length; }
+ bool fit(HAMessage*, uint8_t);
+ bool place(HAMessage*, uint8_t*, uint8_t);
FlowHAClientHandle handle; // Actual handle for the instance
- static uint8_t s_handle_counter; // next handle to be assigned
+ HAClientHeader header;
protected:
- FlowHAClient(bool); // Arg == true for session client
+ FlowHAClient(uint8_t, bool);
+
};
// HighAvailability is instantiated for each packet-thread.
-add_cpputest(ha_test flow)
+add_cpputest(ha_test ha)
+add_cpputest(ha_module_ha ha_module)
AM_DEFAULT_SOURCE_EXT = .cc
check_PROGRAMS = \
-ha_test
+ha_test \
+ha_module_test
TESTS = $(check_PROGRAMS)
ha_test_CPPFLAGS = @AM_CPPFLAGS@ @CPPUTEST_CPPFLAGS@
+ha_module_test_CPPFLAGS = @AM_CPPFLAGS@ @CPPUTEST_CPPFLAGS@
-ha_test_LDADD = ../libflow.a @CPPUTEST_LDFLAGS@
+ha_test_LDADD = \
+../ha.o \
+@CPPUTEST_LDFLAGS@
+
+ha_module_test_LDADD = \
+../ha_module.o \
+../../framework/libframework.a \
+../../sfip/libsfip.a \
+../../catch/libcatch_tests.a \
+@CPPUTEST_LDFLAGS@
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2016 Cisco and/or its affiliates. All rights reserved.
+//
+// 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. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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.
+//--------------------------------------------------------------------------
+
+// ha_module_test.cc author Ed Borgoyn <eborgoyn@cisco.com>
+// unit test main
+
+#include <CppUTest/CommandLineTestRunner.h>
+#include <CppUTest/TestHarness.h>
+
+#include "flow/ha.h"
+#include "flow/ha_module.h"
+
+#include "log/messages.h"
+#include "main/snort_debug.h"
+#include "profiler/profiler.h"
+
+void LogMessage(const char*,...) { }
+
+THREAD_LOCAL SimpleStats ha_stats;
+THREAD_LOCAL ProfileStats ha_perf_stats;
+
+void show_stats(PegCount*, const PegInfo*, unsigned, const char*) { }
+void show_stats(PegCount*, const PegInfo*, IndexVec&, const char*) { }
+void show_stats(PegCount*, const PegInfo*, IndexVec&, const char*, FILE*) { }
+
+void ParseWarning(WarningGroup, const char*, ...) { }
+
+char* snort_strdup(const char* str) { return strdup(str); }
+
+void Debug::print(const char*, int, uint64_t, const char*, ...) { }
+
+static bool s_port_1_set = false;
+static bool s_use_daq = false;
+static bool s_instantiate_called = false;
+
+static char* make_bit_string(int bit)
+{
+ static char bit_string[65537];
+ for ( int i=0; i<65536; i++)
+ bit_string[i] = (bit == i) ? '1' : '0';
+
+ bit_string[65536] = '\0';
+
+ return bit_string;
+}
+
+bool HighAvailabilityManager::instantiate(PortBitSet* mod_ports, bool mod_use_daq_channel)
+{
+ s_instantiate_called = true;
+ s_port_1_set = mod_ports->test(1);
+ s_use_daq = mod_use_daq_channel;
+
+ return true;
+}
+
+TEST_GROUP(high_availability_module_test)
+{
+ void setup()
+ {
+ }
+
+ void teardown()
+ {
+ }
+};
+
+TEST(high_availability_module_test, test_ha_valid)
+{
+ Value ports_val(make_bit_string(1));
+ Value enable_val(true);
+ Parameter ports_param = {"ports", Parameter::PT_BIT_LIST, "65535", nullptr, "ports"};
+ Parameter enable_param = {"enable", Parameter::PT_BOOL, nullptr, "false", nullptr };
+
+ HighAvailabilityModule module;
+
+ ports_val.set(&ports_param);
+ enable_val.set(&enable_param);
+
+ s_instantiate_called = false;
+ s_port_1_set = false;
+ s_use_daq = false;
+
+ module.begin("high_availability", 0, nullptr);
+ module.set("high_availability.ports", ports_val, nullptr);
+ module.set("high_availability.enable", enable_val, nullptr);
+ module.end("high_availability", 0, nullptr);
+
+ CHECK(s_instantiate_called == true);
+ CHECK(s_port_1_set == true);
+ CHECK(s_use_daq == false);
+}
+
+TEST(high_availability_module_test, test_ha_disabled)
+{
+ Value enable_val(false);
+ Parameter enable_param = {"enable", Parameter::PT_BOOL, nullptr, "false", nullptr };
+
+ HighAvailabilityModule module;
+
+ enable_val.set(&enable_param);
+
+ s_instantiate_called = false;
+ s_port_1_set = false;
+ s_use_daq = false;
+
+ module.begin("high_availability", 0, nullptr);
+ module.set("high_availability.enable", enable_val, nullptr);
+ module.end("high_availability", 0, nullptr);
+
+ CHECK(s_instantiate_called == false);
+ CHECK(s_port_1_set == false);
+ CHECK(s_use_daq == false);
+}
+
+TEST(high_availability_module_test, test_ha_valid_daq)
+{
+ Value ports_val(make_bit_string(1));
+ Value enable_val(true);
+ Value daq_val(true);
+ Parameter ports_param = {"ports", Parameter::PT_BIT_LIST, "65535", nullptr, "ports"};
+ Parameter enable_param = {"enable", Parameter::PT_BOOL, nullptr, "false", nullptr };
+ Parameter daq_param = {"daq_channel", Parameter::PT_BOOL, nullptr, "false", nullptr };
+
+ HighAvailabilityModule module;
+
+ ports_val.set(&ports_param);
+ enable_val.set(&enable_param);
+ daq_val.set(&daq_param);
+
+ s_instantiate_called = false;
+ s_port_1_set = false;
+ s_use_daq = false;
+
+ module.begin("high_availability", 0, nullptr);
+ module.set("high_availability.ports", ports_val, nullptr);
+ module.set("high_availability.enable", enable_val, nullptr);
+ module.set("high_availability.daq_channel", daq_val, nullptr);
+ module.end("high_availability", 0, nullptr);
+
+ CHECK(s_instantiate_called == true);
+ CHECK(s_port_1_set == true);
+ CHECK(s_use_daq == true);
+}
+
+int main(int argc, char** argv)
+{
+ return CommandLineTestRunner::RunAllTests(argc, argv);
+}
+
#include "stream/stream_api.h"
#define MSG_SIZE 100
+#define TEST_KEY 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47
+
+class StreamHAClient;
+
+static const uint8_t s_test_key[] =
+{
+TEST_KEY
+};
+
+static const uint8_t s_delete_message[] =
+{
+ 0x01,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x01,
+TEST_KEY
+};
+
+
+static struct timeval s_time = { 0, 0 };
+static uint8_t s_message[MSG_SIZE];
+static SideChannel s_side_channel;
+static SCMessage s_sc_message;
+static SCMessage s_rec_sc_message;
+static bool s_get_session_called = false;
+static bool s_delete_session_called = false;
+static bool s_transmit_message_called = false;
+static uint8_t* s_message_content = nullptr;
+static uint8_t s_message_length = 0;
+static Flow s_flow;
+static FlowKey s_flowkey;
+static DAQ_PktHdr_t s_pkthdr;
+static StreamHAClient* ha_client;
+static std::function<void (SCMessage*)> s_handler = nullptr;
+static SCMsgHdr s_sc_header = { 0, 1, 0, 0, };
class StreamHAClient : public FlowHAClient
{
public:
- StreamHAClient() : FlowHAClient(true) { }
+ StreamHAClient() : FlowHAClient(10, true) { }
~StreamHAClient() { }
bool consume(Flow*, HAMessage*) { return true; }
bool produce(Flow*, HAMessage* msg)
*(msg->cursor)++ = i;
return true;
}
- size_t get_message_size() { return 10; }
+ uint8_t get_message_size() { return 10; }
private:
};
-static struct timeval s_time = { 0, 0 };
-static uint8_t message[MSG_SIZE];
-static SideChannel s_side_channel;
-static SCMessage s_sc_message;
-static Flow s_flow;
-static DAQ_PktHdr_t s_pkthdr;
-
-Flow* Stream::get_session(const FlowKey*) { return &s_flow; }
+Flow* Stream::get_session(const FlowKey* flowkey)
+{
+ s_flowkey = *flowkey;
+ s_get_session_called = true;
+ return &s_flow;
+}
-void Stream::delete_session(const FlowKey*) { }
+void Stream::delete_session(const FlowKey* flowkey)
+{
+ s_flowkey = *flowkey;
+ s_delete_session_called = true;
+}
+void ErrorMessage(const char*,...) { }
void LogMessage(const char*,...) { }
void Debug::print(const char*, int, uint64_t, const char*, ...) { }
void SideChannel::set_default_port(SCPort) { }
-void SideChannel::register_receive_handler(std::function<void (SCMessage*)>) { }
+void SideChannel::register_receive_handler(std::function<void (SCMessage*)> handler)
+{
+ s_handler = handler;
+}
void SideChannel::unregister_receive_handler() { }
{ return true; }
bool SideChannel::process(int)
-{ return false; }
+{
+ if ( s_handler && s_message_content && (s_message_length != 0))
+ {
+ s_rec_sc_message.content = s_message_content;
+ s_rec_sc_message.content_length = s_message_length;
+ s_rec_sc_message.hdr = &s_sc_header;
+ s_rec_sc_message.sc = &s_side_channel;
+ s_handler(&s_rec_sc_message);
+ return true;
+ }
+ else
+ return false;
+}
-bool SideChannel::transmit_message(SCMessage*)
-{ return true; }
+bool SideChannel::transmit_message(SCMessage* msg)
+{
+ s_transmit_message_called = true;
+ s_message_content = msg->content;
+ s_message_length = msg->content_length;
+ return true; }
SCMessage* SideChannel::alloc_transmit_message(uint32_t len)
{
if ( len > MSG_SIZE )
return nullptr;
- s_sc_message.content = message;
+ s_sc_message.content = s_message;
s_sc_message.content_length = len;
return &s_sc_message;
}
TEST_GROUP(high_availability_manager_test)
{
- StreamHAClient* ha_client;
void setup()
{
- ha_client = new StreamHAClient;
+ MemoryLeakWarningPlugin::turnOffNewDeleteOverloads();
}
void teardown()
{
- delete ha_client;
+ MemoryLeakWarningPlugin::turnOnNewDeleteOverloads();
}
};
-TEST(high_availability_manager_test, pre_config_init)
-{
- HighAvailabilityManager::pre_config_init();
-}
-
TEST(high_availability_manager_test, init_term)
{
HighAvailabilityManager::pre_config_init();
port_set.set(1);
HighAvailabilityManager::instantiate(&port_set, false);
HighAvailabilityManager::thread_init();
+ ha_client = new StreamHAClient;
CHECK(HighAvailabilityManager::active()==true);
- HighAvailabilityManager::thread_term();
- CHECK(HighAvailabilityManager::active()==false);
-}
-
-TEST(high_availability_manager_test, inst_init_deletion_term)
-{
- HighAvailabilityManager::pre_config_init();
- PortBitSet port_set;
- port_set.set(1);
- HighAvailabilityManager::instantiate(&port_set, false);
- HighAvailabilityManager::thread_init();
- CHECK(HighAvailabilityManager::active()==true);
- HighAvailabilityManager::process_receive();
- HighAvailabilityManager::process_deletion(&s_flow);
- HighAvailabilityManager::thread_term();
- CHECK(HighAvailabilityManager::active()==false);
-}
-
-TEST(high_availability_manager_test, inst_init_update_term)
-{
- HighAvailabilityManager::pre_config_init();
- PortBitSet port_set;
- port_set.set(1);
- HighAvailabilityManager::instantiate(&port_set, false);
- HighAvailabilityManager::thread_init();
- CHECK(HighAvailabilityManager::active()==true);
- HighAvailabilityManager::process_update(&s_flow, &s_pkthdr);
- HighAvailabilityManager::process_receive();
+ delete ha_client;
HighAvailabilityManager::thread_term();
CHECK(HighAvailabilityManager::active()==false);
}
{
void setup()
{
+ MemoryLeakWarningPlugin::turnOffNewDeleteOverloads();
HighAvailabilityManager::pre_config_init();
PortBitSet port_set;
port_set.set(1);
HighAvailabilityManager::instantiate(&port_set, false);
HighAvailabilityManager::thread_init();
+ ha_client = new StreamHAClient;
}
void teardown()
{
+ delete ha_client;
HighAvailabilityManager::thread_term();
+ MemoryLeakWarningPlugin::turnOnNewDeleteOverloads();
}
};
+TEST(high_availability_test, receive_deletion)
+{
+ s_delete_session_called = false;
+ s_message_content = (uint8_t*)s_delete_message;
+ s_message_length = sizeof(s_delete_message);
+ HighAvailabilityManager::process_receive();
+ CHECK(s_delete_session_called == true);
+ CHECK(memcmp((const void*)&s_flowkey, (const void*)&s_test_key, sizeof(s_test_key)) == 0);
+}
+
+TEST(high_availability_test, transmit_deletion)
+{
+ s_transmit_message_called = false;
+ HighAvailabilityManager::process_deletion(&s_flow);
+ CHECK(s_transmit_message_called == true);
+}
+
+TEST(high_availability_test, transmit_update_stream_only)
+{
+ s_transmit_message_called = false;
+ HighAvailabilityManager::process_update(&s_flow, &s_pkthdr);
+ CHECK(s_transmit_message_called == true);
+}
+
int main(int argc, char** argv)
{
return CommandLineTestRunner::RunAllTests(argc, argv);
EventManager::open_outputs();
IpsManager::setup_options();
ActionManager::thread_init(snort_conf);
- InspectorManager::thread_init(snort_conf);
SideChannelManager::thread_init();
- HighAvailabilityManager::thread_init();
+ HighAvailabilityManager::thread_init(); // must be before InspectorManager::thread_init();
+ InspectorManager::thread_init(snort_conf);
}
void Snort::thread_term()
HighAvailabilityManager::process_deletion(flow);
}
-THREAD_LOCAL StreamHAClient* StreamHAManager::ha_client;
+THREAD_LOCAL StreamHAClient* StreamHAManager::ha_client = nullptr;
void StreamHAManager::tinit()
{
- ha_client = new StreamHAClient();
+ if ( HighAvailabilityManager::active() )
+ ha_client = new StreamHAClient();
}
void StreamHAManager::tterm()
{
- delete ha_client;
+ if ( ha_client )
+ delete ha_client;
}
void StreamHAManager::process_deletion(Flow*)
class StreamHAClient : public FlowHAClient
{
public:
- StreamHAClient() : FlowHAClient(true) { }
+ StreamHAClient() : FlowHAClient(sizeof(LwState), true) { }
bool consume(Flow*, HAMessage*);
bool produce(Flow*, HAMessage*);
- size_t get_message_size()
- { return sizeof(LwState); }
private:
};
DebugMessage(DEBUG_HA,"UdpHA::create_session)\n");
}
-THREAD_LOCAL UdpHA* UdpHAManager::udp_ha;
+THREAD_LOCAL UdpHA* UdpHAManager::udp_ha = nullptr;
+
+void UdpHAManager::process_deletion(Flow* flow)
+{
+ if( udp_ha != nullptr )
+ udp_ha->process_deletion(flow);
+}
void UdpHAManager::tinit()
{
- udp_ha = new UdpHA();
+ if ( HighAvailabilityManager::active() )
+ udp_ha = new UdpHA();
+ else
+ udp_ha = nullptr;
}
void UdpHAManager::tterm()
{
- delete udp_ha;
+ if ( udp_ha )
+ delete udp_ha;
}
class UdpHAManager
{
public:
- static void process_deletion(Flow* flow)
- { udp_ha->process_deletion(flow); }
+ static void process_deletion(Flow* flow);
static void tinit();
static void tterm();
static THREAD_LOCAL UdpHA* udp_ha;