}
void LogNetData(
- TextLog* log, const uint8_t* data, const int len, Packet* p, const char* buf_name)
+ TextLog* log, const uint8_t* data, const int len, Packet* p, const char* buf_name,
+ const char* ins_name)
{
if ( !len )
return;
const HexAsciiLayout& hal = SnortConfig::get_conf()->output_wide_hex() ? hal_wide : hal_std;
const char* hdr_off = SnortConfig::verbose_byte_dump() ? hal.offset_hdr : "";
- const char* ins_name = p->flow and p->flow->gadget ? p->flow->gadget->get_name() : "snort";
+
+ if ( !ins_name )
+ ins_name = p->flow and p->flow->gadget ? p->flow->gadget->get_name() : "snort";
TextLog_Print(log, "\n%s.%s[%u]:\n", ins_name, buf_name, len);
TextLog_Print(log, "%s%s\n", hdr_off, hal.separator);
SO_PUBLIC bool LogAppID(TextLog*, Packet*);
SO_PUBLIC void LogNetData(
- TextLog*, const uint8_t* data, const int len, Packet*, const char* buf_name = nullptr);
+ TextLog*, const uint8_t* data, const int len, Packet*, const char* buf_name = nullptr,
+ const char* ins_name = nullptr);
SO_PUBLIC void Log2ndHeader(TextLog*, Packet*);
SO_PUBLIC void LogTCPHeader(TextLog*, Packet*);
#include "detection/detection_engine.h"
#include "detection/signature.h"
#include "events/event.h"
+#include "flow/flow.h"
+#include "flow/session.h"
#include "framework/logger.h"
#include "framework/module.h"
#include "log/log_text.h"
#include "packet_io/active.h"
#include "packet_io/sfdaq.h"
#include "service_inspectors/http_inspect/http_enum.h"
+#include "stream/stream_splitter.h"
using namespace snort;
using namespace std;
bool log_pkt = true;
TextLog_NewLine(fast_log);
- Inspector* gadget = p->flow ? p->flow->gadget : nullptr;
+ const char* ins_name = "snort";
+ Inspector* gadget = nullptr;
+ if ( p->flow and p->flow->session )
+ {
+ snort::StreamSplitter* ss = p->flow->session->get_splitter(p->is_from_client());
+ if ( ss and ss->is_paf() )
+ {
+ gadget = p->flow->gadget;
+ if ( gadget )
+ ins_name = gadget->get_name();
+ }
+ }
const char** buffers = gadget ? gadget->get_api()->buffers : nullptr;
if ( buffers )
{
if ( gadget->get_buf(id, p, buf) )
- LogNetData(fast_log, buf.data, buf.len, p, buffers[id-1]);
+ LogNetData(fast_log, buf.data, buf.len, p, buffers[id-1], ins_name);
log_pkt = rsp;
}
InspectionBuffer buf;
if ( gadget->get_buf(InspectionBuffer::IBT_KEY, p, buf) )
- LogNetData(fast_log, buf.data, buf.len, p);
+ LogNetData(fast_log, buf.data, buf.len, p, nullptr, ins_name);
if ( gadget->get_buf(InspectionBuffer::IBT_HEADER, p, buf) )
- LogNetData(fast_log, buf.data, buf.len, p);
+ LogNetData(fast_log, buf.data, buf.len, p, nullptr, ins_name);
if ( gadget->get_buf(InspectionBuffer::IBT_BODY, p, buf) )
- LogNetData(fast_log, buf.data, buf.len, p);
+ LogNetData(fast_log, buf.data, buf.len, p, nullptr, ins_name);
}
if (p->has_ip())
LogIPPkt(fast_log, p);
for ( const auto& b : *p->obfuscator )
buf.replace(b.offset, b.length, b.length, p->obfuscator->get_mask_char());
- LogNetData(fast_log, (const uint8_t*)buf.c_str(), p->dsize, p);
+ LogNetData(fast_log, (const uint8_t*)buf.c_str(), p->dsize, p, nullptr, ins_name);
}
else if ( log_pkt )
- LogNetData(fast_log, p->data, p->dsize, p);
+ LogNetData(fast_log, p->data, p->dsize, p, nullptr, ins_name);
DataBuffer& buf = DetectionEngine::get_alt_buffer(p);
#include "dce_tcp_paf.h"
+#include "dce_common.h"
#include "dce_tcp.h"
using namespace snort;
static StreamSplitter::Status dce2_tcp_paf(DCE2_PafTcpData* ds, Flow* flow, const uint8_t* data,
uint32_t len, uint32_t flags, uint32_t* fp)
{
- uint32_t n = 0;
- int start_state;
- StreamSplitter::Status ps = StreamSplitter::SEARCH;
- uint32_t tmp_fp = 0;
DCE2_TcpSsnData* sd = get_dce2_tcp_session_data(flow);
-
- int num_requests = 0;
-
if ( dce2_paf_abort((DCE2_SsnData*)sd) )
- {
return StreamSplitter::ABORT;
- }
- if (sd == nullptr)
+ if ( !sd )
{
bool autodetected = false;
- if (len >= sizeof(DceRpcCoHdr))
+ if ( len >= sizeof(DceRpcCoHdr) )
{
const DceRpcCoHdr* co_hdr = (const DceRpcCoHdr*)data;
- if ((DceRpcCoVersMaj(co_hdr) == DCERPC_PROTO_MAJOR_VERS__5)
+ if ( (DceRpcCoVersMaj(co_hdr) == DCERPC_PROTO_MAJOR_VERS__5)
&& (DceRpcCoVersMin(co_hdr) == DCERPC_PROTO_MINOR_VERS__0)
&& (((flags & PKT_FROM_CLIENT)
&& DceRpcCoPduType(co_hdr) == DCERPC_PDU_TYPE__BIND)
|| ((flags & PKT_FROM_SERVER)
&& DceRpcCoPduType(co_hdr) == DCERPC_PDU_TYPE__BIND_ACK))
- && (DceRpcCoFragLen(co_hdr) >= sizeof(DceRpcCoHdr)))
+ && (DceRpcCoFragLen(co_hdr) >= sizeof(DceRpcCoHdr)) )
{
autodetected = true;
}
}
- else if ((*data == DCERPC_PROTO_MAJOR_VERS__5) && (flags & PKT_FROM_CLIENT))
+ else if ( (*data == DCERPC_PROTO_MAJOR_VERS__5) && (flags & PKT_FROM_CLIENT) )
{
autodetected = true;
}
- if (!autodetected)
- {
+ if ( !autodetected )
return StreamSplitter::ABORT;
- }
}
- start_state = (uint8_t)ds->paf_state;
-
- while (n < len)
+ int start_state = (uint8_t)ds->paf_state;
+ int num_requests = 0;
+ uint32_t tmp_fp = 0;
+ uint32_t n = 0;
+ while ( n < len )
{
- switch (ds->paf_state)
+ switch ( ds->paf_state )
{
case DCE2_PAF_TCP_STATES__4: // Get byte order
ds->byte_order = DceRpcByteOrder(data[n]);
ds->paf_state = (DCE2_PafTcpStates)(((int)ds->paf_state) + 1);
break;
+
case DCE2_PAF_TCP_STATES__8:
- if (ds->byte_order == DCERPC_BO_FLAG__LITTLE_ENDIAN)
+ if ( ds->byte_order == DCERPC_BO_FLAG__LITTLE_ENDIAN )
ds->frag_len = data[n];
else
ds->frag_len = data[n] << 8;
ds->paf_state = (DCE2_PafTcpStates)(((int)ds->paf_state) + 1);
break;
+
case DCE2_PAF_TCP_STATES__9:
- if (ds->byte_order == DCERPC_BO_FLAG__LITTLE_ENDIAN)
+ if ( ds->byte_order == DCERPC_BO_FLAG__LITTLE_ENDIAN )
ds->frag_len |= data[n] << 8;
else
ds->frag_len |= data[n];
/* If we get a bad frag length abort */
- if (ds->frag_len < sizeof(DceRpcCoHdr))
+ if ( ds->frag_len < sizeof(DceRpcCoHdr) )
{
- return StreamSplitter::ABORT;
+ if ( sd )
+ dce_alert(GID_DCE2, DCE2_CO_FRAG_LEN_LT_HDR, (dce2CommonStats*)&dce2_tcp_stats, *(DCE2_SsnData*)sd);
+ return StreamSplitter::ABORT;
}
/* Increment n here so we can continue */
num_requests++;
/* Might have multiple PDUs in one segment. If the last PDU is partial,
* flush just before it */
- if ((num_requests == 1) || (n <= len))
+ if ( (num_requests == 1) || (n <= len) )
tmp_fp += ds->frag_len;
ds->paf_state = DCE2_PAF_TCP_STATES__0;
continue; // we incremented n already
+
default:
ds->paf_state = (DCE2_PafTcpStates)(((int)ds->paf_state) + 1);
break;
n++;
}
- if (tmp_fp != 0)
+ if ( tmp_fp != 0 )
{
*fp = tmp_fp - start_state;
return StreamSplitter::FLUSH;
}
- return ps;
+ return StreamSplitter::SEARCH;
}
Dce2TcpSplitter::Dce2TcpSplitter(bool c2s) : StreamSplitter(c2s)
const uint8_t* data, uint32_t len, uint32_t flags)
{
ps->fpt = 0;
-
ps->paf = ss->scan(pkt, data, len, flags, &ps->fpt);
if ( ps->paf == StreamSplitter::ABORT )
if ( ps->paf != StreamSplitter::SEARCH )
{
ps->fpt += px.idx;
-
if ( ps->fpt <= px.len )
{
px.idx = ps->fpt;
{
case StreamSplitter::SEARCH:
if ( px.len > px.idx )
- {
return paf_callback(ss, ps, px, pkt, data, len, flags);
- }
+
return false;
case StreamSplitter::FLUSH:
uint32_t delta = ps->fpt - px.idx;
if ( delta > len )
return false;
+
data += delta;
len -= delta;
}
{
ps->fpt = 0;
px.ft = FT_MAX;
+ ps->paf = StreamSplitter::ABORT;
return paf_flush(ps, px, flags);
}
*flags = 0;
+ ps->paf = StreamSplitter::ABORT;
return -1;
}
else if ( SEQ_LEQ(seq + len, ps->seq) )
data += shift;
len -= shift;
}
- ps->seq += len;
+ ps->seq += len;
px.idx = total - len;
// if 'total' is greater than the maximum paf_max AND 'total' is greater
len = len + px.len - total;
}
else
- {
px.len = total;
- }
do
{
paf_jump(ps, fp);
return fp;
}
+
if ( !cont )
break;
#include "flow/flow.h"
-
struct HostAttributeEntry;
namespace snort
bool keep_segment;
~SegmentOverlapState()
- {
- seglist.reset();
- }
+ { seglist.reset(); }
void init_sos(TcpSession*, StreamPolicy);
void init_soe(TcpSegmentDescriptor& tsd, TcpSegmentNode* left, TcpSegmentNode* right);
TcpStreamConfig* const config;
};
-StreamTcp::StreamTcp (TcpStreamConfig* c) : config(c) {}
+StreamTcp::StreamTcp (TcpStreamConfig* c)
+ : config(c)
+{ }
StreamTcp::~StreamTcp()
{ delete config; }
}
/* actions */
-#define ACTION_NOTHING 0x00000000
-#define ACTION_FLUSH_SENDER_STREAM 0x00000001
-#define ACTION_FLUSH_RECEIVER_STREAM 0x00000002
-#define ACTION_DROP_SESSION 0x00000004
-#define ACTION_ACK_SENDER_DATA 0x00000008
-#define ACTION_ACK_RECEIVER_DATA 0x00000010
-#define ACTION_SET_SSN 0x00000040
-#define ACTION_COMPLETE_TWH 0x00000080
-#define ACTION_RST 0x00000100
-#define ACTION_BAD_SEQ 0x00000200
-#define ACTION_BAD_PKT 0x00000400
-#define ACTION_LWSSN_CLOSED 0x00000800
-#define ACTION_DISABLE_INSPECTION 0x00001000
-
-#define TF_NONE 0x0000
-#define TF_WSCALE 0x0001
-#define TF_TSTAMP 0x0002
-#define TF_TSTAMP_ZERO 0x0004
-#define TF_MSS 0x0008
-#define TF_FORCE_FLUSH 0x0010
-#define TF_PKT_MISSED 0x0020 // sticky
-#define TF_MISSING_PKT 0x0040 // used internally
-#define TF_MISSING_PREV_PKT 0x0080 // reset for each reassembled
+#define ACTION_NOTHING 0x00000000
+#define ACTION_FLUSH_SENDER_STREAM 0x00000001
+#define ACTION_FLUSH_RECEIVER_STREAM 0x00000002
+#define ACTION_DROP_SESSION 0x00000004
+#define ACTION_ACK_SENDER_DATA 0x00000008
+#define ACTION_ACK_RECEIVER_DATA 0x00000010
+#define ACTION_SET_SSN 0x00000040
+#define ACTION_COMPLETE_TWH 0x00000080
+#define ACTION_RST 0x00000100
+#define ACTION_BAD_SEQ 0x00000200
+#define ACTION_BAD_PKT 0x00000400
+#define ACTION_LWSSN_CLOSED 0x00000800
+#define ACTION_DISABLE_INSPECTION 0x00001000
+
+#define TF_NONE 0x0000
+#define TF_WSCALE 0x0001
+#define TF_TSTAMP 0x0002
+#define TF_TSTAMP_ZERO 0x0004
+#define TF_MSS 0x0008
+#define TF_FORCE_FLUSH 0x0010
+#define TF_PKT_MISSED 0x0020 // sticky
+#define TF_MISSING_PKT 0x0040 // used internally
+#define TF_MISSING_PREV_PKT 0x0080 // reset for each reassembled
#define PAWS_WINDOW 60
#define PAWS_24DAYS 2073600 /* 24 days in seconds */
-#define SUB_STATE_NONE 0x00
-#define SUB_SYN_SENT 0x01
-#define SUB_ACK_SENT 0x02
-#define SUB_SETUP_OK 0x03
-#define SUB_RST_SENT 0x04
-#define SUB_FIN_SENT 0x08
-
#define STREAM_UNALIGNED 0
#define STREAM_ALIGNED 1
{ CountType::MAX, "max_packets_held", "maximum number of packets held simultaneously" },
{ CountType::SUM, "partial_flushes", "number of partial flushes initiated" },
{ CountType::SUM, "partial_flush_bytes", "partial flush total bytes" },
+ { CountType::SUM, "inspector_fallbacks", "count of fallbacks from assigned service inspector" },
+ { CountType::SUM, "partial_fallbacks", "count of fallbacks from assigned service stream splitter" },
{ CountType::END, nullptr, nullptr }
};
PegCount segs_released;
PegCount segs_split;
PegCount segs_used;
- PegCount rebuilt_packets; //iStreamFlushes
+ PegCount rebuilt_packets;
PegCount rebuilt_buffers;
- PegCount rebuilt_bytes; //total_rebuilt_bytes
+ PegCount rebuilt_bytes;
PegCount overlaps;
PegCount gaps;
PegCount exceeded_max_segs;
PegCount max_packets_held;
PegCount partial_flushes;
PegCount partial_flush_bytes;
+ PegCount inspector_fallbacks;
+ PegCount partial_fallbacks;
};
extern THREAD_LOCAL struct TcpStats tcpStats;
/* Windows has some strange behavior here. If the sequence of the reset is the
* next expected sequence, it Resets. Otherwise it ignores the 2nd SYN.
*/
- if (SEQ_EQ(tsd.get_seg_seq(), listener->rcv_nxt))
+ if ( SEQ_EQ(tsd.get_seg_seq(), listener->rcv_nxt) )
{
session->flow->set_session_flags(SSNFLAG_RESET);
talker->set_tcp_state(TcpStreamTracker::TCP_CLOSED);
TcpStreamTracker* talker, const TcpSegmentDescriptor& tsd, TcpStreamSession* session)
{
/* If its not a retransmission of the actual SYN... RESET */
- if (!SEQ_EQ(tsd.get_seg_seq(), talker->get_iss()))
+ if ( !SEQ_EQ(tsd.get_seg_seq(), talker->get_iss()) )
{
session->flow->set_session_flags(SSNFLAG_RESET);
talker->set_tcp_state(TcpStreamTracker::TCP_CLOSED);
{
bool check_ts = true;
- if (talker->get_tf_flags() & TF_TSTAMP_ZERO)
+ if ( talker->get_tf_flags() & TF_TSTAMP_ZERO )
{
talker->clear_tf_flags(TF_TSTAMP);
listener->clear_tf_flags(TF_TSTAMP);
{
uint16_t urg_offset = 0;
- if (tcph->are_flags_set(TH_URG) )
+ if ( tcph->are_flags_set(TH_URG) )
{
urg_offset = tcph->urp();
TcpNormalizerState& tns, TcpSegmentDescriptor& tsd)
{
/* HPUX 11 ignores timestamps for out of order segments */
- if ((tns.tracker->get_tf_flags() & TF_MISSING_PKT) || !SEQ_EQ(tns.tracker->rcv_nxt,
- tsd.get_seg_seq()))
+ if ( (tns.tracker->get_tf_flags() & TF_MISSING_PKT) || !SEQ_EQ(tns.tracker->rcv_nxt,
+ tsd.get_seg_seq()) )
return false;
else
return true;
{
pdu->data = sb.data;
pdu->dsize = sb.length;
- assert(sb.length <= Packet::max_dsize);
}
total_flushed += bytes_copied;
if ( footprint == 0 )
return bytes_processed;
- if ( footprint > Packet::max_dsize )
- /* this is as much as we can pack into a stream buffer */
+ if ( footprint > Packet::max_dsize ) // max stream buffer size
footprint = Packet::max_dsize;
if ( trs.tracker->splitter->is_paf() and
( trs.tracker->get_tf_flags() & TF_MISSING_PREV_PKT ) )
- fallback(trs);
+ fallback(*trs.tracker, trs.server_side);
Packet* pdu = initialize_pdu(trs, p, pkt_flags, trs.sos.seglist.cur_rseg->tv);
int32_t flushed_bytes = flush_data_segments(trs, p, footprint, pdu);
return -1;
}
-void TcpReassembler::fallback(TcpReassemblerState& trs)
+static inline bool both_splitters_aborted(Flow* flow)
{
- bool c2s = trs.tracker->splitter->to_server();
+ uint32_t both_splitters_yoinked = (SSNFLAG_ABORT_CLIENT | SSNFLAG_ABORT_SERVER);
+ return (flow->get_session_flags() & both_splitters_yoinked) == both_splitters_yoinked;
+}
+
+static inline void fallback(TcpStreamTracker& trk, bool server_side, uint16_t max)
+{
+ delete trk.splitter;
+ trk.splitter = new AtomSplitter(!server_side, max);
+ trk.paf_state.paf = StreamSplitter::START;
+ ++tcpStats.partial_fallbacks;
+}
- delete trs.tracker->splitter;
- trs.tracker->splitter = new AtomSplitter(c2s, trs.sos.session->config->paf_max);
- trs.tracker->paf_state.paf = StreamSplitter::SEARCH;
+void TcpReassembler::fallback(TcpStreamTracker& tracker, bool server_side)
+{
+ uint16_t max = tracker.session->config->paf_max;
+ ::fallback(tracker, server_side, max);
- trs.sos.session->flow->set_session_flags(
- c2s ? SSNFLAG_ABORT_CLIENT : SSNFLAG_ABORT_SERVER );
+ Flow* flow = tracker.session->flow;
+ if ( server_side )
+ flow->set_session_flags(SSNFLAG_ABORT_SERVER);
+ else
+ flow->set_session_flags(SSNFLAG_ABORT_CLIENT);
+
+ if ( flow->gadget and both_splitters_aborted(flow) )
+ {
+ flow->clear_gadget();
+ ++tcpStats.inspector_fallbacks;
+ }
}
// iterate over trs.sos.seglist and scan all new acked bytes
if ( !flags && trs.tracker->splitter->is_paf() )
{
- fallback(trs);
+ fallback(*trs.tracker, trs.server_side);
return flush_on_data_policy(trs, p);
}
}
while (flush_amt >= 0)
{
- if (!flush_amt)
+ if ( !flush_amt )
flush_amt = trs.sos.seglist.cur_rseg->c_seq - trs.sos.seglist_base_seq;
+ if ( trs.tracker->paf_state.paf == StreamSplitter::ABORT )
+ trs.tracker->splitter->finish(p->flow);
+
// for consistency with other cases, should return total
// but that breaks flushing pipelined pdus
flushed = flush_to_seq(trs, flush_amt, p, flags);
break; // bail if nothing flushed
}
- if (!flags && trs.tracker->splitter->is_paf())
+ if ( (trs.tracker->paf_state.paf == StreamSplitter::ABORT) &&
+ (trs.tracker->splitter && trs.tracker->splitter->is_paf()) )
{
- fallback(trs);
+ fallback(*trs.tracker, trs.server_side);
return flush_on_ack_policy(trs, p);
}
}
uint32_t get_reverse_packet_dir(TcpReassemblerState&, const snort::Packet*);
uint32_t get_forward_packet_dir(TcpReassemblerState&, const snort::Packet*);
int32_t flush_pdu_ips(TcpReassemblerState&, uint32_t*, snort::Packet*);
- void fallback(TcpReassemblerState&);
+ void fallback(TcpStreamTracker&, bool server_side);
int32_t flush_pdu_ackd(TcpReassemblerState&, uint32_t* flags, snort::Packet*);
void purge_to_seq(TcpReassemblerState&, uint32_t flush_seq);
if ( orig_dsize == c_len )
{
- if ( ( ( c_len <= rsize )and !memcmp(data, rdata, c_len) )
- or ( ( c_len > rsize )and !memcmp(data, rdata, rsize) ) )
- {
+ uint16_t cmp_len = ( c_len <= rsize ) ? c_len : rsize;
+ if ( !memcmp(data, rdata, cmp_len) )
return true;
- }
}
//Checking for a possible split of segment in which case
//we compare complete data of the segment to find a retransmission
}
#endif
-void TcpSession::NewTcpSessionOnSyn(TcpSegmentDescriptor& tsd)
+void TcpSession::init_session_on_syn(TcpSegmentDescriptor& tsd)
{
server.init_on_syn_recv(tsd);
client.init_on_syn_sent(tsd);
tcpStats.sessions_on_syn++;
}
-void TcpSession::NewTcpSessionOnSynAck(TcpSegmentDescriptor& tsd)
+void TcpSession::init_session_on_synack(TcpSegmentDescriptor& tsd)
{
server.init_on_synack_sent(tsd);
client.init_on_synack_recv(tsd);
pkt_action_mask |= ACTION_RST;
return false;
}
- else if (tcph->is_syn_only())
+ else if ( tcph->is_syn_only() )
{
flow->ssn_state.direction = FROM_CLIENT;
flow->session_state = STREAM_STATE_SYN;
flow->set_ttl(tsd.get_pkt(), true);
- NewTcpSessionOnSyn(tsd);
+ init_session_on_syn(tsd);
tcpStats.resyns++;
listener = &server;
talker = &client;
listener->normalizer.ecn_tracker(tcph, config->require_3whs());
flow->update_session_flags(SSNFLAG_SEEN_CLIENT);
}
- else if (tcph->is_syn_ack())
+ else if ( tcph->is_syn_ack() )
{
if (config->midstream_allowed(tsd.get_pkt()))
{
flow->ssn_state.direction = FROM_SERVER;
flow->session_state = STREAM_STATE_SYN_ACK;
flow->set_ttl(tsd.get_pkt(), false);
- NewTcpSessionOnSynAck(tsd);
+ init_session_on_synack(tsd);
tcpStats.resyns++;
}
return;
tracker.set_tf_flags(TF_FORCE_FLUSH);
- if ( tracker.reassembler.flush_stream(p, dir) )
+ if ( tracker.reassembler.flush_stream(p, dir, final_flush) )
tracker.reassembler.purge_flushed_ackd();
tracker.clear_tf_flags(TF_FORCE_FLUSH);
void process_tcp_stream(TcpSegmentDescriptor&);
int process_tcp_data(TcpSegmentDescriptor&);
void swap_trackers();
- void NewTcpSessionOnSyn(TcpSegmentDescriptor&);
- void NewTcpSessionOnSynAck(TcpSegmentDescriptor&);
- int process_dis(snort::Packet*);
+ void init_session_on_syn(TcpSegmentDescriptor&);
+ void init_session_on_synack(TcpSegmentDescriptor&);
void update_on_3whs_complete(TcpSegmentDescriptor&);
bool is_flow_handling_packets(snort::Packet*);
void cleanup_session_if_expired(snort::Packet*);
void TcpStreamSession::update_session_on_client_packet(TcpSegmentDescriptor& tsd)
{
- /* if we got here we had to see the SYN already... */
+ /* if we got here we have seen the SYN already... */
flow->set_session_flags(SSNFLAG_SEEN_CLIENT);
talker = &client;
listener = &server;