mirror of
https://github.com/zeek/zeek.git
synced 2025-10-04 23:58:20 +00:00
323 lines
8.5 KiB
C++
323 lines
8.5 KiB
C++
// See the file "COPYING" in the main distribution directory for copyright.
|
|
|
|
#include <errno.h>
|
|
|
|
#include "Net.h"
|
|
#include "NetVar.h"
|
|
#include "analyzer/protocol/tcp/TCP.h"
|
|
#include "TCP_Reassembler.h"
|
|
#include "Reporter.h"
|
|
#include "Sessions.h"
|
|
#include "Event.h"
|
|
#include "File.h"
|
|
#include "Val.h"
|
|
|
|
#include "events.bif.h"
|
|
|
|
namespace zeek::analyzer::tcp {
|
|
|
|
TCP_Endpoint::TCP_Endpoint(TCP_Analyzer* arg_analyzer, bool arg_is_orig)
|
|
{
|
|
contents_processor = nullptr;
|
|
prev_state = state = TCP_ENDPOINT_INACTIVE;
|
|
peer = nullptr;
|
|
start_time = last_time = 0.0;
|
|
start_seq = last_seq = ack_seq = 0;
|
|
seq_wraps = ack_wraps = 0;
|
|
window = 0;
|
|
window_scale = 0;
|
|
window_seq = window_ack_seq = 0;
|
|
contents_start_seq = 0;
|
|
FIN_seq = 0;
|
|
SYN_cnt = FIN_cnt = RST_cnt = 0;
|
|
did_close = false;
|
|
tcp_analyzer = arg_analyzer;
|
|
is_orig = arg_is_orig;
|
|
|
|
gap_cnt = chk_cnt = rxmt_cnt = win0_cnt = 0;
|
|
gap_thresh = chk_thresh = rxmt_thresh = win0_thresh = 1;
|
|
|
|
hist_last_SYN = hist_last_FIN = hist_last_RST = 0;
|
|
|
|
src_addr = is_orig ? Conn()->RespAddr() : Conn()->OrigAddr();
|
|
dst_addr = is_orig ? Conn()->OrigAddr() : Conn()->RespAddr();
|
|
|
|
checksum_base = ones_complement_checksum(src_addr, 0);
|
|
checksum_base = ones_complement_checksum(dst_addr, checksum_base);
|
|
// Note, for IPv6, strictly speaking this field is 32 bits
|
|
// rather than 16 bits. But because the upper bits are all zero,
|
|
// we get the same checksum either way. The same applies to
|
|
// later when we add in the data length in ValidChecksum().
|
|
checksum_base += htons(IPPROTO_TCP);
|
|
}
|
|
|
|
TCP_Endpoint::~TCP_Endpoint()
|
|
{
|
|
delete contents_processor;
|
|
}
|
|
|
|
zeek::Connection* TCP_Endpoint::Conn() const
|
|
{
|
|
return tcp_analyzer->Conn();
|
|
}
|
|
|
|
void TCP_Endpoint::Done()
|
|
{
|
|
if ( contents_processor )
|
|
contents_processor->Done();
|
|
}
|
|
|
|
void TCP_Endpoint::SetPeer(TCP_Endpoint* p)
|
|
{
|
|
peer = p;
|
|
if ( IsOrig() )
|
|
// Only one Endpoint adds the initial state to the counter.
|
|
zeek::sessions->tcp_stats.StateEntered(state, peer->state);
|
|
}
|
|
|
|
bool TCP_Endpoint::HadGap() const
|
|
{
|
|
return contents_processor && contents_processor->HadGap();
|
|
}
|
|
|
|
void TCP_Endpoint::AddReassembler(TCP_Reassembler* arg_contents_processor)
|
|
{
|
|
if ( contents_processor != arg_contents_processor )
|
|
delete contents_processor;
|
|
contents_processor = arg_contents_processor;
|
|
|
|
if ( contents_file )
|
|
contents_processor->SetContentsFile(contents_file);
|
|
}
|
|
|
|
bool TCP_Endpoint::DataPending() const
|
|
{
|
|
if ( contents_processor )
|
|
return contents_processor->DataPending();
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool TCP_Endpoint::HasUndeliveredData() const
|
|
{
|
|
if ( contents_processor )
|
|
return contents_processor->HasUndeliveredData();
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void TCP_Endpoint::CheckEOF()
|
|
{
|
|
if ( contents_processor )
|
|
contents_processor->CheckEOF();
|
|
}
|
|
|
|
void TCP_Endpoint::SizeBufferedData(uint64_t& waiting_on_hole,
|
|
uint64_t& waiting_on_ack)
|
|
{
|
|
if ( contents_processor )
|
|
contents_processor->SizeBufferedData(waiting_on_hole, waiting_on_ack);
|
|
else
|
|
waiting_on_hole = waiting_on_ack = 0;
|
|
}
|
|
|
|
bool TCP_Endpoint::ValidChecksum(const struct tcphdr* tp, int len) const
|
|
{
|
|
uint32_t sum = checksum_base;
|
|
int tcp_len = tp->th_off * 4 + len;
|
|
|
|
if ( len % 2 == 1 )
|
|
// Add in pad byte.
|
|
sum += htons(((const u_char*) tp)[tcp_len - 1] << 8);
|
|
|
|
sum += htons((unsigned short) tcp_len); // fill out pseudo header
|
|
sum = ones_complement_checksum((void*) tp, tcp_len, sum);
|
|
|
|
return sum == 0xffff;
|
|
}
|
|
|
|
static inline bool is_handshake(EndpointState state)
|
|
{
|
|
return state == TCP_ENDPOINT_INACTIVE ||
|
|
state == TCP_ENDPOINT_SYN_SENT ||
|
|
state == TCP_ENDPOINT_SYN_ACK_SENT;
|
|
}
|
|
|
|
void TCP_Endpoint::SetState(EndpointState new_state)
|
|
{
|
|
if ( new_state != state )
|
|
{
|
|
// Activate inactivity timer if this transition finishes the
|
|
// handshake.
|
|
if ( ! is_handshake(new_state) )
|
|
if ( is_handshake(state) && is_handshake(peer->state) )
|
|
Conn()->SetInactivityTimeout(tcp_inactivity_timeout);
|
|
|
|
prev_state = state;
|
|
state = new_state;
|
|
if ( IsOrig() )
|
|
zeek::sessions->tcp_stats.ChangeState(prev_state, state,
|
|
peer->state, peer->state);
|
|
else
|
|
zeek::sessions->tcp_stats.ChangeState(peer->state, peer->state,
|
|
prev_state, state);
|
|
}
|
|
}
|
|
|
|
uint64_t TCP_Endpoint::Size() const
|
|
{
|
|
if ( prev_state == TCP_ENDPOINT_SYN_SENT && state == TCP_ENDPOINT_RESET &&
|
|
peer->state == TCP_ENDPOINT_INACTIVE && ! NoDataAcked() )
|
|
// This looks like a half-open connection was discovered and aborted.
|
|
// Sequence numbers could be misleading if used in context of data size
|
|
// and there was never a chance for this endpoint to send data anyway.
|
|
return 0;
|
|
|
|
uint64_t size;
|
|
uint64_t last_seq_64 = ToFullSeqSpace(LastSeq(), SeqWraps());
|
|
uint64_t ack_seq_64 = ToFullSeqSpace(AckSeq(), AckWraps());
|
|
|
|
// Going straight to relative sequence numbers and comparing those might
|
|
// make more sense, but there's some cases (e.g. due to RSTs) where
|
|
// last_seq might not be initialized to a trustworthy value such that
|
|
// rel_seq > rel_ack, but last_seq_64 < start_seq, which is obviously wrong.
|
|
if ( last_seq_64 > ack_seq_64 )
|
|
size = last_seq_64 - StartSeqI64();
|
|
else
|
|
size = ack_seq_64 - StartSeqI64();
|
|
|
|
// Don't include SYN octet in sequence space. For partial connections
|
|
// (no SYN seen), we're still careful to adjust start_seq as though
|
|
// there was an initial SYN octet, because if we don't then the
|
|
// packet reassembly code gets confused.
|
|
if ( size != 0 )
|
|
--size;
|
|
|
|
if ( FIN_cnt > 0 && size != 0 )
|
|
--size; // don't include FIN octet.
|
|
|
|
return size;
|
|
}
|
|
|
|
bool TCP_Endpoint::DataSent(double t, uint64_t seq, int len, int caplen,
|
|
const u_char* data,
|
|
const zeek::IP_Hdr* ip, const struct tcphdr* tp)
|
|
{
|
|
bool status = false;
|
|
|
|
if ( contents_processor )
|
|
{
|
|
if ( caplen >= len )
|
|
status = contents_processor->DataSent(t, seq, len, data, TCP_Flags(tp));
|
|
else
|
|
TCP()->Weird("truncated_tcp_payload");
|
|
}
|
|
|
|
if ( caplen <= 0 )
|
|
return status;
|
|
|
|
if ( contents_file && ! contents_processor &&
|
|
seq + len > contents_start_seq )
|
|
{
|
|
int64_t under_seq = contents_start_seq - seq;
|
|
if ( under_seq > 0 )
|
|
{
|
|
seq += under_seq;
|
|
data += under_seq;
|
|
len -= under_seq;
|
|
}
|
|
|
|
// DEBUG_MSG("%d: seek %d, data=%02x len=%d\n", IsOrig(), seq - contents_start_seq, *data, len);
|
|
FILE* f = contents_file->Seek(seq - contents_start_seq);
|
|
|
|
if ( fwrite(data, 1, len, f) < unsigned(len) )
|
|
{
|
|
char buf[256];
|
|
bro_strerror_r(errno, buf, sizeof(buf));
|
|
zeek::reporter->Error("TCP contents write failed: %s", buf);
|
|
|
|
if ( contents_file_write_failure )
|
|
tcp_analyzer->EnqueueConnEvent(contents_file_write_failure,
|
|
Conn()->ConnVal(),
|
|
zeek::val_mgr->Bool(IsOrig()),
|
|
zeek::make_intrusive<zeek::StringVal>(buf)
|
|
);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
void TCP_Endpoint::AckReceived(uint64_t seq)
|
|
{
|
|
if ( contents_processor )
|
|
contents_processor->AckReceived(seq);
|
|
}
|
|
|
|
void TCP_Endpoint::SetContentsFile(zeek::FilePtr f)
|
|
{
|
|
contents_file = std::move(f);
|
|
contents_start_seq = ToRelativeSeqSpace(last_seq, seq_wraps);
|
|
|
|
if ( contents_start_seq == 0 )
|
|
contents_start_seq = 1; // skip SYN
|
|
|
|
if ( contents_processor )
|
|
contents_processor->SetContentsFile(contents_file);
|
|
}
|
|
|
|
bool TCP_Endpoint::CheckHistory(uint32_t mask, char code)
|
|
{
|
|
if ( ! IsOrig() )
|
|
{
|
|
mask <<= 16;
|
|
code = tolower(code);
|
|
}
|
|
|
|
return Conn()->CheckHistory(mask, code);
|
|
}
|
|
|
|
void TCP_Endpoint::AddHistory(char code)
|
|
{
|
|
if ( ! IsOrig() )
|
|
code = tolower(code);
|
|
|
|
Conn()->AddHistory(code);
|
|
}
|
|
|
|
void TCP_Endpoint::ChecksumError()
|
|
{
|
|
uint32_t t = chk_thresh;
|
|
if ( Conn()->ScaledHistoryEntry(IsOrig() ? 'C' : 'c',
|
|
chk_cnt, chk_thresh) )
|
|
Conn()->HistoryThresholdEvent(tcp_multiple_checksum_errors,
|
|
IsOrig(), t);
|
|
}
|
|
|
|
void TCP_Endpoint::DidRxmit()
|
|
{
|
|
uint32_t t = rxmt_thresh;
|
|
if ( Conn()->ScaledHistoryEntry(IsOrig() ? 'T' : 't',
|
|
rxmt_cnt, rxmt_thresh) )
|
|
Conn()->HistoryThresholdEvent(tcp_multiple_retransmissions,
|
|
IsOrig(), t);
|
|
}
|
|
|
|
void TCP_Endpoint::ZeroWindow()
|
|
{
|
|
uint32_t t = win0_thresh;
|
|
if ( Conn()->ScaledHistoryEntry(IsOrig() ? 'W' : 'w',
|
|
win0_cnt, win0_thresh) )
|
|
Conn()->HistoryThresholdEvent(tcp_multiple_zero_windows,
|
|
IsOrig(), t);
|
|
}
|
|
|
|
void TCP_Endpoint::Gap(uint64_t seq, uint64_t len)
|
|
{
|
|
uint32_t t = gap_thresh;
|
|
if ( Conn()->ScaledHistoryEntry(IsOrig() ? 'G' : 'g',
|
|
gap_cnt, gap_thresh) )
|
|
Conn()->HistoryThresholdEvent(tcp_multiple_gap, IsOrig(), t);
|
|
}
|
|
|
|
} // namespace zeek::analyzer::tcp
|