// See the file "COPYING" in the main distribution directory for copyright. #include #include "NetVar.h" #include "PIA.h" #include "File.h" #include "TCP.h" #include "TCP_Reassembler.h" #include "OSFinger.h" #include "Event.h" namespace { // local namespace const bool DEBUG_tcp_data_sent = false; const bool DEBUG_tcp_connection_close = false; } // The following are not included in all systems' tcp.h. #ifndef TH_ECE #define TH_ECE 0x40 #endif #ifndef TH_CWR #define TH_CWR 0x80 #endif #define TOO_LARGE_SEQ_DELTA 1048576 static const int ORIG = 1; static const int RESP = 2; TCP_Analyzer::TCP_Analyzer(Connection* conn) : TransportLayerAnalyzer(AnalyzerTag::TCP, conn) { // Set a timer to eventually time out this connection. ADD_ANALYZER_TIMER(&TCP_Analyzer::ExpireTimer, network_time + tcp_SYN_timeout, 0, TIMER_TCP_EXPIRE); deferred_gen_event = close_deferred = 0; seen_first_ACK = 0; is_active = 1; finished = 0; reassembling = 0; first_packet_seen = 0; orig = new TCP_Endpoint(this, 1); resp = new TCP_Endpoint(this, 0); orig->SetPeer(resp); resp->SetPeer(orig); } TCP_Analyzer::~TCP_Analyzer() { LOOP_OVER_GIVEN_CHILDREN(i, packet_children) delete *i; delete orig; delete resp; } void TCP_Analyzer::Init() { Analyzer::Init(); LOOP_OVER_GIVEN_CHILDREN(i, packet_children) (*i)->Init(); } void TCP_Analyzer::Done() { Analyzer::Done(); if ( connection_pending && is_active && ! BothClosed() ) Event(connection_pending); LOOP_OVER_GIVEN_CHILDREN(i, packet_children) (*i)->Done(); orig->Done(); resp->Done(); finished = 1; } void TCP_Analyzer::EnableReassembly() { SetReassembler(new TCP_Reassembler(this, this, TCP_Reassembler::Forward, true, orig), new TCP_Reassembler(this, this, TCP_Reassembler::Forward, false, resp)); reassembling = 1; if ( new_connection_contents ) Event(new_connection_contents); } void TCP_Analyzer::SetReassembler(TCP_Reassembler* rorig, TCP_Reassembler* rresp) { orig->AddReassembler(rorig); rorig->SetDstAnalyzer(this); resp->AddReassembler(rresp); rresp->SetDstAnalyzer(this); reassembling = 1; if ( new_connection_contents ) Event(new_connection_contents); } const struct tcphdr* TCP_Analyzer::ExtractTCP_Header(const u_char*& data, int& len, int& caplen) { const struct tcphdr* tp = (const struct tcphdr*) data; uint32 tcp_hdr_len = tp->th_off * 4; if ( tcp_hdr_len < sizeof(struct tcphdr) ) { Weird("bad_TCP_header_len"); return 0; } if ( tcp_hdr_len > uint32(len) || sizeof(struct tcphdr) > uint32(caplen) ) { // This can happen even with the above test, due to TCP // options. Weird("truncated_header"); return 0; } len -= tcp_hdr_len; // remove TCP header caplen -= tcp_hdr_len; data += tcp_hdr_len; return tp; } bool TCP_Analyzer::ValidateChecksum(const struct tcphdr* tp, TCP_Endpoint* endpoint, int len, int caplen) { if ( ! ignore_checksums && caplen >= len && ! endpoint->ValidChecksum(tp, len) ) { Weird("bad_TCP_checksum"); endpoint->CheckHistory(HIST_CORRUPT_PKT, 'C'); return false; } else return true; } void TCP_Analyzer::CheckFlagCombos(TCP_Flags flags, TCP_Endpoint* endpoint, uint32 base_seq, int len, int dst_port) { bool is_orig = endpoint->IsOrig(); if ( is_orig && ! (first_packet_seen & ORIG) ) is_partial = ! flags.SYN() || flags.ACK(); if ( ! is_orig && ! (first_packet_seen & RESP) && ! is_partial ) is_partial = ! flags.SYN(); int bits_set = (flags.SYN() ? 1 : 0) + (flags.FIN() ? 1 : 0) + (flags.RST() ? 1 : 0); if ( bits_set > 1 ) { if ( flags.FIN() && flags.RST() ) endpoint->CheckHistory(HIST_FIN_RST_PKT, 'I'); else endpoint->CheckHistory(HIST_MULTI_FLAG_PKT, 'Q'); } else if ( bits_set == 1 ) { if ( flags.SYN() ) { char code = flags.ACK() ? 'H' : 'S'; if ( endpoint->CheckHistory(HIST_SYN_PKT, code) && base_seq != endpoint->hist_last_SYN ) endpoint->AddHistory(code); endpoint->hist_last_SYN = base_seq; } if ( flags.FIN() ) { // For FIN's, the sequence number comes at the // end of (any data in) the packet, not the // beginning as for SYNs and RSTs. if ( endpoint->CheckHistory(HIST_FIN_PKT, 'F') && base_seq + len != endpoint->hist_last_FIN ) endpoint->AddHistory('F'); endpoint->hist_last_FIN = base_seq + len; } if ( flags.RST() ) { if ( endpoint->CheckHistory(HIST_RST_PKT, 'R') && base_seq != endpoint->hist_last_RST ) endpoint->AddHistory('R'); endpoint->hist_last_RST = base_seq; } } else { // bits_set == 0 if ( len ) endpoint->CheckHistory(HIST_DATA_PKT, 'D'); else if ( flags.ACK() ) endpoint->CheckHistory(HIST_ACK_PKT, 'A'); } } void TCP_Analyzer::UpdateWindow(TCP_Endpoint* endpoint, unsigned int window, uint32 base_seq, uint32 ack_seq, TCP_Flags flags) { // Note, the offered window on an initial SYN is unscaled, even // if the SYN includes scaling, so we need to do the following // test *before* updating the scaling information below. (Hmmm, // how does this work for windows on SYN/ACKs? ###) int scale = endpoint->window_scale; window = window << scale; // Don't analyze window values off of SYNs, they're sometimes // immediately rescinded. if ( ! flags.SYN() ) { // ### Decide whether to accept new window based on Active // Mapping policy. if ( int(base_seq - endpoint->window_seq) >= 0 && int(ack_seq - endpoint->window_ack_seq) >= 0 ) { uint32 new_edge = ack_seq + window; uint32 old_edge = endpoint->window_ack_seq + endpoint->window; int advance = new_edge - old_edge; if ( advance < 0 ) { // A window recision. We don't report these // for FINs or RSTs, or if the connection // has already been partially closed, since // such recisions occur frequently in practice, // probably as the receiver loses buffer memory // due to its process going away. // // We also, for window scaling, allow a bit // of slop ###. This is because sometimes // there will be an apparent recision due // to the granularity of the scaling. if ( ! flags.FIN() && ! flags.RST() && endpoint->state != TCP_ENDPOINT_CLOSED && endpoint->state != TCP_ENDPOINT_RESET && (-advance) >= (1 << scale) ) Weird("window_recision"); } endpoint->window = window; endpoint->window_ack_seq = ack_seq; endpoint->window_seq = base_seq; } } } void TCP_Analyzer::ProcessSYN(const IP_Hdr* ip, const struct tcphdr* tp, uint32 tcp_hdr_len, int& seq_len, TCP_Endpoint* endpoint, TCP_Endpoint* peer, uint32 base_seq, uint32 ack_seq, const IPAddr& orig_addr, int is_orig, TCP_Flags flags) { int len = seq_len; ++seq_len; // SYN consumes a byte of sequence space if ( flags.RST() ) Weird("TCP_christmas"); if ( flags.URG() ) Weird("baroque_SYN"); if ( len > 0 ) // T/TCP definitely complicates this. Weird("SYN_with_data"); RecordVal* SYN_vals = BuildSYNPacketVal(is_orig, ip, tp)->AsRecordVal(); // ### In the following, we could be fooled by an // inconsistent SYN retransmission. Where's a normalizer // when you need one? // ### We know that field 5 is the window scaling .... int scale = int(SYN_vals->Lookup(5)->CoerceToInt()); if ( scale < 0 ) { // no window scaling option if ( flags.ACK() ) { // window scaling not negotiated endpoint->window_scale = 0; peer->window_scale = 0; } else // We're not offering window scaling. // Ideally, we'd remember this fact so that // if the SYN/ACK *does* include window // scaling, we know it won't be negotiated. // But it's a pain to track that, and hard // to see how an adversarial responder could // use it to evade. Also, if we *do* want // to track it, we could do so using // connection_SYN_packet. endpoint->window_scale = 0; } else { endpoint->window_scale = scale; endpoint->window_seq = base_seq; endpoint->window_ack_seq = ack_seq; peer->window_seq = ack_seq; peer->window_ack_seq = base_seq; } if ( connection_SYN_packet ) { val_list* vl = new val_list; vl->append(BuildConnVal()); vl->append(SYN_vals); ConnectionEvent(connection_SYN_packet, vl); } else Unref(SYN_vals); // Passive fingerprinting. // // is_orig will be removed once we can do SYN-ACK fingerprinting. if ( OS_version_found && is_orig ) { AddrVal src_addr_val(orig_addr); if ( generate_OS_version_event->Size() == 0 || generate_OS_version_event->Lookup(&src_addr_val) ) { RecordVal* OS_val = BuildOSVal(is_orig, ip, tp, tcp_hdr_len); if ( OS_val ) { // found new OS version val_list* vl = new val_list; vl->append(BuildConnVal()); vl->append(new AddrVal(orig_addr)); vl->append(OS_val); ConnectionEvent(OS_version_found, vl); } } } } void TCP_Analyzer::ProcessFIN(double t, TCP_Endpoint* endpoint, int& seq_len, uint32 base_seq) { if ( endpoint->FIN_cnt == 0 ) { ++seq_len; // FIN consumes a byte of sequence space ++endpoint->FIN_cnt; // remember that we've seen a FIN } else if ( t < endpoint->last_time + tcp_storm_interarrival_thresh && ++endpoint->FIN_cnt == tcp_storm_thresh ) Weird("FIN_storm"); // Remember the relative seq in FIN_seq. endpoint->FIN_seq = base_seq - endpoint->StartSeq() + seq_len; } bool TCP_Analyzer::ProcessRST(double t, TCP_Endpoint* endpoint, const IP_Hdr* ip, uint32 base_seq, int len, int& seq_len) { if ( t < endpoint->last_time + tcp_storm_interarrival_thresh && ++endpoint->RST_cnt == tcp_storm_thresh ) Weird("RST_storm"); else if ( endpoint->RST_cnt == 0 ) ++endpoint->RST_cnt; // Remember we've seen a RST if ( len > 0 ) { // This now happens often enough that it's // not in the least interesting. // Weird("RST_with_data"); // Don't include the data in the computation of // the sequence space for this connection, as // it's not in fact part of the TCP stream. seq_len = 0; } PacketWithRST(); return true; } int TCP_Analyzer::ProcessFlags(double t, const IP_Hdr* ip, const struct tcphdr* tp, uint32 tcp_hdr_len, int len, int& seq_len, TCP_Endpoint* endpoint, TCP_Endpoint* peer, uint32 base_seq, uint32 ack_seq, const IPAddr& orig_addr, int is_orig, TCP_Flags flags) { if ( flags.SYN() ) ProcessSYN(ip, tp, tcp_hdr_len, seq_len, endpoint, peer, base_seq, ack_seq, orig_addr, is_orig, flags); if ( flags.FIN() ) ProcessFIN(t, endpoint, seq_len, base_seq); if ( flags.RST() && ! ProcessRST(t, endpoint, ip, base_seq, len, seq_len) ) return 0; if ( flags.ACK() ) ProcessACK(endpoint, peer, ack_seq, is_orig, flags); return 1; } void TCP_Analyzer::TransitionFromInactive(double t, TCP_Endpoint* endpoint, uint32 base_seq, uint32 last_seq, int SYN) { if ( SYN ) { // ## endpoint->AckSeq() = endpoint->start_seq = base_seq; endpoint->InitAckSeq(base_seq); endpoint->InitStartSeq(base_seq); } else { // This is a partial connection - set up the // initial sequence numbers as though we saw // a SYN, to keep the relative byte numbering // consistent. // ## endpoint->AckSeq() = endpoint->start_seq = base_seq - 1; endpoint->InitAckSeq(base_seq - 1); endpoint->InitStartSeq(base_seq - 1); } // ## endpoint->last_seq = last_seq; endpoint->InitLastSeq(last_seq); endpoint->start_time = t; } int TCP_Analyzer::UpdateLastSeq(TCP_Endpoint* endpoint, uint32 last_seq, TCP_Flags flags) { int delta_last = seq_delta(last_seq, endpoint->LastSeq()); if ( (flags.SYN() || flags.RST()) && (delta_last > TOO_LARGE_SEQ_DELTA || delta_last < -TOO_LARGE_SEQ_DELTA) ) // ### perhaps trust RST seq #'s if initial and not too // outlandish, but not if they're coming after the other // side has sent a FIN - trust the FIN ack instead ; else if ( flags.FIN() && endpoint->LastSeq() == endpoint->StartSeq() + 1 ) // Update last_seq based on the FIN even if delta_last < 0. // This is to accommodate > 2 GB connections for which // we've only seen the SYN and the FIN (hence the check // for last_seq == start_seq + 1). endpoint->UpdateLastSeq(last_seq); else if ( endpoint->state == TCP_ENDPOINT_RESET ) // don't trust any subsequent sequence numbers ; else if ( delta_last > 0 ) // ### check for large jumps here. // ## endpoint->last_seq = last_seq; endpoint->UpdateLastSeq(last_seq); else if ( delta_last <= 0 ) { // ### ++retransmit, unless this is a pure ack } return delta_last; } void TCP_Analyzer::ProcessACK(TCP_Endpoint* endpoint, TCP_Endpoint* peer, uint32 ack_seq, int is_orig, TCP_Flags flags) { if ( is_orig && ! seen_first_ACK && (endpoint->state == TCP_ENDPOINT_ESTABLISHED || endpoint->state == TCP_ENDPOINT_SYN_SENT) ) { seen_first_ACK = 1; Event(connection_first_ACK); } if ( peer->state == TCP_ENDPOINT_INACTIVE ) { if ( ! flags.SYN() && ! flags.FIN() && ! flags.RST() ) { if ( endpoint->state == TCP_ENDPOINT_SYN_SENT || endpoint->state == TCP_ENDPOINT_SYN_ACK_SENT || endpoint->state == TCP_ENDPOINT_ESTABLISHED ) { // We've already sent a SYN, but that // hasn't roused the other end, yet we're // ack'ing their data. if ( ! Conn()->DidWeird() ) Weird("possible_split_routing"); } } // Start the sequence numbering as if there was an initial // SYN, so the relative numbering of subsequent data packets // stays consistent. // ## peer->start_seq = peer->AckSeq() = peer->last_seq = // ## ack_seq - 1; peer->InitStartSeq(ack_seq - 1); peer->InitAckSeq(ack_seq - 1); peer->InitLastSeq(ack_seq - 1); } else if ( ! flags.RST() ) { // don't trust ack's in RST packets int delta_ack = seq_delta(ack_seq, peer->AckSeq()); if ( ack_seq == 0 && delta_ack > TOO_LARGE_SEQ_DELTA ) // More likely that this is a broken ack than a // large connection that happens to land on 0 in the // sequence space. ; else if ( delta_ack > 0 ) peer->UpdateAckSeq(ack_seq); } peer->AckReceived(ack_seq - peer->StartSeq()); } void TCP_Analyzer::UpdateInactiveState(double t, TCP_Endpoint* endpoint, TCP_Endpoint* peer, uint32 base_seq, uint32 ack_seq, int len, int is_orig, TCP_Flags flags, int& do_close, int& gen_event) { if ( flags.SYN() ) { if ( is_orig ) { if ( flags.ACK() ) { Weird("connection_originator_SYN_ack"); endpoint->SetState(TCP_ENDPOINT_SYN_ACK_SENT); } else endpoint->SetState(TCP_ENDPOINT_SYN_SENT); if ( connection_attempt ) ADD_ANALYZER_TIMER(&TCP_Analyzer::AttemptTimer, t + tcp_attempt_delay, 1, TIMER_TCP_ATTEMPT); } else { if ( flags.ACK() ) { if ( peer->state != TCP_ENDPOINT_INACTIVE && peer->state != TCP_ENDPOINT_PARTIAL && ! seq_between(ack_seq, peer->StartSeq(), peer->LastSeq()) ) Weird("bad_SYN_ack"); } else if ( peer->state == TCP_ENDPOINT_SYN_ACK_SENT && base_seq == endpoint->StartSeq() ) { // This is a SYN/SYN-ACK reversal, // per the discussion in IsReuse. // Flip the endpoints and establish // the connection. Conn()->FlipRoles(); peer->SetState(TCP_ENDPOINT_ESTABLISHED); } else Weird("simultaneous_open"); if ( peer->state == TCP_ENDPOINT_SYN_SENT ) peer->SetState(TCP_ENDPOINT_ESTABLISHED); else if ( peer->state == TCP_ENDPOINT_INACTIVE ) { // If we were to ignore SYNs and // only instantiate state on SYN // acks, then we'd do: // peer->SetState(TCP_ENDPOINT_ESTABLISHED); // here. Weird("unsolicited_SYN_response"); } endpoint->SetState(TCP_ENDPOINT_ESTABLISHED); if ( peer->state != TCP_ENDPOINT_PARTIAL ) { Event(connection_established); Conn()->EnableStatusUpdateTimer(); } } } if ( flags.FIN() ) { endpoint->SetState(TCP_ENDPOINT_CLOSED); do_close = gen_event = 1; if ( peer->state != TCP_ENDPOINT_PARTIAL && ! flags.SYN() ) Weird("spontaneous_FIN"); } if ( flags.RST() ) { endpoint->SetState(TCP_ENDPOINT_RESET); int is_reject = 0; if ( is_orig ) { // If our peer is established then we saw // a SYN-ack but not SYN - so a reverse // scan, and we should treat this as a // reject. if ( peer->state == TCP_ENDPOINT_ESTABLISHED ) is_reject = 1; } else if ( peer->state == TCP_ENDPOINT_SYN_SENT || peer->state == TCP_ENDPOINT_SYN_ACK_SENT ) // We're rejecting an initial SYN. is_reject = 1; do_close = 1; gen_event = ! is_reject; if ( is_reject ) Event(connection_rejected); else if ( peer->state == TCP_ENDPOINT_INACTIVE ) Weird("spontaneous_RST"); } if ( endpoint->state == TCP_ENDPOINT_INACTIVE ) { // No control flags to change the state. if ( ! is_orig && len == 0 && orig->state == TCP_ENDPOINT_SYN_SENT ) // Some eccentric TCP's will ack an initial // SYN prior to sending a SYN reply (hello, // ftp.microsoft.com). For those, don't // consider the ack as forming a partial // connection. ; else { endpoint->SetState(TCP_ENDPOINT_PARTIAL); Conn()->EnableStatusUpdateTimer(); if ( peer->state == TCP_ENDPOINT_PARTIAL ) // We've seen both sides of a partial // connection, report it. Event(partial_connection); } } } void TCP_Analyzer::UpdateSYN_SentState(double t, TCP_Endpoint* endpoint, TCP_Endpoint* peer, uint32 base_seq, uint32 last_seq, int len, int is_orig, TCP_Flags flags, int& do_close, int& gen_event) { if ( flags.SYN() ) { if ( is_orig ) { if ( flags.ACK() && ! flags.FIN() && ! flags.RST() && endpoint->state != TCP_ENDPOINT_SYN_ACK_SENT ) Weird("repeated_SYN_with_ack"); } else { if ( ! flags.ACK() && endpoint->state != TCP_ENDPOINT_SYN_SENT ) Weird("repeated_SYN_reply_wo_ack"); } if ( base_seq != endpoint->StartSeq() ) { Weird("SYN_seq_jump"); // ## endpoint->AckSeq() = endpoint->start_seq = base_seq; // ## endpoint->last_seq = last_seq; endpoint->InitStartSeq(base_seq); endpoint->InitAckSeq(base_seq); endpoint->InitLastSeq(last_seq); } } if ( flags.FIN() ) { if ( peer->state == TCP_ENDPOINT_INACTIVE || peer->state == TCP_ENDPOINT_SYN_SENT ) Weird("inappropriate_FIN"); endpoint->SetState(TCP_ENDPOINT_CLOSED); do_close = gen_event = 1; } if ( flags.RST() ) { endpoint->SetState(TCP_ENDPOINT_RESET); ConnectionReset(); do_close = 1; } else if ( len > 0 ) Weird("data_before_established"); } void TCP_Analyzer::UpdateEstablishedState(double t, TCP_Endpoint* endpoint, TCP_Endpoint* peer, uint32 base_seq, uint32 last_seq, int is_orig, TCP_Flags flags, int& do_close, int& gen_event) { if ( flags.SYN() ) { if ( endpoint->state == TCP_ENDPOINT_PARTIAL && peer->state == TCP_ENDPOINT_INACTIVE && ! flags.ACK() ) { Weird("SYN_after_partial"); endpoint->SetState(TCP_ENDPOINT_SYN_SENT); } if ( endpoint->Size() > 0 ) Weird("SYN_inside_connection"); if ( base_seq != endpoint->StartSeq() ) Weird("SYN_seq_jump"); // Make a guess that somehow the connection didn't // get established, and this SYN will be the // one that actually sets it up. // ## endpoint->AckSeq() = endpoint->start_seq = base_seq; // ## endpoint->last_seq = last_seq; endpoint->InitStartSeq(base_seq); endpoint->InitAckSeq(base_seq); endpoint->InitLastSeq(last_seq); } if ( flags.FIN() && ! flags.RST() ) // ### { // should check sequence/ack numbers here ### endpoint->SetState(TCP_ENDPOINT_CLOSED); if ( peer->state == TCP_ENDPOINT_RESET && peer->prev_state == TCP_ENDPOINT_CLOSED ) // The peer sent a FIN followed by a RST. // Turn it back into CLOSED state, because // this was actually normal termination. peer->SetState(TCP_ENDPOINT_CLOSED); do_close = gen_event = 1; } if ( flags.RST() ) { endpoint->SetState(TCP_ENDPOINT_RESET); do_close = 1; if ( peer->state != TCP_ENDPOINT_RESET || peer->prev_state != TCP_ENDPOINT_ESTABLISHED ) ConnectionReset(); } } void TCP_Analyzer::UpdateClosedState(double t, TCP_Endpoint* endpoint, int delta_last, TCP_Flags flags, int& do_close) { if ( flags.SYN() ) Weird("SYN_after_close"); if ( flags.FIN() && delta_last > 0 ) // Probably should also complain on FIN recision. // That requires an extra state variable to avoid // generating slews of weird's when a TCP gets // seriously confused (this from experience). Weird("FIN_advanced_last_seq"); // Previously, our state was CLOSED, since we sent a FIN. // If our peer was also closed, then don't change our state // now on a RST, since this connection has already seen a FIN // exchange. if ( flags.RST() && endpoint->peer->state != TCP_ENDPOINT_CLOSED ) { endpoint->SetState(TCP_ENDPOINT_RESET); if ( ! endpoint->did_close ) // RST after FIN. do_close = 1; if ( connection_reset ) ADD_ANALYZER_TIMER(&TCP_Analyzer::ResetTimer, t + tcp_reset_delay, 1, TIMER_TCP_RESET); } } void TCP_Analyzer::UpdateResetState(int len, TCP_Flags flags) { if ( flags.SYN() ) Weird("SYN_after_reset"); if ( flags.FIN() ) Weird("FIN_after_reset"); if ( len > 0 && ! flags.RST() ) Weird("data_after_reset"); } void TCP_Analyzer::UpdateStateMachine(double t, TCP_Endpoint* endpoint, TCP_Endpoint* peer, uint32 base_seq, uint32 ack_seq, uint32 last_seq, int len, int delta_last, int is_orig, TCP_Flags flags, int& do_close, int& gen_event) { do_close = 0; // whether to report the connection as closed gen_event = 0; // if so, whether to generate an event switch ( endpoint->state ) { case TCP_ENDPOINT_INACTIVE: UpdateInactiveState(t, endpoint, peer, base_seq, ack_seq, len, is_orig, flags, do_close, gen_event); break; case TCP_ENDPOINT_SYN_SENT: case TCP_ENDPOINT_SYN_ACK_SENT: UpdateSYN_SentState(t, endpoint, peer, base_seq, last_seq, len, is_orig, flags, do_close, gen_event); break; case TCP_ENDPOINT_ESTABLISHED: case TCP_ENDPOINT_PARTIAL: UpdateEstablishedState(t, endpoint, peer, base_seq, last_seq, is_orig, flags, do_close, gen_event); break; case TCP_ENDPOINT_CLOSED: UpdateClosedState(t, endpoint, delta_last, flags, do_close); break; case TCP_ENDPOINT_RESET: UpdateResetState(len, flags); break; } } void TCP_Analyzer::GeneratePacketEvent(TCP_Endpoint* endpoint, TCP_Endpoint* peer, uint32 base_seq, uint32 ack_seq, const u_char* data, int len, int caplen, int is_orig, TCP_Flags flags) { char tcp_flags[256]; int tcp_flag_len = 0; if ( flags.SYN() ) tcp_flags[tcp_flag_len++] = 'S'; if ( flags.FIN() ) tcp_flags[tcp_flag_len++] = 'F'; if ( flags.RST() ) tcp_flags[tcp_flag_len++] = 'R'; if ( flags.ACK() ) tcp_flags[tcp_flag_len++] = 'A'; if ( flags.PUSH() ) tcp_flags[tcp_flag_len++] = 'P'; if ( flags.URG() ) tcp_flags[tcp_flag_len++] = 'U'; tcp_flags[tcp_flag_len] = '\0'; val_list* vl = new val_list(); vl->append(BuildConnVal()); vl->append(new Val(is_orig, TYPE_BOOL)); vl->append(new StringVal(tcp_flags)); vl->append(new Val(base_seq - endpoint->StartSeq(), TYPE_COUNT)); vl->append(new Val(flags.ACK() ? ack_seq - peer->StartSeq() : 0, TYPE_COUNT)); vl->append(new Val(len, TYPE_COUNT)); // We need the min() here because Ethernet padding can lead to // caplen > len. vl->append(new StringVal(min(caplen, len), (const char*) data)); ConnectionEvent(tcp_packet, vl); } int TCP_Analyzer::DeliverData(double t, const u_char* data, int len, int caplen, const IP_Hdr* ip, const struct tcphdr* tp, TCP_Endpoint* endpoint, uint32 base_seq, int is_orig, TCP_Flags flags) { int data_seq = base_seq - endpoint->StartSeq(); if ( flags.SYN() ) ++data_seq; // skip over SYN octet int need_contents = endpoint->DataSent(t, data_seq, len, caplen, data, ip, tp); return need_contents; } void TCP_Analyzer::CheckRecording(int need_contents, TCP_Flags flags) { bool record_current_content = need_contents || Conn()->RecordContents(); bool record_current_packet = Conn()->RecordPackets() || flags.SYN() || flags.FIN() || flags.RST(); Conn()->SetRecordCurrentContent(record_current_content); Conn()->SetRecordCurrentPacket(record_current_packet); } void TCP_Analyzer::CheckPIA_FirstPacket(int is_orig, const IP_Hdr* ip) { if ( is_orig && ! (first_packet_seen & ORIG) ) { PIA_TCP* pia = static_cast(Conn()->GetPrimaryPIA()); if ( pia ) pia->FirstPacket(is_orig, ip); first_packet_seen |= ORIG; } if ( ! is_orig && ! (first_packet_seen & RESP) ) { PIA_TCP* pia = static_cast(Conn()->GetPrimaryPIA()); if ( pia ) pia->FirstPacket(is_orig, ip); first_packet_seen |= RESP; } } void TCP_Analyzer::DeliverPacket(int len, const u_char* data, bool is_orig, int seq, const IP_Hdr* ip, int caplen) { TransportLayerAnalyzer::DeliverPacket(len, data, orig, seq, ip, caplen); const struct tcphdr* tp = ExtractTCP_Header(data, len, caplen); if ( ! tp ) return; // We need the min() here because Ethernet frame padding can lead to // caplen > len. if ( packet_contents ) PacketContents(data, min(len, caplen)); TCP_Endpoint* endpoint = is_orig ? orig : resp; TCP_Endpoint* peer = endpoint->peer; if ( ! ValidateChecksum(tp, endpoint, len, caplen) ) return; TCP_Flags flags(tp); uint32 base_seq = ntohl(tp->th_seq); uint32 ack_seq = ntohl(tp->th_ack); CheckFlagCombos(flags, endpoint, base_seq, len, ntohs(tp->th_dport)); UpdateWindow(endpoint, ntohs(tp->th_win), base_seq, ack_seq, flags); double t = current_timestamp; if ( ! orig->did_close || ! resp->did_close ) Conn()->SetLastTime(t); const IPAddr orig_addr = Conn()->OrigAddr(); uint32 tcp_hdr_len = data - (const u_char*) tp; int seq_len = len; // length in terms of sequence space if ( ! ProcessFlags(t, ip, tp, tcp_hdr_len, len, seq_len, endpoint, peer, base_seq, ack_seq, orig_addr, is_orig, flags) ) return; uint32 last_seq = base_seq + seq_len; if ( endpoint->state == TCP_ENDPOINT_INACTIVE ) TransitionFromInactive(t, endpoint, base_seq, last_seq, flags.SYN()); int delta_last = UpdateLastSeq(endpoint, last_seq, flags); endpoint->last_time = t; int do_close; int gen_event; UpdateStateMachine(t, endpoint, peer, base_seq, ack_seq, last_seq, len, delta_last, is_orig, flags, do_close, gen_event); if ( tcp_packet ) GeneratePacketEvent(endpoint, peer, base_seq, ack_seq, data, len, caplen, is_orig, flags); if ( tcp_option && tcp_hdr_len > sizeof(*tp) && tcp_hdr_len <= uint32(caplen) ) ParseTCPOptions(tp, TCPOptionEvent, this, is_orig, 0); if ( DEBUG_tcp_data_sent ) { DEBUG_MSG("%.6f before DataSent: len=%d caplen=%d skip=%d\n", network_time, len, caplen, Skipping()); } int need_contents = 0; if ( len > 0 && (caplen >= len || packet_children.size()) && ! flags.RST() && ! Skipping() ) need_contents = DeliverData(t, data, len, caplen, ip, tp, endpoint, base_seq, is_orig, flags); endpoint->CheckEOF(); if ( do_close ) { // We need to postpone doing this until after we process // DataSent, so we don't generate a connection_finished event // until after data perhaps included with the FIN is processed. ConnectionClosed(endpoint, peer, gen_event); } CheckRecording(need_contents, flags); // Handle child_packet analyzers. Note: This happens *after* the // packet has been processed and the TCP state updated. LOOP_OVER_GIVEN_CHILDREN(i, packet_children) (*i)->NextPacket(len, data, is_orig, base_seq - endpoint->StartSeq(), ip, caplen); if ( ! reassembling ) ForwardPacket(len, data, is_orig, base_seq - endpoint->StartSeq(), ip, caplen); CheckPIA_FirstPacket(is_orig, ip); } void TCP_Analyzer::DeliverStream(int len, const u_char* data, bool orig) { Analyzer::DeliverStream(len, data, orig); } void TCP_Analyzer::Undelivered(int seq, int len, bool is_orig) { Analyzer::Undelivered(seq, len, orig); } void TCP_Analyzer::FlipRoles() { Analyzer::FlipRoles(); sessions->tcp_stats.FlipState(orig->state, resp->state); TCP_Endpoint* tmp_ep = resp; resp = orig; orig = tmp_ep; orig->is_orig = !orig->is_orig; resp->is_orig = !resp->is_orig; } void TCP_Analyzer::UpdateConnVal(RecordVal *conn_val) { int orig_endp_idx = connection_type->FieldOffset("orig"); int resp_endp_idx = connection_type->FieldOffset("resp"); RecordVal *orig_endp_val = conn_val->Lookup(orig_endp_idx)->AsRecordVal(); RecordVal *resp_endp_val = conn_val->Lookup(resp_endp_idx)->AsRecordVal(); orig_endp_val->Assign(0, new Val(orig->Size(), TYPE_COUNT)); orig_endp_val->Assign(1, new Val(int(orig->state), TYPE_COUNT)); resp_endp_val->Assign(0, new Val(resp->Size(), TYPE_COUNT)); resp_endp_val->Assign(1, new Val(int(resp->state), TYPE_COUNT)); // Call children's UpdateConnVal Analyzer::UpdateConnVal(conn_val); // Have to do packet_children ourselves. LOOP_OVER_GIVEN_CHILDREN(i, packet_children) (*i)->UpdateConnVal(conn_val); } Val* TCP_Analyzer::BuildSYNPacketVal(int is_orig, const IP_Hdr* ip, const struct tcphdr* tcp) { int winscale = -1; int MSS = 0; int SACK = 0; // Parse TCP options. u_char* options = (u_char*) tcp + sizeof(struct tcphdr); u_char* opt_end = (u_char*) tcp + tcp->th_off * 4; while ( options < opt_end ) { unsigned int opt = options[0]; if ( opt == TCPOPT_EOL ) // All done - could flag if more junk left over .... break; if ( opt == TCPOPT_NOP ) { ++options; continue; } if ( options + 1 >= opt_end ) // We've run off the end, no room for the length. break; unsigned int opt_len = options[1]; if ( options + opt_len > opt_end ) // No room for rest of option. break; if ( opt_len == 0 ) // Trashed length field. break; switch ( opt ) { case TCPOPT_SACK_PERMITTED: SACK = 1; break; case TCPOPT_MAXSEG: if ( opt_len < 4 ) break; // bad length MSS = (options[2] << 8) | options[3]; break; case 3: // TCPOPT_WSCALE if ( opt_len < 3 ) break; // bad length winscale = options[2]; break; default: // just skip over break; } options += opt_len; } RecordVal* v = new RecordVal(SYN_packet); v->Assign(0, new Val(is_orig, TYPE_BOOL)); v->Assign(1, new Val(int(ip->DF()), TYPE_BOOL)); v->Assign(2, new Val(int(ip->TTL()), TYPE_INT)); v->Assign(3, new Val((ip->TotalLen()), TYPE_INT)); v->Assign(4, new Val(ntohs(tcp->th_win), TYPE_INT)); v->Assign(5, new Val(winscale, TYPE_INT)); v->Assign(6, new Val(MSS, TYPE_INT)); v->Assign(7, new Val(SACK, TYPE_BOOL)); return v; } RecordVal* TCP_Analyzer::BuildOSVal(int is_orig, const IP_Hdr* ip, const struct tcphdr* tcp, uint32 tcp_hdr_len) { if ( ! is_orig ) // Later we might use SYN-ACK fingerprinting here. return 0; // Passive OS fingerprinting wants to know a lot about IP and TCP // options: how many options there are, and in which order. int winscale = 0; int MSS = 0; int optcount = 0; uint32 quirks = 0; uint32 tstamp = 0; uint8 op[MAXOPT]; if ( ip->HdrLen() > 20 ) quirks |= QUIRK_IPOPT; if ( ip->IP_ID() == 0 ) quirks |= QUIRK_ZEROID; if ( tcp->th_seq == 0 ) quirks |= QUIRK_SEQ0; if ( tcp->th_seq == tcp->th_ack ) quirks |= QUIRK_SEQEQ; if ( tcp->th_flags & ~(TH_SYN|TH_ACK|TH_RST|TH_ECE|TH_CWR) ) quirks |= QUIRK_FLAGS; if ( ip->TotalLen() - ip->HdrLen() - tcp_hdr_len > 0 ) quirks |= QUIRK_DATA; // SYN with data if ( tcp->th_ack ) quirks |= QUIRK_ACK; if ( tcp->th_urp ) quirks |= QUIRK_URG; if ( tcp->th_x2 ) quirks |= QUIRK_X2; // Parse TCP options. u_char* options = (u_char*) tcp + sizeof(struct tcphdr); u_char* opt_end = (u_char*) tcp + tcp_hdr_len; while ( options < opt_end ) { unsigned int opt = options[0]; if ( opt == TCPOPT_EOL ) { op[optcount++] = TCPOPT_EOL; if ( ++options < opt_end ) quirks |= QUIRK_PAST; // All done - could flag if more junk left over .... break; } if ( opt == TCPOPT_NOP ) { op[optcount++] = TCPOPT_NOP; ++options; continue; } if ( options + 1 >= opt_end ) { // We've run off the end, no room for the length. quirks |= QUIRK_BROKEN; break; } unsigned int opt_len = options[1]; if ( options + opt_len > opt_end ) { // No room for rest of the options. quirks |= QUIRK_BROKEN; break; } if ( opt_len == 0 ) // Trashed length field. break; switch ( opt ) { case TCPOPT_SACK_PERMITTED: // SACKOK LEN op[optcount] = TCPOPT_SACK_PERMITTED; break; case TCPOPT_MAXSEG: // MSS LEN D0 D1 if ( opt_len < 4 ) break; // bad length op[optcount] = TCPOPT_MAXSEG; MSS = (options[2] << 8) | options[3]; break; case TCPOPT_WINDOW: // WSCALE LEN D0 if ( opt_len < 3 ) break; // bad length op[optcount] = TCPOPT_WINDOW; winscale = options[2]; break; case TCPOPT_TIMESTAMP: // TSTAMP LEN T0 T1 T2 T3 A0 A1 A2 A3 if ( opt_len < 10 ) break; // bad length op[optcount] = TCPOPT_TIMESTAMP; tstamp = ntohl(extract_uint32(options + 2)); if ( extract_uint32(options + 6) ) quirks |= QUIRK_T2; break; default: // just skip over op[optcount]=opt; break; } if ( optcount < MAXOPT - 1 ) ++optcount; else quirks |= QUIRK_BROKEN; options += opt_len; } struct os_type os_from_print; int id = sessions->Get_OS_From_SYN(&os_from_print, uint16(ip->TotalLen()), uint8(ip->DF()), uint8(ip->TTL()), uint16(ntohs(tcp->th_win)), uint8(optcount), op, uint16(MSS), uint8(winscale), tstamp, quirks, uint8(tcp->th_flags & (TH_ECE|TH_CWR))); if ( sessions->CompareWithPreviousOSMatch(ip->SrcAddr(), id) ) { RecordVal* os = new RecordVal(OS_version); os->Assign(0, new StringVal(os_from_print.os)); if ( os_from_print.desc ) os->Assign(1, new StringVal(os_from_print.desc)); else os->Assign(1, new StringVal("")); os->Assign(2, new Val(os_from_print.dist, TYPE_COUNT)); os->Assign(3, new EnumVal(os_from_print.match, OS_version_inference)); return os; } return 0; } int TCP_Analyzer::ParseTCPOptions(const struct tcphdr* tcp, proc_tcp_option_t proc, TCP_Analyzer* analyzer, bool is_orig, void* cookie) { // Parse TCP options. const u_char* options = (const u_char*) tcp + sizeof(struct tcphdr); const u_char* opt_end = (const u_char*) tcp + tcp->th_off * 4; while ( options < opt_end ) { unsigned int opt = options[0]; unsigned int opt_len; if ( opt < 2 ) opt_len = 1; else if ( options + 1 >= opt_end ) // We've run off the end, no room for the length. return -1; else opt_len = options[1]; if ( opt_len == 0 ) return -1; // trashed length field if ( options + opt_len > opt_end ) // No room for rest of option. return -1; if ( (*proc)(opt, opt_len, options, analyzer, is_orig, cookie) == -1 ) return -1; options += opt_len; if ( opt == TCPOPT_EOL ) // All done - could flag if more junk left over .... break; } return 0; } int TCP_Analyzer::TCPOptionEvent(unsigned int opt, unsigned int optlen, const u_char* /* option */, TCP_Analyzer* analyzer, bool is_orig, void* cookie) { if ( tcp_option ) { val_list* vl = new val_list(); vl->append(analyzer->BuildConnVal()); vl->append(new Val(is_orig, TYPE_BOOL)); vl->append(new Val(opt, TYPE_COUNT)); vl->append(new Val(optlen, TYPE_COUNT)); analyzer->ConnectionEvent(tcp_option, vl); } return 0; } void TCP_Analyzer::AttemptTimer(double /* t */) { if ( ! is_active ) return; if ( (orig->state == TCP_ENDPOINT_SYN_SENT || orig->state == TCP_ENDPOINT_SYN_ACK_SENT) && resp->state == TCP_ENDPOINT_INACTIVE ) { Event(connection_attempt); is_active = 0; // All done with this connection. sessions->Remove(Conn()); } } void TCP_Analyzer::PartialCloseTimer(double /* t */) { if ( ! is_active ) return; if ( orig->state != TCP_ENDPOINT_INACTIVE && resp->state != TCP_ENDPOINT_INACTIVE && (! orig->did_close || ! resp->did_close) ) { if ( orig->state == TCP_ENDPOINT_RESET || resp->state == TCP_ENDPOINT_RESET ) // Presumably the RST is what caused the partial // close. Don't report it. return; Event(connection_partial_close); sessions->Remove(Conn()); } } void TCP_Analyzer::ExpireTimer(double t) { if ( ! is_active ) return; if ( Conn()->LastTime() + tcp_connection_linger < t ) { if ( orig->did_close || resp->did_close ) { // No activity for tcp_connection_linger seconds, and // at least one side has closed. See whether // connection has likely terminated. if ( (orig->did_close && resp->did_close) || (orig->state == TCP_ENDPOINT_RESET || resp->state == TCP_ENDPOINT_RESET) || (orig->state == TCP_ENDPOINT_INACTIVE || resp->state == TCP_ENDPOINT_INACTIVE) ) { // Either both closed, or one RST, // or half-closed. // The Timer has Ref()'d us and won't Unref() // us until we return, so it's safe to have // the session remove and Unref() us here. Event(connection_timeout); is_active = 0; sessions->Remove(Conn()); return; } } if ( resp->state == TCP_ENDPOINT_INACTIVE ) { if ( (orig->state == TCP_ENDPOINT_SYN_SENT || orig->state == TCP_ENDPOINT_SYN_ACK_SENT) ) { if ( ! connection_attempt ) { // Time out the connection attempt, // since the AttemptTimer isn't going // to do it for us, and we don't want // to clog the data structures with // old, failed attempts. Event(connection_timeout); is_active = 0; sessions->Remove(Conn()); return; } } else if ( orig->state == TCP_ENDPOINT_INACTIVE ) { // Nothing ever happened on this connection. // This can occur when we see a trashed // packet - it's discarded by NextPacket // before setting up an attempt timer, // so we need to clean it up here. Event(connection_timeout); sessions->Remove(Conn()); return; } } } // Connection still active, so reschedule timer. // ### if PQ_Element's were BroObj's, could just Ref the timer // and adjust its value here, instead of creating a new timer. ADD_ANALYZER_TIMER(&TCP_Analyzer::ExpireTimer, t + tcp_session_timer, 0, TIMER_TCP_EXPIRE); } void TCP_Analyzer::ResetTimer(double /* t */) { if ( ! is_active ) return; if ( ! BothClosed() ) ConnectionReset(); sessions->Remove(Conn()); } void TCP_Analyzer::DeleteTimer(double /* t */) { sessions->Remove(Conn()); } // The following need to be consistent with bro.init. #define CONTENTS_NONE 0 #define CONTENTS_ORIG 1 #define CONTENTS_RESP 2 #define CONTENTS_BOTH 3 void TCP_Analyzer::SetContentsFile(unsigned int direction, BroFile* f) { if ( direction == CONTENTS_NONE ) { orig->SetContentsFile(0); resp->SetContentsFile(0); } else { if ( direction == CONTENTS_ORIG || direction == CONTENTS_BOTH ) orig->SetContentsFile(f); if ( direction == CONTENTS_RESP || direction == CONTENTS_BOTH ) resp->SetContentsFile(f); } } BroFile* TCP_Analyzer::GetContentsFile(unsigned int direction) const { switch ( direction ) { case CONTENTS_NONE: return 0; case CONTENTS_ORIG: return orig->GetContentsFile(); case CONTENTS_RESP: return resp->GetContentsFile(); case CONTENTS_BOTH: if ( orig->GetContentsFile() != resp->GetContentsFile()) // This is an "error". return 0; else return orig->GetContentsFile(); default: break; } reporter->InternalError("inconsistency in TCP_Analyzer::GetContentsFile"); return 0; } void TCP_Analyzer::ConnectionClosed(TCP_Endpoint* endpoint, TCP_Endpoint* peer, int gen_event) { const analyzer_list& children(GetChildren()); LOOP_OVER_CONST_CHILDREN(i) // Using this type of cast here is nasty (will crash if // we inadvertantly have a child analyzer that's not a // TCP_ApplicationAnalyzer), but we have to ... static_cast (*i)->ConnectionClosed(endpoint, peer, gen_event); if ( DataPending(endpoint) ) { // Don't close out the connection yet, there's still data to // deliver. close_deferred = 1; if ( ! deferred_gen_event ) deferred_gen_event = gen_event; return; } close_deferred = 0; if ( endpoint->did_close ) return; // nothing new to report endpoint->did_close = 1; int close_complete = endpoint->state == TCP_ENDPOINT_RESET || peer->did_close || peer->state == TCP_ENDPOINT_INACTIVE; if ( DEBUG_tcp_connection_close ) { DEBUG_MSG("%.6f close_complete=%d tcp_close_delay=%f\n", network_time, close_complete, tcp_close_delay); } if ( close_complete ) { if ( endpoint->prev_state != TCP_ENDPOINT_INACTIVE || peer->state != TCP_ENDPOINT_INACTIVE ) { if ( deferred_gen_event ) { gen_event = 1; deferred_gen_event = 0; // clear flag } // We have something interesting to report. if ( gen_event ) { if ( peer->state == TCP_ENDPOINT_INACTIVE ) ConnectionFinished(1); else ConnectionFinished(0); } } CancelTimers(); // Note, even if tcp_close_delay is zero, we can't // simply do: // // sessions->Remove(this); // // here, because that would cause the object to be // deleted out from under us. if ( tcp_close_delay != 0.0 ) ADD_ANALYZER_TIMER(&TCP_Analyzer::ConnDeleteTimer, Conn()->LastTime() + tcp_close_delay, 0, TIMER_CONN_DELETE); else ADD_ANALYZER_TIMER(&TCP_Analyzer::DeleteTimer, Conn()->LastTime(), 0, TIMER_TCP_DELETE); } else { // We haven't yet seen a full close. if ( endpoint->prev_state == TCP_ENDPOINT_INACTIVE ) { // First time we've seen anything from this side. if ( connection_partial_close ) ADD_ANALYZER_TIMER(&TCP_Analyzer::PartialCloseTimer, Conn()->LastTime() + tcp_partial_close_delay, 0, TIMER_TCP_PARTIAL_CLOSE ); } else { // Create a timer to look for the other side closing, // too. ADD_ANALYZER_TIMER(&TCP_Analyzer::ExpireTimer, Conn()->LastTime() + tcp_session_timer, 0, TIMER_TCP_EXPIRE); } } } void TCP_Analyzer::ConnectionFinished(int half_finished) { const analyzer_list& children(GetChildren()); LOOP_OVER_CONST_CHILDREN(i) // Again, nasty - see TCP_Analyzer::ConnectionClosed. static_cast (*i)->ConnectionFinished(half_finished); if ( half_finished ) Event(connection_half_finished); else Event(connection_finished); is_active = 0; } void TCP_Analyzer::ConnectionReset() { Event(connection_reset); const analyzer_list& children(GetChildren()); LOOP_OVER_CONST_CHILDREN(i) static_cast(*i)->ConnectionReset(); is_active = 0; } bool TCP_Analyzer::HadGap(bool is_orig) const { TCP_Endpoint* endp = is_orig ? orig : resp; return endp && endp->HadGap(); } int TCP_Analyzer::DataPending(TCP_Endpoint* closing_endp) { if ( Skipping() ) return 0; return closing_endp->DataPending(); } void TCP_Analyzer::EndpointEOF(TCP_Reassembler* endp) { if ( connection_EOF ) { val_list* vl = new val_list(); vl->append(BuildConnVal()); vl->append(new Val(endp->IsOrig(), TYPE_BOOL)); ConnectionEvent(connection_EOF, vl); } const analyzer_list& children(GetChildren()); LOOP_OVER_CONST_CHILDREN(i) static_cast(*i)->EndpointEOF(endp->IsOrig()); if ( close_deferred ) { if ( DataPending(endp->Endpoint()) ) { if ( BothClosed() ) Weird("pending_data_when_closed"); // Defer further, until the other endpoint // EOF's, too. } ConnectionClosed(endp->Endpoint(), endp->Endpoint()->peer, deferred_gen_event); close_deferred = 0; } } void TCP_Analyzer::PacketWithRST() { const analyzer_list& children(GetChildren()); LOOP_OVER_CONST_CHILDREN(i) static_cast(*i)->PacketWithRST(); } bool TCP_Analyzer::IsReuse(double t, const u_char* pkt) { const struct tcphdr* tp = (const struct tcphdr*) pkt; if ( unsigned(tp->th_off) < sizeof(struct tcphdr) / 4 ) // Bogus header, don't interpret further. return false; TCP_Endpoint* conn_orig = orig; // Reuse only occurs on initial SYN's, except for half connections // it can occur on SYN-acks. if ( ! (tp->th_flags & TH_SYN) ) return false; if ( (tp->th_flags & TH_ACK) ) { if ( orig->state != TCP_ENDPOINT_INACTIVE ) // Not a half connection. return false; conn_orig = resp; } if ( ! IsClosed() ) { uint32 base_seq = ntohl(tp->th_seq); if ( base_seq == conn_orig->StartSeq() ) return false; if ( (tp->th_flags & TH_ACK) == 0 && conn_orig->state == TCP_ENDPOINT_SYN_ACK_SENT && resp->state == TCP_ENDPOINT_INACTIVE && base_seq == resp->StartSeq() ) { // This is an initial SYN with the right sequence // number, and the state is consistent with the // SYN & the SYN-ACK being flipped (e.g., due to // reading from two interfaces w/ interrupt // coalescence). Don't treat this as a reuse. // NextPacket() will flip set the connection // state correctly return false; } if ( conn_orig->state == TCP_ENDPOINT_SYN_SENT ) Weird("SYN_seq_jump"); else Weird("active_connection_reuse"); } else if ( (orig->IsActive() || resp->IsActive()) && orig->state != TCP_ENDPOINT_RESET && resp->state != TCP_ENDPOINT_RESET ) Weird("active_connection_reuse"); else if ( t - Conn()->LastTime() < tcp_connection_linger && orig->state != TCP_ENDPOINT_RESET && resp->state != TCP_ENDPOINT_RESET ) Weird("premature_connection_reuse"); return true; } void TCP_ApplicationAnalyzer::Init() { Analyzer::Init(); if ( Parent()->GetTag() == AnalyzerTag::TCP ) SetTCP(static_cast(Parent())); } void TCP_ApplicationAnalyzer::ProtocolViolation(const char* reason, const char* data, int len) { TCP_Analyzer* tcp = TCP(); if ( tcp && (tcp->IsPartial() || tcp->HadGap(false) || tcp->HadGap(true)) ) // Filter out incomplete connections. Parsing them is // too unreliable. return; Analyzer::ProtocolViolation(reason, data, len); } void TCP_ApplicationAnalyzer::DeliverPacket(int len, const u_char* data, bool is_orig, int seq, const IP_Hdr* ip, int caplen) { Analyzer::DeliverPacket(len, data, is_orig, seq, ip, caplen); DBG_LOG(DBG_DPD, "TCP_ApplicationAnalyzer ignoring DeliverPacket(%d, %s, %d, %p, %d) [%s%s]", len, is_orig ? "T" : "F", seq, ip, caplen, fmt_bytes((const char*) data, min(40, len)), len > 40 ? "..." : ""); } void TCP_ApplicationAnalyzer::SetEnv(bool /* is_orig */, char* name, char* val) { delete [] name; delete [] val; } void TCP_ApplicationAnalyzer::EndpointEOF(bool is_orig) { SupportAnalyzer* sa = is_orig ? orig_supporters : resp_supporters; for ( ; sa; sa = sa->Sibling() ) static_cast(sa)->EndpointEOF(is_orig); } void TCP_ApplicationAnalyzer::ConnectionClosed(TCP_Endpoint* endpoint, TCP_Endpoint* peer, int gen_event) { SupportAnalyzer* sa = endpoint->IsOrig() ? orig_supporters : resp_supporters; for ( ; sa; sa = sa->Sibling() ) static_cast(sa) ->ConnectionClosed(endpoint, peer, gen_event); } void TCP_ApplicationAnalyzer::ConnectionFinished(int half_finished) { for ( SupportAnalyzer* sa = orig_supporters; sa; sa = sa->Sibling() ) static_cast(sa) ->ConnectionFinished(half_finished); for ( SupportAnalyzer* sa = resp_supporters; sa; sa = sa->Sibling() ) static_cast(sa) ->ConnectionFinished(half_finished); } void TCP_ApplicationAnalyzer::ConnectionReset() { for ( SupportAnalyzer* sa = orig_supporters; sa; sa = sa->Sibling() ) static_cast(sa)->ConnectionReset(); for ( SupportAnalyzer* sa = resp_supporters; sa; sa = sa->Sibling() ) static_cast(sa)->ConnectionReset(); } void TCP_ApplicationAnalyzer::PacketWithRST() { for ( SupportAnalyzer* sa = orig_supporters; sa; sa = sa->Sibling() ) static_cast(sa)->PacketWithRST(); for ( SupportAnalyzer* sa = resp_supporters; sa; sa = sa->Sibling() ) static_cast(sa)->PacketWithRST(); } TCPStats_Endpoint::TCPStats_Endpoint(TCP_Endpoint* e) { endp = e; num_pkts = 0; num_rxmit = 0; num_rxmit_bytes = 0; num_in_order = 0; num_OO = 0; num_repl = 0; max_top_seq = 0; last_id = 0; endian_type = ENDIAN_UNKNOWN; } int endian_flip(int n) { return ((n & 0xff) << 8) | ((n & 0xff00) >> 8); } int TCPStats_Endpoint::DataSent(double /* t */, int seq, int len, int caplen, const u_char* /* data */, const IP_Hdr* ip, const struct tcphdr* /* tp */) { if ( ++num_pkts == 1 ) { // First packet. last_id = ntohs(ip->ID4()); return 0; } int id = ntohs(ip->ID4()); if ( id == last_id ) { ++num_repl; return 0; } short id_delta = id - last_id; short id_endian_delta = endian_flip(id) - endian_flip(last_id); int abs_id_delta = id_delta > 0 ? id_delta : -id_delta; int abs_id_endian_delta = id_endian_delta > 0 ? id_endian_delta : -id_endian_delta; int final_id_delta; if ( abs_id_delta < abs_id_endian_delta ) { // Consistent with big-endian. if ( endian_type == ENDIAN_UNKNOWN ) endian_type = ENDIAN_BIG; else if ( endian_type == ENDIAN_BIG ) ; else endian_type = ENDIAN_CONFUSED; final_id_delta = id_delta; } else { // Consistent with little-endian. if ( endian_type == ENDIAN_UNKNOWN ) endian_type = ENDIAN_LITTLE; else if ( endian_type == ENDIAN_LITTLE ) ; else endian_type = ENDIAN_CONFUSED; final_id_delta = id_endian_delta; } if ( final_id_delta < 0 && final_id_delta > -256 ) { ++num_OO; return 0; } last_id = id; ++num_in_order; int top_seq = seq + len; int data_in_flight = endp->LastSeq() - endp->AckSeq(); if ( data_in_flight < 0 ) data_in_flight = 0; int seq_delta = top_seq - max_top_seq; if ( seq_delta <= 0 ) { if ( ! BifConst::ignore_keep_alive_rexmit || len > 1 || data_in_flight > 0 ) { ++num_rxmit; num_rxmit_bytes += len; } DEBUG_MSG("%.6f rexmit %d + %d <= %d data_in_flight = %d\n", network_time, seq, len, max_top_seq, data_in_flight); if ( tcp_rexmit ) { val_list* vl = new val_list(); vl->append(endp->TCP()->BuildConnVal()); vl->append(new Val(endp->IsOrig(), TYPE_BOOL)); vl->append(new Val(seq, TYPE_COUNT)); vl->append(new Val(len, TYPE_COUNT)); vl->append(new Val(data_in_flight, TYPE_COUNT)); vl->append(new Val(endp->peer->window, TYPE_COUNT)); endp->TCP()->ConnectionEvent(tcp_rexmit, vl); } } else max_top_seq = top_seq; return 0; } RecordVal* TCPStats_Endpoint::BuildStats() { RecordVal* stats = new RecordVal(endpoint_stats); stats->Assign(0, new Val(num_pkts,TYPE_COUNT)); stats->Assign(1, new Val(num_rxmit,TYPE_COUNT)); stats->Assign(2, new Val(num_rxmit_bytes,TYPE_COUNT)); stats->Assign(3, new Val(num_in_order,TYPE_COUNT)); stats->Assign(4, new Val(num_OO,TYPE_COUNT)); stats->Assign(5, new Val(num_repl,TYPE_COUNT)); stats->Assign(6, new Val(endian_type,TYPE_COUNT)); return stats; } TCPStats_Analyzer::TCPStats_Analyzer(Connection* c) : TCP_ApplicationAnalyzer(AnalyzerTag::TCPStats, c) { } TCPStats_Analyzer::~TCPStats_Analyzer() { delete orig_stats; delete resp_stats; } void TCPStats_Analyzer::Init() { TCP_ApplicationAnalyzer::Init(); orig_stats = new TCPStats_Endpoint(TCP()->Orig()); resp_stats = new TCPStats_Endpoint(TCP()->Resp()); } void TCPStats_Analyzer::Done() { TCP_ApplicationAnalyzer::Done(); val_list* vl = new val_list; vl->append(BuildConnVal()); vl->append(orig_stats->BuildStats()); vl->append(resp_stats->BuildStats()); ConnectionEvent(conn_stats, vl); } void TCPStats_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_stats->DataSent(network_time, seq, len, caplen, data, ip, 0); else resp_stats->DataSent(network_time, seq, len, caplen, data, ip, 0); }