diff --git a/src/Conn.cc b/src/Conn.cc index d2d8d0f3da..5d55e47095 100644 --- a/src/Conn.cc +++ b/src/Conn.cc @@ -130,7 +130,7 @@ void Connection::Done() // somewhere, but it's session-related, so maybe not? if ( ConnTransport() == TRANSPORT_TCP ) { - auto ta = static_cast(adapter); + auto* ta = static_cast(adapter); assert(ta->IsAnalyzer("TCP")); analyzer::tcp::TCP_Endpoint* to = ta->Orig(); analyzer::tcp::TCP_Endpoint* tr = ta->Resp(); diff --git a/src/RuleCondition.cc b/src/RuleCondition.cc index b765cbaded..46f02946ca 100644 --- a/src/RuleCondition.cc +++ b/src/RuleCondition.cc @@ -30,7 +30,7 @@ bool RuleConditionTCPState::DoMatch(Rule* rule, RuleEndpointState* state, if ( ! adapter || ! adapter->IsAnalyzer("TCP") ) return false; - auto* ta = static_cast(adapter); + auto* ta = static_cast(adapter); if ( tcpstates & RULE_STATE_STATELESS ) return true; diff --git a/src/analyzer/Manager.cc b/src/analyzer/Manager.cc index 27b0621b31..8911455804 100644 --- a/src/analyzer/Manager.cc +++ b/src/analyzer/Manager.cc @@ -81,14 +81,6 @@ Manager::~Manager() } } -void Manager::InitPreScript() - { - // Cache these tags. - analyzer_connsize = GetComponentTag("CONNSIZE"); - analyzer_stepping = GetComponentTag("STEPPINGSTONE"); - analyzer_tcpstats = GetComponentTag("TCPSTATS"); - } - void Manager::InitPostScript() { const auto& id = detail::global_scope()->Find("Tunnel::vxlan_ports"); @@ -354,132 +346,6 @@ Manager::tag_set* Manager::LookupPort(TransportProto proto, uint32_t port, bool return l; } -bool Manager::BuildInitialAnalyzerTree(Connection* conn) - { - analyzer::tcp::TCP_Analyzer* tcp = nullptr; - packet_analysis::IP::SessionAdapter* root = nullptr; - analyzer::pia::PIA* pia = nullptr; - bool check_port = false; - - switch ( conn->ConnTransport() ) { - - case TRANSPORT_TCP: - root = tcp = new analyzer::tcp::TCP_Analyzer(conn); - pia = new analyzer::pia::PIA_TCP(conn); - check_port = true; - DBG_ANALYZER(conn, "activated TCP analyzer"); - break; - - default: - reporter->InternalWarning("unknown protocol can't build analyzer tree"); - return false; - } - - bool scheduled = ApplyScheduledAnalyzers(conn, false, root); - - // Hmm... Do we want *just* the expected analyzer, or all - // other potential analyzers as well? For now we only take - // the scheduled ones. - if ( ! scheduled ) - { // Let's see if it's a port we know. - if ( check_port && ! zeek::detail::dpd_ignore_ports ) - { - int resp_port = ntohs(conn->RespPort()); - tag_set* ports = LookupPort(conn->ConnTransport(), resp_port, false); - - if ( ports ) - { - for ( tag_set::const_iterator j = ports->begin(); j != ports->end(); ++j ) - { - Analyzer* analyzer = analyzer_mgr->InstantiateAnalyzer(*j, conn); - - if ( ! analyzer ) - continue; - - root->AddChildAnalyzer(analyzer, false); - DBG_ANALYZER_ARGS(conn, "activated %s analyzer due to port %d", - analyzer_mgr->GetComponentName(*j).c_str(), resp_port); - } - } - } - } - - if ( tcp ) - { - // We have to decide whether to reassamble the stream. - // We turn it on right away if we already have an app-layer - // analyzer, reassemble_first_packets is true, or the user - // asks us to do so. In all other cases, reassembly may - // be turned on later by the TCP PIA. - - bool reass = root->GetChildren().size() || - zeek::detail::dpd_reassemble_first_packets || - zeek::detail::tcp_content_deliver_all_orig || - zeek::detail::tcp_content_deliver_all_resp; - - if ( tcp_contents && ! reass ) - { - static auto tcp_content_delivery_ports_orig = id::find_val("tcp_content_delivery_ports_orig"); - static auto tcp_content_delivery_ports_resp = id::find_val("tcp_content_delivery_ports_resp"); - const auto& dport = val_mgr->Port(ntohs(conn->RespPort()), TRANSPORT_TCP); - - if ( ! reass ) - reass = (bool)tcp_content_delivery_ports_orig->FindOrDefault(dport); - - if ( ! reass ) - reass = (bool)tcp_content_delivery_ports_resp->FindOrDefault(dport); - } - - if ( reass ) - tcp->EnableReassembly(); - - if ( IsEnabled(analyzer_stepping) ) - { - // Add a SteppingStone analyzer if requested. The port - // should really not be hardcoded here, but as it can - // handle non-reassembled data, it doesn't really fit into - // our general framing ... Better would be to turn it - // on *after* we discover we have interactive traffic. - uint16_t resp_port = ntohs(conn->RespPort()); - if ( resp_port == 22 || resp_port == 23 || resp_port == 513 ) - { - static auto stp_skip_src = id::find_val("stp_skip_src"); - auto src = make_intrusive(conn->OrigAddr()); - - if ( ! stp_skip_src->FindOrDefault(src) ) - tcp->AddChildAnalyzer(new analyzer::stepping_stone::SteppingStone_Analyzer(conn), false); - } - } - - if ( IsEnabled(analyzer_tcpstats) ) - // Add TCPStats analyzer. This needs to see packets so - // we cannot add it as a normal child. - tcp->AddChildPacketAnalyzer(new analyzer::tcp::TCPStats_Analyzer(conn)); - - if ( IsEnabled(analyzer_connsize) ) - // Add ConnSize analyzer. Needs to see packets, not stream. - tcp->AddChildPacketAnalyzer(new analyzer::conn_size::ConnSize_Analyzer(conn)); - } - - else - { - if ( IsEnabled(analyzer_connsize) ) - // Add ConnSize analyzer. Needs to see packets, not stream. - root->AddChildAnalyzer(new analyzer::conn_size::ConnSize_Analyzer(conn)); - } - - if ( pia ) - root->AddChildAnalyzer(pia->AsAnalyzer()); - - conn->SetSessionAdapter(root, pia); - root->Init(); - root->InitChildren(); - - PLUGIN_HOOK_VOID(HOOK_SETUP_ANALYZER_TREE, HookSetupAnalyzerTree(conn)); - - return true; - } - void Manager::ExpireScheduledAnalyzers() { if ( ! run_state::network_time ) diff --git a/src/analyzer/Manager.h b/src/analyzer/Manager.h index 65c38a6fbf..067d2d4a53 100644 --- a/src/analyzer/Manager.h +++ b/src/analyzer/Manager.h @@ -67,12 +67,6 @@ public: */ ~Manager(); - /** - * First-stage initializion of the manager. This is called early on - * during Bro's initialization, before any scripts are processed. - */ - void InitPreScript(); - /** * Second-stage initialization of the manager. This is called late * during Bro's initialization after any scripts are processed. @@ -246,17 +240,6 @@ public: */ Analyzer* InstantiateAnalyzer(const char* name, Connection* c); - /** - * Given the first packet of a connection, builds its initial - * analyzer tree. - * - * @param conn The connection to add the initial set of analyzers to. - * - * @return False if the tree cannot be built; that's usually an - * internal error. - */ - bool BuildInitialAnalyzerTree(Connection* conn); - /** * Schedules a particular analyzer for an upcoming connection. Once * the connection is seen, BuildInitAnalyzerTree() will add the @@ -366,10 +349,6 @@ private: analyzer_map_by_port analyzers_by_port_tcp; analyzer_map_by_port analyzers_by_port_udp; - Tag analyzer_connsize; - Tag analyzer_stepping; - Tag analyzer_tcpstats; - //// Data structures to track analyzed scheduled for future connections. // The index for a scheduled connection. diff --git a/src/analyzer/protocol/login/RSH.cc b/src/analyzer/protocol/login/RSH.cc index 094930480a..621a45672b 100644 --- a/src/analyzer/protocol/login/RSH.cc +++ b/src/analyzer/protocol/login/RSH.cc @@ -35,7 +35,7 @@ Contents_Rsh_Analyzer::~Contents_Rsh_Analyzer() void Contents_Rsh_Analyzer::DoDeliver(int len, const u_char* data) { - analyzer::tcp::TCP_Analyzer* tcp = static_cast(Parent())->TCP(); + auto* tcp = static_cast(Parent())->TCP(); assert(tcp); int endp_state = IsOrig() ? tcp->OrigState() : tcp->RespState(); diff --git a/src/analyzer/protocol/pia/PIA.cc b/src/analyzer/protocol/pia/PIA.cc index daf156dae3..c5343902f0 100644 --- a/src/analyzer/protocol/pia/PIA.cc +++ b/src/analyzer/protocol/pia/PIA.cc @@ -199,7 +199,7 @@ void PIA_TCP::Init() if ( Parent()->IsAnalyzer("TCP") ) { - tcp::TCP_Analyzer* tcp = static_cast(Parent()); + auto* tcp = static_cast(Parent()); SetTCP(tcp); tcp->SetPIA(this); } @@ -375,15 +375,13 @@ void PIA_TCP::ActivateAnalyzer(analyzer::Tag tag, const zeek::detail::Rule* rule return; } - tcp::TCP_Analyzer* tcp = (tcp::TCP_Analyzer*) Parent(); + auto* tcp = static_cast(Parent()); - tcp::TCP_Reassembler* reass_orig = - new tcp::TCP_Reassembler(this, tcp, tcp::TCP_Reassembler::Direct, - tcp->Orig()); + auto* reass_orig = new tcp::TCP_Reassembler(this, tcp, tcp::TCP_Reassembler::Direct, + tcp->Orig()); - tcp::TCP_Reassembler* reass_resp = - new tcp::TCP_Reassembler(this, tcp, tcp::TCP_Reassembler::Direct, - tcp->Resp()); + auto* reass_resp = new tcp::TCP_Reassembler(this, tcp, tcp::TCP_Reassembler::Direct, + tcp->Resp()); uint64_t orig_seq = 0; uint64_t resp_seq = 0; diff --git a/src/analyzer/protocol/rpc/RPC.cc b/src/analyzer/protocol/rpc/RPC.cc index 1d4d40d4ee..34c0d39268 100644 --- a/src/analyzer/protocol/rpc/RPC.cc +++ b/src/analyzer/protocol/rpc/RPC.cc @@ -456,8 +456,7 @@ bool Contents_RPC::CheckResync(int& len, const u_char*& data, bool orig) // is fully established we are in sync (since it's the first chunk // of data after the SYN if its not established we need to // resync. - analyzer::tcp::TCP_Analyzer* tcp = - static_cast(Parent())->TCP(); + auto* tcp = static_cast(Parent())->TCP(); assert(tcp); if ( (IsOrig() ? tcp->OrigState() : tcp->RespState()) != diff --git a/src/analyzer/protocol/tcp/ContentLine.cc b/src/analyzer/protocol/tcp/ContentLine.cc index 8de2d50c19..97d9a33e62 100644 --- a/src/analyzer/protocol/tcp/ContentLine.cc +++ b/src/analyzer/protocol/tcp/ContentLine.cc @@ -85,8 +85,7 @@ void ContentLine_Analyzer::DeliverStream(int len, const u_char* data, if ( skip_partial ) { - TCP_Analyzer* tcp = - static_cast(Parent())->TCP(); + auto* tcp = static_cast(Parent())->TCP(); if ( tcp && tcp->IsPartial() ) return; @@ -300,8 +299,7 @@ void ContentLine_Analyzer::CheckNUL() // had been an initial SYN, so we check for whether // the connection has at most two bytes so far. - TCP_Analyzer* tcp = - static_cast(Parent())->TCP(); + auto* tcp = static_cast(Parent())->TCP(); if ( tcp ) { diff --git a/src/analyzer/protocol/tcp/Plugin.cc b/src/analyzer/protocol/tcp/Plugin.cc index 8d4cccceb4..0b2f7e2445 100644 --- a/src/analyzer/protocol/tcp/Plugin.cc +++ b/src/analyzer/protocol/tcp/Plugin.cc @@ -10,7 +10,6 @@ class Plugin : public zeek::plugin::Plugin { public: zeek::plugin::Configuration Configure() override { - AddComponent(new zeek::analyzer::Component("TCP", zeek::analyzer::tcp::TCP_Analyzer::Instantiate)); AddComponent(new zeek::analyzer::Component("TCPStats", zeek::analyzer::tcp::TCPStats_Analyzer::Instantiate)); AddComponent(new zeek::analyzer::Component("CONTENTLINE", nullptr)); AddComponent(new zeek::analyzer::Component("Contents", nullptr)); diff --git a/src/analyzer/protocol/tcp/TCP.cc b/src/analyzer/protocol/tcp/TCP.cc index 8dd7479df1..45b0465a24 100644 --- a/src/analyzer/protocol/tcp/TCP.cc +++ b/src/analyzer/protocol/tcp/TCP.cc @@ -19,1858 +19,13 @@ #include "zeek/analyzer/protocol/tcp/events.bif.h" #include "zeek/analyzer/protocol/tcp/types.bif.h" -namespace { // local namespace - const bool DEBUG_tcp_data_sent = false; - const bool DEBUG_tcp_connection_close = false; -} - namespace zeek::analyzer::tcp { -// 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; - -static RecordVal* build_syn_packet_val(bool is_orig, const IP_Hdr* ip, - const struct tcphdr* tcp) +packet_analysis::TCP::TCPSessionAdapter* TCP_ApplicationAnalyzer::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; - } - - static auto SYN_packet = id::find_type("SYN_packet"); - auto* v = new RecordVal(SYN_packet); - - v->Assign(0, is_orig); - v->Assign(1, static_cast(ip->DF())); - v->Assign(2, ip->TTL()); - v->Assign(3, ip->TotalLen()); - v->Assign(4, ntohs(tcp->th_win)); - v->Assign(5, winscale); - v->Assign(6, MSS); - v->Assign(7, static_cast(SACK)); - - return v; - } - - -TCP_Analyzer::TCP_Analyzer(Connection* conn) - : packet_analysis::IP::SessionAdapter("TCP", conn) - { - // Set a timer to eventually time out this connection. - ADD_ANALYZER_TIMER(&TCP_Analyzer::ExpireTimer, - run_state::network_time + detail::tcp_SYN_timeout, false, - detail::TIMER_TCP_EXPIRE); - - deferred_gen_event = close_deferred = 0; - - seen_first_ACK = 0; - is_active = 1; - finished = 0; - reassembling = 0; - first_packet_seen = 0; - is_partial = 0; - - orig = new TCP_Endpoint(this, true); - resp = new TCP_Endpoint(this, false); - - 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 ( run_state::terminating && connection_pending && is_active && ! BothClosed() ) - Event(connection_pending); - - LOOP_OVER_GIVEN_CHILDREN(i, packet_children) - (*i)->Done(); - - orig->Done(); - resp->Done(); - - finished = 1; - } - -analyzer::Analyzer* TCP_Analyzer::FindChild(analyzer::ID arg_id) - { - analyzer::Analyzer* child = packet_analysis::IP::SessionAdapter::FindChild(arg_id); - - if ( child ) - return child; - - LOOP_OVER_GIVEN_CHILDREN(i, packet_children) - { - analyzer::Analyzer* child = (*i)->FindChild(arg_id); - if ( child ) - return child; - } - - return nullptr; - } - -analyzer::Analyzer* TCP_Analyzer::FindChild(analyzer::Tag arg_tag) - { - analyzer::Analyzer* child = packet_analysis::IP::SessionAdapter::FindChild(arg_tag); - - if ( child ) - return child; - - LOOP_OVER_GIVEN_CHILDREN(i, packet_children) - { - analyzer::Analyzer* child = (*i)->FindChild(arg_tag); - if ( child ) - return child; - } - - return nullptr; - } - -bool TCP_Analyzer::RemoveChildAnalyzer(analyzer::ID id) - { - auto rval = packet_analysis::IP::SessionAdapter::RemoveChildAnalyzer(id); - - if ( rval ) - return rval; - - return RemoveChild(packet_children, id); - } - -void TCP_Analyzer::EnableReassembly() - { - SetReassembler(new TCP_Reassembler(this, this, - TCP_Reassembler::Forward, orig), - new TCP_Reassembler(this, this, - TCP_Reassembler::Forward, resp)); - } - -void TCP_Analyzer::SetReassembler(TCP_Reassembler* rorig, - TCP_Reassembler* rresp) - { - orig->AddReassembler(rorig); - rorig->SetDstAnalyzer(this); - resp->AddReassembler(rresp); - rresp->SetDstAnalyzer(this); - - if ( new_connection_contents && reassembling == 0 ) - Event(new_connection_contents); - - reassembling = 1; - } - -const struct tcphdr* TCP_Analyzer::ExtractTCP_Header(const u_char*& data, - int& len, int& caplen) - { - const struct tcphdr* tp = (const struct tcphdr*) data; - uint32_t tcp_hdr_len = tp->th_off * 4; - - if ( tcp_hdr_len < sizeof(struct tcphdr) ) - { - Weird("bad_TCP_header_len"); - return nullptr; - } - - if ( tcp_hdr_len > uint32_t(len) || - tcp_hdr_len > uint32_t(caplen) ) - { - // This can happen even with the above test, due to TCP - // options. - Weird("truncated_header"); - return nullptr; - } - - len -= tcp_hdr_len; // remove TCP header - caplen -= tcp_hdr_len; - data += tcp_hdr_len; - - return tp; - } - -bool TCP_Analyzer::ValidateChecksum(const IP_Hdr* ip, const struct tcphdr* tp, - TCP_Endpoint* endpoint, int len, int caplen) - { - if ( ! run_state::current_pkt->l3_checksummed && - ! detail::ignore_checksums && - ! zeek::id::find_val("ignore_checksums_nets")->Contains(ip->IPHeaderSrcAddr()) && - caplen >= len && ! endpoint->ValidChecksum(tp, len, ip->IP4_Hdr()) ) - { - Weird("bad_TCP_checksum"); - endpoint->ChecksumError(); - return false; - } - else - return true; - } - -void TCP_Analyzer::SetPartialStatus(TCP_Flags flags, bool is_orig) - { - if ( is_orig ) - { - if ( ! (first_packet_seen & ORIG) ) - is_partial = ! flags.SYN() || flags.ACK(); - } - else - { - if ( ! (first_packet_seen & RESP) && ! is_partial ) - is_partial = ! flags.SYN(); - } - } - -static void update_history(TCP_Flags flags, TCP_Endpoint* endpoint, - uint64_t rel_seq, int len) - { - 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) && - rel_seq != endpoint->hist_last_SYN ) - endpoint->AddHistory(code); - - endpoint->hist_last_SYN = rel_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') && - rel_seq + len != endpoint->hist_last_FIN ) - endpoint->AddHistory('F'); - - endpoint->hist_last_FIN = rel_seq + len; - } - - if ( flags.RST() ) - { - if ( endpoint->CheckHistory(HIST_RST_PKT, 'R') && - rel_seq != endpoint->hist_last_RST ) - endpoint->AddHistory('R'); - - endpoint->hist_last_RST = rel_seq; - } - } - - else - { // bits_set == 0 - if ( len ) - endpoint->CheckHistory(HIST_DATA_PKT, 'D'); - - else if ( flags.ACK() ) - endpoint->CheckHistory(HIST_ACK_PKT, 'A'); - } - } - -static void init_window(TCP_Endpoint* endpoint, TCP_Endpoint* peer, - TCP_Flags flags, bro_int_t scale, uint32_t base_seq, - uint32_t ack_seq) - { - // ### In the following, we could be fooled by an - // inconsistent SYN retransmission. Where's a normalizer - // when you need one? - - 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; - } - } - -static void update_window(TCP_Endpoint* endpoint, unsigned int window, - uint32_t base_seq, uint32_t ack_seq, TCP_Flags flags) - { - // Note, applying scaling here would be incorrect for an initial SYN, - // whose window value is always unscaled. However, we don't - // check the window's value for recision in that case anyway, so - // no-harm-no-foul. - int scale = endpoint->window_scale; - window = window << scale; - - // Zero windows are boring if either (1) they come with a RST packet - // or after a RST packet, or (2) they come after the peer has sent - // a FIN (because there's no relevant window at that point anyway). - // (They're also boring if they come after the peer has sent a RST, - // but *nothing* should be sent in response to a RST, so we ignore - // that case.) - // - // However, they *are* potentially interesting if sent by an - // endpoint that's already sent a FIN, since that FIN meant "I'm - // not going to send any more", but doesn't mean "I won't receive - // any more". - if ( window == 0 && ! flags.RST() && - endpoint->peer->state != TCP_ENDPOINT_CLOSED && - endpoint->state != TCP_ENDPOINT_RESET ) - endpoint->ZeroWindow(); - - // Don't analyze window values off of SYNs, they're sometimes - // immediately rescinded. Also don't do so 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. - - if ( ! flags.SYN() && ! flags.FIN() && ! flags.RST() && - endpoint->state != TCP_ENDPOINT_CLOSED && - endpoint->state != TCP_ENDPOINT_RESET ) - { - // ### Decide whether to accept new window based on Active - // Mapping policy. - if ( seq_delta(base_seq, endpoint->window_seq) >= 0 && - seq_delta(ack_seq, endpoint->window_ack_seq) >= 0 ) - { - uint32_t new_edge = ack_seq + window; - uint32_t old_edge = endpoint->window_ack_seq + endpoint->window; - int32_t advance = seq_delta(new_edge, old_edge); - - if ( advance < 0 ) - { - // An apparent window recision. Allow a - // bit of slop for window scaling. This is - // because sometimes there will be an - // apparent recision due to the granularity - // of the scaling. - if ( (-advance) >= (1 << scale) ) - endpoint->Conn()->Weird("window_recision"); - } - - endpoint->window = window; - endpoint->window_ack_seq = ack_seq; - endpoint->window_seq = base_seq; - } - } - } - -void TCP_Analyzer::SynWeirds(TCP_Flags flags, TCP_Endpoint* endpoint, int data_len) const - { - if ( flags.RST() ) - endpoint->Conn()->Weird("TCP_christmas", "", GetAnalyzerName()); - - if ( flags.URG() ) - endpoint->Conn()->Weird("baroque_SYN", "", GetAnalyzerName()); - - if ( data_len > 0 ) - // Not technically wrong according to RFC 793, but the other side - // would be forced to buffer data until the handshake succeeds, and - // that could be bad in some cases, e.g. SYN floods. - // T/TCP definitely complicates this. - endpoint->Conn()->Weird("SYN_with_data", "", GetAnalyzerName()); - } - -void TCP_Analyzer::UpdateInactiveState(double t, - TCP_Endpoint* endpoint, TCP_Endpoint* peer, - uint32_t base_seq, uint32_t ack_seq, - int len, bool is_orig, TCP_Flags flags, - bool& do_close, bool& 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 ( zeek::detail::tcp_attempt_delay ) - ADD_ANALYZER_TIMER(&TCP_Analyzer::AttemptTimer, - t + detail::tcp_attempt_delay, true, - detail::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. - is_partial = 0; - 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 = true; - if ( peer->state != TCP_ENDPOINT_PARTIAL && ! flags.SYN() ) - Weird("spontaneous_FIN"); - } - - if ( flags.RST() ) - { - endpoint->SetState(TCP_ENDPOINT_RESET); - - bool is_reject = false; - - 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 = true; - } - - else if ( peer->state == TCP_ENDPOINT_SYN_SENT || - peer->state == TCP_ENDPOINT_SYN_ACK_SENT ) - // We're rejecting an initial SYN. - is_reject = true; - - do_close = true; - 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 if ( flags.ACK() && peer->state == TCP_ENDPOINT_ESTABLISHED ) - { - // No SYN packet from originator but SYN/ACK from - // responder, and now a pure ACK. Problably means we - // just missed that initial SYN. Let's not treat it - // as partial and instead establish the connection. - endpoint->SetState(TCP_ENDPOINT_ESTABLISHED); - is_partial = 0; - } - - 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(TCP_Endpoint* endpoint, TCP_Endpoint* peer, - int len, bool is_orig, TCP_Flags flags, - bool& do_close, bool& 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 ( 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 = true; - } - - if ( flags.RST() ) - { - endpoint->SetState(TCP_ENDPOINT_RESET); - ConnectionReset(); - do_close = true; - } - - else if ( len > 0 ) - Weird("data_before_established"); - } - -void TCP_Analyzer::UpdateEstablishedState( - TCP_Endpoint* endpoint, TCP_Endpoint* peer, - TCP_Flags flags, bool& do_close, bool& 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 ( 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 = true; - } - - if ( flags.RST() ) - { - endpoint->SetState(TCP_ENDPOINT_RESET); - do_close = true; - - if ( peer->state != TCP_ENDPOINT_RESET || - peer->prev_state != TCP_ENDPOINT_ESTABLISHED ) - ConnectionReset(); - } - } - -void TCP_Analyzer::UpdateClosedState(double t, TCP_Endpoint* endpoint, - int32_t delta_last, TCP_Flags flags, bool& 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 = true; - - if ( connection_reset ) - ADD_ANALYZER_TIMER(&TCP_Analyzer::ResetTimer, - t + zeek::detail::tcp_reset_delay, true, - zeek::detail::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_t base_seq, uint32_t ack_seq, - int len, int32_t delta_last, bool is_orig, TCP_Flags flags, - bool& do_close, bool& gen_event) - { - do_close = false; // whether to report the connection as closed - gen_event = false; // 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(endpoint, peer, len, is_orig, flags, do_close, - gen_event); - break; - - case TCP_ENDPOINT_ESTABLISHED: - case TCP_ENDPOINT_PARTIAL: - UpdateEstablishedState(endpoint, peer, 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( - uint64_t rel_seq, uint64_t rel_ack, - const u_char* data, int len, int caplen, - bool is_orig, TCP_Flags flags) - { - EnqueueConnEvent(tcp_packet, - ConnVal(), - val_mgr->Bool(is_orig), - make_intrusive(flags.AsString()), - val_mgr->Count(rel_seq), - val_mgr->Count(flags.ACK() ? rel_ack : 0), - val_mgr->Count(len), - // We need the min() here because Ethernet padding can lead to - // caplen > len. - make_intrusive(std::min(caplen, len), (const char*) data) - ); - } - -bool TCP_Analyzer::DeliverData(double t, const u_char* data, int len, int caplen, - const IP_Hdr* ip, const struct tcphdr* tp, - TCP_Endpoint* endpoint, uint64_t rel_data_seq, - bool is_orig, TCP_Flags flags) - { - return endpoint->DataSent(t, rel_data_seq, len, caplen, data, ip, tp); - } - -void TCP_Analyzer::CheckRecording(bool 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(bool is_orig, const IP_Hdr* ip) - { - if ( is_orig && ! (first_packet_seen & ORIG) ) - { - auto* pia = static_cast(Conn()->GetPrimaryPIA()); - if ( pia ) - pia->FirstPacket(is_orig, ip); - first_packet_seen |= ORIG; - } - - if ( ! is_orig && ! (first_packet_seen & RESP) ) - { - auto* pia = static_cast(Conn()->GetPrimaryPIA()); - if ( pia ) - pia->FirstPacket(is_orig, ip); - first_packet_seen |= RESP; - } - } - -uint64_t TCP_Analyzer::get_relative_seq(const TCP_Endpoint* endpoint, - uint32_t cur_base, uint32_t last, - uint32_t wraps, bool* underflow) - { - int32_t delta = seq_delta(cur_base, last); - - if ( delta < 0 ) - { - if ( wraps && cur_base > last ) - // Seems to be a part of a previous 32-bit sequence space. - --wraps; - } - - else if ( delta > 0 ) - { - if ( cur_base < last ) - // The sequence space wrapped around. - ++wraps; - } - - if ( wraps == 0 ) - { - delta = seq_delta(cur_base, endpoint->StartSeq()); - - if ( underflow && delta < 0 ) - *underflow = true; - - return delta; - } - - return endpoint->ToRelativeSeqSpace(cur_base, wraps); - } - -int TCP_Analyzer::get_segment_len(int payload_len, TCP_Flags flags) - { - int seg_len = payload_len; - - if ( flags.SYN() ) - // SYN consumes a byte of sequence space. - ++seg_len; - - if ( flags.FIN() ) - // FIN consumes a bytes of sequence space. - ++seg_len; - - if ( flags.RST() ) - // 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. - seg_len -= payload_len; - - return seg_len; - } - -static void init_endpoint(TCP_Endpoint* endpoint, TCP_Flags flags, - uint32_t first_seg_seq, uint32_t last_seq, double t) - { - switch ( endpoint->state ) { - case TCP_ENDPOINT_INACTIVE: - if ( flags.SYN() ) - { - endpoint->InitAckSeq(first_seg_seq); - endpoint->InitStartSeq(first_seg_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->InitAckSeq(first_seg_seq - 1); - endpoint->InitStartSeq(first_seg_seq - 1); - // But ensure first packet is not marked duplicate - last_seq = first_seg_seq; - } - - endpoint->InitLastSeq(last_seq); - endpoint->start_time = t; - break; - - case TCP_ENDPOINT_SYN_SENT: - case TCP_ENDPOINT_SYN_ACK_SENT: - if ( flags.SYN() && first_seg_seq != endpoint->StartSeq() ) - { - endpoint->Conn()->Weird("SYN_seq_jump"); - endpoint->InitStartSeq(first_seg_seq); - endpoint->InitAckSeq(first_seg_seq); - endpoint->InitLastSeq(last_seq); - } - break; - - case TCP_ENDPOINT_ESTABLISHED: - case TCP_ENDPOINT_PARTIAL: - if ( flags.SYN() ) - { - if ( endpoint->Size() > 0 ) - endpoint->Conn()->Weird("SYN_inside_connection"); - - if ( first_seg_seq != endpoint->StartSeq() ) - endpoint->Conn()->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->InitStartSeq(first_seg_seq); - endpoint->InitAckSeq(first_seg_seq); - endpoint->InitLastSeq(last_seq); - } - break; - - case TCP_ENDPOINT_RESET: - if ( flags.SYN() ) - { - if ( endpoint->prev_state == TCP_ENDPOINT_INACTIVE ) - { - // Seq. numbers were initialized by a RST packet from this - // endpoint, but now that a SYN is seen from it, that could mean - // the earlier RST was spoofed/injected, so re-initialize. This - // mostly just helps prevent misrepresentations of payload sizes - // that are based on bad initial sequence values. - endpoint->InitStartSeq(first_seg_seq); - endpoint->InitAckSeq(first_seg_seq); - endpoint->InitLastSeq(last_seq); - } - } - break; - - default: - break; - } - } - -static void init_peer(TCP_Endpoint* peer, TCP_Endpoint* endpoint, - TCP_Flags flags, uint32_t ack_seq) - { - 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 ( ! endpoint->Conn()->DidWeird() ) - endpoint->Conn()->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->InitStartSeq(ack_seq - 1); - peer->InitAckSeq(ack_seq - 1); - peer->InitLastSeq(ack_seq - 1); - } - -static void update_ack_seq(TCP_Endpoint* endpoint, uint32_t ack_seq) - { - int32_t delta_ack = seq_delta(ack_seq, endpoint->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 ) - endpoint->UpdateAckSeq(ack_seq); - } - -// Returns the difference between last_seq and the last sequence -// seen by the endpoint (may be negative). -static int32_t update_last_seq(TCP_Endpoint* endpoint, uint32_t last_seq, - TCP_Flags flags, int len) - { - int32_t 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 && len > 0 ) - endpoint->DidRxmit(); - - return delta_last; - } - -void TCP_Analyzer::DeliverPacket(int len, const u_char* data, bool is_orig, - uint64_t seq, const IP_Hdr* ip, int caplen) - { - packet_analysis::IP::SessionAdapter::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, std::min(len, caplen)); - - TCP_Endpoint* endpoint = is_orig ? orig : resp; - TCP_Endpoint* peer = endpoint->peer; - - if ( ! ValidateChecksum(ip, tp, endpoint, len, caplen) ) - return; - - uint32_t tcp_hdr_len = data - (const u_char*) tp; - TCP_Flags flags(tp); - SetPartialStatus(flags, endpoint->IsOrig()); - - uint32_t base_seq = ntohl(tp->th_seq); - uint32_t ack_seq = ntohl(tp->th_ack); - - int seg_len = get_segment_len(len, flags); - uint32_t seq_one_past_segment = base_seq + seg_len; - - init_endpoint(endpoint, flags, base_seq, seq_one_past_segment, - run_state::current_timestamp); - - bool seq_underflow = false; - uint64_t rel_seq = get_relative_seq(endpoint, base_seq, endpoint->LastSeq(), - endpoint->SeqWraps(), &seq_underflow); - - if ( seq_underflow && ! flags.RST() ) - // Can't tell if if this is a retransmit/out-of-order or something - // before the sequence Bro initialized the endpoint at or the TCP is - // just broken and sending garbage sequences. In either case, some - // standard analysis doesn't apply (e.g. reassembly). - Weird("TCP_seq_underflow_or_misorder"); - - update_history(flags, endpoint, rel_seq, len); - update_window(endpoint, ntohs(tp->th_win), base_seq, ack_seq, flags); - - if ( ! orig->did_close || ! resp->did_close ) - Conn()->SetLastTime(run_state::current_timestamp); - - if ( flags.SYN() ) - { - SynWeirds(flags, endpoint, len); - RecordVal* SYN_vals = build_syn_packet_val(is_orig, ip, tp); - init_window(endpoint, peer, flags, SYN_vals->GetFieldAs(5), - base_seq, ack_seq); - - if ( connection_SYN_packet ) - EnqueueConnEvent(connection_SYN_packet, - ConnVal(), - IntrusivePtr{NewRef{}, SYN_vals} - ); - - Unref(SYN_vals); - } - - if ( flags.FIN() ) - { - ++endpoint->FIN_cnt; - - if ( endpoint->FIN_cnt >= detail::tcp_storm_thresh && run_state::current_timestamp < - endpoint->last_time + detail::tcp_storm_interarrival_thresh ) - Weird("FIN_storm"); - - endpoint->FIN_seq = rel_seq + seg_len; - } - - if ( flags.RST() ) - { - ++endpoint->RST_cnt; - - if ( endpoint->RST_cnt >= detail::tcp_storm_thresh && run_state::current_timestamp < - endpoint->last_time + detail::tcp_storm_interarrival_thresh ) - Weird("RST_storm"); - - // This now happens often enough that it's - // not in the least interesting. - //if ( len > 0 ) - // Weird("RST_with_data"); - - PacketWithRST(); - } - - uint64_t rel_ack = 0; - - if ( flags.ACK() ) - { - 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 ) - { - rel_ack = 1; - init_peer(peer, endpoint, flags, ack_seq); - } - else - { - bool ack_underflow = false; - rel_ack = get_relative_seq(peer, ack_seq, peer->AckSeq(), - peer->AckWraps(), &ack_underflow); - - if ( ack_underflow ) - { - rel_ack = 0; - Weird("TCP_ack_underflow_or_misorder"); - } - else if ( ! flags.RST() ) - // Don't trust ack's in RST packets. - update_ack_seq(peer, ack_seq); - } - } - - int32_t delta_last = update_last_seq(endpoint, seq_one_past_segment, flags, len); - endpoint->last_time = run_state::current_timestamp; - - bool do_close; - bool gen_event; - UpdateStateMachine(run_state::current_timestamp, endpoint, peer, base_seq, ack_seq, - len, delta_last, is_orig, flags, do_close, gen_event); - - if ( flags.ACK() ) - // We wait on doing this until we've updated the state - // machine so that if the ack reveals a content gap, - // we can tell whether it came at the very end of the - // connection (in a FIN or RST). Those gaps aren't - // reliable - especially those for RSTs - and we refrain - // from flagging them in the connection history. - peer->AckReceived(rel_ack); - - if ( tcp_packet ) - GeneratePacketEvent(rel_seq, rel_ack, data, len, caplen, is_orig, - flags); - - if ( (tcp_option || tcp_options) && tcp_hdr_len > sizeof(*tp) ) - ParseTCPOptions(tp, is_orig); - - // PIA/signature matching state needs to be initialized before - // processing/reassembling any TCP data, since that processing may - // itself try to perform signature matching. Also note that a SYN - // packet may technically carry data (see RFC793 Section 3.4 and also - // TCP Fast Open). - CheckPIA_FirstPacket(is_orig, ip); - - if ( DEBUG_tcp_data_sent ) - { - DEBUG_MSG("%.6f before DataSent: len=%d caplen=%d skip=%d\n", - run_state::network_time, len, caplen, Skipping()); - } - - uint64_t rel_data_seq = flags.SYN() ? rel_seq + 1 : rel_seq; - - int need_contents = 0; - if ( len > 0 && (caplen >= len || packet_children.size()) && - ! flags.RST() && ! Skipping() && ! seq_underflow ) - need_contents = DeliverData(run_state::current_timestamp, data, len, caplen, ip, - tp, endpoint, rel_data_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. - analyzer_list::iterator next; - - for ( auto i = packet_children.begin(); i != packet_children.end(); /* nop */ ) - { - auto child = *i; - - if ( child->IsFinished() || child->Removing() ) - { - if ( child->Removing() ) - child->Done(); - - DBG_LOG(DBG_ANALYZER, "%s deleted child %s", - fmt_analyzer(this).c_str(), fmt_analyzer(child).c_str()); - i = packet_children.erase(i); - delete child; - } - else - { - child->NextPacket(len, data, is_orig, rel_data_seq, ip, caplen); - ++i; - } - } - - if ( ! reassembling ) - ForwardPacket(len, data, is_orig, rel_data_seq, ip, caplen); - } - -void TCP_Analyzer::DeliverStream(int len, const u_char* data, bool orig) - { - Analyzer::DeliverStream(len, data, orig); - } - -void TCP_Analyzer::Undelivered(uint64_t seq, int len, bool is_orig) - { - Analyzer::Undelivered(seq, len, orig); - } - -void TCP_Analyzer::FlipRoles() - { - Analyzer::FlipRoles(); - - session_mgr->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) - { - auto orig_endp_val = conn_val->GetFieldAs("orig"); - auto resp_endp_val = conn_val->GetFieldAs("resp"); - - orig_endp_val->Assign(0, orig->Size()); - orig_endp_val->Assign(1, orig->state); - resp_endp_val->Assign(0, resp->Size()); - resp_endp_val->Assign(1, resp->state); - - // 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); - } - -int TCP_Analyzer::ParseTCPOptions(const struct tcphdr* tcp, bool is_orig) - { - // 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; - std::vector opts; - - 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. - break; - - else - opt_len = options[1]; - - if ( opt_len == 0 ) - break; // trashed length field - - if ( options + opt_len > opt_end ) - // No room for rest of option. - break; - - opts.emplace_back(options); - options += opt_len; - - if ( opt == TCPOPT_EOL ) - // All done - could flag if more junk left over .... - break; - } - - if ( tcp_option ) - for ( const auto& o : opts ) - { - auto kind = o[0]; - auto length = kind < 2 ? 1 : o[1]; - EnqueueConnEvent(tcp_option, - ConnVal(), - val_mgr->Bool(is_orig), - val_mgr->Count(kind), - val_mgr->Count(length) - ); - } - - if ( tcp_options ) - { - auto option_list = make_intrusive(BifType::Vector::TCP::OptionList); - - auto add_option_data = [](const RecordValPtr& rv, const u_char* odata, int olen) - { - if ( olen <= 2 ) - return; - - auto data_len = olen - 2; - auto data = reinterpret_cast(odata + 2); - rv->Assign(2, make_intrusive(data_len, data)); - }; - - for ( const auto& o : opts ) - { - auto kind = o[0]; - auto length = kind < 2 ? 1 : o[1]; - auto option_record = make_intrusive(BifType::Record::TCP::Option); - option_list->Assign(option_list->Size(), option_record); - option_record->Assign(0, kind); - option_record->Assign(1, length); - - switch ( kind ) { - case 2: - // MSS - if ( length == 4 ) - { - auto mss = ntohs(*reinterpret_cast(o + 2)); - option_record->Assign(3, mss); - } - else - { - add_option_data(option_record, o, length); - Weird("tcp_option_mss_invalid_len", util::fmt("%d", length)); - } - break; - - case 3: - // window scale - if ( length == 3 ) - { - auto scale = o[2]; - option_record->Assign(4, scale); - } - else - { - add_option_data(option_record, o, length); - Weird("tcp_option_window_scale_invalid_len", util::fmt("%d", length)); - } - break; - - case 4: - // sack permitted (implicit boolean) - if ( length != 2 ) - { - add_option_data(option_record, o, length); - Weird("tcp_option_sack_invalid_len", util::fmt("%d", length)); - } - break; - - case 5: - // SACK blocks (1-4 pairs of 32-bit begin+end pointers) - if ( length == 10 || length == 18 || - length == 26 || length == 34 ) - { - auto p = reinterpret_cast(o + 2); - auto num_pointers = (length - 2) / 4; - auto vt = id::index_vec; - auto sack = make_intrusive(std::move(vt)); - - for ( auto i = 0; i < num_pointers; ++i ) - sack->Assign(sack->Size(), val_mgr->Count(ntohl(p[i]))); - - option_record->Assign(5, sack); - } - else - { - add_option_data(option_record, o, length); - Weird("tcp_option_sack_blocks_invalid_len", util::fmt("%d", length)); - } - break; - - case 8: - // timestamps - if ( length == 10 ) - { - auto send = ntohl(*reinterpret_cast(o + 2)); - auto echo = ntohl(*reinterpret_cast(o + 6)); - option_record->Assign(6, send); - option_record->Assign(7, echo); - } - else - { - add_option_data(option_record, o, length); - Weird("tcp_option_timestamps_invalid_len", util::fmt("%d", length)); - } - break; - - default: - add_option_data(option_record, o, length); - break; - } - } - - EnqueueConnEvent(tcp_options, - ConnVal(), - val_mgr->Bool(is_orig), - std::move(option_list) - ); - } - - if ( options < opt_end ) - return -1; - - 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. - session_mgr->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); - session_mgr->Remove(Conn()); - } - } - -void TCP_Analyzer::ExpireTimer(double t) - { - if ( ! is_active ) - return; - - if ( Conn()->LastTime() + zeek::detail::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; - session_mgr->Remove(Conn()); - return; - } - } - - if ( resp->state == TCP_ENDPOINT_INACTIVE ) - { - 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); - session_mgr->Remove(Conn()); - return; - } - } - } - - // Connection still active, so reschedule timer. - // ### if PQ_Element's were Obj's, could just Ref the timer - // and adjust its value here, instead of creating a new timer. - ADD_ANALYZER_TIMER(&TCP_Analyzer::ExpireTimer, t + zeek::detail::tcp_session_timer, - false, zeek::detail::TIMER_TCP_EXPIRE); - } - -void TCP_Analyzer::ResetTimer(double /* t */) - { - if ( ! is_active ) - return; - - if ( ! BothClosed() ) - ConnectionReset(); - - session_mgr->Remove(Conn()); - } - -void TCP_Analyzer::DeleteTimer(double /* t */) - { - session_mgr->Remove(Conn()); - } - -void TCP_Analyzer::ConnDeleteTimer(double t) - { - Conn()->DeleteTimer(t); - } - -void TCP_Analyzer::SetContentsFile(unsigned int direction, FilePtr f) - { - if ( direction == CONTENTS_NONE ) - { - orig->SetContentsFile(nullptr); - resp->SetContentsFile(nullptr); - } - - else - { - if ( direction == CONTENTS_ORIG || direction == CONTENTS_BOTH ) - orig->SetContentsFile(f); - if ( direction == CONTENTS_RESP || direction == CONTENTS_BOTH ) - resp->SetContentsFile(f); - } - } - -FilePtr TCP_Analyzer::GetContentsFile(unsigned int direction) const - { - switch ( direction ) { - case CONTENTS_NONE: - return nullptr; - - 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 nullptr; - else - return orig->GetContentsFile(); - - default: - break; - } - - reporter->Error("bad direction %u in TCP_Analyzer::GetContentsFile", - direction); - return nullptr; - } - -void TCP_Analyzer::ConnectionClosed(TCP_Endpoint* endpoint, TCP_Endpoint* peer, - bool 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 = true; - - 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", - run_state::network_time, close_complete, detail::tcp_close_delay); - } - - if ( close_complete ) - { - if ( endpoint->prev_state != TCP_ENDPOINT_INACTIVE || - peer->state != TCP_ENDPOINT_INACTIVE ) - { - if ( deferred_gen_event ) - { - gen_event = true; - deferred_gen_event = 0; // clear flag - } - - // We have something interesting to report. - if ( gen_event ) - { - if ( peer->state == TCP_ENDPOINT_INACTIVE ) - ConnectionFinished(true); - else - ConnectionFinished(false); - } - } - - CancelTimers(); - - // Note, even if tcp_close_delay is zero, we can't - // simply do: - // - // session_mgr->Remove(this); - // - // here, because that would cause the object to be - // deleted out from under us. - if ( zeek::detail::tcp_close_delay != 0.0 ) - ADD_ANALYZER_TIMER(&TCP_Analyzer::ConnDeleteTimer, - Conn()->LastTime() + zeek::detail::tcp_close_delay, false, - zeek::detail::TIMER_CONN_DELETE); - else - ADD_ANALYZER_TIMER(&TCP_Analyzer::DeleteTimer, Conn()->LastTime(), false, - zeek::detail::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() + zeek::detail::tcp_partial_close_delay, false, - zeek::detail::TIMER_TCP_PARTIAL_CLOSE ); - } - - else - { - // Create a timer to look for the other side closing, - // too. - ADD_ANALYZER_TIMER(&TCP_Analyzer::ExpireTimer, - Conn()->LastTime() + zeek::detail::tcp_session_timer, false, - zeek::detail::TIMER_TCP_EXPIRE); - } - } - } - -void TCP_Analyzer::ConnectionFinished(bool 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(); - } - -void TCP_Analyzer::AddChildPacketAnalyzer(analyzer::Analyzer* a) - { - DBG_LOG(DBG_ANALYZER, "%s added packet child %s", - this->GetAnalyzerName(), a->GetAnalyzerName()); - - packet_children.push_back(a); - a->SetParent(this); - } - -bool TCP_Analyzer::DataPending(TCP_Endpoint* closing_endp) - { - if ( Skipping() ) - return false; - - return closing_endp->DataPending(); - } - -void TCP_Analyzer::EndpointEOF(TCP_Reassembler* endp) - { - if ( connection_EOF ) - EnqueueConnEvent(connection_EOF, - ConnVal(), - val_mgr->Bool(endp->IsOrig()) - ); - - 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_t 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() < zeek::detail::tcp_connection_linger && - orig->state != TCP_ENDPOINT_RESET && - resp->state != TCP_ENDPOINT_RESET ) - Weird("premature_connection_reuse"); - - return true; + return tcp ? + tcp : + static_cast(Conn()->FindAnalyzer("TCP")); } void TCP_ApplicationAnalyzer::Init() @@ -1878,13 +33,13 @@ void TCP_ApplicationAnalyzer::Init() Analyzer::Init(); if ( Parent()->IsAnalyzer("TCP") ) - SetTCP(static_cast(Parent())); + SetTCP(static_cast(Parent())); } void TCP_ApplicationAnalyzer::ProtocolViolation(const char* reason, const char* data, int len) { - TCP_Analyzer* tcp = TCP(); + auto* tcp = TCP(); if ( tcp && (tcp->IsPartial() || tcp->HadGap(false) || tcp->HadGap(true)) ) diff --git a/src/analyzer/protocol/tcp/TCP.h b/src/analyzer/protocol/tcp/TCP.h index d03920d031..8fa5331652 100644 --- a/src/analyzer/protocol/tcp/TCP.h +++ b/src/analyzer/protocol/tcp/TCP.h @@ -6,16 +6,11 @@ #include "zeek/IPAddr.h" #include "zeek/analyzer/protocol/tcp/TCP_Endpoint.h" #include "zeek/analyzer/protocol/tcp/TCP_Flags.h" -#include "zeek/packet_analysis/protocol/ip/SessionAdapter.h" +#include "zeek/packet_analysis/protocol/tcp/TCPSessionAdapter.h" #include "zeek/Conn.h" -// We define two classes here: -// - TCP_Analyzer is the analyzer for the TCP protocol itself. -// - TCP_ApplicationAnalyzer is an abstract base class for analyzers for a -// protocol running on top of TCP. -// - namespace zeek::analyzer::pia { class PIA_TCP; } +namespace zeek::packet_analysis::TCP { class TCPSessionAdapter; } namespace zeek::analyzer::tcp { @@ -23,179 +18,13 @@ class TCP_Endpoint; class TCP_Reassembler; class TCP_ApplicationAnalyzer; -class TCP_Analyzer final : public packet_analysis::IP::SessionAdapter { -public: - explicit TCP_Analyzer(Connection* conn); - ~TCP_Analyzer() override; - - void EnableReassembly(); - - // Add a child analyzer that will always get the packets, - // independently of whether we do any reassembly. - void AddChildPacketAnalyzer(analyzer::Analyzer* a); - - Analyzer* FindChild(analyzer::ID id) override; - Analyzer* FindChild(analyzer::Tag tag) override; - bool RemoveChildAnalyzer(analyzer::ID id) override; - - // True if the connection has closed in some sense, false otherwise. - bool IsClosed() const { return orig->did_close || resp->did_close; } - bool BothClosed() const { return orig->did_close && resp->did_close; } - - bool IsPartial() const { return is_partial; } - - bool HadGap(bool orig) const; - - TCP_Endpoint* Orig() const { return orig; } - TCP_Endpoint* Resp() const { return resp; } - int OrigState() const { return orig->state; } - int RespState() const { return resp->state; } - int OrigPrevState() const { return orig->prev_state; } - int RespPrevState() const { return resp->prev_state; } - uint32_t OrigSeq() const { return orig->LastSeq(); } - uint32_t RespSeq() const { return resp->LastSeq(); } - - // True if either endpoint still has pending data. closing_endp - // is an endpoint that has indicated it is closing (i.e., for - // which we have seen a FIN) - for it, data is pending unless - // everything's been delivered up to the FIN. For its peer, - // the test is whether it has any outstanding, un-acked data. - bool DataPending(TCP_Endpoint* closing_endp); - - void SetContentsFile(unsigned int direction, FilePtr f) override; - FilePtr GetContentsFile(unsigned int direction) const override; - - // From Analyzer.h - void UpdateConnVal(RecordVal *conn_val) override; - - int ParseTCPOptions(const struct tcphdr* tcp, bool is_orig); - - static analyzer::Analyzer* Instantiate(Connection* conn) - { return new TCP_Analyzer(conn); } - - void AddExtraAnalyzers(Connection* conn) override {} - -protected: - friend class TCP_ApplicationAnalyzer; - friend class TCP_Reassembler; - friend class analyzer::pia::PIA_TCP; - - // Analyzer interface. - void Init() override; - void Done() override; - void DeliverPacket(int len, const u_char* data, bool orig, uint64_t seq, - const IP_Hdr* ip, int caplen) override; - void DeliverStream(int len, const u_char* data, bool orig) override; - void Undelivered(uint64_t seq, int len, bool orig) override; - void FlipRoles() override; - bool IsReuse(double t, const u_char* pkt) override; - - // Returns the TCP header pointed to by data (which we assume is - // aligned), updating data, len & caplen. Returns nil if the header - // isn't fully present. - const struct tcphdr* ExtractTCP_Header(const u_char*& data, int& len, - int& caplen); - - // Returns true if the checksum is valid, false if not (and in which - // case also updates the status history of the endpoint). - bool ValidateChecksum(const IP_Hdr* ip, const struct tcphdr* tp, TCP_Endpoint* endpoint, - int len, int caplen); - - void SetPartialStatus(TCP_Flags flags, bool is_orig); - - // Update the state machine of the TCPs based on the activity. This - // includes our pseudo-states such as TCP_ENDPOINT_PARTIAL. - // - // On return, do_close is true if we should consider the connection - // as closed, and gen_event if we shouuld generate an event about - // this fact. - void UpdateStateMachine(double t, - TCP_Endpoint* endpoint, TCP_Endpoint* peer, - uint32_t base_seq, uint32_t ack_seq, - int len, int32_t delta_last, bool is_orig, TCP_Flags flags, - bool& do_close, bool& gen_event); - - void UpdateInactiveState(double t, - TCP_Endpoint* endpoint, TCP_Endpoint* peer, - uint32_t base_seq, uint32_t ack_seq, - int len, bool is_orig, TCP_Flags flags, - bool& do_close, bool& gen_event); - - void UpdateSYN_SentState(TCP_Endpoint* endpoint, TCP_Endpoint* peer, - int len, bool is_orig, TCP_Flags flags, - bool& do_close, bool& gen_event); - - void UpdateEstablishedState(TCP_Endpoint* endpoint, TCP_Endpoint* peer, - TCP_Flags flags, bool& do_close, bool& gen_event); - - void UpdateClosedState(double t, TCP_Endpoint* endpoint, - int32_t delta_last, TCP_Flags flags, - bool& do_close); - - void UpdateResetState(int len, TCP_Flags flags); - - void GeneratePacketEvent(uint64_t rel_seq, uint64_t rel_ack, - const u_char* data, int len, int caplen, - bool is_orig, TCP_Flags flags); - - bool DeliverData(double t, const u_char* data, int len, int caplen, - const IP_Hdr* ip, const struct tcphdr* tp, - TCP_Endpoint* endpoint, uint64_t rel_data_seq, - bool is_orig, TCP_Flags flags); - - void CheckRecording(bool need_contents, TCP_Flags flags); - void CheckPIA_FirstPacket(bool is_orig, const IP_Hdr* ip); - - friend class session::detail::Timer; - void AttemptTimer(double t); - void PartialCloseTimer(double t); - void ExpireTimer(double t); - void ResetTimer(double t); - void DeleteTimer(double t); - void ConnDeleteTimer(double t); - - void EndpointEOF(TCP_Reassembler* endp); - void ConnectionClosed(TCP_Endpoint* endpoint, - TCP_Endpoint* peer, bool gen_event); - void ConnectionFinished(bool half_finished); - void ConnectionReset(); - void PacketWithRST(); - - void SetReassembler(tcp::TCP_Reassembler* rorig, tcp::TCP_Reassembler* rresp); - - // A couple utility functions that may also be useful to derived analyzers. - static uint64_t get_relative_seq(const TCP_Endpoint* endpoint, - uint32_t cur_base, uint32_t last, - uint32_t wraps, bool* underflow = nullptr); - - static int get_segment_len(int payload_len, TCP_Flags flags); - -private: - - void SynWeirds(TCP_Flags flags, TCP_Endpoint* endpoint, int data_len) const; - - TCP_Endpoint* orig; - TCP_Endpoint* resp; - - analyzer_list packet_children; - - unsigned int first_packet_seen: 2; - unsigned int reassembling: 1; - unsigned int is_partial: 1; - unsigned int is_active: 1; - unsigned int finished: 1; - - // Whether we're waiting on final data delivery before closing - // this connection. - unsigned int close_deferred: 1; - - // Whether to generate an event when we finally do close it. - unsigned int deferred_gen_event: 1; - - // Whether we have seen the first ACK from the originator. - unsigned int seen_first_ACK: 1; -}; +using TCP_Analyzer [[deprecated("Remove in v5.1. Use zeek::packet_analysis::TCP::TCPSessionAdapter.")]] = + packet_analysis::TCP::TCPSessionAdapter; +/** + * An abstract base class for analyzers for a protocol running on top + * of TCP. + */ class TCP_ApplicationAnalyzer : public analyzer::Analyzer { public: TCP_ApplicationAnalyzer(const char* name, Connection* conn) @@ -208,14 +37,9 @@ public: // This may be nil if we are not directly associated with a TCP // analyzer (e.g., we're part of a tunnel decapsulation pipeline). - TCP_Analyzer* TCP() - { - return tcp ? - tcp : - static_cast(Conn()->FindAnalyzer("TCP")); - } + packet_analysis::TCP::TCPSessionAdapter* TCP(); - void SetTCP(TCP_Analyzer* arg_tcp) { tcp = arg_tcp; } + void SetTCP(packet_analysis::TCP::TCPSessionAdapter* arg_tcp) { tcp = arg_tcp; } // The given endpoint's data delivery is complete. virtual void EndpointEOF(bool is_orig); @@ -225,7 +49,8 @@ public: // is now fully closed, a connection_finished event will be // generated; otherwise not. virtual void ConnectionClosed(analyzer::tcp::TCP_Endpoint* endpoint, - analyzer::tcp::TCP_Endpoint* peer, bool gen_event); + analyzer::tcp::TCP_Endpoint* peer, + bool gen_event); virtual void ConnectionFinished(bool half_finished); virtual void ConnectionReset(); @@ -247,7 +72,7 @@ public: virtual void SetEnv(bool orig, char* name, char* val); private: - TCP_Analyzer* tcp; + packet_analysis::TCP::TCPSessionAdapter* tcp; }; class TCP_SupportAnalyzer : public analyzer::SupportAnalyzer { @@ -257,10 +82,10 @@ public: ~TCP_SupportAnalyzer() override {} - // These are passed on from TCP_Analyzer. + // These are passed on from TCPSessionAdapter. virtual void EndpointEOF(bool is_orig) { } virtual void ConnectionClosed(TCP_Endpoint* endpoint, - TCP_Endpoint* peer, bool gen_event) { } + TCP_Endpoint* peer, bool gen_event) { } virtual void ConnectionFinished(bool half_finished) { } virtual void ConnectionReset() { } virtual void PacketWithRST() { } diff --git a/src/analyzer/protocol/tcp/TCP_Endpoint.cc b/src/analyzer/protocol/tcp/TCP_Endpoint.cc index e6bb9f4e1b..98e3265337 100644 --- a/src/analyzer/protocol/tcp/TCP_Endpoint.cc +++ b/src/analyzer/protocol/tcp/TCP_Endpoint.cc @@ -18,7 +18,7 @@ namespace zeek::analyzer::tcp { -TCP_Endpoint::TCP_Endpoint(TCP_Analyzer* arg_analyzer, bool arg_is_orig) +TCP_Endpoint::TCP_Endpoint(packet_analysis::TCP::TCPSessionAdapter* arg_analyzer, bool arg_is_orig) { contents_processor = nullptr; prev_state = state = TCP_ENDPOINT_INACTIVE; diff --git a/src/analyzer/protocol/tcp/TCP_Endpoint.h b/src/analyzer/protocol/tcp/TCP_Endpoint.h index 0a2a867693..d6b69c2306 100644 --- a/src/analyzer/protocol/tcp/TCP_Endpoint.h +++ b/src/analyzer/protocol/tcp/TCP_Endpoint.h @@ -10,9 +10,13 @@ namespace zeek { class Connection; class IP_Hdr; +namespace packet_analysis::TCP { class TCPSessionAdapter; } + namespace analyzer::tcp { -class TCP_Analyzer; +using TCP_Analyzer [[deprecated("Remove in v5.1. Use zeek::packet_analysis::TCP::TCPSessionAdapter.")]] = + zeek::packet_analysis::TCP::TCPSessionAdapter; + class TCP_Reassembler; enum EndpointState { @@ -29,12 +33,12 @@ enum EndpointState { // One endpoint of a TCP connection. class TCP_Endpoint { public: - TCP_Endpoint(TCP_Analyzer* analyzer, bool is_orig); + TCP_Endpoint(packet_analysis::TCP::TCPSessionAdapter* analyzer, bool is_orig); ~TCP_Endpoint(); void Done(); - TCP_Analyzer* TCP() { return tcp_analyzer; } + packet_analysis::TCP::TCPSessionAdapter* TCP() { return tcp_analyzer; } void SetPeer(TCP_Endpoint* p); @@ -212,7 +216,7 @@ public: EndpointState state, prev_state; TCP_Endpoint* peer; TCP_Reassembler* contents_processor; - TCP_Analyzer* tcp_analyzer; + packet_analysis::TCP::TCPSessionAdapter* tcp_analyzer; FilePtr contents_file; double start_time, last_time; diff --git a/src/analyzer/protocol/tcp/TCP_Reassembler.cc b/src/analyzer/protocol/tcp/TCP_Reassembler.cc index 25547410bb..b23981541d 100644 --- a/src/analyzer/protocol/tcp/TCP_Reassembler.cc +++ b/src/analyzer/protocol/tcp/TCP_Reassembler.cc @@ -6,6 +6,7 @@ #include "zeek/File.h" #include "zeek/analyzer/Analyzer.h" #include "zeek/analyzer/protocol/tcp/TCP.h" +#include "zeek/packet_analysis/protocol/tcp/TCPSessionAdapter.h" #include "zeek/ZeekString.h" #include "zeek/Reporter.h" #include "zeek/RuleMatcher.h" @@ -21,7 +22,7 @@ constexpr bool DEBUG_tcp_connection_close = false; constexpr bool DEBUG_tcp_match_undelivered = false; TCP_Reassembler::TCP_Reassembler(analyzer::Analyzer* arg_dst_analyzer, - TCP_Analyzer* arg_tcp_analyzer, + packet_analysis::TCP::TCPSessionAdapter* arg_tcp_analyzer, TCP_Reassembler::Type arg_type, TCP_Endpoint* arg_endp) : Reassembler(1, REASSEM_TCP) diff --git a/src/analyzer/protocol/tcp/TCP_Reassembler.h b/src/analyzer/protocol/tcp/TCP_Reassembler.h index 7f955dedf8..7aedb5ee85 100644 --- a/src/analyzer/protocol/tcp/TCP_Reassembler.h +++ b/src/analyzer/protocol/tcp/TCP_Reassembler.h @@ -6,6 +6,7 @@ #include "zeek/File.h" namespace zeek { +namespace packet_analysis::TCP { class TCPSessionAdapter; } class Connection; @@ -15,7 +16,8 @@ class Analyzer; namespace tcp { -class TCP_Analyzer; +using TCP_Analyzer [[deprecated("Remove in v5.1. Use zeek::packet_analysis::TCP::TCPSessionAdapter.")]] = + zeek::packet_analysis::TCP::TCPSessionAdapter; class TCP_Reassembler final : public Reassembler { public: @@ -25,7 +27,7 @@ public: }; TCP_Reassembler(analyzer::Analyzer* arg_dst_analyzer, - TCP_Analyzer* arg_tcp_analyzer, + packet_analysis::TCP::TCPSessionAdapter* arg_tcp_analyzer, Type arg_type, TCP_Endpoint* arg_endp); void Done(); @@ -33,7 +35,7 @@ public: void SetDstAnalyzer(analyzer::Analyzer* analyzer) { dst_analyzer = analyzer; } void SetType(Type arg_type) { type = arg_type; } - TCP_Analyzer* GetTCPAnalyzer() { return tcp_analyzer; } + packet_analysis::TCP::TCPSessionAdapter* GetTCPAnalyzer() { return tcp_analyzer; } // Returns the volume of data buffered in the reassembler. // First parameter returns data that is above a hole, and thus is @@ -66,7 +68,7 @@ public: void AckReceived(uint64_t seq); // Checks if we have delivered all contents that we can possibly - // deliver for this endpoint. Calls TCP_Analyzer::EndpointEOF() + // deliver for this endpoint. Calls TCPSessionAdapter::EndpointEOF() // when so. void CheckEOF(); @@ -113,7 +115,7 @@ private: FilePtr record_contents_file; // file on which to reassemble contents analyzer::Analyzer* dst_analyzer; - TCP_Analyzer* tcp_analyzer; + packet_analysis::TCP::TCPSessionAdapter* tcp_analyzer; Type type; }; diff --git a/src/analyzer/protocol/tcp/functions.bif b/src/analyzer/protocol/tcp/functions.bif index 65669e524a..7571e34cbb 100644 --- a/src/analyzer/protocol/tcp/functions.bif +++ b/src/analyzer/protocol/tcp/functions.bif @@ -27,7 +27,7 @@ function get_orig_seq%(cid: conn_id%): count zeek::analyzer::Analyzer* tc = c->FindAnalyzer("TCP"); if ( tc ) - return zeek::val_mgr->Count(static_cast(tc)->OrigSeq()); + return zeek::val_mgr->Count(static_cast(tc)->OrigSeq()); else { reporter->Error("connection does not have TCP analyzer"); @@ -56,7 +56,7 @@ function get_resp_seq%(cid: conn_id%): count zeek::analyzer::Analyzer* tc = c->FindAnalyzer("TCP"); if ( tc ) - return zeek::val_mgr->Count(static_cast(tc)->RespSeq()); + return zeek::val_mgr->Count(static_cast(tc)->RespSeq()); else { reporter->Error("connection does not have TCP analyzer"); diff --git a/src/fuzzers/pop3-fuzzer.cc b/src/fuzzers/pop3-fuzzer.cc index 7bd036af89..d9668247bc 100644 --- a/src/fuzzers/pop3-fuzzer.cc +++ b/src/fuzzers/pop3-fuzzer.cc @@ -36,7 +36,7 @@ static zeek::Connection* add_connection() static zeek::analyzer::Analyzer* add_analyzer(zeek::Connection* conn) { - auto* tcp = new zeek::analyzer::tcp::TCP_Analyzer(conn); + auto* tcp = new zeek::packet_analysis::TCP::TCPSessionAdapter(conn); auto* pia = new zeek::analyzer::pia::PIA_TCP(conn); auto a = zeek::analyzer_mgr->InstantiateAnalyzer(ZEEK_FUZZ_ANALYZER, conn); tcp->AddChildAnalyzer(a); diff --git a/src/packet_analysis/protocol/ip/IPBasedAnalyzer.cc b/src/packet_analysis/protocol/ip/IPBasedAnalyzer.cc index 660d238366..e7da71897d 100644 --- a/src/packet_analysis/protocol/ip/IPBasedAnalyzer.cc +++ b/src/packet_analysis/protocol/ip/IPBasedAnalyzer.cc @@ -194,16 +194,7 @@ zeek::Connection* IPBasedAnalyzer::NewConn(const ConnTuple* id, const detail::Co if ( flip ) conn->FlipRoles(); - if ( ! new_plugin ) - { - if ( ! analyzer_mgr->BuildInitialAnalyzerTree(conn) ) - { - conn->Done(); - Unref(conn); - return nullptr; - } - } - else if ( ! BuildSessionAnalyzerTree(conn) ) + if ( ! BuildSessionAnalyzerTree(conn) ) { conn->Done(); Unref(conn); diff --git a/src/packet_analysis/protocol/tcp/CMakeLists.txt b/src/packet_analysis/protocol/tcp/CMakeLists.txt index c42cca2b25..8f440a70bd 100644 --- a/src/packet_analysis/protocol/tcp/CMakeLists.txt +++ b/src/packet_analysis/protocol/tcp/CMakeLists.txt @@ -4,5 +4,5 @@ include(ZeekPlugin) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) zeek_plugin_begin(PacketAnalyzer TCP_PKT) -zeek_plugin_cc(TCP.cc Plugin.cc) +zeek_plugin_cc(TCP.cc TCPSessionAdapter.cc Plugin.cc) zeek_plugin_end() diff --git a/src/packet_analysis/protocol/tcp/Plugin.cc b/src/packet_analysis/protocol/tcp/Plugin.cc index 45b41c3884..83b19e2cbf 100644 --- a/src/packet_analysis/protocol/tcp/Plugin.cc +++ b/src/packet_analysis/protocol/tcp/Plugin.cc @@ -3,6 +3,7 @@ #include "zeek/plugin/Plugin.h" #include "zeek/packet_analysis/Component.h" #include "zeek/packet_analysis/protocol/tcp/TCP.h" +#include "zeek/packet_analysis/protocol/tcp/TCPSessionAdapter.h" namespace zeek::plugin::Zeek_TCP { @@ -12,6 +13,8 @@ public: { AddComponent(new zeek::packet_analysis::Component("TCP", zeek::packet_analysis::TCP::TCPAnalyzer::Instantiate)); + AddComponent(new zeek::analyzer::Component("TCP", + zeek::packet_analysis::TCP::TCPSessionAdapter::Instantiate)); zeek::plugin::Configuration config; config.name = "Zeek::TCP_PKT"; diff --git a/src/packet_analysis/protocol/tcp/TCP.cc b/src/packet_analysis/protocol/tcp/TCP.cc index e373d7b48c..a34656f024 100644 --- a/src/packet_analysis/protocol/tcp/TCP.cc +++ b/src/packet_analysis/protocol/tcp/TCP.cc @@ -2,18 +2,41 @@ #include "zeek/packet_analysis/protocol/tcp/TCP.h" #include "zeek/RunState.h" +#include "zeek/analyzer/protocol/pia/PIA.h" +#include "zeek/packet_analysis/protocol/tcp/TCPSessionAdapter.h" using namespace zeek::packet_analysis::TCP; using namespace zeek::packet_analysis::IP; TCPAnalyzer::TCPAnalyzer() : IPBasedAnalyzer("TCP", TRANSPORT_TCP, TCP_PORT_MASK, false) { + new_plugin = true; } TCPAnalyzer::~TCPAnalyzer() { } +void TCPAnalyzer::Initialize() + { + } + +SessionAdapter* TCPAnalyzer::MakeSessionAdapter(Connection* conn) + { + auto* root = new TCPSessionAdapter(conn); + root->SetParent(this); + + conn->EnableStatusUpdateTimer(); + conn->SetInactivityTimeout(zeek::detail::udp_inactivity_timeout); + + return root; + } + +zeek::analyzer::pia::PIA* TCPAnalyzer::MakePIA(Connection* conn) + { + return new analyzer::pia::PIA_TCP(conn); + } + bool TCPAnalyzer::BuildConnTuple(size_t len, const uint8_t* data, Packet* packet, ConnTuple& tuple) { @@ -74,3 +97,13 @@ bool TCPAnalyzer::WantConnection(uint16_t src_port, uint16_t dst_port, return true; } + +void TCPAnalyzer::DeliverPacket(Connection* c, double t, bool is_orig, int remaining, Packet* pkt) + { + auto* ta = static_cast(c->GetSessionAdapter()); + + const u_char* data = pkt->ip_hdr->Payload(); + int len = pkt->ip_hdr->PayloadLen(); + + ta->DeliverPacket(len, data, is_orig, {}, pkt->ip_hdr.get(), remaining); + } diff --git a/src/packet_analysis/protocol/tcp/TCP.h b/src/packet_analysis/protocol/tcp/TCP.h index cb5ec8e214..9a6eeda83e 100644 --- a/src/packet_analysis/protocol/tcp/TCP.h +++ b/src/packet_analysis/protocol/tcp/TCP.h @@ -18,14 +18,12 @@ public: return std::make_shared(); } - /** - * Returns an adapter appropriate for this IP-based analyzer. This adapter is used to - * hook into the session analyzer framework. This function can also be used to do any - * extra initialization of connection timers, etc. - * - * TODO: this is a stub until the TCP analyzer moves to the packet analysis framework. + /* + * Initialize the analyzer. This method is called after the configuration + * was read. Derived classes can override this method to implement custom + * initialization. */ - IP::SessionAdapter* MakeSessionAdapter(Connection* conn) override { return nullptr; } + void Initialize() override; protected: @@ -35,6 +33,9 @@ protected: bool BuildConnTuple(size_t len, const uint8_t* data, Packet* packet, ConnTuple& tuple) override; + void DeliverPacket(Connection* c, double t, bool is_orig, int remaining, + Packet* pkt) override; + /** * Upon seeing the first packet of a connection, checks whether we want * to analyze it (e.g. we may not want to look at partial connections) @@ -49,6 +50,19 @@ protected: */ bool WantConnection(uint16_t src_port, uint16_t dst_port, const u_char* data, bool& flip_roles) const override; + + /** + * Returns an analyzer adapter appropriate for this IP-based analyzer. This adapter + * is used to hook into the session analyzer framework. This function can also be used + * to do any extra initialization of connection timers, etc. + */ + packet_analysis::IP::SessionAdapter* MakeSessionAdapter(Connection* conn) override; + + /** + * Returns a PIA appropriate for this IP-based analyzer. This method is optional to + * override in child classes, as not all analyzers need a PIA. + */ + analyzer::pia::PIA* MakePIA(Connection* conn) override; }; } diff --git a/src/packet_analysis/protocol/tcp/TCPSessionAdapter.cc b/src/packet_analysis/protocol/tcp/TCPSessionAdapter.cc new file mode 100644 index 0000000000..881592373a --- /dev/null +++ b/src/packet_analysis/protocol/tcp/TCPSessionAdapter.cc @@ -0,0 +1,1933 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#include "zeek/packet_analysis/protocol/tcp/TCPSessionAdapter.h" + +#include "zeek/Val.h" +#include "zeek/RunState.h" + +#include "zeek/analyzer/Manager.h" +#include "zeek/analyzer/protocol/tcp/TCP_Endpoint.h" +#include "zeek/analyzer/protocol/tcp/TCP_Flags.h" +#include "zeek/analyzer/protocol/tcp/TCP_Reassembler.h" +#include "zeek/analyzer/protocol/pia/PIA.h" +#include "zeek/analyzer/protocol/stepping-stone/SteppingStone.h" +#include "zeek/analyzer/protocol/conn-size/ConnSize.h" + +#include "zeek/analyzer/protocol/tcp/events.bif.h" +#include "zeek/analyzer/protocol/tcp/types.bif.h" + +// 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; + +constexpr bool DEBUG_tcp_data_sent = false; +constexpr bool DEBUG_tcp_connection_close = false; + +using namespace zeek; +using namespace zeek::packet_analysis::TCP; + +static zeek::RecordVal* build_syn_packet_val(bool is_orig, const zeek::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; + } + + static auto SYN_packet = zeek::id::find_type("SYN_packet"); + auto* v = new zeek::RecordVal(SYN_packet); + + v->Assign(0, is_orig); + v->Assign(1, static_cast(ip->DF())); + v->Assign(2, ip->TTL()); + v->Assign(3, ip->TotalLen()); + v->Assign(4, ntohs(tcp->th_win)); + v->Assign(5, winscale); + v->Assign(6, MSS); + v->Assign(7, static_cast(SACK)); + + return v; + } + + +TCPSessionAdapter::TCPSessionAdapter(Connection* conn) + : packet_analysis::IP::SessionAdapter("TCP", conn) + { + // Set a timer to eventually time out this connection. + ADD_ANALYZER_TIMER(&TCPSessionAdapter::ExpireTimer, + run_state::network_time + detail::tcp_SYN_timeout, false, + detail::TIMER_TCP_EXPIRE); + + deferred_gen_event = close_deferred = 0; + + seen_first_ACK = 0; + is_active = 1; + finished = 0; + reassembling = 0; + first_packet_seen = 0; + is_partial = 0; + + orig = new analyzer::tcp::TCP_Endpoint(this, true); + resp = new analyzer::tcp::TCP_Endpoint(this, false); + + orig->SetPeer(resp); + resp->SetPeer(orig); + } + +TCPSessionAdapter::~TCPSessionAdapter() + { + LOOP_OVER_GIVEN_CHILDREN(i, packet_children) + delete *i; + + delete orig; + delete resp; + } + +void TCPSessionAdapter::Init() + { + Analyzer::Init(); + LOOP_OVER_GIVEN_CHILDREN(i, packet_children) + (*i)->Init(); + } + +void TCPSessionAdapter::Done() + { + Analyzer::Done(); + + if ( run_state::terminating && connection_pending && is_active && ! BothClosed() ) + Event(connection_pending); + + LOOP_OVER_GIVEN_CHILDREN(i, packet_children) + (*i)->Done(); + + orig->Done(); + resp->Done(); + + finished = 1; + } + +analyzer::Analyzer* TCPSessionAdapter::FindChild(analyzer::ID arg_id) + { + analyzer::Analyzer* child = packet_analysis::IP::SessionAdapter::FindChild(arg_id); + + if ( child ) + return child; + + LOOP_OVER_GIVEN_CHILDREN(i, packet_children) + { + analyzer::Analyzer* child = (*i)->FindChild(arg_id); + if ( child ) + return child; + } + + return nullptr; + } + +analyzer::Analyzer* TCPSessionAdapter::FindChild(analyzer::Tag arg_tag) + { + analyzer::Analyzer* child = packet_analysis::IP::SessionAdapter::FindChild(arg_tag); + + if ( child ) + return child; + + LOOP_OVER_GIVEN_CHILDREN(i, packet_children) + { + analyzer::Analyzer* child = (*i)->FindChild(arg_tag); + if ( child ) + return child; + } + + return nullptr; + } + +bool TCPSessionAdapter::RemoveChildAnalyzer(analyzer::ID id) + { + auto rval = packet_analysis::IP::SessionAdapter::RemoveChildAnalyzer(id); + + if ( rval ) + return rval; + + return RemoveChild(packet_children, id); + } + +void TCPSessionAdapter::EnableReassembly() + { + SetReassembler(new analyzer::tcp::TCP_Reassembler( + this, this, analyzer::tcp::TCP_Reassembler::Forward, orig), + new analyzer::tcp::TCP_Reassembler( + this, this, analyzer::tcp::TCP_Reassembler::Forward, resp)); + } + +void TCPSessionAdapter::SetReassembler(analyzer::tcp::TCP_Reassembler* rorig, + analyzer::tcp::TCP_Reassembler* rresp) + { + orig->AddReassembler(rorig); + rorig->SetDstAnalyzer(this); + resp->AddReassembler(rresp); + rresp->SetDstAnalyzer(this); + + if ( new_connection_contents && reassembling == 0 ) + Event(new_connection_contents); + + reassembling = 1; + } + +const struct tcphdr* TCPSessionAdapter::ExtractTCP_Header(const u_char*& data, + int& len, int& caplen) + { + const struct tcphdr* tp = (const struct tcphdr*) data; + uint32_t tcp_hdr_len = tp->th_off * 4; + + if ( tcp_hdr_len < sizeof(struct tcphdr) ) + { + Weird("bad_TCP_header_len"); + return nullptr; + } + + if ( tcp_hdr_len > uint32_t(len) || + tcp_hdr_len > uint32_t(caplen) ) + { + // This can happen even with the above test, due to TCP + // options. + Weird("truncated_header"); + return nullptr; + } + + len -= tcp_hdr_len; // remove TCP header + caplen -= tcp_hdr_len; + data += tcp_hdr_len; + + return tp; + } + +bool TCPSessionAdapter::ValidateChecksum(const IP_Hdr* ip, const struct tcphdr* tp, + analyzer::tcp::TCP_Endpoint* endpoint, int len, int caplen) + { + if ( ! run_state::current_pkt->l3_checksummed && + ! detail::ignore_checksums && + ! zeek::id::find_val("ignore_checksums_nets")->Contains(ip->IPHeaderSrcAddr()) && + caplen >= len && ! endpoint->ValidChecksum(tp, len, ip->IP4_Hdr()) ) + { + Weird("bad_TCP_checksum"); + endpoint->ChecksumError(); + return false; + } + else + return true; + } + +void TCPSessionAdapter::SetPartialStatus(analyzer::tcp::TCP_Flags flags, bool is_orig) + { + if ( is_orig ) + { + if ( ! (first_packet_seen & ORIG) ) + is_partial = ! flags.SYN() || flags.ACK(); + } + else + { + if ( ! (first_packet_seen & RESP) && ! is_partial ) + is_partial = ! flags.SYN(); + } + } + +static void update_history(analyzer::tcp::TCP_Flags flags, analyzer::tcp::TCP_Endpoint* endpoint, + uint64_t rel_seq, int len) + { + 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) && + rel_seq != endpoint->hist_last_SYN ) + endpoint->AddHistory(code); + + endpoint->hist_last_SYN = rel_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') && + rel_seq + len != endpoint->hist_last_FIN ) + endpoint->AddHistory('F'); + + endpoint->hist_last_FIN = rel_seq + len; + } + + if ( flags.RST() ) + { + if ( endpoint->CheckHistory(HIST_RST_PKT, 'R') && + rel_seq != endpoint->hist_last_RST ) + endpoint->AddHistory('R'); + + endpoint->hist_last_RST = rel_seq; + } + } + + else + { // bits_set == 0 + if ( len ) + endpoint->CheckHistory(HIST_DATA_PKT, 'D'); + + else if ( flags.ACK() ) + endpoint->CheckHistory(HIST_ACK_PKT, 'A'); + } + } + +static void init_window(analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer, + analyzer::tcp::TCP_Flags flags, bro_int_t scale, uint32_t base_seq, + uint32_t ack_seq) + { + // ### In the following, we could be fooled by an + // inconsistent SYN retransmission. Where's a normalizer + // when you need one? + + 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; + } + } + +static void update_window(analyzer::tcp::TCP_Endpoint* endpoint, unsigned int window, + uint32_t base_seq, uint32_t ack_seq, analyzer::tcp::TCP_Flags flags) + { + // Note, applying scaling here would be incorrect for an initial SYN, + // whose window value is always unscaled. However, we don't + // check the window's value for recision in that case anyway, so + // no-harm-no-foul. + int scale = endpoint->window_scale; + window = window << scale; + + // Zero windows are boring if either (1) they come with a RST packet + // or after a RST packet, or (2) they come after the peer has sent + // a FIN (because there's no relevant window at that point anyway). + // (They're also boring if they come after the peer has sent a RST, + // but *nothing* should be sent in response to a RST, so we ignore + // that case.) + // + // However, they *are* potentially interesting if sent by an + // endpoint that's already sent a FIN, since that FIN meant "I'm + // not going to send any more", but doesn't mean "I won't receive + // any more". + if ( window == 0 && ! flags.RST() && + endpoint->peer->state != analyzer::tcp::TCP_ENDPOINT_CLOSED && + endpoint->state != analyzer::tcp::TCP_ENDPOINT_RESET ) + endpoint->ZeroWindow(); + + // Don't analyze window values off of SYNs, they're sometimes + // immediately rescinded. Also don't do so 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. + + if ( ! flags.SYN() && ! flags.FIN() && ! flags.RST() && + endpoint->state != analyzer::tcp::TCP_ENDPOINT_CLOSED && + endpoint->state != analyzer::tcp::TCP_ENDPOINT_RESET ) + { + // ### Decide whether to accept new window based on Active + // Mapping policy. + if ( seq_delta(base_seq, endpoint->window_seq) >= 0 && + seq_delta(ack_seq, endpoint->window_ack_seq) >= 0 ) + { + uint32_t new_edge = ack_seq + window; + uint32_t old_edge = endpoint->window_ack_seq + endpoint->window; + int32_t advance = seq_delta(new_edge, old_edge); + + if ( advance < 0 ) + { + // An apparent window recision. Allow a + // bit of slop for window scaling. This is + // because sometimes there will be an + // apparent recision due to the granularity + // of the scaling. + if ( (-advance) >= (1 << scale) ) + endpoint->Conn()->Weird("window_recision"); + } + + endpoint->window = window; + endpoint->window_ack_seq = ack_seq; + endpoint->window_seq = base_seq; + } + } + } + +void TCPSessionAdapter::SynWeirds(analyzer::tcp::TCP_Flags flags, analyzer::tcp::TCP_Endpoint* endpoint, int data_len) const + { + if ( flags.RST() ) + endpoint->Conn()->Weird("TCP_christmas", "", GetAnalyzerName()); + + if ( flags.URG() ) + endpoint->Conn()->Weird("baroque_SYN", "", GetAnalyzerName()); + + if ( data_len > 0 ) + // Not technically wrong according to RFC 793, but the other side + // would be forced to buffer data until the handshake succeeds, and + // that could be bad in some cases, e.g. SYN floods. + // T/TCP definitely complicates this. + endpoint->Conn()->Weird("SYN_with_data", "", GetAnalyzerName()); + } + +void TCPSessionAdapter::UpdateInactiveState( + double t, analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer, + uint32_t base_seq, uint32_t ack_seq, + int len, bool is_orig, analyzer::tcp::TCP_Flags flags, + bool& do_close, bool& gen_event) + { + if ( flags.SYN() ) + { + if ( is_orig ) + { + if ( flags.ACK() ) + { + Weird("connection_originator_SYN_ack"); + endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT); + } + else + endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_SYN_SENT); + + if ( zeek::detail::tcp_attempt_delay ) + ADD_ANALYZER_TIMER(&TCPSessionAdapter::AttemptTimer, + t + detail::tcp_attempt_delay, true, + detail::TIMER_TCP_ATTEMPT); + } + else + { + if ( flags.ACK() ) + { + if ( peer->state != analyzer::tcp::TCP_ENDPOINT_INACTIVE && + peer->state != analyzer::tcp::TCP_ENDPOINT_PARTIAL && + ! seq_between(ack_seq, peer->StartSeq(), peer->LastSeq()) ) + Weird("bad_SYN_ack"); + } + + else if ( peer->state == analyzer::tcp::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. + is_partial = 0; + Conn()->FlipRoles(); + peer->SetState(analyzer::tcp::TCP_ENDPOINT_ESTABLISHED); + } + + else + Weird("simultaneous_open"); + + if ( peer->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT ) + peer->SetState(analyzer::tcp::TCP_ENDPOINT_ESTABLISHED); + else if ( peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) + { + // If we were to ignore SYNs and + // only instantiate state on SYN + // acks, then we'd do: + // peer->SetState(analyzer::tcp::TCP_ENDPOINT_ESTABLISHED); + // here. + Weird("unsolicited_SYN_response"); + } + + endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_ESTABLISHED); + + if ( peer->state != analyzer::tcp::TCP_ENDPOINT_PARTIAL ) + { + Event(connection_established); + Conn()->EnableStatusUpdateTimer(); + } + } + } + + if ( flags.FIN() ) + { + endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_CLOSED); + do_close = gen_event = true; + if ( peer->state != analyzer::tcp::TCP_ENDPOINT_PARTIAL && ! flags.SYN() ) + Weird("spontaneous_FIN"); + } + + if ( flags.RST() ) + { + endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_RESET); + + bool is_reject = false; + + 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 == analyzer::tcp::TCP_ENDPOINT_ESTABLISHED ) + is_reject = true; + } + + else if ( peer->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT || + peer->state == analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT ) + // We're rejecting an initial SYN. + is_reject = true; + + do_close = true; + gen_event = ! is_reject; + + if ( is_reject ) + Event(connection_rejected); + + else if ( peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) + Weird("spontaneous_RST"); + } + + if ( endpoint->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) + { // No control flags to change the state. + if ( ! is_orig && len == 0 && + orig->state == analyzer::tcp::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 if ( flags.ACK() && peer->state == analyzer::tcp::TCP_ENDPOINT_ESTABLISHED ) + { + // No SYN packet from originator but SYN/ACK from + // responder, and now a pure ACK. Problably means we + // just missed that initial SYN. Let's not treat it + // as partial and instead establish the connection. + endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_ESTABLISHED); + is_partial = 0; + } + + else + { + endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_PARTIAL); + Conn()->EnableStatusUpdateTimer(); + + if ( peer->state == analyzer::tcp::TCP_ENDPOINT_PARTIAL ) + // We've seen both sides of a partial + // connection, report it. + Event(partial_connection); + } + } + } + +void TCPSessionAdapter::UpdateSYN_SentState(analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer, + int len, bool is_orig, analyzer::tcp::TCP_Flags flags, + bool& do_close, bool& gen_event) + { + if ( flags.SYN() ) + { + if ( is_orig ) + { + if ( flags.ACK() && ! flags.FIN() && ! flags.RST() && + endpoint->state != analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT ) + Weird("repeated_SYN_with_ack"); + } + else + { + if ( ! flags.ACK() && + endpoint->state != analyzer::tcp::TCP_ENDPOINT_SYN_SENT ) + Weird("repeated_SYN_reply_wo_ack"); + } + } + + if ( flags.FIN() ) + { + if ( peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE || + peer->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT ) + Weird("inappropriate_FIN"); + + endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_CLOSED); + do_close = gen_event = true; + } + + if ( flags.RST() ) + { + endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_RESET); + ConnectionReset(); + do_close = true; + } + + else if ( len > 0 ) + Weird("data_before_established"); + } + +void TCPSessionAdapter::UpdateEstablishedState( + analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer, + analyzer::tcp::TCP_Flags flags, bool& do_close, bool& gen_event) + { + if ( flags.SYN() ) + { + if ( endpoint->state == analyzer::tcp::TCP_ENDPOINT_PARTIAL && + peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE && ! flags.ACK() ) + { + Weird("SYN_after_partial"); + endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_SYN_SENT); + } + } + + if ( flags.FIN() && ! flags.RST() ) // ### + { // should check sequence/ack numbers here ### + endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_CLOSED); + + if ( peer->state == analyzer::tcp::TCP_ENDPOINT_RESET && + peer->prev_state == analyzer::tcp::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(analyzer::tcp::TCP_ENDPOINT_CLOSED); + + do_close = gen_event = true; + } + + if ( flags.RST() ) + { + endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_RESET); + do_close = true; + + if ( peer->state != analyzer::tcp::TCP_ENDPOINT_RESET || + peer->prev_state != analyzer::tcp::TCP_ENDPOINT_ESTABLISHED ) + ConnectionReset(); + } + } + +void TCPSessionAdapter::UpdateClosedState(double t, analyzer::tcp::TCP_Endpoint* endpoint, + int32_t delta_last, analyzer::tcp::TCP_Flags flags, + bool& 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 != analyzer::tcp::TCP_ENDPOINT_CLOSED ) + { + endpoint->SetState(analyzer::tcp::TCP_ENDPOINT_RESET); + + if ( ! endpoint->did_close ) + // RST after FIN. + do_close = true; + + if ( connection_reset ) + ADD_ANALYZER_TIMER(&TCPSessionAdapter::ResetTimer, + t + zeek::detail::tcp_reset_delay, true, + zeek::detail::TIMER_TCP_RESET); + } + } + +void TCPSessionAdapter::UpdateResetState(int len, analyzer::tcp::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 TCPSessionAdapter::UpdateStateMachine( + double t, analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer, + uint32_t base_seq, uint32_t ack_seq, + int len, int32_t delta_last, bool is_orig, analyzer::tcp::TCP_Flags flags, + bool& do_close, bool& gen_event) + { + do_close = false; // whether to report the connection as closed + gen_event = false; // if so, whether to generate an event + + switch ( endpoint->state ) { + + case analyzer::tcp::TCP_ENDPOINT_INACTIVE: + UpdateInactiveState(t, endpoint, peer, base_seq, ack_seq, + len, is_orig, flags, + do_close, gen_event); + break; + + case analyzer::tcp::TCP_ENDPOINT_SYN_SENT: + case analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT: + UpdateSYN_SentState(endpoint, peer, len, is_orig, flags, do_close, + gen_event); + break; + + case analyzer::tcp::TCP_ENDPOINT_ESTABLISHED: + case analyzer::tcp::TCP_ENDPOINT_PARTIAL: + UpdateEstablishedState(endpoint, peer, flags, do_close, gen_event); + break; + + case analyzer::tcp::TCP_ENDPOINT_CLOSED: + UpdateClosedState(t, endpoint, delta_last, flags, do_close); + break; + + case analyzer::tcp::TCP_ENDPOINT_RESET: + UpdateResetState(len, flags); + break; + } + } + +void TCPSessionAdapter::GeneratePacketEvent( + uint64_t rel_seq, uint64_t rel_ack, + const u_char* data, int len, int caplen, + bool is_orig, analyzer::tcp::TCP_Flags flags) + { + EnqueueConnEvent(tcp_packet, + ConnVal(), + val_mgr->Bool(is_orig), + make_intrusive(flags.AsString()), + val_mgr->Count(rel_seq), + val_mgr->Count(flags.ACK() ? rel_ack : 0), + val_mgr->Count(len), + // We need the min() here because Ethernet padding can lead to + // caplen > len. + make_intrusive(std::min(caplen, len), (const char*) data) + ); + } + +bool TCPSessionAdapter::DeliverData(double t, const u_char* data, int len, int caplen, + const IP_Hdr* ip, const struct tcphdr* tp, + analyzer::tcp::TCP_Endpoint* endpoint, uint64_t rel_data_seq, + bool is_orig, analyzer::tcp::TCP_Flags flags) + { + return endpoint->DataSent(t, rel_data_seq, len, caplen, data, ip, tp); + } + +void TCPSessionAdapter::CheckRecording(bool need_contents, analyzer::tcp::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 TCPSessionAdapter::CheckPIA_FirstPacket(bool is_orig, const IP_Hdr* ip) + { + if ( is_orig && ! (first_packet_seen & ORIG) ) + { + auto* pia = static_cast(Conn()->GetPrimaryPIA()); + if ( pia ) + pia->FirstPacket(is_orig, ip); + first_packet_seen |= ORIG; + } + + if ( ! is_orig && ! (first_packet_seen & RESP) ) + { + auto* pia = static_cast(Conn()->GetPrimaryPIA()); + if ( pia ) + pia->FirstPacket(is_orig, ip); + first_packet_seen |= RESP; + } + } + +uint64_t TCPSessionAdapter::get_relative_seq(const analyzer::tcp::TCP_Endpoint* endpoint, + uint32_t cur_base, uint32_t last, + uint32_t wraps, bool* underflow) + { + int32_t delta = seq_delta(cur_base, last); + + if ( delta < 0 ) + { + if ( wraps && cur_base > last ) + // Seems to be a part of a previous 32-bit sequence space. + --wraps; + } + + else if ( delta > 0 ) + { + if ( cur_base < last ) + // The sequence space wrapped around. + ++wraps; + } + + if ( wraps == 0 ) + { + delta = seq_delta(cur_base, endpoint->StartSeq()); + + if ( underflow && delta < 0 ) + *underflow = true; + + return delta; + } + + return endpoint->ToRelativeSeqSpace(cur_base, wraps); + } + +int TCPSessionAdapter::get_segment_len(int payload_len, analyzer::tcp::TCP_Flags flags) + { + int seg_len = payload_len; + + if ( flags.SYN() ) + // SYN consumes a byte of sequence space. + ++seg_len; + + if ( flags.FIN() ) + // FIN consumes a bytes of sequence space. + ++seg_len; + + if ( flags.RST() ) + // 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. + seg_len -= payload_len; + + return seg_len; + } + +static void init_endpoint(analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Flags flags, + uint32_t first_seg_seq, uint32_t last_seq, double t) + { + switch ( endpoint->state ) { + case analyzer::tcp::TCP_ENDPOINT_INACTIVE: + if ( flags.SYN() ) + { + endpoint->InitAckSeq(first_seg_seq); + endpoint->InitStartSeq(first_seg_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->InitAckSeq(first_seg_seq - 1); + endpoint->InitStartSeq(first_seg_seq - 1); + // But ensure first packet is not marked duplicate + last_seq = first_seg_seq; + } + + endpoint->InitLastSeq(last_seq); + endpoint->start_time = t; + break; + + case analyzer::tcp::TCP_ENDPOINT_SYN_SENT: + case analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT: + if ( flags.SYN() && first_seg_seq != endpoint->StartSeq() ) + { + endpoint->Conn()->Weird("SYN_seq_jump"); + endpoint->InitStartSeq(first_seg_seq); + endpoint->InitAckSeq(first_seg_seq); + endpoint->InitLastSeq(last_seq); + } + break; + + case analyzer::tcp::TCP_ENDPOINT_ESTABLISHED: + case analyzer::tcp::TCP_ENDPOINT_PARTIAL: + if ( flags.SYN() ) + { + if ( endpoint->Size() > 0 ) + endpoint->Conn()->Weird("SYN_inside_connection"); + + if ( first_seg_seq != endpoint->StartSeq() ) + endpoint->Conn()->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->InitStartSeq(first_seg_seq); + endpoint->InitAckSeq(first_seg_seq); + endpoint->InitLastSeq(last_seq); + } + break; + + case analyzer::tcp::TCP_ENDPOINT_RESET: + if ( flags.SYN() ) + { + if ( endpoint->prev_state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) + { + // Seq. numbers were initialized by a RST packet from this + // endpoint, but now that a SYN is seen from it, that could mean + // the earlier RST was spoofed/injected, so re-initialize. This + // mostly just helps prevent misrepresentations of payload sizes + // that are based on bad initial sequence values. + endpoint->InitStartSeq(first_seg_seq); + endpoint->InitAckSeq(first_seg_seq); + endpoint->InitLastSeq(last_seq); + } + } + break; + + default: + break; + } + } + +static void init_peer(analyzer::tcp::TCP_Endpoint* peer, analyzer::tcp::TCP_Endpoint* endpoint, + analyzer::tcp::TCP_Flags flags, uint32_t ack_seq) + { + if ( ! flags.SYN() && ! flags.FIN() && ! flags.RST() ) + { + if ( endpoint->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT || + endpoint->state == analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT || + endpoint->state == analyzer::tcp::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 ( ! endpoint->Conn()->DidWeird() ) + endpoint->Conn()->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->InitStartSeq(ack_seq - 1); + peer->InitAckSeq(ack_seq - 1); + peer->InitLastSeq(ack_seq - 1); + } + +static void update_ack_seq(analyzer::tcp::TCP_Endpoint* endpoint, uint32_t ack_seq) + { + int32_t delta_ack = seq_delta(ack_seq, endpoint->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 ) + endpoint->UpdateAckSeq(ack_seq); + } + +// Returns the difference between last_seq and the last sequence +// seen by the endpoint (may be negative). +static int32_t update_last_seq(analyzer::tcp::TCP_Endpoint* endpoint, uint32_t last_seq, + analyzer::tcp::TCP_Flags flags, int len) + { + int32_t 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 == analyzer::tcp::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 && len > 0 ) + endpoint->DidRxmit(); + + return delta_last; + } + +void TCPSessionAdapter::DeliverPacket(int len, const u_char* data, bool is_orig, + uint64_t seq, const IP_Hdr* ip, int caplen) + { + packet_analysis::IP::SessionAdapter::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, std::min(len, caplen)); + + analyzer::tcp::TCP_Endpoint* endpoint = is_orig ? orig : resp; + analyzer::tcp::TCP_Endpoint* peer = endpoint->peer; + + if ( ! ValidateChecksum(ip, tp, endpoint, len, caplen) ) + return; + + uint32_t tcp_hdr_len = data - (const u_char*) tp; + analyzer::tcp::TCP_Flags flags(tp); + SetPartialStatus(flags, endpoint->IsOrig()); + + uint32_t base_seq = ntohl(tp->th_seq); + uint32_t ack_seq = ntohl(tp->th_ack); + + int seg_len = get_segment_len(len, flags); + uint32_t seq_one_past_segment = base_seq + seg_len; + + init_endpoint(endpoint, flags, base_seq, seq_one_past_segment, + run_state::current_timestamp); + + bool seq_underflow = false; + uint64_t rel_seq = get_relative_seq(endpoint, base_seq, endpoint->LastSeq(), + endpoint->SeqWraps(), &seq_underflow); + + if ( seq_underflow && ! flags.RST() ) + // Can't tell if if this is a retransmit/out-of-order or something + // before the sequence Bro initialized the endpoint at or the TCP is + // just broken and sending garbage sequences. In either case, some + // standard analysis doesn't apply (e.g. reassembly). + Weird("TCP_seq_underflow_or_misorder"); + + update_history(flags, endpoint, rel_seq, len); + update_window(endpoint, ntohs(tp->th_win), base_seq, ack_seq, flags); + + if ( ! orig->did_close || ! resp->did_close ) + Conn()->SetLastTime(run_state::current_timestamp); + + if ( flags.SYN() ) + { + SynWeirds(flags, endpoint, len); + RecordVal* SYN_vals = build_syn_packet_val(is_orig, ip, tp); + init_window(endpoint, peer, flags, SYN_vals->GetFieldAs(5), + base_seq, ack_seq); + + if ( connection_SYN_packet ) + EnqueueConnEvent(connection_SYN_packet, + ConnVal(), + IntrusivePtr{NewRef{}, SYN_vals} + ); + + Unref(SYN_vals); + } + + if ( flags.FIN() ) + { + ++endpoint->FIN_cnt; + + if ( endpoint->FIN_cnt >= detail::tcp_storm_thresh && run_state::current_timestamp < + endpoint->last_time + detail::tcp_storm_interarrival_thresh ) + Weird("FIN_storm"); + + endpoint->FIN_seq = rel_seq + seg_len; + } + + if ( flags.RST() ) + { + ++endpoint->RST_cnt; + + if ( endpoint->RST_cnt >= detail::tcp_storm_thresh && run_state::current_timestamp < + endpoint->last_time + detail::tcp_storm_interarrival_thresh ) + Weird("RST_storm"); + + // This now happens often enough that it's + // not in the least interesting. + //if ( len > 0 ) + // Weird("RST_with_data"); + + PacketWithRST(); + } + + uint64_t rel_ack = 0; + + if ( flags.ACK() ) + { + if ( is_orig && ! seen_first_ACK && + (endpoint->state == analyzer::tcp::TCP_ENDPOINT_ESTABLISHED || + endpoint->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT) ) + { + seen_first_ACK = 1; + Event(connection_first_ACK); + } + + if ( peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) + { + rel_ack = 1; + init_peer(peer, endpoint, flags, ack_seq); + } + else + { + bool ack_underflow = false; + rel_ack = get_relative_seq(peer, ack_seq, peer->AckSeq(), + peer->AckWraps(), &ack_underflow); + + if ( ack_underflow ) + { + rel_ack = 0; + Weird("TCP_ack_underflow_or_misorder"); + } + else if ( ! flags.RST() ) + // Don't trust ack's in RST packets. + update_ack_seq(peer, ack_seq); + } + } + + int32_t delta_last = update_last_seq(endpoint, seq_one_past_segment, flags, len); + endpoint->last_time = run_state::current_timestamp; + + bool do_close; + bool gen_event; + UpdateStateMachine(run_state::current_timestamp, endpoint, peer, base_seq, ack_seq, + len, delta_last, is_orig, flags, do_close, gen_event); + + if ( flags.ACK() ) + // We wait on doing this until we've updated the state + // machine so that if the ack reveals a content gap, + // we can tell whether it came at the very end of the + // connection (in a FIN or RST). Those gaps aren't + // reliable - especially those for RSTs - and we refrain + // from flagging them in the connection history. + peer->AckReceived(rel_ack); + + if ( tcp_packet ) + GeneratePacketEvent(rel_seq, rel_ack, data, len, caplen, is_orig, + flags); + + if ( (tcp_option || tcp_options) && tcp_hdr_len > sizeof(*tp) ) + ParseTCPOptions(tp, is_orig); + + // PIA/signature matching state needs to be initialized before + // processing/reassembling any TCP data, since that processing may + // itself try to perform signature matching. Also note that a SYN + // packet may technically carry data (see RFC793 Section 3.4 and also + // TCP Fast Open). + CheckPIA_FirstPacket(is_orig, ip); + + if ( DEBUG_tcp_data_sent ) + { + DEBUG_MSG("%.6f before DataSent: len=%d caplen=%d skip=%d\n", + run_state::network_time, len, caplen, Skipping()); + } + + uint64_t rel_data_seq = flags.SYN() ? rel_seq + 1 : rel_seq; + + int need_contents = 0; + if ( len > 0 && (caplen >= len || packet_children.size()) && + ! flags.RST() && ! Skipping() && ! seq_underflow ) + need_contents = DeliverData(run_state::current_timestamp, data, len, caplen, ip, + tp, endpoint, rel_data_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. + analyzer::analyzer_list::iterator next; + + for ( auto i = packet_children.begin(); i != packet_children.end(); /* nop */ ) + { + auto child = *i; + + if ( child->IsFinished() || child->Removing() ) + { + if ( child->Removing() ) + child->Done(); + + DBG_LOG(DBG_ANALYZER, "%s deleted child %s", + fmt_analyzer(this).c_str(), fmt_analyzer(child).c_str()); + i = packet_children.erase(i); + delete child; + } + else + { + child->NextPacket(len, data, is_orig, rel_data_seq, ip, caplen); + ++i; + } + } + + if ( ! reassembling ) + ForwardPacket(len, data, is_orig, rel_data_seq, ip, caplen); + } + +void TCPSessionAdapter::DeliverStream(int len, const u_char* data, bool orig) + { + Analyzer::DeliverStream(len, data, orig); + } + +void TCPSessionAdapter::Undelivered(uint64_t seq, int len, bool is_orig) + { + Analyzer::Undelivered(seq, len, orig); + } + +void TCPSessionAdapter::FlipRoles() + { + Analyzer::FlipRoles(); + + session_mgr->tcp_stats.FlipState(orig->state, resp->state); + analyzer::tcp::TCP_Endpoint* tmp_ep = resp; + resp = orig; + orig = tmp_ep; + orig->is_orig = !orig->is_orig; + resp->is_orig = !resp->is_orig; + } + +void TCPSessionAdapter::UpdateConnVal(RecordVal *conn_val) + { + auto orig_endp_val = conn_val->GetFieldAs("orig"); + auto resp_endp_val = conn_val->GetFieldAs("resp"); + + orig_endp_val->Assign(0, orig->Size()); + orig_endp_val->Assign(1, orig->state); + resp_endp_val->Assign(0, resp->Size()); + resp_endp_val->Assign(1, resp->state); + + // 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); + } + +int TCPSessionAdapter::ParseTCPOptions(const struct tcphdr* tcp, bool is_orig) + { + // 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; + std::vector opts; + + 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. + break; + + else + opt_len = options[1]; + + if ( opt_len == 0 ) + break; // trashed length field + + if ( options + opt_len > opt_end ) + // No room for rest of option. + break; + + opts.emplace_back(options); + options += opt_len; + + if ( opt == TCPOPT_EOL ) + // All done - could flag if more junk left over .... + break; + } + + if ( tcp_option ) + for ( const auto& o : opts ) + { + auto kind = o[0]; + auto length = kind < 2 ? 1 : o[1]; + EnqueueConnEvent(tcp_option, + ConnVal(), + val_mgr->Bool(is_orig), + val_mgr->Count(kind), + val_mgr->Count(length) + ); + } + + if ( tcp_options ) + { + auto option_list = make_intrusive(BifType::Vector::TCP::OptionList); + + auto add_option_data = [](const RecordValPtr& rv, const u_char* odata, int olen) + { + if ( olen <= 2 ) + return; + + auto data_len = olen - 2; + auto data = reinterpret_cast(odata + 2); + rv->Assign(2, make_intrusive(data_len, data)); + }; + + for ( const auto& o : opts ) + { + auto kind = o[0]; + auto length = kind < 2 ? 1 : o[1]; + auto option_record = make_intrusive(BifType::Record::TCP::Option); + option_list->Assign(option_list->Size(), option_record); + option_record->Assign(0, kind); + option_record->Assign(1, length); + + switch ( kind ) { + case 2: + // MSS + if ( length == 4 ) + { + auto mss = ntohs(*reinterpret_cast(o + 2)); + option_record->Assign(3, mss); + } + else + { + add_option_data(option_record, o, length); + Weird("tcp_option_mss_invalid_len", util::fmt("%d", length)); + } + break; + + case 3: + // window scale + if ( length == 3 ) + { + auto scale = o[2]; + option_record->Assign(4, scale); + } + else + { + add_option_data(option_record, o, length); + Weird("tcp_option_window_scale_invalid_len", util::fmt("%d", length)); + } + break; + + case 4: + // sack permitted (implicit boolean) + if ( length != 2 ) + { + add_option_data(option_record, o, length); + Weird("tcp_option_sack_invalid_len", util::fmt("%d", length)); + } + break; + + case 5: + // SACK blocks (1-4 pairs of 32-bit begin+end pointers) + if ( length == 10 || length == 18 || + length == 26 || length == 34 ) + { + auto p = reinterpret_cast(o + 2); + auto num_pointers = (length - 2) / 4; + auto vt = id::index_vec; + auto sack = make_intrusive(std::move(vt)); + + for ( auto i = 0; i < num_pointers; ++i ) + sack->Assign(sack->Size(), val_mgr->Count(ntohl(p[i]))); + + option_record->Assign(5, sack); + } + else + { + add_option_data(option_record, o, length); + Weird("tcp_option_sack_blocks_invalid_len", util::fmt("%d", length)); + } + break; + + case 8: + // timestamps + if ( length == 10 ) + { + auto send = ntohl(*reinterpret_cast(o + 2)); + auto echo = ntohl(*reinterpret_cast(o + 6)); + option_record->Assign(6, send); + option_record->Assign(7, echo); + } + else + { + add_option_data(option_record, o, length); + Weird("tcp_option_timestamps_invalid_len", util::fmt("%d", length)); + } + break; + + default: + add_option_data(option_record, o, length); + break; + } + } + + EnqueueConnEvent(tcp_options, + ConnVal(), + val_mgr->Bool(is_orig), + std::move(option_list) + ); + } + + if ( options < opt_end ) + return -1; + + return 0; + } + +void TCPSessionAdapter::AttemptTimer(double /* t */) + { + if ( ! is_active ) + return; + + if ( (orig->state == analyzer::tcp::TCP_ENDPOINT_SYN_SENT || + orig->state == analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT) && + resp->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) + { + Event(connection_attempt); + is_active = 0; + + // All done with this connection. + session_mgr->Remove(Conn()); + } + } + +void TCPSessionAdapter::PartialCloseTimer(double /* t */) + { + if ( ! is_active ) + return; + + if ( orig->state != analyzer::tcp::TCP_ENDPOINT_INACTIVE && + resp->state != analyzer::tcp::TCP_ENDPOINT_INACTIVE && + (! orig->did_close || ! resp->did_close) ) + { + if ( orig->state == analyzer::tcp::TCP_ENDPOINT_RESET || + resp->state == analyzer::tcp::TCP_ENDPOINT_RESET ) + // Presumably the RST is what caused the partial + // close. Don't report it. + return; + + Event(connection_partial_close); + session_mgr->Remove(Conn()); + } + } + +void TCPSessionAdapter::ExpireTimer(double t) + { + if ( ! is_active ) + return; + + if ( Conn()->LastTime() + zeek::detail::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 == analyzer::tcp::TCP_ENDPOINT_RESET || + resp->state == analyzer::tcp::TCP_ENDPOINT_RESET) || + (orig->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE || + resp->state == analyzer::tcp::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; + session_mgr->Remove(Conn()); + return; + } + } + + if ( resp->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) + { + if ( orig->state == analyzer::tcp::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); + session_mgr->Remove(Conn()); + return; + } + } + } + + // Connection still active, so reschedule timer. + // ### if PQ_Element's were Obj's, could just Ref the timer + // and adjust its value here, instead of creating a new timer. + ADD_ANALYZER_TIMER(&TCPSessionAdapter::ExpireTimer, t + zeek::detail::tcp_session_timer, + false, zeek::detail::TIMER_TCP_EXPIRE); + } + +void TCPSessionAdapter::ResetTimer(double /* t */) + { + if ( ! is_active ) + return; + + if ( ! BothClosed() ) + ConnectionReset(); + + session_mgr->Remove(Conn()); + } + +void TCPSessionAdapter::DeleteTimer(double /* t */) + { + session_mgr->Remove(Conn()); + } + +void TCPSessionAdapter::ConnDeleteTimer(double t) + { + Conn()->DeleteTimer(t); + } + +void TCPSessionAdapter::SetContentsFile(unsigned int direction, FilePtr f) + { + if ( direction == CONTENTS_NONE ) + { + orig->SetContentsFile(nullptr); + resp->SetContentsFile(nullptr); + } + + else + { + if ( direction == CONTENTS_ORIG || direction == CONTENTS_BOTH ) + orig->SetContentsFile(f); + if ( direction == CONTENTS_RESP || direction == CONTENTS_BOTH ) + resp->SetContentsFile(f); + } + } + +FilePtr TCPSessionAdapter::GetContentsFile(unsigned int direction) const + { + switch ( direction ) { + case CONTENTS_NONE: + return nullptr; + + 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 nullptr; + else + return orig->GetContentsFile(); + + default: + break; + } + + reporter->Error("bad direction %u in TCPSessionAdapter::GetContentsFile", + direction); + return nullptr; + } + +void TCPSessionAdapter::ConnectionClosed(analyzer::tcp::TCP_Endpoint* endpoint, + analyzer::tcp::TCP_Endpoint* peer, + bool gen_event) + { + const analyzer::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 = true; + + int close_complete = + endpoint->state == analyzer::tcp::TCP_ENDPOINT_RESET || + peer->did_close || + peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE; + + if ( DEBUG_tcp_connection_close ) + { + DEBUG_MSG("%.6f close_complete=%d tcp_close_delay=%f\n", + run_state::network_time, close_complete, detail::tcp_close_delay); + } + + if ( close_complete ) + { + if ( endpoint->prev_state != analyzer::tcp::TCP_ENDPOINT_INACTIVE || + peer->state != analyzer::tcp::TCP_ENDPOINT_INACTIVE ) + { + if ( deferred_gen_event ) + { + gen_event = true; + deferred_gen_event = 0; // clear flag + } + + // We have something interesting to report. + if ( gen_event ) + { + if ( peer->state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) + ConnectionFinished(true); + else + ConnectionFinished(false); + } + } + + CancelTimers(); + + // Note, even if tcp_close_delay is zero, we can't + // simply do: + // + // session_mgr->Remove(this); + // + // here, because that would cause the object to be + // deleted out from under us. + if ( zeek::detail::tcp_close_delay != 0.0 ) + ADD_ANALYZER_TIMER(&TCPSessionAdapter::ConnDeleteTimer, + Conn()->LastTime() + zeek::detail::tcp_close_delay, false, + zeek::detail::TIMER_CONN_DELETE); + else + ADD_ANALYZER_TIMER(&TCPSessionAdapter::DeleteTimer, Conn()->LastTime(), false, + zeek::detail::TIMER_TCP_DELETE); + } + + else + { // We haven't yet seen a full close. + if ( endpoint->prev_state == analyzer::tcp::TCP_ENDPOINT_INACTIVE ) + { // First time we've seen anything from this side. + if ( connection_partial_close ) + ADD_ANALYZER_TIMER(&TCPSessionAdapter::PartialCloseTimer, + Conn()->LastTime() + zeek::detail::tcp_partial_close_delay, false, + zeek::detail::TIMER_TCP_PARTIAL_CLOSE ); + } + + else + { + // Create a timer to look for the other side closing, + // too. + ADD_ANALYZER_TIMER(&TCPSessionAdapter::ExpireTimer, + Conn()->LastTime() + zeek::detail::tcp_session_timer, false, + zeek::detail::TIMER_TCP_EXPIRE); + } + } + } + +void TCPSessionAdapter::ConnectionFinished(bool half_finished) + { + const analyzer::analyzer_list& children(GetChildren()); + LOOP_OVER_CONST_CHILDREN(i) + // Again, nasty - see TCPSessionAdapter::ConnectionClosed. + static_cast + (*i)->ConnectionFinished(half_finished); + + if ( half_finished ) + Event(connection_half_finished); + else + Event(connection_finished); + + is_active = 0; + } + +void TCPSessionAdapter::ConnectionReset() + { + Event(connection_reset); + + const analyzer::analyzer_list& children(GetChildren()); + LOOP_OVER_CONST_CHILDREN(i) + static_cast(*i)->ConnectionReset(); + + is_active = 0; + } + +bool TCPSessionAdapter::HadGap(bool is_orig) const + { + analyzer::tcp::TCP_Endpoint* endp = is_orig ? orig : resp; + return endp && endp->HadGap(); + } + +void TCPSessionAdapter::AddChildPacketAnalyzer(analyzer::Analyzer* a) + { + DBG_LOG(DBG_ANALYZER, "%s added packet child %s", + this->GetAnalyzerName(), a->GetAnalyzerName()); + + packet_children.push_back(a); + a->SetParent(this); + } + +bool TCPSessionAdapter::DataPending(analyzer::tcp::TCP_Endpoint* closing_endp) + { + if ( Skipping() ) + return false; + + return closing_endp->DataPending(); + } + +void TCPSessionAdapter::EndpointEOF(analyzer::tcp::TCP_Reassembler* endp) + { + if ( connection_EOF ) + EnqueueConnEvent(connection_EOF, + ConnVal(), + val_mgr->Bool(endp->IsOrig()) + ); + + const analyzer::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 TCPSessionAdapter::PacketWithRST() + { + const analyzer::analyzer_list& children(GetChildren()); + LOOP_OVER_CONST_CHILDREN(i) + static_cast(*i)->PacketWithRST(); + } + +bool TCPSessionAdapter::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; + + analyzer::tcp::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 != analyzer::tcp::TCP_ENDPOINT_INACTIVE ) + // Not a half connection. + return false; + + conn_orig = resp; + } + + if ( ! IsClosed() ) + { + uint32_t base_seq = ntohl(tp->th_seq); + if ( base_seq == conn_orig->StartSeq() ) + return false; + + if ( (tp->th_flags & TH_ACK) == 0 && + conn_orig->state == analyzer::tcp::TCP_ENDPOINT_SYN_ACK_SENT && + resp->state == analyzer::tcp::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 == analyzer::tcp::TCP_ENDPOINT_SYN_SENT ) + Weird("SYN_seq_jump"); + else + Weird("active_connection_reuse"); + } + + else if ( (orig->IsActive() || resp->IsActive()) && + orig->state != analyzer::tcp::TCP_ENDPOINT_RESET && + resp->state != analyzer::tcp::TCP_ENDPOINT_RESET ) + Weird("active_connection_reuse"); + + else if ( t - Conn()->LastTime() < zeek::detail::tcp_connection_linger && + orig->state != analyzer::tcp::TCP_ENDPOINT_RESET && + resp->state != analyzer::tcp::TCP_ENDPOINT_RESET ) + Weird("premature_connection_reuse"); + + return true; + } + +void TCPSessionAdapter::AddExtraAnalyzers(Connection* conn) + { + static analyzer::Tag analyzer_connsize = analyzer_mgr->GetComponentTag("CONNSIZE"); + static analyzer::Tag analyzer_stepping = analyzer_mgr->GetComponentTag("STEPPINGSTONE"); + static analyzer::Tag analyzer_tcpstats = analyzer_mgr->GetComponentTag("TCPSTATS"); + + // We have to decide whether to reassamble the stream. + // We turn it on right away if we already have an app-layer + // analyzer, reassemble_first_packets is true, or the user + // asks us to do so. In all other cases, reassembly may + // be turned on later by the TCP PIA. + + bool reass = ( ! GetChildren().empty() ) || + zeek::detail::dpd_reassemble_first_packets || + zeek::detail::tcp_content_deliver_all_orig || + zeek::detail::tcp_content_deliver_all_resp; + + if ( tcp_contents && ! reass ) + { + static auto tcp_content_delivery_ports_orig = id::find_val("tcp_content_delivery_ports_orig"); + static auto tcp_content_delivery_ports_resp = id::find_val("tcp_content_delivery_ports_resp"); + const auto& dport = val_mgr->Port(ntohs(conn->RespPort()), TRANSPORT_TCP); + + if ( ! reass ) + reass = (bool)tcp_content_delivery_ports_orig->FindOrDefault(dport); + + if ( ! reass ) + reass = (bool)tcp_content_delivery_ports_resp->FindOrDefault(dport); + } + + if ( reass ) + EnableReassembly(); + + if ( analyzer_mgr->IsEnabled(analyzer_stepping) ) + { + // Add a SteppingStone analyzer if requested. The port + // should really not be hardcoded here, but as it can + // handle non-reassembled data, it doesn't really fit into + // our general framing ... Better would be to turn it + // on *after* we discover we have interactive traffic. + uint16_t resp_port = ntohs(conn->RespPort()); + if ( resp_port == 22 || resp_port == 23 || resp_port == 513 ) + { + static auto stp_skip_src = id::find_val("stp_skip_src"); + auto src = make_intrusive(conn->OrigAddr()); + + if ( ! stp_skip_src->FindOrDefault(src) ) + AddChildAnalyzer(new analyzer::stepping_stone::SteppingStone_Analyzer(conn), false); + } + } + + if ( analyzer_mgr->IsEnabled(analyzer_tcpstats) ) + // Add TCPStats analyzer. This needs to see packets so + // we cannot add it as a normal child. + AddChildPacketAnalyzer(new analyzer::tcp::TCPStats_Analyzer(conn)); + + if ( analyzer_mgr->IsEnabled(analyzer_connsize) ) + // Add ConnSize analyzer. Needs to see packets, not stream. + AddChildPacketAnalyzer(new analyzer::conn_size::ConnSize_Analyzer(conn)); + } diff --git a/src/packet_analysis/protocol/tcp/TCPSessionAdapter.h b/src/packet_analysis/protocol/tcp/TCPSessionAdapter.h new file mode 100644 index 0000000000..014c4c9bc2 --- /dev/null +++ b/src/packet_analysis/protocol/tcp/TCPSessionAdapter.h @@ -0,0 +1,197 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#pragma once + +#include "zeek/packet_analysis/Analyzer.h" +#include "zeek/packet_analysis/Component.h" +#include "zeek/packet_analysis/protocol/ip/SessionAdapter.h" +#include "zeek/session/Manager.h" +#include "zeek/analyzer/protocol/tcp/TCP_Flags.h" + +namespace zeek::analyzer::pia { class PIA_TCP; } +namespace zeek::analyzer::tcp { + +class TCP_Endpoint; +class TCP_Reassembler; + +} + +namespace zeek::packet_analysis::TCP { + +class TCPAnalyzer; + +class TCPSessionAdapter final : public packet_analysis::IP::SessionAdapter { +public: + explicit TCPSessionAdapter(Connection* conn); + ~TCPSessionAdapter() override; + + void EnableReassembly(); + + // Add a child analyzer that will always get the packets, + // independently of whether we do any reassembly. + void AddChildPacketAnalyzer(analyzer::Analyzer* a); + + Analyzer* FindChild(analyzer::ID id) override; + Analyzer* FindChild(analyzer::Tag tag) override; + bool RemoveChildAnalyzer(analyzer::ID id) override; + + // True if the connection has closed in some sense, false otherwise. + bool IsClosed() const { return orig->did_close || resp->did_close; } + bool BothClosed() const { return orig->did_close && resp->did_close; } + + bool IsPartial() const { return is_partial; } + + bool HadGap(bool orig) const; + + analyzer::tcp::TCP_Endpoint* Orig() const { return orig; } + analyzer::tcp::TCP_Endpoint* Resp() const { return resp; } + int OrigState() const { return orig->state; } + int RespState() const { return resp->state; } + int OrigPrevState() const { return orig->prev_state; } + int RespPrevState() const { return resp->prev_state; } + uint32_t OrigSeq() const { return orig->LastSeq(); } + uint32_t RespSeq() const { return resp->LastSeq(); } + + // True if either endpoint still has pending data. closing_endp + // is an endpoint that has indicated it is closing (i.e., for + // which we have seen a FIN) - for it, data is pending unless + // everything's been delivered up to the FIN. For its peer, + // the test is whether it has any outstanding, un-acked data. + bool DataPending(analyzer::tcp::TCP_Endpoint* closing_endp); + + void SetContentsFile(unsigned int direction, FilePtr f) override; + FilePtr GetContentsFile(unsigned int direction) const override; + + // From Analyzer.h + void UpdateConnVal(RecordVal *conn_val) override; + + int ParseTCPOptions(const struct tcphdr* tcp, bool is_orig); + + static analyzer::Analyzer* Instantiate(Connection* conn) + { return new TCPSessionAdapter(conn); } + + void AddExtraAnalyzers(Connection* conn) override; + +protected: + friend class analyzer::tcp::TCP_ApplicationAnalyzer; + friend class analyzer::tcp::TCP_Reassembler; + friend class analyzer::pia::PIA_TCP; + friend class packet_analysis::TCP::TCPAnalyzer; + + // Analyzer interface. + void Init() override; + void Done() override; + void DeliverPacket(int len, const u_char* data, bool orig, uint64_t seq, + const IP_Hdr* ip, int caplen) override; + void DeliverStream(int len, const u_char* data, bool orig) override; + void Undelivered(uint64_t seq, int len, bool orig) override; + void FlipRoles() override; + bool IsReuse(double t, const u_char* pkt) override; + + // Returns the TCP header pointed to by data (which we assume is + // aligned), updating data, len & caplen. Returns nil if the header + // isn't fully present. + const struct tcphdr* ExtractTCP_Header(const u_char*& data, int& len, + int& caplen); + + // Returns true if the checksum is valid, false if not (and in which + // case also updates the status history of the endpoint). + bool ValidateChecksum(const IP_Hdr* ip, const struct tcphdr* tp, analyzer::tcp::TCP_Endpoint* endpoint, + int len, int caplen); + + void SetPartialStatus(analyzer::tcp::TCP_Flags flags, bool is_orig); + + // Update the state machine of the TCPs based on the activity. This + // includes our pseudo-states such as TCP_ENDPOINT_PARTIAL. + // + // On return, do_close is true if we should consider the connection + // as closed, and gen_event if we shouuld generate an event about + // this fact. + void UpdateStateMachine(double t, + analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer, + uint32_t base_seq, uint32_t ack_seq, + int len, int32_t delta_last, bool is_orig, analyzer::tcp::TCP_Flags flags, + bool& do_close, bool& gen_event); + + void UpdateInactiveState(double t, + analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer, + uint32_t base_seq, uint32_t ack_seq, + int len, bool is_orig, analyzer::tcp::TCP_Flags flags, + bool& do_close, bool& gen_event); + + void UpdateSYN_SentState(analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer, + int len, bool is_orig, analyzer::tcp::TCP_Flags flags, + bool& do_close, bool& gen_event); + + void UpdateEstablishedState(analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer, + analyzer::tcp::TCP_Flags flags, bool& do_close, bool& gen_event); + + void UpdateClosedState(double t, analyzer::tcp::TCP_Endpoint* endpoint, + int32_t delta_last, analyzer::tcp::TCP_Flags flags, + bool& do_close); + + void UpdateResetState(int len, analyzer::tcp::TCP_Flags flags); + + void GeneratePacketEvent(uint64_t rel_seq, uint64_t rel_ack, + const u_char* data, int len, int caplen, + bool is_orig, analyzer::tcp::TCP_Flags flags); + + bool DeliverData(double t, const u_char* data, int len, int caplen, + const IP_Hdr* ip, const struct tcphdr* tp, + analyzer::tcp::TCP_Endpoint* endpoint, uint64_t rel_data_seq, + bool is_orig, analyzer::tcp::TCP_Flags flags); + + void CheckRecording(bool need_contents, analyzer::tcp::TCP_Flags flags); + void CheckPIA_FirstPacket(bool is_orig, const IP_Hdr* ip); + + friend class session::detail::Timer; + void AttemptTimer(double t); + void PartialCloseTimer(double t); + void ExpireTimer(double t); + void ResetTimer(double t); + void DeleteTimer(double t); + void ConnDeleteTimer(double t); + + void EndpointEOF(analyzer::tcp::TCP_Reassembler* endp); + void ConnectionClosed(analyzer::tcp::TCP_Endpoint* endpoint, + analyzer::tcp::TCP_Endpoint* peer, bool gen_event); + void ConnectionFinished(bool half_finished); + void ConnectionReset(); + void PacketWithRST(); + + void SetReassembler(analyzer::tcp::TCP_Reassembler* rorig, analyzer::tcp::TCP_Reassembler* rresp); + + // A couple utility functions that may also be useful to derived analyzers. + static uint64_t get_relative_seq(const analyzer::tcp::TCP_Endpoint* endpoint, + uint32_t cur_base, uint32_t last, + uint32_t wraps, bool* underflow = nullptr); + + static int get_segment_len(int payload_len, analyzer::tcp::TCP_Flags flags); + +private: + + void SynWeirds(analyzer::tcp::TCP_Flags flags, analyzer::tcp::TCP_Endpoint* endpoint, int data_len) const; + + analyzer::tcp::TCP_Endpoint* orig; + analyzer::tcp::TCP_Endpoint* resp; + + analyzer::analyzer_list packet_children; + + unsigned int first_packet_seen: 2; + unsigned int reassembling: 1; + unsigned int is_partial: 1; + unsigned int is_active: 1; + unsigned int finished: 1; + + // Whether we're waiting on final data delivery before closing + // this connection. + unsigned int close_deferred: 1; + + // Whether to generate an event when we finally do close it. + unsigned int deferred_gen_event: 1; + + // Whether we have seen the first ACK from the originator. + unsigned int seen_first_ACK: 1; +}; + +} // namespace zeek::packet_analysis::tcp diff --git a/src/zeek-setup.cc b/src/zeek-setup.cc index e349b77de4..4e1d046b56 100644 --- a/src/zeek-setup.cc +++ b/src/zeek-setup.cc @@ -595,7 +595,6 @@ SetupResult setup(int argc, char** argv, Options* zopts) trigger_mgr = new trigger::Manager(); plugin_mgr->InitPreScript(); - analyzer_mgr->InitPreScript(); file_mgr->InitPreScript(); zeekygen_mgr->InitPreScript();