// See the file "COPYING" in the main distribution directory for copyright. #include "config.h" #include "InterConn.h" #include "Event.h" #include "Net.h" #include "TCP.h" InterConnEndpoint::InterConnEndpoint(TCP_Endpoint* e) { endp = e; max_top_seq = 0; num_pkts = num_keystrokes_two_in_a_row = num_normal_interarrivals = num_8k0_pkts = num_8k4_pkts = num_bytes = num_7bit_ascii = num_lines = num_normal_lines = 0; is_partial = keystroke_just_seen = 0; last_keystroke_time = 0.0; } #define NORMAL_LINE_LENGTH 80 int InterConnEndpoint::DataSent(double t, int seq, int len, int caplen, const u_char* data, const IP_Hdr* /* ip */, const struct tcphdr* /* tp */) { if ( caplen < len ) len = caplen; if ( len <= 0 ) return 0; if ( endp->state == TCP_ENDPOINT_PARTIAL ) is_partial = 1; int ack = endp->AckSeq() - endp->StartSeq(); int top_seq = seq + len; if ( top_seq <= ack || top_seq <= max_top_seq ) // There is no new data in this packet return 0; if ( seq < max_top_seq ) { // Only consider new data int amount_seen = max_top_seq - seq; seq += amount_seen; data += amount_seen; len -= amount_seen; } if ( max_top_seq && seq > max_top_seq ) // We've got a pkt above a hole num_pkts += EstimateGapPacketNum(seq - max_top_seq); ++num_pkts; max_top_seq = top_seq; // Count the bytes. num_bytes += len; int last_char = 0; int offset = 0; // where we consider the latest line to have begun for ( int i = 0; i < len; ++i ) { unsigned int c = data[i]; if ( c == '\n' && last_char == '\r' ) { // Compress CRLF to just one line termination. last_char = c; continue; } if ( c == '\n' || c == '\r' ) { ++num_lines; if ( i - offset <= NORMAL_LINE_LENGTH ) ++num_normal_lines; offset = i; } else if ( c != 0 && c < 128 ) ++num_7bit_ascii; last_char = c; } if ( IsPotentialKeystrokePacket(len) ) { if ( keystroke_just_seen ) { ++num_keystrokes_two_in_a_row; if ( IsNormalKeystrokeInterarrival(t - last_keystroke_time) ) ++num_normal_interarrivals; } else keystroke_just_seen = 1; // Look for packets matching the SSH signature of // being either 0 or 4 modulo 8. switch ( len & 7 ) { case 0: if ( len >= 16 ) ++num_8k0_pkts; break; case 4: ++num_8k4_pkts; break; } last_keystroke_time = t; } else keystroke_just_seen = 0; return 1; } RecordVal* InterConnEndpoint::BuildStats() { RecordVal* stats = new RecordVal(interconn_endp_stats); stats->Assign(0, new Val(num_pkts, TYPE_COUNT)); stats->Assign(1, new Val(num_keystrokes_two_in_a_row, TYPE_COUNT)); stats->Assign(2, new Val(num_normal_interarrivals, TYPE_COUNT)); stats->Assign(3, new Val(num_8k0_pkts, TYPE_COUNT)); stats->Assign(4, new Val(num_8k4_pkts, TYPE_COUNT)); stats->Assign(5, new Val(is_partial, TYPE_BOOL)); stats->Assign(6, new Val(num_bytes, TYPE_COUNT)); stats->Assign(7, new Val(num_7bit_ascii, TYPE_COUNT)); stats->Assign(8, new Val(num_lines, TYPE_COUNT)); stats->Assign(9, new Val(num_normal_lines, TYPE_COUNT)); return stats; } int InterConnEndpoint::EstimateGapPacketNum(int gap) const { return (gap + interconn_default_pkt_size - 1) / interconn_default_pkt_size; } int InterConnEndpoint::IsPotentialKeystrokePacket(int len) const { return len <= interconn_max_keystroke_pkt_size; } int InterConnEndpoint::IsNormalKeystrokeInterarrival(double t) const { return interconn_min_interarrival <= t && t <= interconn_max_interarrival; } InterConn_Analyzer::InterConn_Analyzer(Connection* c) : TCP_ApplicationAnalyzer(AnalyzerTag::InterConn, c) { orig_endp = resp_endp = 0; orig_stream_pos = resp_stream_pos = 1; timeout = backdoor_stat_period; backoff = backdoor_stat_backoff; c->GetTimerMgr()->Add(new InterConnTimer(network_time + timeout, this)); } InterConn_Analyzer::~InterConn_Analyzer() { Unref(orig_endp); Unref(resp_endp); } void InterConn_Analyzer::Init() { TCP_ApplicationAnalyzer::Init(); assert(TCP()); orig_endp = new InterConnEndpoint(TCP()->Orig()); resp_endp = new InterConnEndpoint(TCP()->Resp()); } void InterConn_Analyzer::DeliverPacket(int len, const u_char* data, bool is_orig, int seq, const IP_Hdr* ip, int caplen) { TCP_ApplicationAnalyzer::DeliverPacket(len, data, is_orig, seq, ip, caplen); if ( is_orig ) orig_endp->DataSent(network_time, seq, len, caplen, data, 0, 0); else resp_endp->DataSent(network_time, seq, len, caplen, data, 0, 0); } void InterConn_Analyzer::DeliverStream(int len, const u_char* data, bool is_orig) { TCP_ApplicationAnalyzer::DeliverStream(len, data, is_orig); if ( is_orig ) { orig_endp->DataSent(network_time, orig_stream_pos, len, len, data, 0, 0); orig_stream_pos += len; } else { resp_endp->DataSent(network_time, resp_stream_pos, len, len, data, 0, 0); resp_stream_pos += len; } } void InterConn_Analyzer::Done() { if ( ! IsFinished() ) { if ( ! Conn()->Skipping() ) StatEvent(); RemoveEvent(); } TCP_ApplicationAnalyzer::Done(); } void InterConn_Analyzer::StatTimer(double t, int is_expire) { if ( IsFinished() || Conn()->Skipping() ) return; StatEvent(); if ( ! is_expire ) { timeout *= backoff; timer_mgr->Add(new InterConnTimer(t + timeout, this)); } } void InterConn_Analyzer::StatEvent() { val_list* vl = new val_list; vl->append(Conn()->BuildConnVal()); vl->append(orig_endp->BuildStats()); vl->append(resp_endp->BuildStats()); Conn()->ConnectionEvent(interconn_stats, this, vl); } void InterConn_Analyzer::RemoveEvent() { val_list* vl = new val_list; vl->append(Conn()->BuildConnVal()); Conn()->ConnectionEvent(interconn_remove_conn, this, vl); } InterConnTimer::InterConnTimer(double t, InterConn_Analyzer* a) : Timer(t, TIMER_INTERCONN) { analyzer = a; // Make sure connection does not expire. Ref(a->Conn()); } InterConnTimer::~InterConnTimer() { Unref(analyzer->Conn()); } void InterConnTimer::Dispatch(double t, int is_expire) { analyzer->StatTimer(t, is_expire); }