mirror of
https://github.com/zeek/zeek.git
synced 2025-10-04 07:38:19 +00:00
1043 lines
27 KiB
C++
1043 lines
27 KiB
C++
// $Id: ConnCompressor.cc 7008 2010-03-25 02:42:20Z vern $
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include "ConnCompressor.h"
|
|
#include "Event.h"
|
|
#include "ConnSizeAnalyzer.h"
|
|
#include "net_util.h"
|
|
|
|
// The basic model of the compressor is to wait for an answer before
|
|
// instantiating full connection state. Until we see a reply, only a minimal
|
|
// amount of state is stored. This has some consequences:
|
|
//
|
|
// - We try to mimic TCP.cc as close as possible, but this works only to a
|
|
// certain degree; e.g., we don't consider any of the wait-a-bit-after-
|
|
// the-connection-has-been-closed timers. That means we will get differences
|
|
// in connection semantics if the compressor is turned on. On the other
|
|
// hand, these differences will occur only for not well-established
|
|
// sessions, and experience shows that for these kinds of connections
|
|
// semantics are ill-defined in any case.
|
|
//
|
|
// - If an originator sends multiple different packets before we see a reply,
|
|
// we lose the information about additional packets (more precisely, we
|
|
// merge the packet headers into one). In particular, we lose any payload.
|
|
// This is a major problem if we see only one direction of a connection.
|
|
// When analyzing only SYN/FIN/RSTs this leads to differences if we miss
|
|
// the SYN/ACK.
|
|
//
|
|
// To avoid losing payload, there is the option cc_instantiate_on_data:
|
|
// if enabled and the originator sends a non-control packet after the
|
|
// initial packet, we instantiate full connection state.
|
|
//
|
|
// - We lose some of the information contained in initial packets (e.g., most
|
|
// IP/TCP options and any payload). If you depend on them, you don't
|
|
// want to use the compressor.
|
|
//
|
|
// Optionally, the compressor can take care only of initial SYNs and
|
|
// instantiate full connection state for all other connection setups.
|
|
// To enable, set cc_handle_only_syns to true.
|
|
//
|
|
// - The compressor may handle refused connections (i.e., initial packets
|
|
// followed by RST from responder) itself. Again, this leads to differences
|
|
// from default TCP processing and is therefore turned off by default.
|
|
// To enable, set cc_handle_resets to true.
|
|
//
|
|
// - We don't match signatures on connections which are completely handled
|
|
// by the compressor. Matching would require significant additional state
|
|
// w/o being very helpful.
|
|
//
|
|
// - If use_conn_size_analyzer is True, the reported counts for bytes and
|
|
// packets may not account for some packets/data that is part of those
|
|
// packets which the connection compressor handles. The error, if any, will
|
|
// however be small.
|
|
|
|
|
|
#ifdef DEBUG
|
|
static inline const char* fmt_conn_id(const ConnCompressor::PendingConn* c)
|
|
{
|
|
if ( c->ip1_is_src )
|
|
return fmt_conn_id(c->key.ip1, c->key.port1,
|
|
c->key.ip2, c->key.port2);
|
|
else
|
|
return fmt_conn_id(c->key.ip2, c->key.port2,
|
|
c->key.ip1, c->key.port1);
|
|
}
|
|
|
|
static inline const char* fmt_conn_id(const Connection* c)
|
|
{
|
|
return fmt_conn_id(c->OrigAddr(), c->OrigPort(),
|
|
c->RespAddr(), c->RespPort());
|
|
}
|
|
|
|
static inline const char* fmt_conn_id(const IP_Hdr* ip)
|
|
{
|
|
const struct tcphdr* tp = (const struct tcphdr*) ip->Payload();
|
|
return fmt_conn_id(ip->SrcAddr(), tp->th_sport,
|
|
ip->DstAddr(), tp->th_dport);
|
|
}
|
|
#endif
|
|
|
|
ConnCompressor::ConnCompressor()
|
|
{
|
|
first_block = last_block = 0;
|
|
first_non_expired = 0;
|
|
conn_val = 0;
|
|
|
|
sizes.connections = sizes.connections_total = 0;
|
|
sizes.pending_valid = sizes.pending_total = sizes.pending_in_mem = 0;
|
|
sizes.hash_table_size = 0;
|
|
sizes.memory = 0;
|
|
}
|
|
|
|
ConnCompressor::~ConnCompressor()
|
|
{
|
|
Block* next;
|
|
for ( Block* b = first_block; b; b = next )
|
|
{
|
|
next = b->next;
|
|
delete b;
|
|
}
|
|
}
|
|
|
|
Connection* ConnCompressor::NextPacket(double t, HashKey* key, const IP_Hdr* ip,
|
|
const struct pcap_pkthdr* hdr, const u_char* const pkt)
|
|
{
|
|
// Expire old stuff.
|
|
DoExpire(t);
|
|
|
|
// Most sanity checks on header sizes are already done ...
|
|
const struct tcphdr* tp = (const struct tcphdr*) ip->Payload();
|
|
|
|
// ... except this one.
|
|
uint32 tcp_hdr_len = tp->th_off * 4;
|
|
if ( tcp_hdr_len > uint32(ip->TotalLen() - ip->HdrLen()) )
|
|
{
|
|
sessions->Weird("truncated_header", hdr, pkt);
|
|
delete key;
|
|
return 0;
|
|
}
|
|
|
|
bool external = current_iosrc->GetCurrentTag();
|
|
ConnData* c = conns.Lookup(key);
|
|
|
|
Unref(conn_val);
|
|
conn_val = 0;
|
|
|
|
// Do we already have a Connection object?
|
|
if ( c && IsConnPtr(c) )
|
|
{
|
|
Connection* conn = MakeConnPtr(c);
|
|
int consistent = 1;
|
|
|
|
if ( external )
|
|
{
|
|
// External, and we already have a full connection.
|
|
// That means we use the same logic as in NetSessions
|
|
// to compare the tags.
|
|
consistent = sessions->CheckConnectionTag(conn);
|
|
if ( consistent < 0 )
|
|
{
|
|
delete key;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if ( ! consistent || conn->IsReuse(t, ip->Payload()) )
|
|
{
|
|
if ( consistent )
|
|
{
|
|
DBG_LOG(DBG_COMPRESSOR, "%s reuse", fmt_conn_id(conn));
|
|
conn->Event(connection_reused, 0);
|
|
}
|
|
|
|
sessions->Remove(conn);
|
|
--sizes.connections;
|
|
|
|
return Instantiate(t, key, ip);
|
|
}
|
|
|
|
DBG_LOG(DBG_COMPRESSOR, "%s pass through", fmt_conn_id(conn));
|
|
delete key;
|
|
return conn;
|
|
}
|
|
|
|
PendingConn* pending = c ? MakePendingConnPtr(c) : 0;
|
|
|
|
if ( c && external )
|
|
{
|
|
// External, but previous packets were not, i.e., they used
|
|
// the global timer queue. We finish the old connection
|
|
// and instantiate a full one now.
|
|
DBG_LOG(DBG_TM, "got packet with tag %s for already"
|
|
"known cc connection, instantiating full conn",
|
|
current_iosrc->GetCurrentTag()->c_str());
|
|
|
|
Event(pending, 0, connection_attempt,
|
|
TCP_ENDPOINT_INACTIVE, 0, TCP_ENDPOINT_INACTIVE);
|
|
Event(pending, 0, connection_state_remove,
|
|
TCP_ENDPOINT_INACTIVE, 0, TCP_ENDPOINT_INACTIVE);
|
|
Remove(key);
|
|
|
|
return Instantiate(t, key, ip);
|
|
}
|
|
|
|
if ( c && pending->invalid &&
|
|
network_time < pending->time + tcp_session_timer )
|
|
{
|
|
// The old connection has terminated sooner than
|
|
// tcp_session_timer. We assume this packet to be
|
|
// a latecomer, and ignore it.
|
|
DBG_LOG(DBG_COMPRESSOR, "%s ignored", fmt_conn_id(pending));
|
|
sessions->DumpPacket(hdr, pkt);
|
|
delete key;
|
|
return 0;
|
|
}
|
|
|
|
// Simulate tcp_{reset,close}_delay for initial FINs/RSTs
|
|
if ( c && ! pending->invalid &&
|
|
((pending->FIN && pending->time + tcp_close_delay < t ) ||
|
|
(pending->RST && pending->time + tcp_reset_delay < t )) )
|
|
{
|
|
DBG_LOG(DBG_COMPRESSOR, "%s closed", fmt_conn_id(pending));
|
|
int orig_state =
|
|
pending->FIN ? TCP_ENDPOINT_CLOSED : TCP_ENDPOINT_RESET;
|
|
|
|
Event(pending, 0, connection_partial_close, orig_state,
|
|
ip->PayloadLen() - (tp->th_off * 4),
|
|
TCP_ENDPOINT_INACTIVE);
|
|
Event(pending, 0, connection_state_remove, orig_state,
|
|
ip->PayloadLen() - (tp->th_off * 4),
|
|
TCP_ENDPOINT_INACTIVE);
|
|
|
|
Remove(key);
|
|
|
|
Connection* tc = FirstFromOrig(t, key, ip, tp);
|
|
if ( ! tc )
|
|
{
|
|
delete key;
|
|
sessions->DumpPacket(hdr, pkt);
|
|
}
|
|
|
|
return tc;
|
|
}
|
|
|
|
Connection* tc;
|
|
|
|
if ( ! c || pending->invalid )
|
|
{
|
|
// First packet of a connection.
|
|
if ( c )
|
|
Remove(key);
|
|
|
|
if ( external )
|
|
// External, we directly instantiate a full connection.
|
|
tc = Instantiate(t, key, ip);
|
|
else
|
|
tc = FirstFromOrig(t, key, ip, tp);
|
|
}
|
|
|
|
else if ( addr_eq(ip->SrcAddr(), SrcAddr(pending)) &&
|
|
tp->th_sport == SrcPort(pending) )
|
|
// Another packet from originator.
|
|
tc = NextFromOrig(pending, t, key, ip, tp);
|
|
|
|
else
|
|
// A reply.
|
|
tc = Response(pending, t, key, ip, tp);
|
|
|
|
if ( ! tc )
|
|
{
|
|
delete key;
|
|
sessions->DumpPacket(hdr, pkt);
|
|
}
|
|
|
|
return tc;
|
|
}
|
|
|
|
static int parse_tcp_options(unsigned int opt, unsigned int optlen,
|
|
const u_char* option, TCP_Analyzer* analyzer,
|
|
bool is_orig, void* cookie)
|
|
{
|
|
ConnCompressor::PendingConn* c = (ConnCompressor::PendingConn*) cookie;
|
|
|
|
// We're only interested in window_scale.
|
|
if ( opt == 3 )
|
|
c->window_scale = option[2];
|
|
|
|
return 0;
|
|
}
|
|
|
|
Connection* ConnCompressor::FirstFromOrig(double t, HashKey* key,
|
|
const IP_Hdr* ip, const tcphdr* tp)
|
|
{
|
|
if ( cc_handle_only_syns && ! (tp->th_flags & TH_SYN) )
|
|
return Instantiate(t, key, ip);
|
|
|
|
// The first packet of a connection.
|
|
PendingConn* pending = MakeNewState(t);
|
|
PktHdrToPendingConn(t, key, ip, tp, pending);
|
|
|
|
DBG_LOG(DBG_COMPRESSOR, "%s our", fmt_conn_id(pending));
|
|
|
|
// The created DictEntry will point directly into our PendingConn.
|
|
// So, we have to be careful when we delete it.
|
|
conns.Dictionary::Insert(&pending->key, sizeof(pending->key),
|
|
pending->hash, MakeMapPtr(pending), 0);
|
|
|
|
// Mimic some of TCP_Analyzer's weirds for SYNs.
|
|
// To be completely precise, we'd need to check this at a few
|
|
// more locations in NextFromOrig() and Reply(). However, that
|
|
// does not really seem worth it, as this is the standard case.
|
|
if ( tp->th_flags & TH_SYN )
|
|
{
|
|
if ( tp->th_flags & TH_RST )
|
|
Weird(pending, t, "TCP_christmas");
|
|
|
|
if ( tp->th_flags & TH_URG )
|
|
Weird(pending, t, "baroque_SYN");
|
|
|
|
int len = ip->TotalLen() - ip->HdrLen() - tp->th_off * 4;
|
|
|
|
if ( len > 0 )
|
|
// T/TCP definitely complicates this.
|
|
Weird(pending, t, "SYN_with_data");
|
|
}
|
|
|
|
if ( tp->th_flags & TH_FIN )
|
|
{
|
|
if ( ! (tp->th_flags & TH_SYN) )
|
|
Weird(pending, t, "spontaneous_FIN");
|
|
}
|
|
|
|
if ( tp->th_flags & TH_RST )
|
|
{
|
|
if ( ! (tp->th_flags & TH_SYN) )
|
|
Weird(pending, t, "spontaneous_RST");
|
|
}
|
|
|
|
++sizes.pending_valid;
|
|
++sizes.pending_total;
|
|
++sizes.pending_in_mem;
|
|
|
|
Event(pending, 0, new_connection,
|
|
TCP_ENDPOINT_INACTIVE, 0, TCP_ENDPOINT_INACTIVE);
|
|
|
|
if ( current_iosrc->GetCurrentTag() )
|
|
{
|
|
Val* tag =
|
|
new StringVal(current_iosrc->GetCurrentTag()->c_str());
|
|
Event(pending, 0, connection_external,
|
|
TCP_ENDPOINT_INACTIVE, 0, TCP_ENDPOINT_INACTIVE, tag);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Connection* ConnCompressor::NextFromOrig(PendingConn* pending, double t,
|
|
HashKey* key, const IP_Hdr* ip,
|
|
const tcphdr* tp)
|
|
{
|
|
// Another packet from the same host without seeing an answer so far.
|
|
DBG_LOG(DBG_COMPRESSOR, "%s same again", fmt_conn_id(pending));
|
|
|
|
++pending->num_pkts;
|
|
++pending->num_bytes_ip += ip->PayloadLen();
|
|
|
|
// New window scale overrides old - not great, this is a (subtle)
|
|
// evasion opportunity.
|
|
if ( TCP_Analyzer::ParseTCPOptions(tp, parse_tcp_options, 0, 0,
|
|
pending) < 0 )
|
|
Weird(pending, t, "corrupt_tcp_options");
|
|
|
|
if ( tp->th_flags & TH_SYN )
|
|
// New seq overrides old.
|
|
pending->seq = tp->th_seq;
|
|
|
|
// Mimic TCP_Endpoint::Size()
|
|
int size = ntohl(tp->th_seq) - ntohl(pending->seq);
|
|
if ( size != 0 )
|
|
--size;
|
|
|
|
if ( size != 0 && (pending->FIN || (tp->th_flags & TH_FIN)) )
|
|
--size;
|
|
|
|
if ( size < 0 )
|
|
// We only care about the size for broken connections.
|
|
// Surely for those it's more likely that the sequence
|
|
// numbers are confused than that they really transferred
|
|
// > 2 GB of data. Plus, for 64-bit ints these sign-extend
|
|
// up to truly huge, non-sensical unsigned values.
|
|
size = 0;
|
|
|
|
if ( pending->SYN )
|
|
{
|
|
// We're in state SYN_SENT or SYN_ACK_SENT.
|
|
if ( tp->th_flags & TH_RST)
|
|
{
|
|
Event(pending, t, connection_reset,
|
|
TCP_ENDPOINT_RESET, size, TCP_ENDPOINT_INACTIVE);
|
|
Event(pending, t, connection_state_remove,
|
|
TCP_ENDPOINT_RESET, size, TCP_ENDPOINT_INACTIVE);
|
|
|
|
Invalidate(key);
|
|
return 0;
|
|
}
|
|
|
|
else if ( tp->th_flags & TH_FIN)
|
|
{
|
|
Event(pending, t, connection_partial_close,
|
|
TCP_ENDPOINT_CLOSED, size, TCP_ENDPOINT_INACTIVE);
|
|
Event(pending, t, connection_state_remove,
|
|
TCP_ENDPOINT_CLOSED, size, TCP_ENDPOINT_INACTIVE);
|
|
Invalidate(key);
|
|
return 0;
|
|
}
|
|
|
|
else if ( tp->th_flags & TH_SYN )
|
|
{
|
|
if ( (tp->th_flags & TH_ACK) && ! pending->ACK )
|
|
Weird(pending, t, "repeated_SYN_with_ack");
|
|
}
|
|
|
|
else
|
|
{
|
|
// A data packet without seeing a SYN/ACK first. As
|
|
// long as we stick with the principle of instantiating
|
|
// state only when we see a reply, we have to throw
|
|
// this data away. Optionally we may instantiate a
|
|
// real connection now.
|
|
|
|
if ( cc_instantiate_on_data )
|
|
return Instantiate(key, pending);
|
|
// else
|
|
// Weird(pending, t, "data_without_SYN_ACK");
|
|
}
|
|
}
|
|
|
|
else
|
|
{ // We're in state INACTIVE.
|
|
if ( tp->th_flags & TH_RST)
|
|
{
|
|
Event(pending, t, connection_reset,
|
|
TCP_ENDPOINT_RESET, size, TCP_ENDPOINT_INACTIVE);
|
|
Event(pending, t, connection_state_remove,
|
|
TCP_ENDPOINT_RESET, size, TCP_ENDPOINT_INACTIVE);
|
|
|
|
Invalidate(key);
|
|
return 0;
|
|
}
|
|
|
|
else if ( tp->th_flags & TH_FIN)
|
|
{
|
|
Event(pending, t, connection_half_finished,
|
|
TCP_ENDPOINT_CLOSED, size, TCP_ENDPOINT_INACTIVE);
|
|
Event(pending, t, connection_state_remove,
|
|
TCP_ENDPOINT_CLOSED, size, TCP_ENDPOINT_INACTIVE);
|
|
|
|
Invalidate(key);
|
|
return 0;
|
|
}
|
|
|
|
else if ( tp->th_flags & TH_SYN )
|
|
{
|
|
if ( ! tp->th_flags & TH_ACK )
|
|
{
|
|
Weird(pending, t, "SYN_after_partial");
|
|
pending->SYN = 1;
|
|
}
|
|
}
|
|
|
|
else
|
|
// Another data packet. See discussion above.
|
|
if ( cc_instantiate_on_data )
|
|
return Instantiate(key, pending);
|
|
|
|
// else
|
|
// Weird(pending, t, "data_without_SYN_ACK");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Connection* ConnCompressor::Response(PendingConn* pending, double t,
|
|
HashKey* key, const IP_Hdr* ip,
|
|
const tcphdr* tp)
|
|
{
|
|
// The packet comes from the former responder. That means we are
|
|
// seeing a reply, so we are going to create a "real" connection now.
|
|
DBG_LOG(DBG_COMPRESSOR, "%s response", fmt_conn_id(pending));
|
|
|
|
// Optional: if it's a RST after SYN, we directly generate a
|
|
// connection_rejected and throw the state away.
|
|
if ( cc_handle_resets && (tp->th_flags & TH_RST) && pending->SYN )
|
|
{
|
|
// See discussion of size in DoExpire().
|
|
DBG_LOG(DBG_COMPRESSOR, "%s reset", fmt_conn_id(pending));
|
|
|
|
Event(pending, t, connection_reset,
|
|
TCP_ENDPOINT_SYN_SENT, 0, TCP_ENDPOINT_RESET);
|
|
Event(pending, t, connection_state_remove,
|
|
TCP_ENDPOINT_SYN_SENT, 0, TCP_ENDPOINT_RESET);
|
|
|
|
Invalidate(key);
|
|
return 0;
|
|
}
|
|
|
|
// If a connection's initial packet is a RST, Bro's standard TCP
|
|
// processing considers the connection done right away. We simulate
|
|
// this by instantiating a second connection in this case. The
|
|
// first one will time out eventually.
|
|
if ( pending->RST && ! pending->SYN )
|
|
{
|
|
int orig_state =
|
|
pending->RST ? TCP_ENDPOINT_RESET : TCP_ENDPOINT_CLOSED;
|
|
Event(pending, 0, connection_attempt,
|
|
orig_state, 0, TCP_ENDPOINT_INACTIVE);
|
|
Event(pending, 0, connection_state_remove,
|
|
orig_state, 0, TCP_ENDPOINT_INACTIVE);
|
|
|
|
// Override with current packet.
|
|
PktHdrToPendingConn(t, key, ip, tp, pending);
|
|
return 0;
|
|
}
|
|
|
|
return Instantiate(key, pending);
|
|
}
|
|
|
|
Connection* ConnCompressor::Instantiate(HashKey* key, PendingConn* pending)
|
|
{
|
|
// Instantantiate a Connection.
|
|
ConnID conn_id;
|
|
conn_id.src_addr = SrcAddr(pending);
|
|
conn_id.dst_addr = DstAddr(pending);
|
|
conn_id.src_port = SrcPort(pending);
|
|
conn_id.dst_port = DstPort(pending);
|
|
|
|
pending->invalid = 1;
|
|
--sizes.pending_valid;
|
|
--sizes.pending_total;
|
|
|
|
// Fake the first packet.
|
|
const IP_Hdr* faked_pkt = PendingConnToPacket(pending);
|
|
Connection* new_conn = sessions->NewConn(key, pending->time, &conn_id,
|
|
faked_pkt->Payload(), IPPROTO_TCP, 0);
|
|
|
|
if ( ! new_conn )
|
|
{
|
|
// This connection is not to be analyzed (e.g., it may be
|
|
// a partial one).
|
|
DBG_LOG(DBG_COMPRESSOR, "%s nop", fmt_conn_id(pending));
|
|
return 0;
|
|
}
|
|
|
|
new_conn->SetUID(pending->uid);
|
|
|
|
DBG_LOG(DBG_COMPRESSOR, "%s instantiated", fmt_conn_id(pending));
|
|
|
|
++sizes.connections;
|
|
++sizes.connections_total;
|
|
|
|
if ( new_packet )
|
|
new_conn->Event(new_packet, 0,
|
|
sessions->BuildHeader(faked_pkt->IP4_Hdr()));
|
|
|
|
// NewConn() may have swapped originator and responder.
|
|
int is_orig = addr_eq(conn_id.src_addr, new_conn->OrigAddr()) &&
|
|
conn_id.src_port == new_conn->OrigPort();
|
|
|
|
// Pass the faked packet to the connection.
|
|
const u_char* payload = faked_pkt->Payload();
|
|
|
|
int dummy_record_packet, dummy_record_content;
|
|
new_conn->NextPacket(pending->time, is_orig,
|
|
faked_pkt, faked_pkt->PayloadLen(),
|
|
faked_pkt->PayloadLen(), payload,
|
|
dummy_record_packet, dummy_record_content, 0, 0, 0);
|
|
|
|
// Removing necessary because the key will be destroyed at some point.
|
|
conns.Remove(&pending->key, sizeof(pending->key), pending->hash, true);
|
|
conns.Insert(key, MakeMapPtr(new_conn));
|
|
|
|
return new_conn;
|
|
}
|
|
|
|
Connection* ConnCompressor::Instantiate(double t, HashKey* key,
|
|
const IP_Hdr* ip)
|
|
{
|
|
const struct tcphdr* tp = (const struct tcphdr*) ip->Payload();
|
|
|
|
ConnID conn_id;
|
|
conn_id.src_addr = ip->SrcAddr();
|
|
conn_id.dst_addr = ip->DstAddr();
|
|
conn_id.src_port = tp->th_sport;
|
|
conn_id.dst_port = tp->th_dport;
|
|
|
|
Connection* new_conn =
|
|
sessions->NewConn(key, t, &conn_id, ip->Payload(), IPPROTO_TCP, 0);
|
|
|
|
if ( ! new_conn )
|
|
{
|
|
// This connection is not to be analyzed (e.g., it may be
|
|
// a partial one).
|
|
DBG_LOG(DBG_COMPRESSOR, "%s nop", fmt_conn_id(ip));
|
|
return 0;
|
|
}
|
|
|
|
DBG_LOG(DBG_COMPRESSOR, "%s instantiated", fmt_conn_id(ip));
|
|
|
|
conns.Insert(key, MakeMapPtr(new_conn));
|
|
++sizes.connections;
|
|
++sizes.connections_total;
|
|
|
|
if ( new_connection )
|
|
new_conn->Event(new_connection, 0);
|
|
|
|
if ( current_iosrc->GetCurrentTag() )
|
|
{
|
|
Val* tag =
|
|
new StringVal(current_iosrc->GetCurrentTag()->c_str());
|
|
new_conn->Event(connection_external, 0, tag);
|
|
}
|
|
|
|
return new_conn;
|
|
}
|
|
|
|
void ConnCompressor::PktHdrToPendingConn(double time, const HashKey* key,
|
|
const IP_Hdr* ip, const struct tcphdr* tp, PendingConn* c)
|
|
{
|
|
memcpy(&c->key, key->Key(), key->Size());
|
|
|
|
c->hash = key->Hash();
|
|
c->ip1_is_src = addr_eq(c->key.ip1, ip->SrcAddr()) &&
|
|
c->key.port1 == tp->th_sport;
|
|
c->time = time;
|
|
c->window = tp->th_win;
|
|
c->seq = tp->th_seq;
|
|
c->ack = tp->th_ack;
|
|
c->window_scale = 0;
|
|
c->SYN = (tp->th_flags & TH_SYN) != 0;
|
|
c->FIN = (tp->th_flags & TH_FIN) != 0;
|
|
c->RST = (tp->th_flags & TH_RST) != 0;
|
|
c->ACK = (tp->th_flags & TH_ACK) != 0;
|
|
c->uid = calculate_unique_id();
|
|
c->num_bytes_ip = ip->TotalLen();
|
|
c->num_pkts = 1;
|
|
c->invalid = 0;
|
|
|
|
if ( TCP_Analyzer::ParseTCPOptions(tp, parse_tcp_options, 0, 0, c) < 0 )
|
|
sessions->Weird("corrupt_tcp_options", ip);
|
|
}
|
|
|
|
// Fakes an empty TCP packet based on the information in PendingConn.
|
|
const IP_Hdr* ConnCompressor::PendingConnToPacket(const PendingConn* c)
|
|
{
|
|
static ip* ip = 0;
|
|
static tcphdr* tp = 0;
|
|
static IP_Hdr* ip_hdr = 0;
|
|
|
|
if ( ! ip )
|
|
{ // Initialize. ### Note, only handles IPv4 for now.
|
|
int packet_length = sizeof(*ip) + sizeof(*tp);
|
|
ip = (struct ip*) new char[packet_length];
|
|
tp = (struct tcphdr*) (((char*) ip) + sizeof(*ip));
|
|
ip_hdr = new IP_Hdr(ip);
|
|
|
|
// Constant fields.
|
|
ip->ip_v = 4;
|
|
ip->ip_hl = sizeof(*ip) / 4; // no options
|
|
ip->ip_tos = 0;
|
|
ip->ip_len = htons(packet_length);
|
|
ip->ip_id = 0;
|
|
ip->ip_off = 0;
|
|
ip->ip_ttl = 255;
|
|
ip->ip_p = IPPROTO_TCP;
|
|
ip->ip_sum = 0; // is not going to be checked
|
|
|
|
tp->th_off = sizeof(*tp) / 4; // no options for now
|
|
tp->th_urp = 0;
|
|
}
|
|
|
|
// Note, do *not* use copy_addr() here. This is because we're
|
|
// copying to an IPv4 header, which has room for exactly and
|
|
// only an IPv4 address.
|
|
#ifdef BROv6
|
|
if ( ! is_v4_addr(c->key.ip1) || ! is_v4_addr(c->key.ip2) )
|
|
reporter->InternalError("IPv6 snuck into connection compressor");
|
|
#endif
|
|
*(uint32*) &ip->ip_src =
|
|
to_v4_addr(c->ip1_is_src ? c->key.ip1 : c->key.ip2);
|
|
*(uint32*) &ip->ip_dst =
|
|
to_v4_addr(c->ip1_is_src ? c->key.ip2 : c->key.ip1);
|
|
|
|
if ( c->ip1_is_src )
|
|
{
|
|
tp->th_sport = c->key.port1;
|
|
tp->th_dport = c->key.port2;
|
|
}
|
|
else
|
|
{
|
|
tp->th_sport = c->key.port2;
|
|
tp->th_dport = c->key.port1;
|
|
}
|
|
|
|
tp->th_win = c->window;
|
|
tp->th_seq = c->seq;
|
|
tp->th_ack = c->ack;
|
|
tp->th_flags = MakeFlags(c);
|
|
tp->th_sum = 0;
|
|
tp->th_sum = 0xffff - tcp_checksum(ip, tp, 0);
|
|
|
|
// FIXME: Add TCP options.
|
|
return ip_hdr;
|
|
}
|
|
|
|
uint8 ConnCompressor::MakeFlags(const PendingConn* c) const
|
|
{
|
|
uint8 tcp_flags = 0;
|
|
if ( c->SYN )
|
|
tcp_flags |= TH_SYN;
|
|
if ( c->FIN )
|
|
tcp_flags |= TH_FIN;
|
|
if ( c->RST )
|
|
tcp_flags |= TH_RST;
|
|
if ( c->ACK )
|
|
tcp_flags |= TH_ACK;
|
|
|
|
return tcp_flags;
|
|
}
|
|
|
|
ConnCompressor::PendingConn* ConnCompressor::MakeNewState(double t)
|
|
{
|
|
// See if there is enough space in the current block.
|
|
if ( last_block &&
|
|
int(sizeof(PendingConn)) <= BLOCK_SIZE - last_block->bytes_used )
|
|
{
|
|
PendingConn* c = (PendingConn*) &last_block->data[last_block->bytes_used];
|
|
last_block->bytes_used += sizeof(PendingConn);
|
|
c->is_pending = true;
|
|
return c;
|
|
}
|
|
|
|
// Get new block.
|
|
Block* b = new Block;
|
|
b->time = t;
|
|
b->bytes_used = sizeof(PendingConn);
|
|
b->next = 0;
|
|
b->prev = last_block;
|
|
|
|
if ( last_block )
|
|
last_block->next = b;
|
|
else
|
|
first_block = b;
|
|
|
|
last_block = b;
|
|
|
|
sizes.memory += padded_sizeof(*b);
|
|
PendingConn* c = (PendingConn*) &b->data;
|
|
c->is_pending = true;
|
|
return c;
|
|
}
|
|
|
|
void ConnCompressor::DoExpire(double t)
|
|
{
|
|
while ( first_block )
|
|
{
|
|
Block* b = first_block;
|
|
|
|
unsigned char* p =
|
|
first_non_expired ? first_non_expired : b->data;
|
|
|
|
while ( p < b->data + b->bytes_used )
|
|
{
|
|
Unref(conn_val);
|
|
conn_val = 0;
|
|
|
|
PendingConn* c = (PendingConn*) p;
|
|
if ( t && (c->time + tcp_SYN_timeout > t) )
|
|
{
|
|
// All following entries are still
|
|
// recent enough.
|
|
first_non_expired = p;
|
|
return;
|
|
}
|
|
|
|
if ( ! c->invalid )
|
|
{
|
|
// Expired.
|
|
DBG_LOG(DBG_COMPRESSOR, "%s expire", fmt_conn_id(c));
|
|
|
|
HashKey key(&c->key, sizeof(c->key), c->hash, true);
|
|
|
|
ConnData* cd = conns.Lookup(&key);
|
|
if ( cd && ! IsConnPtr(cd) )
|
|
conns.Remove(&c->key, sizeof(c->key),
|
|
c->hash, true);
|
|
|
|
int orig_state = TCP_ENDPOINT_INACTIVE;
|
|
|
|
if ( c->FIN )
|
|
orig_state = TCP_ENDPOINT_CLOSED;
|
|
if ( c->RST )
|
|
orig_state = TCP_ENDPOINT_RESET;
|
|
if ( c->SYN )
|
|
orig_state = TCP_ENDPOINT_SYN_SENT;
|
|
|
|
// We're not able to get the correct size
|
|
// here (with "correct" meaning value that
|
|
// standard connection processing reports).
|
|
// We could if would also store last_seq, but
|
|
// doesn't seem worth it.
|
|
|
|
Event(c, 0, connection_attempt,
|
|
orig_state, 0, TCP_ENDPOINT_INACTIVE);
|
|
Event(c, 0, connection_state_remove,
|
|
orig_state, 0, TCP_ENDPOINT_INACTIVE);
|
|
|
|
c->invalid = 1;
|
|
--sizes.pending_valid;
|
|
}
|
|
|
|
p += sizeof(PendingConn);
|
|
--sizes.pending_in_mem;
|
|
}
|
|
|
|
// Full block expired, so delete it.
|
|
first_block = b->next;
|
|
|
|
if ( b->next )
|
|
b->next->prev = 0;
|
|
else
|
|
last_block = 0;
|
|
|
|
delete b;
|
|
|
|
first_non_expired = 0;
|
|
sizes.memory -= padded_sizeof(*b);
|
|
}
|
|
}
|
|
|
|
void ConnCompressor::Event(const PendingConn* pending, double t,
|
|
const EventHandlerPtr& event, int orig_state,
|
|
int orig_size, int resp_state, Val* arg)
|
|
{
|
|
if ( ! conn_val )
|
|
{
|
|
if ( ! event )
|
|
return;
|
|
|
|
// We only raise events if NewConn() would have actually
|
|
// instantiated the Connection.
|
|
bool flip_roles;
|
|
if ( ! sessions->WantConnection(ntohs(SrcPort(pending)),
|
|
ntohs(DstPort(pending)),
|
|
TRANSPORT_TCP,
|
|
MakeFlags(pending),
|
|
flip_roles) )
|
|
return;
|
|
|
|
conn_val = new RecordVal(connection_type);
|
|
RecordVal* id_val = new RecordVal(conn_id);
|
|
RecordVal* orig_endp = new RecordVal(endpoint);
|
|
RecordVal* resp_endp = new RecordVal(endpoint);
|
|
|
|
if ( orig_state == TCP_ENDPOINT_INACTIVE )
|
|
{
|
|
if ( pending->SYN )
|
|
orig_state = pending->ACK ?
|
|
TCP_ENDPOINT_SYN_ACK_SENT :
|
|
TCP_ENDPOINT_SYN_SENT;
|
|
else
|
|
orig_state = TCP_ENDPOINT_PARTIAL;
|
|
}
|
|
|
|
int tcp_state = TCP_ENDPOINT_INACTIVE;
|
|
|
|
if ( ! flip_roles )
|
|
{
|
|
id_val->Assign(0, new AddrVal(SrcAddr(pending)));
|
|
id_val->Assign(1, new PortVal(ntohs(SrcPort(pending)),
|
|
TRANSPORT_TCP));
|
|
id_val->Assign(2, new AddrVal(DstAddr(pending)));
|
|
id_val->Assign(3, new PortVal(ntohs(DstPort(pending)),
|
|
TRANSPORT_TCP));
|
|
orig_endp->Assign(0, new Val(orig_size, TYPE_COUNT));
|
|
orig_endp->Assign(1, new Val(orig_state, TYPE_COUNT));
|
|
|
|
if ( ConnSize_Analyzer::Available() )
|
|
{
|
|
// Fill in optional fields if ConnSize_Analyzer is on.
|
|
orig_endp->Assign(2, new Val(pending->num_pkts, TYPE_COUNT));
|
|
orig_endp->Assign(3, new Val(pending->num_bytes_ip, TYPE_COUNT));
|
|
}
|
|
|
|
resp_endp->Assign(0, new Val(0, TYPE_COUNT));
|
|
resp_endp->Assign(1, new Val(resp_state, TYPE_COUNT));
|
|
resp_endp->Assign(2, new Val(0, TYPE_COUNT));
|
|
resp_endp->Assign(3, new Val(0, TYPE_COUNT));
|
|
}
|
|
else
|
|
{
|
|
id_val->Assign(0, new AddrVal(DstAddr(pending)));
|
|
id_val->Assign(1, new PortVal(ntohs(DstPort(pending)),
|
|
TRANSPORT_TCP));
|
|
id_val->Assign(2, new AddrVal(SrcAddr(pending)));
|
|
id_val->Assign(3, new PortVal(ntohs(SrcPort(pending)),
|
|
TRANSPORT_TCP));
|
|
|
|
orig_endp->Assign(0, new Val(0, TYPE_COUNT));
|
|
orig_endp->Assign(1, new Val(resp_state, TYPE_COUNT));
|
|
orig_endp->Assign(2, new Val(0, TYPE_COUNT));
|
|
orig_endp->Assign(3, new Val(0, TYPE_COUNT));
|
|
|
|
resp_endp->Assign(0, new Val(orig_size, TYPE_COUNT));
|
|
resp_endp->Assign(1, new Val(orig_state, TYPE_COUNT));
|
|
|
|
if ( ConnSize_Analyzer::Available() )
|
|
{
|
|
// Fill in optional fields if ConnSize_Analyzer is on
|
|
resp_endp->Assign(2, new Val(pending->num_pkts, TYPE_COUNT));
|
|
resp_endp->Assign(3, new Val(pending->num_bytes_ip, TYPE_COUNT));
|
|
}
|
|
|
|
DBG_LOG(DBG_COMPRESSOR, "%s swapped direction", fmt_conn_id(pending));
|
|
}
|
|
|
|
conn_val->Assign(0, id_val);
|
|
conn_val->Assign(1, orig_endp);
|
|
conn_val->Assign(2, resp_endp);
|
|
conn_val->Assign(3, new Val(pending->time, TYPE_TIME));
|
|
conn_val->Assign(4, new Val(t > 0 ? t - pending->time : 0,
|
|
TYPE_INTERVAL)); // duration
|
|
conn_val->Assign(5, new TableVal(string_set)); // service
|
|
conn_val->Assign(6, new StringVal("cc=1")); // addl
|
|
conn_val->Assign(7, new Val(0, TYPE_COUNT)); // hot
|
|
conn_val->Assign(8, new StringVal("")); // history
|
|
|
|
char tmp[20]; // uid.
|
|
conn_val->Assign(9, new StringVal(uitoa_n(pending->uid, tmp, sizeof(tmp), 62)));
|
|
|
|
conn_val->SetOrigin(0);
|
|
}
|
|
|
|
if ( event == conn_weird )
|
|
{
|
|
// Special case to go through the logger.
|
|
const char* msg = arg->AsString()->CheckString();
|
|
reporter->Weird(conn_val->Ref(), msg);
|
|
return;
|
|
}
|
|
|
|
val_list* vl = new val_list;
|
|
if ( arg )
|
|
vl->append(arg);
|
|
vl->append(conn_val->Ref());
|
|
|
|
mgr.QueueEvent(event, vl, SOURCE_LOCAL);
|
|
}
|
|
|
|
void ConnCompressor::Drain()
|
|
{
|
|
IterCookie* cookie = conns.InitForIteration();
|
|
ConnData* c;
|
|
|
|
DoExpire(0);
|
|
|
|
while ( (c = conns.NextEntry(cookie)) )
|
|
{
|
|
Unref(conn_val);
|
|
conn_val = 0;
|
|
|
|
if ( IsConnPtr(c) )
|
|
{
|
|
Connection* tc = MakeConnPtr(c);
|
|
tc->Done();
|
|
tc->Event(connection_state_remove, 0);
|
|
Unref(tc);
|
|
--sizes.connections;
|
|
}
|
|
|
|
else
|
|
{
|
|
PendingConn* pc = MakePendingConnPtr(c);
|
|
if ( ! pc->invalid )
|
|
{
|
|
// Same discussion for size here than
|
|
// in DoExpire().
|
|
Event(pc, 0, connection_attempt,
|
|
TCP_ENDPOINT_INACTIVE, 0,
|
|
TCP_ENDPOINT_INACTIVE);
|
|
Event(pc, 0, connection_state_remove,
|
|
TCP_ENDPOINT_INACTIVE, 0,
|
|
TCP_ENDPOINT_INACTIVE);
|
|
|
|
--sizes.pending_valid;
|
|
pc->invalid = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ConnCompressor::Invalidate(HashKey* k)
|
|
{
|
|
ConnData* c = (ConnData*) conns.Lookup(k);
|
|
|
|
assert(c && ! IsConnPtr(c));
|
|
PendingConn* pc = MakePendingConnPtr(c);
|
|
|
|
DBG_LOG(DBG_COMPRESSOR, "%s invalidate", fmt_conn_id(pc));
|
|
|
|
if ( ! pc->invalid )
|
|
{
|
|
conns.Remove(&pc->key, sizeof(pc->key), pc->hash, true);
|
|
pc->invalid = 1;
|
|
--sizes.pending_valid;
|
|
}
|
|
}
|
|
|
|
Connection* ConnCompressor::Insert(Connection* newconn)
|
|
{
|
|
HashKey* key = newconn->Key();
|
|
ConnData* c = conns.Lookup(key);
|
|
Connection* old = 0;
|
|
|
|
// Do we already have a Connection object?
|
|
if ( c )
|
|
{
|
|
if ( IsConnPtr(c) )
|
|
old = MakeConnPtr(c);
|
|
Remove(key);
|
|
}
|
|
|
|
conns.Insert(key, MakeMapPtr(newconn));
|
|
return old;
|
|
}
|
|
|
|
bool ConnCompressor::Remove(HashKey* k)
|
|
{
|
|
ConnData* c = (ConnData*) conns.Lookup(k);
|
|
if ( ! c )
|
|
return false;
|
|
|
|
if ( IsConnPtr(c) )
|
|
{
|
|
DBG_LOG(DBG_COMPRESSOR, "%s remove", fmt_conn_id(MakeConnPtr(c)));
|
|
conns.Remove(k);
|
|
--sizes.connections;
|
|
}
|
|
else
|
|
{
|
|
PendingConn* pc = MakePendingConnPtr(c);
|
|
DBG_LOG(DBG_COMPRESSOR, "%s remove", fmt_conn_id(pc));
|
|
|
|
conns.Remove(&pc->key, sizeof(pc->key), pc->hash, true);
|
|
|
|
if ( ! pc->invalid )
|
|
{
|
|
pc->invalid = 1;
|
|
--sizes.pending_valid;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|