Move IP and IP tunnel code from Sessions into packet analyzers

This commit is contained in:
Tim Wojtulewicz 2020-09-23 16:17:06 -07:00
parent 69da2d7b1d
commit 1cf251d1ca
53 changed files with 1226 additions and 907 deletions

View file

@ -1,37 +1,274 @@
// See the file "COPYING" in the main distribution directory for copyright.
#include "IP.h"
#include "NetVar.h"
#include "zeek/NetVar.h"
#include "zeek/IP.h"
#include "zeek/Discard.h"
#include "zeek/PacketFilter.h"
#include "zeek/Sessions.h"
#include "zeek/RunState.h"
#include "zeek/Frag.h"
#include "zeek/Event.h"
#include "zeek/TunnelEncapsulation.h"
using namespace zeek::packet_analysis::IP;
IPAnalyzer::IPAnalyzer()
: zeek::packet_analysis::Analyzer("IP")
{
discarder = new detail::Discarder();
if ( ! discarder->IsActive() )
{
delete discarder;
discarder = nullptr;
}
}
IPAnalyzer::~IPAnalyzer()
{
}
bool IPAnalyzer::AnalyzePacket(size_t len, const uint8_t* data, Packet* packet)
{
// Assume we're pointing at IP. Just figure out which version.
if ( sizeof(struct ip) >= len )
EncapsulationStack* encapsulation = nullptr;
auto it = packet->key_store.find("encap");
if ( it != packet->key_store.end() )
encapsulation = std::any_cast<EncapsulationStack*>(it->second);
// Check to make sure we have enough data left for an IP header to be here. Note we only
// check ipv4 here. We'll check ipv6 later once we determine we have an ipv6 header.
if ( len < sizeof(struct ip) )
{
packet->Weird("packet_analyzer_truncated_header");
packet->Weird("truncated_IP");
return false;
}
// TODO: i feel like this could be generated as we move along the header hierarchy.
// TODO: the sessions code expects that the header size does not include the ip header. Should
// this change?
packet->hdr_size = static_cast<int32_t>(data - packet->data);
// Cast the current data pointer to an IP header pointer so we can use it to get some
// data about the header.
auto ip = (const struct ip *)data;
uint32_t protocol = ip->ip_v;
auto inner_analyzer = Lookup(protocol);
if ( inner_analyzer == nullptr )
std::unique_ptr<IP_Hdr> ip_hdr = nullptr;
if ( protocol == 4 )
{
DBG_LOG(DBG_PACKET_ANALYSIS, "Analysis in %s failed, could not find analyzer for identifier %#x.",
GetAnalyzerName(), protocol);
packet->Weird("no_suitable_analyzer_found");
ip_hdr = std::make_unique<IP_Hdr>(ip, false);
packet->l3_proto = L3_IPV4;
}
else if ( protocol == 6 )
{
if ( len < sizeof(struct ip6_hdr) )
{
packet->Weird("truncated_IP");
return false;
}
ip_hdr = std::make_unique<IP_Hdr>((const struct ip6_hdr*) data, false, len);
packet->l3_proto = L3_IPV6;
}
else
{
packet->Weird("unknown_ip_version");
return false;
}
DBG_LOG(DBG_PACKET_ANALYSIS, "Analysis in %s succeeded, next layer identifier is %#x.",
GetAnalyzerName(), protocol);
return inner_analyzer->AnalyzePacket(len, data, packet);
}
const struct ip* ip4 = ip_hdr->IP4_Hdr();
// total_len is the length of the packet minus all of the headers so far, including IP
uint32_t total_len = ip_hdr->TotalLen();
if ( total_len == 0 )
{
// TCP segmentation offloading can zero out the ip_len field.
packet->Weird("ip_hdr_len_zero", encapsulation);
// Cope with the zero'd out ip_len field by using the caplen.
total_len = packet->cap_len - packet->hdr_size;
}
if ( packet->len < total_len + packet->hdr_size )
{
packet->Weird("truncated_IP", encapsulation);
return false;
}
// For both of these it is safe to pass ip_hdr because the presence
// is guaranteed for the functions that pass data to us.
uint16_t ip_hdr_len = ip_hdr->HdrLen();
if ( ip_hdr_len > total_len )
{
sessions->Weird("invalid_IP_header_size", ip_hdr.get(), encapsulation);
return false;
}
if ( ip_hdr_len > len )
{
sessions->Weird("internally_truncated_header", ip_hdr.get(), encapsulation);
return false;
}
if ( ip_hdr->IP4_Hdr() )
{
if ( ip_hdr_len < sizeof(struct ip) )
{
packet->Weird("IPv4_min_header_size");
return false;
}
}
else
{
if ( ip_hdr_len < sizeof(struct ip6_hdr) )
{
packet->Weird("IPv6_min_header_size");
return false;
}
}
// Ignore if packet matches packet filter.
detail::PacketFilter* packet_filter = sessions->GetPacketFilter(false);
if ( packet_filter && packet_filter->Match(ip_hdr.get(), total_len, len) )
return false;
if ( ! packet->l2_checksummed && ! detail::ignore_checksums && ip4 &&
detail::in_cksum(reinterpret_cast<const uint8_t*>(ip4), ip_hdr_len) != 0xffff )
{
sessions->Weird("bad_IP_checksum", packet, encapsulation);
return false;
}
if ( discarder && discarder->NextPacket(ip_hdr.get(), total_len, len) )
return false;
detail::FragReassembler* f = nullptr;
if ( ip_hdr->IsFragment() )
{
packet->dump_packet = true; // always record fragments
if ( len < total_len )
{
sessions->Weird("incompletely_captured_fragment", ip_hdr.get(), encapsulation);
// 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 false;
}
else
{
f = detail::fragment_mgr->NextFragment(run_state::processing_start_time, ip_hdr.get(), packet->data + packet->hdr_size);
IP_Hdr* ih = f->ReassembledPkt();
if ( ! ih )
// It didn't reassemble into anything yet.
return true;
ip4 = ih->IP4_Hdr();
// Delete the old ip_hdr and replace it with this one.
ip_hdr.reset(ih);
len = total_len = ip_hdr->TotalLen();
ip_hdr_len = ip_hdr->HdrLen();
packet->cap_len = total_len + packet->hdr_size;
// TODO: in the old code, the data pointer is updated to point at the IP header's
// payload, so it contains all of the data when it's processed. This isn't a big
// deal for when we pass it down into the session analyzers, since that does the
// same itself. should it be updated here for the case where a fragmented packet
// is actually tunneled? is that a thing that can happen? Does updating the data
// pointer without also updating the one in packet cause any problems?
if ( ip_hdr_len > total_len )
{
sessions->Weird("invalid_IP_header_size", ip_hdr.get(), encapsulation);
return false;
}
}
}
detail::FragReassemblerTracker frt(f);
// We stop building the chain when seeing IPPROTO_ESP so if it's
// there, it's always the last.
if ( ip_hdr->LastHeader() == IPPROTO_ESP )
{
packet->dump_packet = true;
if ( esp_packet )
event_mgr.Enqueue(esp_packet, ip_hdr->ToPktHdrVal());
// Can't do more since upper-layer payloads are going to be encrypted.
return true;
}
#ifdef ENABLE_MOBILE_IPV6
// We stop building the chain when seeing IPPROTO_MOBILITY so it's always
// last if present.
if ( ip_hdr->LastHeader() == IPPROTO_MOBILITY )
{
dump_this_packet = true;
if ( ! ignore_checksums && mobility_header_checksum(ip_hdr) != 0xffff )
{
sessions->Weird("bad_MH_checksum", packet, encapsulation);
return false;
}
if ( mobile_ipv6_message )
event_mgr.Enqueue(mobile_ipv6_message, ip_hdr->ToPktHdrVal());
if ( ip_hdr->NextProto() != IPPROTO_NONE )
sessions->Weird("mobility_piggyback", packet, encapsulation);
return true;
}
#endif
// Advance the data pointer past the IP header based on the header length
data += ip_hdr_len;
len -= ip_hdr_len;
bool return_val = true;
int proto = ip_hdr->NextProto();
packet->key_store["ip_hdr"] = ip_hdr.get();
packet->key_store["proto"] = proto;
switch ( proto ) {
case IPPROTO_TCP:
case IPPROTO_UDP:
case IPPROTO_ICMP:
case IPPROTO_ICMPV6:
sessions->DoNextPacket(run_state::processing_start_time, packet, ip_hdr.get(), encapsulation);
break;
case IPPROTO_NONE:
// If the packet is encapsulated in Teredo, then it was a bubble and
// the Teredo analyzer may have raised an event for that, else we're
// not sure the reason for the No Next header in the packet.
if ( ! ( encapsulation &&
encapsulation->LastType() == BifEnum::Tunnel::TEREDO ) )
{
sessions->Weird("ipv6_no_next", packet);
return_val = false;
}
break;
default:
// For everything else, pass it on to another analyzer. If there's no one to handle that,
// it'll report a Weird.
return_val = ForwardPacket(len, data, packet, proto);
break;
}
if ( f )
{
// If this was a fragment, we need to release the pointer here so that it doesn't get
// deleted. Deleting this one will be the responsibility of the fragment tracker.
ip_hdr.release();
f->DeleteTimer();
}
return return_val;
}

View file

@ -2,15 +2,18 @@
#pragma once
#include <packet_analysis/Analyzer.h>
#include <packet_analysis/Component.h>
#include "zeek/packet_analysis/Analyzer.h"
#include "zeek/packet_analysis/Component.h"
#include "zeek/Frag.h"
ZEEK_FORWARD_DECLARE_NAMESPACED(Discarder, zeek::detail);
namespace zeek::packet_analysis::IP {
class IPAnalyzer : public Analyzer {
public:
IPAnalyzer();
~IPAnalyzer() override = default;
~IPAnalyzer() override;
bool AnalyzePacket(size_t len, const uint8_t* data, Packet* packet) override;
@ -18,6 +21,15 @@ public:
{
return std::make_shared<IPAnalyzer>();
}
private:
// Returns a reassembled packet, or nil if there are still
// some missing fragments.
zeek::detail::FragReassembler* NextFragment(double t, const IP_Hdr* ip,
const u_char* pkt);
zeek::detail::Discarder* discarder = nullptr;
};
}