From: Michael Altizer (mialtize) Date: Tue, 21 Jul 2020 17:58:16 +0000 (+0000) Subject: Merge pull request #2325 in SNORT/snort3 from ~MIALTIZE/snort3:wizardry2 to master X-Git-Tag: 3.0.2-3~15 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9d38b5d6db99721e02bcfc1dc65defc9fdd2b89b;p=thirdparty%2Fsnort3.git Merge pull request #2325 in SNORT/snort3 from ~MIALTIZE/snort3:wizardry2 to master Squashed commit of the following: commit 5b1527473e3a55457a3a091e1a5e718abd9a584b Author: Michael Altizer Date: Thu Jul 16 17:07:22 2020 -0400 wizard: Improve wizard tracing to indicate direction and abandonment commit c2cba2ec1205251803b3e501e59113e6a92737eb Author: Michael Altizer Date: Thu Jul 9 18:12:48 2020 -0400 wizard: Add peg counts for abandoned searches per protocol commit 558df5a45cfbfee4b783d84973f77a9d95dfb710 Author: Michael Altizer Date: Thu Jul 9 18:05:20 2020 -0400 wizard: Abort the splitter once we've hit the max PDU size commit 04dbc4e5c9949316c70f4faf26b1c37e10da312b Author: Michael Altizer Date: Tue Jul 7 18:19:18 2020 -0400 dce_rpc: Improve PAF autodetection for heavily segmented TCP traffic commit 76b0e4f6c5faf77fa28ed45472d1ca9476e37a99 Author: Michael Altizer Date: Tue Jul 7 12:25:51 2020 -0400 snort_defaults: Remove the NOTIFY, SUBSCRIBE, and UPDATE HTTP methods These methods overlap with SIP methods, where they are much more commonly found. Until there is a priority/fallback mechanism for the Wizard, these patterns will be retired from the HTTP spell. commit f5561a1697ec6ac38981e0af094bb225b70910ca Author: Michael Altizer Date: Mon Jul 6 18:33:27 2020 -0400 wizard: Abandon the wizard on UDP flows after the first packet commit 7f65256f9b6a7470ebf5737273e360fe6a1491c6 Author: Michael Altizer Date: Tue Nov 5 17:27:10 2019 -0500 wizard: Report spell and hex configuration errors and warnings commit 1b08923942d23744a6291cce0d39b4f24c12edbb Author: Michael Altizer Date: Tue Nov 5 12:58:07 2019 -0500 wizard: Properly terminate hex matching --- diff --git a/lua/snort_defaults.lua b/lua/snort_defaults.lua index c05a47e83..5a41e12ac 100644 --- a/lua/snort_defaults.lua +++ b/lua/snort_defaults.lua @@ -299,14 +299,13 @@ http_methods = -- build from default_http_methods { 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT', 'VERSION_CONTROL', 'REPORT', 'CHECKOUT', 'CHECKIN', 'UNCHECKOUT', - 'MKWORKSPACE', 'UPDATE', 'LABEL', 'MERGE', 'BASELINE_CONTROL', + 'MKWORKSPACE', 'LABEL', 'MERGE', 'BASELINE_CONTROL', 'MKACTIVITY', 'ORDERPATCH', 'ACL', 'PATCH', 'BIND', 'LINK', 'MKCALENDAR', 'MKREDIRECTREF', 'REBIND', 'UNBIND', 'UNLINK', 'UPDATEREDIRECTREF', 'PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK', 'SEARCH', 'BCOPY', 'BDELETE', 'BMOVE', - 'BPROPFIND', 'BPROPPATCH', 'NOTIFY', 'POLL', 'SUBSCRIBE', - 'UNSUBSCRIBE', 'X_MS_ENUMATTS', - --'OPTIONS', + 'BPROPFIND', 'BPROPPATCH', 'POLL', 'UNSUBSCRIBE', 'X_MS_ENUMATTS', + --'NOTIFY', 'OPTIONS', 'SUBSCRIBE', 'UPDATE' } sip_methods = diff --git a/src/service_inspectors/dce_rpc/dce_tcp_paf.cc b/src/service_inspectors/dce_rpc/dce_tcp_paf.cc index fea8cb9a3..f27d3bf39 100644 --- a/src/service_inspectors/dce_rpc/dce_tcp_paf.cc +++ b/src/service_inspectors/dce_rpc/dce_tcp_paf.cc @@ -30,9 +30,15 @@ using namespace snort; +Dce2TcpSplitter::Dce2TcpSplitter(bool c2s) : StreamSplitter(c2s) +{ + state.paf_state = DCE2_PAF_TCP_STATES__0; + state.byte_order = DCERPC_BO_FLAG__NONE; + state.frag_len = 0; + state.autodetected = false; +} + /********************************************************************* - * Function: dce2_tcp_paf() - * * Purpose: The DCE/RPC over TCP PAF callback. * Inspects a byte at a time changing state. At state 4 * gets byte order of PDU. At states 8 and 9 gets @@ -41,115 +47,98 @@ using namespace snort; * be multiple PDUs in a single TCP segment (evasion case). * *********************************************************************/ -static StreamSplitter::Status dce2_tcp_paf(DCE2_PafTcpData* ds, Flow* flow, const uint8_t* data, - uint32_t len, uint32_t flags, uint32_t* fp) +StreamSplitter::Status Dce2TcpSplitter::scan( + Packet* pkt, const uint8_t* data, uint32_t len, + uint32_t flags, uint32_t* fp) { - DCE2_TcpSsnData* sd = get_dce2_tcp_session_data(flow); - if ( dce2_paf_abort((DCE2_SsnData*)sd) ) + DCE2_TcpSsnData* sd = get_dce2_tcp_session_data(pkt->flow); + + if (dce2_paf_abort((DCE2_SsnData*) sd)) return StreamSplitter::ABORT; - if ( !sd ) + uint32_t n = 0; + uint32_t new_fp = 0; + int start_state = (uint8_t) state.paf_state; + int num_requests = 0; + + while (n < len) { - bool autodetected = false; - if ( len >= sizeof(DceRpcCoHdr) ) + switch (state.paf_state) { - const DceRpcCoHdr* co_hdr = (const DceRpcCoHdr*)data; - - 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)) ) + case DCE2_PAF_TCP_STATES__0: // Major version + if (!sd && !state.autodetected) // Autodetection validation { - autodetected = true; + if (data[n] != DCERPC_PROTO_MAJOR_VERS__5) + return StreamSplitter::ABORT; + if (len < sizeof(DceRpcCoHdr) && (flags & PKT_FROM_CLIENT)) + state.autodetected = true; } - } - else if ( (*data == DCERPC_PROTO_MAJOR_VERS__5) && (flags & PKT_FROM_CLIENT) ) - { - autodetected = true; - } - - if ( !autodetected ) - return StreamSplitter::ABORT; - } - - 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 ) - { - 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 ) - ds->frag_len = data[n]; + case DCE2_PAF_TCP_STATES__1: // Minor version + if (!sd && !state.autodetected) // Autodetection validation + { + if (data[n] != DCERPC_PROTO_MINOR_VERS__0) + return StreamSplitter::ABORT; + } + break; + case DCE2_PAF_TCP_STATES__2: // PDU type + if (!sd && !state.autodetected) // Autodetection validation + { + if (((flags & PKT_FROM_CLIENT) && data[n] != DCERPC_PDU_TYPE__BIND) || + ((flags & PKT_FROM_SERVER) && data[n] != DCERPC_PDU_TYPE__BIND_ACK)) + return StreamSplitter::ABORT; + } + break; + case DCE2_PAF_TCP_STATES__4: // Byte order + state.byte_order = DceRpcByteOrder(data[n]); + break; + case DCE2_PAF_TCP_STATES__8: // First byte of fragment length + if (state.byte_order == DCERPC_BO_FLAG__LITTLE_ENDIAN) + state.frag_len = data[n]; else - ds->frag_len = data[n] << 8; - ds->paf_state = (DCE2_PafTcpStates)(((int)ds->paf_state) + 1); + state.frag_len = data[n] << 8; break; - - case DCE2_PAF_TCP_STATES__9: - if ( ds->byte_order == DCERPC_BO_FLAG__LITTLE_ENDIAN ) - ds->frag_len |= data[n] << 8; + case DCE2_PAF_TCP_STATES__9: // Second byte of fragment length + if (state.byte_order == DCERPC_BO_FLAG__LITTLE_ENDIAN) + state.frag_len |= data[n] << 8; else - ds->frag_len |= data[n]; + state.frag_len |= data[n]; - /* If we get a bad frag length abort */ - if ( ds->frag_len < sizeof(DceRpcCoHdr) ) + /* Abort if we get a bad frag length */ + if (state.frag_len < sizeof(DceRpcCoHdr)) { - if ( sd ) + if (sd) dce_alert(GID_DCE2, DCE2_CO_FRAG_LEN_LT_HDR, (dce2CommonStats*)&dce2_tcp_stats, *(DCE2_SsnData*)sd); - return StreamSplitter::ABORT; + return StreamSplitter::ABORT; } + /* In the non-degenerate case, we can now declare that we think this looks like DCE */ + if (!state.autodetected) + state.autodetected = true; + /* Increment n here so we can continue */ - n += ds->frag_len - (uint8_t)ds->paf_state; + n += state.frag_len - (uint8_t) state.paf_state; 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) ) - tmp_fp += ds->frag_len; - - ds->paf_state = DCE2_PAF_TCP_STATES__0; - continue; // we incremented n already + if ((num_requests == 1) || (n <= len)) + new_fp += state.frag_len; + state.paf_state = DCE2_PAF_TCP_STATES__0; + continue; // we incremented n and set the state already default: - ds->paf_state = (DCE2_PafTcpStates)(((int)ds->paf_state) + 1); break; } + state.paf_state = (DCE2_PafTcpStates)(((int)state.paf_state) + 1); n++; } - if ( tmp_fp != 0 ) + if (new_fp != 0) { - *fp = tmp_fp - start_state; + *fp = new_fp - start_state; return StreamSplitter::FLUSH; } return StreamSplitter::SEARCH; } - -Dce2TcpSplitter::Dce2TcpSplitter(bool c2s) : StreamSplitter(c2s) -{ - state.paf_state = DCE2_PAF_TCP_STATES__0; - state.byte_order = DCERPC_BO_FLAG__NONE; - state.frag_len = 0; -} - -StreamSplitter::Status Dce2TcpSplitter::scan( - Packet* pkt, const uint8_t* data, uint32_t len, - uint32_t flags, uint32_t* fp) -{ - DCE2_PafTcpData* pfdata = &state; - return dce2_tcp_paf(pfdata, pkt->flow, data, len, flags, fp); -} - diff --git a/src/service_inspectors/dce_rpc/dce_tcp_paf.h b/src/service_inspectors/dce_rpc/dce_tcp_paf.h index 667f23c57..da3cf5986 100644 --- a/src/service_inspectors/dce_rpc/dce_tcp_paf.h +++ b/src/service_inspectors/dce_rpc/dce_tcp_paf.h @@ -25,20 +25,18 @@ #include "dce_common.h" #include "stream/stream_splitter.h" -#define DCE2_DEBUG__PAF_START_MSG_TCP "DCE/RPC over TCP PAF =====================================" - enum DCE2_PafTcpStates { DCE2_PAF_TCP_STATES__0 = 0, - DCE2_PAF_TCP_STATES__1, - DCE2_PAF_TCP_STATES__2, - DCE2_PAF_TCP_STATES__3, - DCE2_PAF_TCP_STATES__4, // Byte order + DCE2_PAF_TCP_STATES__1, // Major version + DCE2_PAF_TCP_STATES__2, // Minor version + DCE2_PAF_TCP_STATES__3, // PDU type + DCE2_PAF_TCP_STATES__4, // Byte order DCE2_PAF_TCP_STATES__5, DCE2_PAF_TCP_STATES__6, DCE2_PAF_TCP_STATES__7, - DCE2_PAF_TCP_STATES__8, // First byte of fragment length - DCE2_PAF_TCP_STATES__9 // Second byte of fragment length + DCE2_PAF_TCP_STATES__8, // First byte of fragment length + DCE2_PAF_TCP_STATES__9 // Second byte of fragment length }; // State tracker for DCE/RPC over TCP PAF @@ -47,6 +45,7 @@ struct DCE2_PafTcpData DCE2_PafTcpStates paf_state; DceRpcBoFlag byte_order; uint16_t frag_len; + bool autodetected; }; class Dce2TcpSplitter : public snort::StreamSplitter @@ -57,12 +56,9 @@ public: Status scan(snort::Packet*, const uint8_t* data, uint32_t len, uint32_t flags, uint32_t* fp) override; - bool is_paf() override - { - return true; - } + bool is_paf() override { return true; } -public: +private: DCE2_PafTcpData state; }; diff --git a/src/service_inspectors/wizard/hexes.cc b/src/service_inspectors/wizard/hexes.cc index 3880025d3..ad581c4df 100644 --- a/src/service_inspectors/wizard/hexes.cc +++ b/src/service_inspectors/wizard/hexes.cc @@ -94,12 +94,15 @@ void HexBook::add_spell( p->value = val; } -bool HexBook::add_spell(const char* key, const char* val) +bool HexBook::add_spell(const char* key, const char*& val) { HexVector hv; if ( !translate(key, hv) ) + { + val = nullptr; return false; + } unsigned i = 0; MagicPage* p = root; @@ -120,7 +123,10 @@ bool HexBook::add_spell(const char* key, const char* val) ++i; } if ( p->key == key ) + { + val = p->value.c_str(); return false; + } add_spell(key, val, hv, i, p); return true; @@ -152,7 +158,7 @@ const MagicPage* HexBook::find_spell( if ( const MagicPage* q = find_spell(s, n, p->any, i+1) ) return q; } - break; + return p->value.empty() ? nullptr : p; } return p; } @@ -162,7 +168,7 @@ const char* HexBook::find_spell( { p = find_spell(data, len, p, 0); - if ( !p->value.empty() ) + if ( p and !p->value.empty() ) return p->value.c_str(); return nullptr; diff --git a/src/service_inspectors/wizard/magic.h b/src/service_inspectors/wizard/magic.h index 3c548623a..d81d27aa4 100644 --- a/src/service_inspectors/wizard/magic.h +++ b/src/service_inspectors/wizard/magic.h @@ -51,7 +51,7 @@ public: MagicBook(const MagicBook&) = delete; MagicBook& operator=(const MagicBook&) = delete; - virtual bool add_spell(const char* key, const char* val) = 0; + virtual bool add_spell(const char* key, const char*& val) = 0; virtual const char* find_spell(const uint8_t*, unsigned len, const MagicPage*&) const = 0; const MagicPage* page1() @@ -72,7 +72,7 @@ class SpellBook : public MagicBook public: SpellBook(); - bool add_spell(const char*, const char*) override; + bool add_spell(const char*, const char*&) override; const char* find_spell(const uint8_t*, unsigned len, const MagicPage*&) const override; private: @@ -91,7 +91,7 @@ class HexBook : public MagicBook public: HexBook() = default; - bool add_spell(const char*, const char*) override; + bool add_spell(const char*, const char*&) override; const char* find_spell(const uint8_t*, unsigned len, const MagicPage*&) const override; private: diff --git a/src/service_inspectors/wizard/spells.cc b/src/service_inspectors/wizard/spells.cc index 7634a36e7..dd990ad38 100644 --- a/src/service_inspectors/wizard/spells.cc +++ b/src/service_inspectors/wizard/spells.cc @@ -45,6 +45,9 @@ bool SpellBook::translate(const char* in, HexVector& out) while ( in[i] ) { + if ( !isprint(in[i]) ) + return false; + if ( wild ) { if ( in[i] != '*' ) @@ -84,12 +87,15 @@ void SpellBook::add_spell( p->value = val; } -bool SpellBook::add_spell(const char* key, const char* val) +bool SpellBook::add_spell(const char* key, const char*& val) { HexVector hv; if ( !translate(key, hv) ) + { + val = nullptr; return false; + } unsigned i = 0; MagicPage* p = root; @@ -111,7 +117,10 @@ bool SpellBook::add_spell(const char* key, const char* val) ++i; } if ( p->key == key ) + { + val = p->value.c_str(); return false; + } add_spell(key, val, hv, i, p); return true; diff --git a/src/service_inspectors/wizard/wiz_module.cc b/src/service_inspectors/wizard/wiz_module.cc index da4828a1b..c2acf44db 100644 --- a/src/service_inspectors/wizard/wiz_module.cc +++ b/src/service_inspectors/wizard/wiz_module.cc @@ -24,6 +24,7 @@ #include "wiz_module.h" +#include "log/messages.h" #include "trace/trace.h" #include "curses.h" @@ -176,60 +177,75 @@ bool WizardModule::begin(const char* fqn, int, SnortConfig*) curses = new CurseBook; } - else if ( !strcmp(fqn, "wizard.hexes") ) - hex = true; - - else if ( !strcmp(fqn, "wizard.spells") ) - hex = false; - - else if ( !strcmp(fqn, "wizard.hexes.to_client") ) - c2s = false; - - else if ( !strcmp(fqn, "wizard.spells.to_client") ) - c2s = false; - - else if ( !strcmp(fqn, "wizard.hexes.to_server") ) - c2s = true; - - else if ( !strcmp(fqn, "wizard.spells.to_server") ) - c2s = true; + else if ( !strcmp(fqn, "wizard.hexes") || !strcmp(fqn, "wizard.spells") ) + { + service.clear(); + } + else if ( !strcmp(fqn, "wizard.hexes.to_client") || !strcmp(fqn, "wizard.hexes.to_server") || + !strcmp(fqn, "wizard.spells.to_client") || !strcmp(fqn, "wizard.spells.to_server") ) + { + spells.clear(); + } return true; } -void WizardModule::add_spells(MagicBook* b, string& service) +bool WizardModule::add_spells(MagicBook* b, string& service, bool hex) { for ( const auto& p : spells ) - b->add_spell(p.c_str(), service.c_str()); + { + const char* val = service.c_str(); + if ( !b->add_spell(p.c_str(), val) ) + { + if ( !val ) + { + ParseError("Invalid %s '%s' for service '%s'", + hex ? "hex" : "spell", service.c_str(), p.c_str()); + return false; + } + else if ( service != val ) + { + ParseWarning(WARN_CONF, "%s '%s' for service '%s' already exists for service '%s'", + hex ? "Hex" : "Spell", p.c_str(), service.c_str(), val); + } + else + { + ParseWarning(WARN_CONF, "Duplicate %s '%s' for service '%s'", + hex ? "hex" : "spell", p.c_str(), val); + } + } + } + return true; } -bool WizardModule::end(const char* fqn, int idx, SnortConfig*) +bool WizardModule::end(const char* fqn, int, SnortConfig*) { - if ( idx ) + if ( !strcmp(fqn, "wizard") ) { service.clear(); - return true; + spells.clear(); } - if ( !strstr(fqn, "to_client") and !strstr(fqn, "to_server") ) + else if ( !strcmp(fqn, "wizard.hexes.to_client") ) { - return true; + if ( !add_spells(s2c_hexes, service, true) ) + return false; + } + else if ( !strcmp(fqn, "wizard.spells.to_client") ) + { + if ( !add_spells(s2c_spells, service, false) ) + return false; } - if ( hex ) + else if ( !strcmp(fqn, "wizard.hexes.to_server") ) { - if ( c2s ) - add_spells(c2s_hexes, service); - else - add_spells(s2c_hexes, service); + if ( !add_spells(c2s_hexes, service, true) ) + return false; } - else + else if ( !strcmp(fqn, "wizard.spells.to_server") ) { - if ( c2s ) - add_spells(c2s_spells, service); - else - add_spells(s2c_spells, service); + if ( !add_spells(c2s_spells, service, false) ) + return false; } - spells.clear(); return true; } diff --git a/src/service_inspectors/wizard/wiz_module.h b/src/service_inspectors/wizard/wiz_module.h index 26ca6ba6f..62db7629d 100644 --- a/src/service_inspectors/wizard/wiz_module.h +++ b/src/service_inspectors/wizard/wiz_module.h @@ -65,11 +65,9 @@ public: const snort::TraceOption* get_trace_options() const override; private: - void add_spells(MagicBook*, std::string&); + bool add_spells(MagicBook*, std::string&, bool hex); private: - bool hex; - bool c2s; std::string service; std::vector spells; diff --git a/src/service_inspectors/wizard/wizard.cc b/src/service_inspectors/wizard/wizard.cc index 3585b1c21..c6fad20e1 100644 --- a/src/service_inspectors/wizard/wizard.cc +++ b/src/service_inspectors/wizard/wizard.cc @@ -41,20 +41,26 @@ struct WizStats { PegCount tcp_scans; PegCount tcp_hits; + PegCount tcp_misses; PegCount udp_scans; PegCount udp_hits; + PegCount udp_misses; PegCount user_scans; PegCount user_hits; + PegCount user_misses; }; const PegInfo wiz_pegs[] = { { CountType::SUM, "tcp_scans", "tcp payload scans" }, { CountType::SUM, "tcp_hits", "tcp identifications" }, + { CountType::SUM, "tcp_misses", "tcp searches abandoned" }, { CountType::SUM, "udp_scans", "udp payload scans" }, { CountType::SUM, "udp_hits", "udp identifications" }, + { CountType::SUM, "udp_misses", "udp searches abandoned" }, { CountType::SUM, "user_scans", "user payload scans" }, { CountType::SUM, "user_hits", "user identifications" }, + { CountType::SUM, "user_misses", "user searches abandoned" }, { CountType::END, nullptr, nullptr } }; @@ -107,9 +113,18 @@ private: ++tstats.user_hits; } + void count_miss(const Flow* f) + { + if ( f->pkt_type == PktType::TCP ) + ++tstats.tcp_misses; + else + ++tstats.user_misses; + } + private: Wizard* wizard; Wand wand; + unsigned bytes_scanned = 0; }; class Wizard : public Inspector @@ -169,14 +184,20 @@ StreamSplitter::Status MagicSplitter::scan( Profile profile(wizPerfStats); count_scan(pkt->flow); + bytes_scanned += len; if ( wizard->cast_spell(wand, pkt->flow, data, len) ) { - trace_logf(wizard_trace, pkt, "service set to %s\n", pkt->flow->service); + trace_logf(wizard_trace, pkt, "%s streaming search found service %s\n", + to_server() ? "c2s" : "s2c", pkt->flow->service); count_hit(pkt->flow); } - else if ( wizard->finished(wand) ) + else if ( wizard->finished(wand) || bytes_scanned >= max(pkt->flow) ) + { + count_miss(pkt->flow); + trace_logf(wizard_trace, pkt, "%s streaming search abandoned\n", to_server() ? "c2s" : "s2c"); return ABORT; + } // ostensibly continue but splitter will be swapped out upon hit return SEARCH; @@ -244,16 +265,23 @@ void Wizard::eval(Packet* p) if ( !p->data || !p->dsize ) return; + bool c2s = p->is_from_client(); Wand wand; - reset(wand, false, p->is_from_client()); + reset(wand, false, c2s); + ++tstats.udp_scans; if ( cast_spell(wand, p->flow, p->data, p->dsize) ) { - trace_logf(wizard_trace, p, "service set to %s\n", p->flow->service); + trace_logf(wizard_trace, p, "%s datagram search found service %s\n", + c2s ? "c2s" : "s2c", p->flow->service); ++tstats.udp_hits; } - - ++tstats.udp_scans; + else + { + p->flow->clear_clouseau(); + trace_logf(wizard_trace, p, "%s datagram search abandoned\n", c2s ? "c2s" : "s2c"); + ++tstats.udp_misses; + } } StreamSplitter* Wizard::get_splitter(bool c2s)