coverage.info
coverage.txt
src/uncovered.txt
+build/
+
+# vscode config
+snort3.code-workspace
+
return SEARCH;
}
+bool AtomSplitter::restart()
+{
+ reset();
+ return true;
+}
+
void AtomSplitter::reset()
{ segs = bytes_scanned = 0; }
unsigned& copied // actual data copied (1 <= copied <= len)
);
+ virtual bool restart() { return false; }
virtual bool sync_on_start() const { return false; }
virtual bool is_paf() { return false; }
virtual unsigned max(Flow* = nullptr);
Status scan(Packet*, const uint8_t*, uint32_t, uint32_t, uint32_t*) override;
+ bool restart() override;
+
private:
void reset();
{ 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::SUM, "splitter_restarts", "count of splitter restarts from skipping seglist holes" },
{ CountType::MAX, "max_segs", "maximum number of segments queued in any flow" },
{ CountType::MAX, "max_bytes", "maximum number of bytes queued in any flow" },
{ CountType::SUM, "zero_len_tcp_opt", "number of zero length tcp options" },
PegCount partial_flush_bytes;
PegCount inspector_fallbacks;
PegCount partial_fallbacks;
+ PegCount splitter_restarts;
PegCount max_segs;
PegCount max_bytes;
PegCount zero_len_tcp_opt;
and SEQ_GEQ(tsn.next_seq(), tracker.get_fin_i_seq());
}
-// If we are skipping seglist hole, update tsn so that we can purge
-void TcpReassemblerBase::update_skipped_bytes(uint32_t remaining_bytes)
-{
- TcpSegmentNode* tsn;
-
- while ( remaining_bytes and (tsn = seglist.cur_rseg) )
- {
- auto bytes_skipped = ( tsn->unscanned() <= remaining_bytes ) ? tsn->unscanned() : remaining_bytes;
-
- remaining_bytes -= bytes_skipped;
- tsn->advance_cursor(bytes_skipped);
-
- if ( !tsn->unscanned() )
- {
- seglist.flush_count++;
- seglist.update_next(tsn);
- }
- }
-}
-
void TcpReassemblerBase::purge_to_seq(uint32_t flush_seq)
{
seglist.purge_flushed_segments(flush_seq);
/* Check for a gap/missing packet */
// FIXIT-L FIN may be in to_seq causing bogus gap counts.
- if ( tsn->is_packet_missing(to_seq) or paf.state == StreamSplitter::SKIP )
+ if ( tsn->is_packet_missing(to_seq) )
{
- // FIXIT-H // assert(false); find when this scenario happens
// FIXIT-L this is suboptimal - better to exclude fin from to_seq
if ( !tracker.is_fin_seq_set() or
SEQ_LEQ(to_seq, tracker.get_fin_final_seq()) )
break;
}
- if ( paf.state == StreamSplitter::SKIP )
- update_skipped_bytes(remaining_bytes);
-
return total_flushed;
}
}
}
-
-void TcpReassemblerBase::check_first_segment_hole()
-{
- if ( SEQ_LT(seglist.seglist_base_seq, seglist.head->start_seq()) )
- {
- seglist.seglist_base_seq = seglist.head->start_seq();
- seglist.advance_rcv_nxt();
- paf.state = StreamSplitter::START;
- }
-}
-
uint32_t TcpReassemblerBase::perform_partial_flush(Flow* flow, Packet*& p)
{
p = get_packet(flow, packet_dir, server_side);
bool fin_no_gap(const TcpSegmentNode&);
bool fin_acked_no_gap(const TcpSegmentNode&);
- void update_skipped_bytes(uint32_t);
- void check_first_segment_hole();
uint32_t perform_partial_flush(snort::Packet*);
bool final_flush_on_fin(int32_t flush_amt, snort::Packet*, FinSeqNumStatus);
bool asymmetric_flow_flushed(uint32_t flushed, snort::Packet *p);
using namespace snort;
-bool TcpReassemblerIds::has_seglist_hole(TcpSegmentNode& tsn, uint32_t& total, uint32_t& flags)
-{
- if ( !tsn.prev or SEQ_GEQ(tsn.prev->scan_seq() + tsn.prev->unscanned(), tsn.scan_seq())
- or SEQ_GEQ(tsn.scan_seq(), tracker.r_win_base) )
- {
- check_first_segment_hole();
- return false;
- }
-
- // safety - prevent seq + total < seq
- if ( total > 0x7FFFFFFF )
- total = 0x7FFFFFFF;
-
- if ( !paf.tot )
- flags |= PKT_PDU_HEAD;
-
- paf.state = StreamSplitter::SKIP;
- return true;
-}
-
-void TcpReassemblerIds::skip_seglist_hole(Packet* p, uint32_t flags, int32_t flush_amt)
-{
- if ( is_splitter_paf() )
- {
- if ( flush_amt > 0 )
- update_skipped_bytes(flush_amt);
- tracker.fallback();
- }
- else
- {
- if ( flush_amt > 0 )
- flush_to_seq(flush_amt, p, flags);
- paf.state = StreamSplitter::START;
- }
-
- if ( seglist.head )
- {
- if ( flush_amt > 0 )
- purge_to_seq(seglist.seglist_base_seq + flush_amt);
- seglist.seglist_base_seq = seglist.head->scan_seq();
- }
- else
- seglist.seglist_base_seq = tracker.r_win_base; // FIXIT-H - do we need to set rcv_nxt here?
-
- seglist.cur_rseg = seglist.head;
- tracker.set_order(TcpStreamTracker::OUT_OF_SEQUENCE);
-}
-
// iterate over seglist and scan all new acked bytes
// - new means not yet scanned
// - must use seglist data (not packet) since this packet may plug a
// know where we left off and can resume scanning the remainder
int32_t TcpReassemblerIds::scan_data_post_ack(uint32_t* flags, Packet* p)
{
- assert(seglist.session->flow == p->flow);
-
- int32_t ret_val = FINAL_FLUSH_HOLD;
-
- if ( !seglist.cur_sseg || SEQ_GEQ(seglist.seglist_base_seq, tracker.r_win_base) )
- return ret_val ;
+ assert( seglist.cur_sseg );
if ( !seglist.cur_rseg )
seglist.cur_rseg = seglist.cur_sseg;
if ( SEQ_EQ(end_seq, paf.paf_position()) )
{
total = end_seq - seglist.seglist_base_seq;
- tsn = tsn->next;
+ if ( tsn->next_no_gap() )
+ tsn = tsn->next;
+ else if ( tsn->next )
+ {
+ if ( SEQ_GT(tracker.r_win_base, tsn->next->start_seq())
+ or SEQ_GT(tracker.r_win_base, tsn->next_seq()) )
+ {
+ paf.state = StreamSplitter::SKIP;
+ return total;
+ }
+ }
+ else
+ return FINAL_FLUSH_OK;
}
else
total = tsn->scan_seq() - seglist.cur_rseg->scan_seq();
}
- ret_val = FINAL_FLUSH_OK;
+ int32_t ret_val = FINAL_FLUSH_OK;
while (tsn && *flags && SEQ_LT(tsn->scan_seq(), tracker.r_win_base))
{
- // only flush acked data that fits in pdu reassembly buffer...
uint32_t end = tsn->scan_seq() + tsn->unscanned();
uint32_t flush_len;
int32_t flush_pt;
- if ( SEQ_GT(end, tracker.r_win_base))
+ if ( SEQ_GT(end, tracker.r_win_base) )
flush_len = tracker.r_win_base - tsn->scan_seq();
else
flush_len = tsn->unscanned();
else
*flags &= ~PKT_MORE_TO_FLUSH;
- if ( has_seglist_hole(*tsn, total, *flags) )
- flush_pt = total;
- else
- {
- total += flush_len;
- flush_pt = paf.paf_check(p, tsn->paf_data(), flush_len, total, tsn->scan_seq(), flags);
- }
-
- // Get splitter from tracker as paf check may change it.
+ total += flush_len;
+ flush_pt = paf.paf_check(p, tsn->paf_data(), flush_len, total, tsn->scan_seq(), flags);
seglist.cur_sseg = tsn;
if ( flush_pt >= 0 )
+ return flush_pt;
+ else if ( !tsn->next_no_gap() )
{
- seglist.seglist_base_seq = seglist.cur_rseg->scan_seq();
+ // if ack is past the hole flush what we have...
+ if ( tsn->next && SEQ_LEQ(tsn->next->start_seq(), tracker.r_win_base))
+ {
+ paf.state = StreamSplitter::SKIP;
+ flush_pt = total;
+ }
+
return flush_pt;
}
- if (flush_len < tsn->unscanned() || (splitter->is_paf() and !tsn->next_no_gap()) ||
- (paf.state == StreamSplitter::STOP))
+ if ( flush_len < tsn->unscanned() || (splitter->is_paf() and !tsn->next_no_gap())
+ or (paf.state == StreamSplitter::STOP) )
{
if ( !(tsn->next_no_gap() || fin_acked_no_gap(*tsn)) )
ret_val = FINAL_FLUSH_HOLD;
return ret_val;
}
+void TcpReassemblerIds::skip_seglist_hole()
+{
+ if ( SEQ_LT(seglist.seglist_base_seq, seglist.head->start_seq()) )
+ {
+ uint32_t old_seglist_base_seq = seglist.seglist_base_seq;
+
+ if ( SEQ_LT(tracker.r_win_base, seglist.head->start_seq()) )
+ {
+ seglist.seglist_base_seq = tracker.r_win_base;
+ tracker.set_rcv_nxt(tracker.r_win_base);
+ }
+ else
+ {
+ seglist.seglist_base_seq = seglist.head->start_seq();
+ seglist.advance_rcv_nxt();
+ }
+
+ uint32_t hole_size = seglist.seglist_base_seq - old_seglist_base_seq;
+ tracker.bytes_missing += hole_size;
+ tracker.holes_detected++;
+
+ if (PacketTracer::is_active())
+ PacketTracer::log("stream_tcp: IDS mode - skipping hole from %u to %u, size: %u"
+ " total holes: %u, total bytes skipped: %lu total bytes missing: %lu\n",
+ old_seglist_base_seq, seglist.seglist_base_seq,
+ hole_size, tracker.holes_detected, tracker.bytes_skipped, tracker.bytes_missing);
+
+ if ( is_splitter_paf() )
+ tracker.fallback();
+ else
+ {
+ splitter->restart();
+ paf.paf_reset();
+ tcpStats.splitter_restarts++;
+
+ if (PacketTracer::is_active())
+ PacketTracer::log("stream_tcp: IDS mode - splitter restarted due to seglist hole\n");
+ }
+
+ seglist.cur_rseg = seglist.head;
+ tracker.set_order(TcpStreamTracker::OUT_OF_SEQUENCE);
+ }
+}
+
int TcpReassemblerIds::eval_flush_policy_on_ack(Packet* p)
{
+ // anything here to scan/flush?
+ if ( !seglist.head || SEQ_GEQ(seglist.seglist_base_seq, tracker.r_win_base) )
+ return 0;
+
last_pdu = nullptr;
uint32_t flushed = 0;
int32_t flush_amt;
uint32_t flags;
-
+
do
{
+ skip_seglist_hole();
+
flags = packet_dir;
flush_amt = scan_data_post_ack(&flags, p);
- if ( flush_amt <= 0 or paf.state == StreamSplitter::SKIP )
+ if ( flush_amt <= 0 )
break;
- // for consistency with other cases, should return total
- // but that breaks flushing pipelined pdus
+ if ( paf.state == StreamSplitter::SKIP and is_splitter_paf() )
+ {
+ // hole in seglist with paf splitter, fallback to AtomSplitter and rescan data
+ tracker.fallback();
+ continue;
+ }
+
flushed += flush_to_seq(flush_amt, p, flags);
- assert( flushed );
-
- // ideally we would purge just once after this loop but that throws off base
if ( seglist.head )
purge_to_seq(seglist.seglist_base_seq);
- } while ( seglist.head and !p->flow->is_inspection_disabled() );
+
+ } while ( seglist.head and !p->flow->is_inspection_disabled()
+ and SEQ_LT(seglist.seglist_base_seq, tracker.r_win_base) );
if ( (paf.state == StreamSplitter::ABORT) && is_splitter_paf() )
{
tracker.fallback();
return eval_flush_policy_on_ack(p);
}
- else if ( paf.state == StreamSplitter::SKIP )
- {
- skip_seglist_hole(p, flags, flush_amt);
- return eval_flush_policy_on_ack(p);
- }
else if ( final_flush_on_fin(flush_amt, p, FIN_WITH_SEQ_ACKED) )
finish_and_final_flush(p->flow, true, p);
private:
int32_t scan_data_post_ack(uint32_t* flags, snort::Packet*);
- bool has_seglist_hole(TcpSegmentNode&, uint32_t& total, uint32_t& flags);
- void skip_seglist_hole(snort::Packet*, uint32_t flags, int32_t flush_amt);
+ void skip_seglist_hole();
};
#endif
ss << " seglist_base_seq: " << seglist_base_seq;
ss << ", rcv_next: " << tracker->get_rcv_nxt();
ss << ", r_win_base: " << talker->r_win_base;
- if(head)
+ if( head )
ss << ", head: " << head->start_seq();
- if(cur_sseg)
+ if( cur_sseg )
ss << ", cur_sseg: " << cur_sseg->start_seq();
- if(cur_rseg)
+ if( cur_rseg )
ss << ", cur_rseg: " << cur_rseg->start_seq();
ss << "\n";
PacketTracer::log("%s", ss.str().c_str());
insert(prev, tsn);
if ( !cur_sseg )
+ {
cur_sseg = tsn;
+ cur_rseg = tsn;
+ if ( SEQ_LT(tsn->scan_seq(), seglist_base_seq) )
+ seglist_base_seq = tsn->scan_seq();
+ }
else if ( SEQ_LT(tsn->scan_seq(), cur_sseg->scan_seq()) )
{
cur_sseg = tsn;
if ( SEQ_GT(tsn->seq, seglist_base_seq) )
{
+ uint32_t hole_size = tsn->seq - seglist_base_seq;
+ if ( PacketTracer::is_active() )
+ PacketTracer::log("stream_tcp: Seglist hole %u-->%u skipped at beginning of the seglist,"
+ " bytes missing: %u\n", seglist_base_seq, tsn->seq, hole_size);
+
+ tracker->bytes_missing += hole_size;
+ tracker->holes_detected++;
+
hole_skipped = true;
seglist_base_seq = tsn->seq;
tracker->set_order(TcpStreamTracker::OUT_OF_SEQUENCE);
- if ( PacketTracer::is_active() )
- PacketTracer::log("stream_tcp: Skipped hole at beginning of the seglist\n");
+
}
return hole_skipped;
assert( head );
TcpSegmentNode* tsn = head;
- uint32_t num_segs = 0, total_segs = 0, num_holes = 0;
+ uint32_t num_segs = 0, num_holes = 0;
// if there is a hole at the beginning, skip it...
if ( skip_hole_at_beginning(tsn) )
if ( tsn->next and SEQ_GT(tsn->next->start_seq(), tsn->next_seq()) )
{
++num_holes;
- total_segs += num_segs;
+ uint32_t hole_size = tsn->next->start_seq() - tsn->next_seq();
if ( PacketTracer::is_active() )
- PacketTracer::log("stream_tcp: Seglist hole(%u): %u-->%u:%u. Segments purged: %u Total purged: %u\n",
- tsn->seq, tsn->next->seq, tsn->next->seq - tsn->seq, num_holes, num_segs, total_segs);
+ PacketTracer::log("stream_tcp: Seglist hole[%u]: %u-->%u segments purged: %u, bytes purged: %u,"
+ " bytes missing: %u\n",
+ num_holes, tsn->next_seq(), tsn->next->start_seq(), num_segs,
+ tsn->next_seq() - seglist_base_seq, hole_size);
+
+ tracker->bytes_skipped += tsn->next_seq() - seglist_base_seq;
+ tracker->bytes_missing += hole_size;
+ tracker->holes_detected++;
+
tsn = tsn->next;
purge_segments_left_of_hole(tsn);
seglist_base_seq = head->start_seq();
tsn = tsn->next;
purge_segments_left_of_hole(tsn);
seglist_base_seq = ack;
- }
+ }
else
tsn = tsn->next;
}
if ( !next_no_gap() )
return false;
- return SEQ_LT(next->start_seq() + next->cursor, seq_acked);
+ return SEQ_LT(next->start_seq(), seq_acked);
}
public:
bool TcpSession::filter_packet_for_reassembly(TcpSegmentDescriptor& tsd, TcpStreamTracker* listener)
{
if ( tsd.are_packet_flags_set(PKT_IGNORE) or listener->get_flush_policy() == STREAM_FLPOLICY_IGNORE )
+ {
+ listener->update_stream_order(tsd, tsd.is_packet_inorder());
+
+ if ( tsd.is_packet_inorder() )
+ listener->set_rcv_nxt(tsd.get_end_seq());
+ else if ( SEQ_GT(listener->r_win_base, listener->rcv_nxt) )
+ listener->set_rcv_nxt(listener->r_win_base);
+
return false;
+ }
return !check_reassembly_queue_thresholds(tsd, listener);
}
tel.set_tcp_event(EVENT_EXCESSIVE_OVERLAP);
listener->seglist.set_overlap_count(0);
}
+
+ listener->update_stream_order(tsd, tsd.is_packet_inorder());
}
- else if ( tsd.is_packet_inorder() )
- listener->set_rcv_nxt(tsd.get_end_seq());
-
- listener->update_stream_order(tsd, tsd.is_packet_inorder());
break;
"TCP_RST_SENT_EVENT", "TCP_RST_RECV_EVENT"
};
+static constexpr uint32_t BOTH_SPLITTERS_YOINKED = (SSNFLAG_ABORT_CLIENT | SSNFLAG_ABORT_SERVER);
+
TcpStreamTracker::TcpStreamTracker(bool client) :
client_tracker(client), tcp_state(client ? TCP_STATE_NONE : TCP_LISTEN)
{
static inline bool both_splitters_aborted(Flow* flow)
{
- uint32_t both_splitters_yoinked = (SSNFLAG_ABORT_CLIENT | SSNFLAG_ABORT_SERVER);
- return (flow->get_session_flags() & both_splitters_yoinked) == both_splitters_yoinked;
+ return (flow->get_session_flags() & BOTH_SPLITTERS_YOINKED) == BOTH_SPLITTERS_YOINKED;
}
void TcpStreamTracker::fallback()
static void thread_init();
static void thread_term();
-public:
uint32_t rcv_nxt = 0; // RCV.NXT - receive next
uint32_t rcv_wnd = 0; // RCV.WND - receive window
TcpSession* session = nullptr;
TcpAlerts tcp_alerts;
+ uint64_t bytes_skipped = 0; // number of bytes skipped in the stream
+ uint64_t bytes_missing = 0; // number of bytes missing in the stream
+ uint32_t holes_detected = 0; // number of holes detected in the stream
+
uint32_t r_win_base = 0; // remote side window base sequence number (the last ack we got)
uint32_t small_seg_count = 0;
FinSeqNumStatus fin_seq_status = FIN_NOT_SEEN;
uint32_t irs = 0; // IRS - initial receive sequence number
uint16_t snd_up = 0; // SND.UP - send urgent pointer
uint16_t rcv_up = 0; // RCV.UP - receive urgent pointer
+
};
// <--- note -- the 'state' parameter must be a reference