// See the file "COPYING" in the main distribution directory for copyright. #include "config.h" #include #include #include #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 "DPM.h" #include "PacketSort.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_ADDR)); // source IP address t->Append(base_type(TYPE_ADDR)); // 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; } NetSessions::~NetSessions() { delete ch; delete packet_filter; delete SYN_OS_Fingerprinter; delete pkt_profiler; Unref(arp_analyzer); delete discarder; delete stp_manager; } void NetSessions::Done() { } 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(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 ( encap_hdr_size > 0 && ip_data ) { // We're doing tunnel encapsulation. Check whether there's // a particular associated port. // // Should we discourage the use of encap_hdr_size for UDP // tunnneling? It is probably better handled by enabling // BifConst::parse_udp_tunnels instead of specifying a fixed // encap_hdr_size. if ( udp_tunnel_port > 0 ) { ASSERT(ip_hdr); if ( ip_hdr->ip_p == IPPROTO_UDP ) { const struct udphdr* udp_hdr = reinterpret_cast (ip_data); if ( ntohs(udp_hdr->uh_dport) == udp_tunnel_port ) { // A match. hdr_size += encap_hdr_size; } } } else // Blanket encapsulation hdr_size += encap_hdr_size; } // Check IP packets encapsulated through UDP tunnels. // Specifying a udp_tunnel_port is optional but recommended (to avoid // the cost of checking every UDP packet). else if ( BifConst::parse_udp_tunnels && ip_data && ip_hdr->ip_p == IPPROTO_UDP ) { const struct udphdr* udp_hdr = reinterpret_cast(ip_data); if ( udp_tunnel_port == 0 || // 0 matches any port udp_tunnel_port == ntohs(udp_hdr->uh_dport) ) { const u_char* udp_data = ip_data + sizeof(struct udphdr); const struct ip* ip_encap = reinterpret_cast(udp_data); const int ip_encap_len = ntohs(udp_hdr->uh_ulen) - sizeof(struct udphdr); const int ip_encap_caplen = hdr->caplen - (udp_data - pkt); if ( looks_like_IPv4_packet(ip_encap_len, ip_encap) ) hdr_size = udp_data - pkt; } } 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, false); DoNextPacket(t, hdr, &ip_hdr, pkt, hdr_size); } else if ( ip->ip_v == 6 ) { IP_Hdr ip_hdr((const struct ip6_hdr*) (pkt + hdr_size), false); DoNextPacket(t, hdr, &ip_hdr, pkt, hdr_size); } else if ( ARP_Analyzer::IsARP(pkt, hdr_size) ) { if ( arp_analyzer ) arp_analyzer->NextPacket(t, hdr, pkt, hdr_size); } else { Weird("unknown_packet_type", hdr, pkt); return; } } 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); IP_Hdr ip_hdr(ip, false); args->append(ip_hdr.BuildPktHdrVal()); // ### Need to queue event here. try { sp->Event()->Event()->Call(args); } catch ( InterpreterException& e ) { /* Already reported. */ } 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; } 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; // [Robin] dump_this_packet = 1 for non-ICMP/UDP/TCP removed here. Why? FragReassembler* f = 0; if ( ip_hdr->IsFragment() ) { 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 ( ip_hdr->FragOffset() != 0 ) return; } else { f = NextFragment(t, ip_hdr, pkt + hdr_size); 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; // [Robin] Does ESP need to be the last header? if ( ip_hdr->LastHeader() == IPPROTO_ESP ) { if ( esp_packet ) { val_list* vl = new val_list(); vl->append(ip_hdr->BuildPktHdrVal()); mgr.QueueEvent(esp_packet, vl); } Remove(f); // Can't do more since upper-layer payloads are going to be encrypted. return; } int proto = ip_hdr->NextProto(); if ( CheckHeaderTrunc(proto, len, caplen, hdr, pkt) ) { Remove(f); return; } // [Robin] The Remove(f) used to be here, while it's now before every // return statement. I'm not seeing why? const u_char* data = ip_hdr->Payload(); ConnID id; id.src_addr = ip_hdr->SrcAddr(); id.dst_addr = ip_hdr->DstAddr(); Dictionary* d = 0; 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; 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); Remove(f); return; } HashKey* h = BuildConnIDHashKey(id); if ( ! h ) reporter->InternalError("hash computation failed"); Connection* conn = 0; // FIXME: The following is getting pretty complex. Need to split up // into separate functions. conn = (Connection*) d->Lookup(h); if ( ! conn ) { conn = NewConn(h, t, &id, data, proto); if ( conn ) d->Insert(h, conn); } else { // We already know that connection. int consistent = CheckConnectionTag(conn); if ( consistent < 0 ) { delete h; Remove(f); return; } if ( ! consistent || conn->IsReuse(t, data) ) { if ( consistent ) conn->Event(connection_reused, 0); Remove(conn); conn = NewConn(h, t, &id, data, proto); if ( conn ) d->Insert(h, conn); } else delete h; } if ( ! conn ) { delete h; 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 = (id.src_addr == conn->OrigAddr()) && (id.src_port == conn->OrigPort()); Val* pkt_hdr_val = 0; if ( ipv6_ext_headers && ip_hdr->NumHeaders() > 1 ) { pkt_hdr_val = ip_hdr->BuildPktHdrVal(); conn->Event(ipv6_ext_headers, 0, pkt_hdr_val); } if ( new_packet ) conn->Event(new_packet, 0, pkt_hdr_val ? pkt_hdr_val->Ref() : ip_hdr->BuildPktHdrVal()); conn->NextPacket(t, is_orig, ip_hdr, len, caplen, data, record_packet, record_content, hdr, pkt, hdr_size); 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 } } } bool NetSessions::CheckHeaderTrunc(int proto, uint32 len, uint32 caplen, const struct pcap_pkthdr* h, const u_char* p) { uint32 min_hdr_len = 0; switch ( proto ) { case IPPROTO_TCP: min_hdr_len = sizeof(struct tcphdr); break; case IPPROTO_UDP: min_hdr_len = sizeof(struct udphdr); break; case IPPROTO_ICMP: default: min_hdr_len = ICMP_MINLEN; break; } if ( len < min_hdr_len ) { Weird("truncated_header", h, p); return true; } if ( caplen < min_hdr_len ) { Weird("internally_truncated_header", h, p); return true; } return false; } FragReassembler* NetSessions::NextFragment(double t, const IP_Hdr* ip, const u_char* pkt) { uint32 frag_id = ip->ID(); ListVal* key = new ListVal(TYPE_ANY); key->Append(new AddrVal(ip->SrcAddr())); key->Append(new AddrVal(ip->DstAddr())); 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, h, t); fragments.Insert(h, f); Unref(key); return f; } delete h; Unref(key); f->AddFragment(t, ip, pkt); 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(const IPAddr& 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. } const IPAddr& orig_addr = (*vl)[orig_h]->AsAddr(); const IPAddr& resp_addr = (*vl)[resp_h]->AsAddr(); PortVal* orig_portv = (*vl)[orig_p]->AsPortVal(); PortVal* resp_portv = (*vl)[resp_p]->AsPortVal(); ConnID id; id.src_addr = orig_addr; id.dst_addr = resp_addr; 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 = BuildConnIDHashKey(id); if ( ! h ) reporter->InternalError("hash computation failed"); Dictionary* d; if ( orig_portv->IsTCP() ) 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 ( ! 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) { if ( ! f ) return; 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: 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() { 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) { // 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; // Hmm... This is not great. TransportProto tproto = TRANSPORT_UNKNOWN; 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 IPAddr 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; } Connection* conn = new Connection(this, k, t, id); 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 ( new_connection ) { 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 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. ; }