zeek/src/Sessions.cc

1353 lines
32 KiB
C++

// $Id: Sessions.cc 7075 2010-09-13 02:39:38Z vern $
//
// See the file "COPYING" in the main distribution directory for copyright.
#include "config.h"
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include "Net.h"
#include "Event.h"
#include "Timer.h"
#include "NetVar.h"
#include "Sessions.h"
#include "Reporter.h"
#include "OSFinger.h"
#include "ICMP.h"
#include "UDP.h"
#include "DNS-binpac.h"
#include "HTTP-binpac.h"
#include "SteppingStone.h"
#include "BackDoor.h"
#include "InterConn.h"
#include "Discard.h"
#include "RuleMatcher.h"
#include "ConnCompressor.h"
#include "DPM.h"
#include "PacketSort.h"
#include "TunnelHandler.h"
// These represent NetBIOS services on ephemeral ports. They're numbered
// so that we can use a single int to hold either an actual TCP/UDP server
// port or one of these.
enum NetBIOS_Service {
NETBIOS_SERVICE_START = 0x10000L, // larger than any port
NETBIOS_SERVICE_DCE_RPC,
};
NetSessions* sessions;
void TimerMgrExpireTimer::Dispatch(double t, int is_expire)
{
if ( mgr->LastAdvance() + timer_mgr_inactivity_timeout < timer_mgr->Time() )
{
// Expired.
DBG_LOG(DBG_TM, "TimeMgr %p has timed out", mgr);
mgr->Expire();
// Make sure events are executed. They depend on the TimerMgr.
::mgr.Drain();
sessions->timer_mgrs.erase(mgr->GetTag());
delete mgr;
}
else
{
// Reinstall timer.
if ( ! is_expire )
{
double n = mgr->LastAdvance() +
timer_mgr_inactivity_timeout;
timer_mgr->Add(new TimerMgrExpireTimer(n, mgr));
}
}
}
NetSessions::NetSessions()
{
TypeList* t = new TypeList();
t->Append(base_type(TYPE_COUNT)); // source IP address
t->Append(base_type(TYPE_COUNT)); // dest IP address
t->Append(base_type(TYPE_COUNT)); // source and dest ports
ch = new CompositeHash(t);
Unref(t);
tcp_conns.SetDeleteFunc(bro_obj_delete_func);
udp_conns.SetDeleteFunc(bro_obj_delete_func);
fragments.SetDeleteFunc(bro_obj_delete_func);
if ( stp_correlate_pair )
stp_manager = new SteppingStoneManager();
else
stp_manager = 0;
discarder = new Discarder();
if ( ! discarder->IsActive() )
{
delete discarder;
discarder = 0;
}
packet_filter = 0;
build_backdoor_analyzer =
backdoor_stats || rlogin_signature_found ||
telnet_signature_found || ssh_signature_found ||
root_backdoor_signature_found || ftp_signature_found ||
napster_signature_found || kazaa_signature_found ||
http_signature_found || http_proxy_signature_found;
dump_this_packet = 0;
num_packets_processed = 0;
if ( OS_version_found )
{
SYN_OS_Fingerprinter = new OSFingerprint(SYN_FINGERPRINT_MODE);
if ( SYN_OS_Fingerprinter->Error() )
exit(1);
}
else
SYN_OS_Fingerprinter = 0;
if ( pkt_profile_mode && pkt_profile_freq > 0 && pkt_profile_file )
pkt_profiler = new PacketProfiler(pkt_profile_mode,
pkt_profile_freq, pkt_profile_file->AsFile());
else
pkt_profiler = 0;
if ( arp_request || arp_reply || bad_arp )
arp_analyzer = new ARP_Analyzer();
else
arp_analyzer = 0;
if ( BifConst::Tunnel::decapsulate_ip || BifConst::Tunnel::decapsulate_udp )
tunnel_handler = new TunnelHandler(this);
else
tunnel_handler = 0;
}
NetSessions::~NetSessions()
{
delete ch;
delete packet_filter;
delete SYN_OS_Fingerprinter;
delete pkt_profiler;
Unref(arp_analyzer);
}
void NetSessions::Done()
{
delete stp_manager;
delete discarder;
}
namespace // private namespace
{
bool looks_like_IPv4_packet(int len, const struct ip* ip_hdr)
{
if ( len < int(sizeof(struct ip)) )
return false;
return ip_hdr->ip_v == 4 && ntohs(ip_hdr->ip_len) == len;
}
}
void NetSessions::DispatchPacket(double t, const struct pcap_pkthdr* hdr,
const u_char* pkt, int hdr_size,
PktSrc* src_ps, PacketSortElement* pkt_elem)
{
const struct ip* ip_hdr = 0;
const u_char* ip_data = 0;
int proto = 0;
if ( hdr->caplen >= hdr_size + sizeof(struct ip) )
{
ip_hdr = reinterpret_cast<const struct ip*>(pkt + hdr_size);
if ( hdr->caplen >= unsigned(hdr_size + (ip_hdr->ip_hl << 2)) )
ip_data = pkt + hdr_size + (ip_hdr->ip_hl << 2);
}
if ( src_ps->FilterType() == TYPE_FILTER_NORMAL )
NextPacket(t, hdr, pkt, hdr_size, pkt_elem);
else
NextPacketSecondary(t, hdr, pkt, hdr_size, src_ps);
}
void NetSessions::NextPacket(double t, const struct pcap_pkthdr* hdr,
const u_char* const pkt, int hdr_size,
PacketSortElement* pkt_elem)
{
SegmentProfiler(segment_logger, "processing-packet");
if ( pkt_profiler )
pkt_profiler->ProfilePkt(t, hdr->caplen);
++num_packets_processed;
dump_this_packet = 0;
if ( record_all_packets )
DumpPacket(hdr, pkt);
if ( pkt_elem && pkt_elem->IPHdr() )
// Fast path for "normal" IP packets if an IP_Hdr is
// already extracted when doing PacketSort. Otherwise
// the code below tries to extract the IP header, the
// difference here is that header extraction in
// PacketSort does not generate Weird events.
DoNextPacket(t, hdr, pkt_elem->IPHdr(), pkt, hdr_size);
else
{
// ### The following isn't really correct. What we *should*
// do is understanding the different link layers in order to
// find the network-layer protocol ID. That's a big
// portability pain, though, unless we just assume everything's
// Ethernet .... not great, given the potential need to deal
// with PPP or FDDI (for some older traces). So instead
// we look to see if what we have is consistent with an
// IPv4 packet. If not, it's either ARP or IPv6 or weird.
uint32 caplen = hdr->caplen - hdr_size;
if ( caplen < sizeof(struct ip) )
{
Weird("truncated_IP", hdr, pkt);
return;
}
const struct ip* ip = (const struct ip*) (pkt + hdr_size);
if ( ip->ip_v == 4 )
{
IP_Hdr ip_hdr(ip);
DoNextPacket(t, hdr, &ip_hdr, pkt, hdr_size);
}
else if ( arp_analyzer && arp_analyzer->IsARP(pkt, hdr_size) )
arp_analyzer->NextPacket(t, hdr, pkt, hdr_size);
else
{
#ifdef BROv6
IP_Hdr ip_hdr((const struct ip6_hdr*) (pkt + hdr_size));
DoNextPacket(t, hdr, &ip_hdr, pkt, hdr_size);
#else
Weird("non_IPv4_packet", hdr, pkt);
return;
#endif
}
}
if ( dump_this_packet && ! record_all_packets )
DumpPacket(hdr, pkt);
}
void NetSessions::NextPacketSecondary(double /* t */, const struct pcap_pkthdr* hdr,
const u_char* const pkt, int hdr_size,
const PktSrc* src_ps)
{
SegmentProfiler(segment_logger, "processing-secondary-packet");
++num_packets_processed;
uint32 caplen = hdr->caplen - hdr_size;
if ( caplen < sizeof(struct ip) )
{
Weird("truncated_IP", hdr, pkt);
return;
}
const struct ip* ip = (const struct ip*) (pkt + hdr_size);
if ( ip->ip_v == 4 )
{
const secondary_program_list& spt = src_ps->ProgramTable();
loop_over_list(spt, i)
{
SecondaryProgram* sp = spt[i];
if ( ! net_packet_match(sp->Program(), pkt,
hdr->len, hdr->caplen) )
continue;
val_list* args = new val_list;
StringVal* cmd_val =
new StringVal(sp->Event()->Filter());
args->append(cmd_val);
args->append(BuildHeader(ip));
// ### Need to queue event here.
sp->Event()->Event()->Call(args);
delete args;
}
}
}
int NetSessions::CheckConnectionTag(Connection* conn)
{
if ( current_iosrc->GetCurrentTag() )
{
// Packet is tagged.
if ( conn->GetTimerMgr() == timer_mgr )
{
// Connection uses global timer queue. But the
// packet has a tag that means we got it externally,
// probably from the Time Machine.
DBG_LOG(DBG_TM, "got packet with tag %s for already"
"known connection, reinstantiating",
current_iosrc->GetCurrentTag()->c_str());
return 0;
}
else
{
// Connection uses local timer queue.
TimerMgrMap::iterator i =
timer_mgrs.find(*current_iosrc->GetCurrentTag());
if ( i != timer_mgrs.end() &&
conn->GetTimerMgr() != i->second )
{
// Connection uses different local queue
// than the tag for the current packet
// indicates.
//
// This can happen due to:
// (1) getting same packets with
// different tags
// (2) timer mgr having already expired
DBG_LOG(DBG_TM, "packet ignored due old/inconsistent tag");
return -1;
}
return 1;
}
}
// Packet is not tagged.
if ( conn->GetTimerMgr() != timer_mgr )
{
// Connection does not use the global timer queue. That
// means that this is a live packet belonging to a
// connection for which we have already switched to
// processing external input.
DBG_LOG(DBG_TM, "packet ignored due to processing it in external data");
return -1;
}
return 1;
}
static bool looks_like_IPv4_packet(int len, const struct ip* ip_hdr)
{
if ( (unsigned int) len < sizeof(struct ip) )
return false;
if ( ip_hdr->ip_v == 4 && ntohs(ip_hdr->ip_len) == len )
return true;
else
return false;
}
static inline void delete_tunnel_info(TunnelInfo *ti)
{
if ( ti )
delete ti;
}
void NetSessions::DoNextPacket(double t, const struct pcap_pkthdr* hdr,
const IP_Hdr* ip_hdr, const u_char* const pkt,
int hdr_size)
{
uint32 caplen = hdr->caplen - hdr_size;
const struct ip* ip4 = ip_hdr->IP4_Hdr();
uint32 len = ip_hdr->TotalLen();
if ( hdr->len < len + hdr_size )
{
Weird("truncated_IP", hdr, pkt);
return;
}
// Ignore if packet matches packet filter.
if ( packet_filter && packet_filter->Match(ip_hdr, len, caplen) )
return;
int ip_hdr_len = ip_hdr->HdrLen();
if ( ! ignore_checksums && ip4 &&
ones_complement_checksum((void*) ip4, ip_hdr_len, 0) != 0xffff )
{
Weird("bad_IP_checksum", hdr, pkt);
return;
}
if ( discarder && discarder->NextPacket(ip_hdr, len, caplen) )
return;
FragReassembler* f = 0;
uint32 frag_field = ip_hdr->FragField();
if ( (frag_field & 0x3fff) != 0 )
{
dump_this_packet = 1; // always record fragments
if ( caplen < len )
{
Weird("incompletely_captured_fragment", ip_hdr);
// Don't try to reassemble, that's doomed.
// Discard all except the first fragment (which
// is useful in analyzing header-only traces)
if ( (frag_field & 0x1fff) != 0 )
return;
}
else
{
f = NextFragment(t, ip_hdr, pkt + hdr_size, frag_field);
const IP_Hdr* ih = f->ReassembledPkt();
if ( ! ih )
// It didn't reassemble into anything yet.
return;
ip4 = ih->IP4_Hdr();
ip_hdr = ih;
caplen = len = ip_hdr->TotalLen();
ip_hdr_len = ip_hdr->HdrLen();
}
}
len -= ip_hdr_len; // remove IP header
caplen -= ip_hdr_len;
TunnelInfo *tunnel_info = 0;
if ( tunnel_handler )
{
tunnel_info = tunnel_handler->DecapsulateTunnel(ip_hdr, len, caplen, hdr, pkt);
if (tunnel_info)
{
ip4 = tunnel_info->child->IP4_Hdr();
ip_hdr = tunnel_info->child;
len -= tunnel_info->hdr_len;
caplen -= tunnel_info->hdr_len;
}
}
int proto = ip_hdr->NextProto();
if ( proto != IPPROTO_TCP && proto != IPPROTO_UDP &&
proto != IPPROTO_ICMP )
{
dump_this_packet = 1;
if ( f )
Remove(t);
delete_tunnel_info(tunnel_info);
return;
}
uint32 min_hdr_len = (proto == IPPROTO_TCP) ? sizeof(struct tcphdr) :
(proto == IPPROTO_UDP ? sizeof(struct udphdr) : ICMP_MINLEN);
if ( len < min_hdr_len )
{
Weird("truncated_header", hdr, pkt);
if ( f )
Remove(f); // ###
delete_tunnel_info(tunnel_info);
return;
}
if ( caplen < min_hdr_len )
{
Weird("internally_truncated_header", hdr, pkt);
if ( f )
Remove(f); // ###
delete_tunnel_info(tunnel_info);
return;
}
const u_char* data = ip_hdr->Payload();
ConnID id;
id.src_addr = ip_hdr->SrcAddr();
id.dst_addr = ip_hdr->DstAddr();
Dictionary* d = 0;
bool pass_to_conn_compressor = false;
switch ( proto ) {
case IPPROTO_TCP:
{
const struct tcphdr* tp = (const struct tcphdr *) data;
id.src_port = tp->th_sport;
id.dst_port = tp->th_dport;
id.is_one_way = 0;
d = &tcp_conns;
pass_to_conn_compressor = ip4 && use_connection_compressor;
break;
}
case IPPROTO_UDP:
{
const struct udphdr* up = (const struct udphdr *) data;
id.src_port = up->uh_sport;
id.dst_port = up->uh_dport;
id.is_one_way = 0;
d = &udp_conns;
break;
}
case IPPROTO_ICMP:
{
const struct icmp* icmpp = (const struct icmp *) data;
id.src_port = icmpp->icmp_type;
id.dst_port = ICMP_counterpart(icmpp->icmp_type,
icmpp->icmp_code,
id.is_one_way);
id.src_port = htons(id.src_port);
id.dst_port = htons(id.dst_port);
d = &icmp_conns;
break;
}
default:
Weird(fmt("unknown_protocol %d", proto), hdr, pkt);
if ( f )
Remove(f);
delete_tunnel_info(tunnel_info);
return;
}
HashKey* h = id.BuildConnKey();
if ( ! h )
reporter->InternalError("hash computation failed");
Connection* conn = 0;
// FIXME: The following is getting pretty complex. Need to split up
// into separate functions.
if ( pass_to_conn_compressor )
conn = conn_compressor->NextPacket(t, h, ip_hdr, hdr, pkt);
else
{
conn = (Connection*) d->Lookup(h);
if ( ! conn )
{
conn = NewConn(h, t, &id, data, proto, tunnel_info);
if ( conn )
d->Insert(h, conn);
}
else
{
// We already know that connection.
int consistent = CheckConnectionTag(conn);
if ( consistent < 0 )
{
delete h;
if ( f )
Remove(f);
delete_tunnel_info(tunnel_info);
return;
}
if ( ! consistent || conn->IsReuse(t, data) )
{
if ( consistent )
conn->Event(connection_reused, 0);
Remove(conn);
conn = NewConn(h, t, &id, data, proto, tunnel_info);
if ( conn )
d->Insert(h, conn);
}
else
delete h;
}
if ( ! conn )
delete h;
}
if ( ! conn )
{
delete_tunnel_info(tunnel_info);
if ( f )
Remove(f);
return;
}
int record_packet = 1; // whether to record the packet at all
int record_content = 1; // whether to record its data
int is_orig = addr_eq(id.src_addr, conn->OrigAddr()) &&
id.src_port == conn->OrigPort();
if ( new_packet && ip4 )
conn->Event(new_packet, 0, BuildHeader(ip4));
conn->NextPacket(t, is_orig, ip_hdr, len, caplen, data,
record_packet, record_content,
hdr, pkt, hdr_size);
if ( tunnel_info )
delete tunnel_info;
if ( f )
{
// Above we already recorded the fragment in its entirety.
f->DeleteTimer();
Remove(f); // ###
}
else if ( record_packet )
{
if ( record_content )
dump_this_packet = 1; // save the whole thing
else
{
int hdr_len = data - pkt;
DumpPacket(hdr, pkt, hdr_len); // just save the header
}
}
}
Val* NetSessions::BuildHeader(const struct ip* ip)
{
static RecordType* pkt_hdr_type = 0;
static RecordType* ip_hdr_type = 0;
static RecordType* tcp_hdr_type = 0;
static RecordType* udp_hdr_type = 0;
static RecordType* icmp_hdr_type;
if ( ! pkt_hdr_type )
{
pkt_hdr_type = internal_type("pkt_hdr")->AsRecordType();
ip_hdr_type = internal_type("ip_hdr")->AsRecordType();
tcp_hdr_type = internal_type("tcp_hdr")->AsRecordType();
udp_hdr_type = internal_type("udp_hdr")->AsRecordType();
icmp_hdr_type = internal_type("icmp_hdr")->AsRecordType();
}
RecordVal* pkt_hdr = new RecordVal(pkt_hdr_type);
RecordVal* ip_hdr = new RecordVal(ip_hdr_type);
int ip_hdr_len = ip->ip_hl * 4;
int ip_pkt_len = ntohs(ip->ip_len);
ip_hdr->Assign(0, new Val(ip->ip_hl * 4, TYPE_COUNT));
ip_hdr->Assign(1, new Val(ip->ip_tos, TYPE_COUNT));
ip_hdr->Assign(2, new Val(ip_pkt_len, TYPE_COUNT));
ip_hdr->Assign(3, new Val(ntohs(ip->ip_id), TYPE_COUNT));
ip_hdr->Assign(4, new Val(ip->ip_ttl, TYPE_COUNT));
ip_hdr->Assign(5, new Val(ip->ip_p, TYPE_COUNT));
ip_hdr->Assign(6, new AddrVal(ip->ip_src.s_addr));
ip_hdr->Assign(7, new AddrVal(ip->ip_dst.s_addr));
pkt_hdr->Assign(0, ip_hdr);
// L4 header.
const u_char* data = ((const u_char*) ip) + ip_hdr_len;
int proto = ip->ip_p;
switch ( proto ) {
case IPPROTO_TCP:
{
const struct tcphdr* tp = (const struct tcphdr*) data;
RecordVal* tcp_hdr = new RecordVal(tcp_hdr_type);
int tcp_hdr_len = tp->th_off * 4;
int data_len = ip_pkt_len - ip_hdr_len - tcp_hdr_len;
tcp_hdr->Assign(0, new PortVal(ntohs(tp->th_sport), TRANSPORT_TCP));
tcp_hdr->Assign(1, new PortVal(ntohs(tp->th_dport), TRANSPORT_TCP));
tcp_hdr->Assign(2, new Val(uint32(ntohl(tp->th_seq)), TYPE_COUNT));
tcp_hdr->Assign(3, new Val(uint32(ntohl(tp->th_ack)), TYPE_COUNT));
tcp_hdr->Assign(4, new Val(tcp_hdr_len, TYPE_COUNT));
tcp_hdr->Assign(5, new Val(data_len, TYPE_COUNT));
tcp_hdr->Assign(6, new Val(tp->th_flags, TYPE_COUNT));
tcp_hdr->Assign(7, new Val(ntohs(tp->th_win), TYPE_COUNT));
pkt_hdr->Assign(1, tcp_hdr);
break;
}
case IPPROTO_UDP:
{
const struct udphdr* up = (const struct udphdr*) data;
RecordVal* udp_hdr = new RecordVal(udp_hdr_type);
udp_hdr->Assign(0, new PortVal(ntohs(up->uh_sport), TRANSPORT_UDP));
udp_hdr->Assign(1, new PortVal(ntohs(up->uh_dport), TRANSPORT_UDP));
udp_hdr->Assign(2, new Val(ntohs(up->uh_ulen), TYPE_COUNT));
pkt_hdr->Assign(2, udp_hdr);
break;
}
case IPPROTO_ICMP:
{
const struct icmp* icmpp = (const struct icmp *) data;
RecordVal* icmp_hdr = new RecordVal(icmp_hdr_type);
icmp_hdr->Assign(0, new Val(icmpp->icmp_type, TYPE_COUNT));
pkt_hdr->Assign(3, icmp_hdr);
break;
}
default:
{
// This is not a protocol we understand.
}
}
return pkt_hdr;
}
FragReassembler* NetSessions::NextFragment(double t, const IP_Hdr* ip,
const u_char* pkt, uint32 frag_field)
{
uint32 src_addr = uint32(ip->SrcAddr4());
uint32 dst_addr = uint32(ip->DstAddr4());
uint32 frag_id = ntohs(ip->ID4()); // we actually could skip conv.
ListVal* key = new ListVal(TYPE_ANY);
key->Append(new Val(src_addr, TYPE_COUNT));
key->Append(new Val(dst_addr, TYPE_COUNT));
key->Append(new Val(frag_id, TYPE_COUNT));
HashKey* h = ch->ComputeHash(key, 1);
if ( ! h )
reporter->InternalError("hash computation failed");
FragReassembler* f = fragments.Lookup(h);
if ( ! f )
{
f = new FragReassembler(this, ip, pkt, frag_field, h, t);
fragments.Insert(h, f);
Unref(key);
return f;
}
delete h;
Unref(key);
f->AddFragment(t, ip, pkt, frag_field);
return f;
}
int NetSessions::Get_OS_From_SYN(struct os_type* retval,
uint16 tot, uint8 DF_flag, uint8 TTL, uint16 WSS,
uint8 ocnt, uint8* op, uint16 MSS, uint8 win_scale,
uint32 tstamp, /* uint8 TOS, */ uint32 quirks,
uint8 ECN) const
{
return SYN_OS_Fingerprinter ?
SYN_OS_Fingerprinter->FindMatch(retval, tot, DF_flag, TTL,
WSS, ocnt, op, MSS, win_scale, tstamp,
quirks, ECN) : 0;
}
bool NetSessions::CompareWithPreviousOSMatch(uint32 addr, int id) const
{
return SYN_OS_Fingerprinter ?
SYN_OS_Fingerprinter->CacheMatch(addr, id) : 0;
}
Connection* NetSessions::FindConnection(Val* v)
{
BroType* vt = v->Type();
if ( ! IsRecord(vt->Tag()) )
return 0;
RecordType* vr = vt->AsRecordType();
const val_list* vl = v->AsRecord();
int orig_h, orig_p; // indices into record's value list
int resp_h, resp_p;
if ( vr == conn_id )
{
orig_h = 0;
orig_p = 1;
resp_h = 2;
resp_p = 3;
}
else
{
// While it's not a conn_id, it may have equivalent fields.
orig_h = vr->FieldOffset("orig_h");
resp_h = vr->FieldOffset("resp_h");
orig_p = vr->FieldOffset("orig_p");
resp_p = vr->FieldOffset("resp_p");
if ( orig_h < 0 || resp_h < 0 || orig_p < 0 || resp_p < 0 )
return 0;
// ### we ought to check that the fields have the right
// types, too.
}
addr_type orig_addr = (*vl)[orig_h]->AsAddr();
addr_type resp_addr = (*vl)[resp_h]->AsAddr();
PortVal* orig_portv = (*vl)[orig_p]->AsPortVal();
PortVal* resp_portv = (*vl)[resp_p]->AsPortVal();
ConnID id;
#ifdef BROv6
id.src_addr = orig_addr;
id.dst_addr = resp_addr;
#else
id.src_addr = &orig_addr;
id.dst_addr = &resp_addr;
#endif
id.src_port = htons((unsigned short) orig_portv->Port());
id.dst_port = htons((unsigned short) resp_portv->Port());
id.is_one_way = 0; // ### incorrect for ICMP connections
HashKey* h = id.BuildConnKey();
if ( ! h )
reporter->InternalError("hash computation failed");
Dictionary* d;
if ( orig_portv->IsTCP() )
{
if ( use_connection_compressor )
{
Connection* conn = conn_compressor->Lookup(h);
delete h;
return conn;
}
else
d = &tcp_conns;
}
else if ( orig_portv->IsUDP() )
d = &udp_conns;
else if ( orig_portv->IsICMP() )
d = &icmp_conns;
else
{
// This can happen due to pseudo-connections we
// construct, for example for packet headers embedded
// in ICMPs.
delete h;
return 0;
}
Connection* conn = (Connection*) d->Lookup(h);
delete h;
return conn;
}
void NetSessions::Remove(Connection* c)
{
HashKey* k = c->Key();
if ( k )
{
c->CancelTimers();
TCP_Analyzer* ta = (TCP_Analyzer*) c->GetRootAnalyzer();
if ( ta && c->ConnTransport() == TRANSPORT_TCP )
{
assert(ta->GetTag() == AnalyzerTag::TCP);
TCP_Endpoint* to = ta->Orig();
TCP_Endpoint* tr = ta->Resp();
tcp_stats.StateLeft(to->state, tr->state);
}
if ( c->IsPersistent() )
persistence_serializer->Unregister(c);
c->Done();
if ( connection_state_remove )
c->Event(connection_state_remove, 0);
// Zero out c's copy of the key, so that if c has been Ref()'d
// up, we know on a future call to Remove() that it's no
// longer in the dictionary.
c->ClearKey();
switch ( c->ConnTransport() ) {
case TRANSPORT_TCP:
if ( use_connection_compressor &&
conn_compressor->Remove(k) )
// Note, if the Remove() returned false
// then the compressor doesn't know about
// this connection, which *should* mean that
// we never gave it the connection in the
// first place, and thus we should check
// the regular TCP table instead.
;
else if ( ! tcp_conns.RemoveEntry(k) )
reporter->InternalError("connection missing");
break;
case TRANSPORT_UDP:
if ( ! udp_conns.RemoveEntry(k) )
reporter->InternalError("connection missing");
break;
case TRANSPORT_ICMP:
if ( ! icmp_conns.RemoveEntry(k) )
reporter->InternalError("connection missing");
break;
case TRANSPORT_UNKNOWN:
reporter->InternalError("unknown transport when removing connection");
break;
}
Unref(c);
delete k;
}
}
void NetSessions::Remove(FragReassembler* f)
{
HashKey* k = f->Key();
if ( ! k )
reporter->InternalError("fragment block not in dictionary");
if ( ! fragments.RemoveEntry(k) )
reporter->InternalError("fragment block missing");
Unref(f);
}
void NetSessions::Insert(Connection* c)
{
assert(c->Key());
Connection* old = 0;
switch ( c->ConnTransport() ) {
// Remove first. Otherwise the dictioanry would still
// reference the old key for already existing connections.
case TRANSPORT_TCP:
if ( use_connection_compressor )
old = conn_compressor->Insert(c);
else
{
old = (Connection*) tcp_conns.Remove(c->Key());
tcp_conns.Insert(c->Key(), c);
}
break;
case TRANSPORT_UDP:
old = (Connection*) udp_conns.Remove(c->Key());
udp_conns.Insert(c->Key(), c);
break;
case TRANSPORT_ICMP:
old = (Connection*) icmp_conns.Remove(c->Key());
icmp_conns.Insert(c->Key(), c);
break;
default:
reporter->InternalError("unknown connection type");
}
if ( old && old != c )
{
// Some clean-ups similar to those in Remove() (but invisible
// to the script layer).
old->CancelTimers();
if ( old->IsPersistent() )
persistence_serializer->Unregister(old);
delete old->Key();
old->ClearKey();
Unref(old);
}
}
void NetSessions::Drain()
{
if ( use_connection_compressor )
conn_compressor->Drain();
IterCookie* cookie = tcp_conns.InitForIteration();
Connection* tc;
while ( (tc = tcp_conns.NextEntry(cookie)) )
{
tc->Done();
tc->Event(connection_state_remove, 0);
}
cookie = udp_conns.InitForIteration();
Connection* uc;
while ( (uc = udp_conns.NextEntry(cookie)) )
{
uc->Done();
uc->Event(connection_state_remove, 0);
}
cookie = icmp_conns.InitForIteration();
Connection* ic;
while ( (ic = icmp_conns.NextEntry(cookie)) )
{
ic->Done();
ic->Event(connection_state_remove, 0);
}
ExpireTimerMgrs();
}
void NetSessions::GetStats(SessionStats& s) const
{
s.num_TCP_conns = tcp_conns.Length();
s.num_UDP_conns = udp_conns.Length();
s.num_ICMP_conns = icmp_conns.Length();
s.num_fragments = fragments.Length();
s.num_packets = num_packets_processed;
s.num_timers = timer_mgr->Size();
s.num_events_queued = num_events_queued;
s.num_events_dispatched = num_events_dispatched;
s.max_TCP_conns = tcp_conns.MaxLength();
s.max_UDP_conns = udp_conns.MaxLength();
s.max_ICMP_conns = icmp_conns.MaxLength();
s.max_fragments = fragments.MaxLength();
s.max_timers = timer_mgr->PeakSize();
}
Connection* NetSessions::NewConn(HashKey* k, double t, const ConnID* id,
const u_char* data, int proto, TunnelInfo* tunnel_info)
{
// FIXME: This should be cleaned up a bit, it's too protocol-specific.
// But I'm not yet sure what the right abstraction for these things is.
int src_h = ntohs(id->src_port);
int dst_h = ntohs(id->dst_port);
int flags = 0;
TunnelParent *tunnel_parent = 0;
// Hmm... This is not great.
TransportProto tproto;
switch ( proto ) {
case IPPROTO_ICMP:
tproto = TRANSPORT_ICMP;
break;
case IPPROTO_TCP:
tproto = TRANSPORT_TCP;
break;
case IPPROTO_UDP:
tproto = TRANSPORT_UDP;
break;
default:
reporter->InternalError("unknown transport protocol");
break;
};
if ( tproto == TRANSPORT_TCP )
{
const struct tcphdr* tp = (const struct tcphdr*) data;
flags = tp->th_flags;
}
bool flip = false;
if ( ! WantConnection(src_h, dst_h, tproto, flags, flip) )
return 0;
if ( flip )
{
// Make a guess that we're seeing the tail half of
// an analyzable connection.
ConnID flip_id = *id;
const uint32* ta = flip_id.src_addr;
flip_id.src_addr = flip_id.dst_addr;
flip_id.dst_addr = ta;
uint32 t = flip_id.src_port;
flip_id.src_port = flip_id.dst_port;
flip_id.dst_port = t;
id = &flip_id;
}
if ( tunnel_info )
tunnel_parent = new TunnelParent(&(tunnel_info->parent));
Connection* conn = new Connection(this, k, t, id, tunnel_parent);
conn->SetTransport(tproto);
dpm->BuildInitialAnalyzerTree(tproto, conn, data);
bool external = conn->IsExternal();
if ( external )
conn->AppendAddl(fmt("tag=%s",
conn->GetTimerMgr()->GetTag().c_str()));
// If the connection compressor is active, it takes care of the
// new_connection/connection_external events for TCP connections.
if ( new_connection &&
(tproto != TRANSPORT_TCP || ! use_connection_compressor) )
{
conn->Event(new_connection, 0);
if ( external )
{
val_list* vl = new val_list(2);
vl->append(conn->BuildConnVal());
vl->append(new StringVal(conn->GetTimerMgr()->GetTag().c_str()));
conn->ConnectionEvent(connection_external, 0, vl);
}
}
return conn;
}
bool NetSessions::IsLikelyServerPort(uint32 port, TransportProto proto) const
{
// We keep a cached in-core version of the table to speed up the lookup.
static set<bro_uint_t> port_cache;
static bool have_cache = false;
if ( ! have_cache )
{
ListVal* lv = likely_server_ports->ConvertToPureList();
for ( int i = 0; i < lv->Length(); i++ )
port_cache.insert(lv->Index(i)->InternalUnsigned());
have_cache = true;
Unref(lv);
}
// We exploit our knowledge of PortVal's internal storage mechanism
// here.
if ( proto == TRANSPORT_TCP )
port |= TCP_PORT_MASK;
else if ( proto == TRANSPORT_UDP )
port |= UDP_PORT_MASK;
else if ( proto == TRANSPORT_ICMP )
port |= ICMP_PORT_MASK;
return port_cache.find(port) != port_cache.end();
}
bool NetSessions::WantConnection(uint16 src_port, uint16 dst_port,
TransportProto transport_proto,
uint8 tcp_flags, bool& flip_roles)
{
flip_roles = false;
if ( transport_proto == TRANSPORT_TCP )
{
if ( ! (tcp_flags & TH_SYN) || (tcp_flags & TH_ACK) )
{
// The new connection is starting either without a SYN,
// or with a SYN ack. This means it's a partial connection.
if ( ! partial_connection_ok )
return false;
if ( tcp_flags & TH_SYN && ! tcp_SYN_ack_ok )
return false;
// Try to guess true responder by the port numbers.
// (We might also think that for SYN acks we could
// safely flip the roles, but that doesn't work
// for stealth scans.)
if ( IsLikelyServerPort(src_port, TRANSPORT_TCP) )
{ // connection is a candidate for flipping
if ( IsLikelyServerPort(dst_port, TRANSPORT_TCP) )
// Hmmm, both source and destination
// are plausible. Heuristic: flip only
// if (1) this isn't a SYN ACK (to avoid
// confusing stealth scans) and
// (2) dest port > src port (to favor
// more plausible servers).
flip_roles = ! (tcp_flags & TH_SYN) && src_port < dst_port;
else
// Source is plausible, destination isn't.
flip_roles = true;
}
}
}
else if ( transport_proto == TRANSPORT_UDP )
flip_roles =
IsLikelyServerPort(src_port, TRANSPORT_UDP) &&
! IsLikelyServerPort(dst_port, TRANSPORT_UDP);
return true;
}
TimerMgr* NetSessions::LookupTimerMgr(const TimerMgr::Tag* tag, bool create)
{
if ( ! tag )
{
DBG_LOG(DBG_TM, "no tag, using global timer mgr %p", timer_mgr);
return timer_mgr;
}
TimerMgrMap::iterator i = timer_mgrs.find(*tag);
if ( i != timer_mgrs.end() )
{
DBG_LOG(DBG_TM, "tag %s, using non-global timer mgr %p", tag->c_str(), i->second);
return i->second;
}
else
{
if ( ! create )
return 0;
// Create new queue for tag.
TimerMgr* mgr = new CQ_TimerMgr(*tag);
DBG_LOG(DBG_TM, "tag %s, creating new non-global timer mgr %p", tag->c_str(), mgr);
timer_mgrs.insert(TimerMgrMap::value_type(*tag, mgr));
double t = timer_mgr->Time() + timer_mgr_inactivity_timeout;
timer_mgr->Add(new TimerMgrExpireTimer(t, mgr));
return mgr;
}
}
void NetSessions::ExpireTimerMgrs()
{
for ( TimerMgrMap::iterator i = timer_mgrs.begin();
i != timer_mgrs.end(); ++i )
{
i->second->Expire();
delete i->second;
}
}
void NetSessions::DumpPacket(const struct pcap_pkthdr* hdr,
const u_char* pkt, int len)
{
if ( ! pkt_dumper )
return;
if ( len == 0 )
pkt_dumper->Dump(hdr, pkt);
else
{
struct pcap_pkthdr h = *hdr;
h.caplen = len;
if ( h.caplen > hdr->caplen )
reporter->InternalError("bad modified caplen");
pkt_dumper->Dump(&h, pkt);
}
}
void NetSessions::Internal(const char* msg, const struct pcap_pkthdr* hdr,
const u_char* pkt)
{
DumpPacket(hdr, pkt);
reporter->InternalError("%s", msg);
}
void NetSessions::Weird(const char* name,
const struct pcap_pkthdr* hdr, const u_char* pkt)
{
if ( hdr )
dump_this_packet = 1;
reporter->Weird(name);
}
void NetSessions::Weird(const char* name, const IP_Hdr* ip)
{
reporter->Weird(ip->SrcAddr(), ip->DstAddr(), name);
}
unsigned int NetSessions::ConnectionMemoryUsage()
{
unsigned int mem = 0;
if ( terminating )
// Connections have been flushed already.
return 0;
IterCookie* cookie = tcp_conns.InitForIteration();
Connection* tc;
while ( (tc = tcp_conns.NextEntry(cookie)) )
mem += tc->MemoryAllocation();
cookie = udp_conns.InitForIteration();
Connection* uc;
while ( (uc = udp_conns.NextEntry(cookie)) )
mem += uc->MemoryAllocation();
cookie = icmp_conns.InitForIteration();
Connection* ic;
while ( (ic = icmp_conns.NextEntry(cookie)) )
mem += ic->MemoryAllocation();
return mem;
}
unsigned int NetSessions::ConnectionMemoryUsageConnVals()
{
unsigned int mem = 0;
if ( terminating )
// Connections have been flushed already.
return 0;
IterCookie* cookie = tcp_conns.InitForIteration();
Connection* tc;
while ( (tc = tcp_conns.NextEntry(cookie)) )
mem += tc->MemoryAllocationConnVal();
cookie = udp_conns.InitForIteration();
Connection* uc;
while ( (uc = udp_conns.NextEntry(cookie)) )
mem += uc->MemoryAllocationConnVal();
cookie = icmp_conns.InitForIteration();
Connection* ic;
while ( (ic = icmp_conns.NextEntry(cookie)) )
mem += ic->MemoryAllocationConnVal();
return mem;
}
unsigned int NetSessions::MemoryAllocation()
{
if ( terminating )
// Connections have been flushed already.
return 0;
return ConnectionMemoryUsage()
+ padded_sizeof(*this)
+ ch->MemoryAllocation()
// must take care we don't count the HaskKeys twice.
+ tcp_conns.MemoryAllocation() - padded_sizeof(tcp_conns) -
// 12 is sizeof(Key) from ConnID::BuildConnKey();
// it can't be (easily) accessed here. :-(
(tcp_conns.Length() * pad_size(12))
+ udp_conns.MemoryAllocation() - padded_sizeof(udp_conns) -
(udp_conns.Length() * pad_size(12))
+ icmp_conns.MemoryAllocation() - padded_sizeof(icmp_conns) -
(icmp_conns.Length() * pad_size(12))
+ fragments.MemoryAllocation() - padded_sizeof(fragments)
// FIXME: MemoryAllocation() not implemented for rest.
;
}