Add new UDP packet analyzer, remove old one

This commit is contained in:
Tim Wojtulewicz 2021-04-14 10:44:51 -07:00
parent d8adfaef65
commit c21af39a30
22 changed files with 357 additions and 392 deletions

View file

@ -3,6 +3,7 @@ include(ZeekPlugin)
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
zeek_plugin_begin(PacketAnalyzer UDP_PKT)
zeek_plugin_begin(Zeek UDP)
zeek_plugin_cc(UDP.cc Plugin.cc)
zeek_plugin_bif(events.bif)
zeek_plugin_end()

View file

@ -12,9 +12,11 @@ public:
{
AddComponent(new zeek::packet_analysis::Component("UDP",
zeek::packet_analysis::UDP::UDPAnalyzer::Instantiate));
AddComponent(new zeek::analyzer::Component("UDP",
zeek::packet_analysis::UDP::UDPTransportAnalyzer::Instantiate));
zeek::plugin::Configuration config;
config.name = "Zeek::UDP_PKT";
config.name = "Zeek::UDP";
config.description = "Packet analyzer for UDP";
return config;
}

View file

@ -2,12 +2,31 @@
#include "zeek/packet_analysis/protocol/udp/UDP.h"
#include "zeek/RunState.h"
#include "zeek/Conn.h"
#include "zeek/session/Manager.h"
#include "zeek/analyzer/Manager.h"
#include "zeek/analyzer/protocol/pia/PIA.h"
#include "zeek/analyzer/protocol/conn-size/ConnSize.h"
#include "zeek/packet_analysis/protocol/udp/events.bif.h"
using namespace zeek::packet_analysis::UDP;
using namespace zeek::packet_analysis::IP;
constexpr uint32_t HIST_ORIG_DATA_PKT = 0x1;
constexpr uint32_t HIST_RESP_DATA_PKT = 0x2;
constexpr uint32_t HIST_ORIG_CORRUPT_PKT = 0x4;
constexpr uint32_t HIST_RESP_CORRUPT_PKT = 0x8;
enum UDP_EndpointState {
UDP_INACTIVE, // no packet seen
UDP_ACTIVE, // packets seen
};
UDPAnalyzer::UDPAnalyzer() : IPBasedAnalyzer("UDP", TRANSPORT_UDP, UDP_PORT_MASK, false)
{
// TODO: remove once the other plugins are done
new_plugin = true;
}
UDPAnalyzer::~UDPAnalyzer()
@ -33,9 +52,34 @@ bool UDPAnalyzer::AnalyzePacket(size_t len, const uint8_t* data, Packet* packet)
return true;
}
void UDPAnalyzer::CreateTransportAnalyzer(Connection* conn, IPBasedTransportAnalyzer*& root,
analyzer::pia::PIA*& pia, bool& check_port)
{
root = new UDPTransportAnalyzer(conn);
root->SetParent(this);
conn->EnableStatusUpdateTimer();
conn->SetInactivityTimeout(zeek::detail::udp_inactivity_timeout);
pia = new analyzer::pia::PIA_UDP(conn);
check_port = true;
}
void UDPAnalyzer::Initialize()
{
IPBasedAnalyzer::Initialize();
const auto& id = detail::global_scope()->Find("Tunnel::vxlan_ports");
if ( ! (id && id->GetVal()) )
reporter->FatalError("Tunnel::vxlan_ports not defined");
auto table_val = id->GetVal()->AsTableVal();
auto port_list = table_val->ToPureListVal();
for ( auto i = 0; i < port_list->Length(); ++i )
vxlan_ports.emplace_back(port_list->Idx(i)->AsPortVal()->Port());
}
bool UDPAnalyzer::WantConnection(uint16_t src_port, uint16_t dst_port,
@ -44,3 +88,253 @@ bool UDPAnalyzer::WantConnection(uint16_t src_port, uint16_t dst_port,
flip_roles = IsLikelyServerPort(src_port) && ! IsLikelyServerPort(dst_port);
return true;
}
void UDPAnalyzer::ContinueProcessing(Connection* c, double t, bool is_orig, int remaining, Packet* pkt)
{
conn = c;
auto* ta = static_cast<UDPTransportAnalyzer*>(conn->GetRootAnalyzer());
const u_char* data = pkt->ip_hdr->Payload();
int len = pkt->ip_hdr->PayloadLen();
const struct udphdr* up = (const struct udphdr*) data;
const std::unique_ptr<IP_Hdr>& ip = pkt->ip_hdr;
ta->DeliverPacket(len, data, is_orig, -1, ip.get(), remaining);
// Increment data before checksum check so that data will
// point to UDP payload even if checksum fails. Particularly,
// it allows event packet_contents to get to the data.
data += sizeof(struct udphdr);
// We need the min() here because Ethernet frame padding can lead to
// remaining > len.
if ( packet_contents )
ta->PacketContents(data, std::min(len, remaining) - sizeof(struct udphdr));
int chksum = up->uh_sum;
auto validate_checksum =
! run_state::current_pkt->l3_checksummed &&
! zeek::detail::ignore_checksums &&
! zeek::id::find_val<TableVal>("ignore_checksums_nets")->Contains(ip->IPHeaderSrcAddr()) &&
remaining >=len;
constexpr auto vxlan_len = 8;
constexpr auto eth_len = 14;
if ( validate_checksum &&
len > ((int)sizeof(struct udphdr) + vxlan_len + eth_len) &&
(data[0] & 0x08) == 0x08 )
{
if ( std::find(vxlan_ports.begin(), vxlan_ports.end(),
ntohs(up->uh_dport)) != vxlan_ports.end() )
{
// Looks like VXLAN on a well-known port, so the checksum should be
// transmitted as zero, and we should accept that. If not
// transmitted as zero, then validating the checksum is optional.
if ( chksum == 0 )
validate_checksum = false;
else
validate_checksum = BifConst::Tunnel::validate_vxlan_checksums;
}
}
if ( validate_checksum )
{
bool bad = false;
if ( ip->IP4_Hdr() )
{
if ( chksum && ! ValidateChecksum(ip.get(), up, len) )
bad = true;
}
/* checksum is not optional for IPv6 */
else if ( ! ValidateChecksum(ip.get(), up, len) )
bad = true;
if ( bad )
{
ta->Weird("bad_UDP_checksum");
if ( is_orig )
{
uint32_t t = ta->req_chk_thresh;
if ( conn->ScaledHistoryEntry('C',
ta->req_chk_cnt,
ta->req_chk_thresh) )
ChecksumEvent(is_orig, t);
}
else
{
uint32_t t = ta->rep_chk_thresh;
if ( conn->ScaledHistoryEntry('c',
ta->rep_chk_cnt,
ta->rep_chk_thresh) )
ChecksumEvent(is_orig, t);
}
return;
}
}
int ulen = ntohs(up->uh_ulen);
if ( ulen != len )
ta->Weird("UDP_datagram_length_mismatch", util::fmt("%d != %d", ulen, len));
len -= sizeof(struct udphdr);
ulen -= sizeof(struct udphdr);
remaining -= sizeof(struct udphdr);
conn->SetLastTime(run_state::current_timestamp);
if ( udp_contents )
{
static auto udp_content_ports = id::find_val<TableVal>("udp_content_ports");
static auto udp_content_delivery_ports_orig = id::find_val<TableVal>("udp_content_delivery_ports_orig");
static auto udp_content_delivery_ports_resp = id::find_val<TableVal>("udp_content_delivery_ports_resp");
bool do_udp_contents = false;
const auto& sport_val = val_mgr->Port(ntohs(up->uh_sport), TRANSPORT_UDP);
const auto& dport_val = val_mgr->Port(ntohs(up->uh_dport), TRANSPORT_UDP);
if ( udp_content_ports->FindOrDefault(dport_val) ||
udp_content_ports->FindOrDefault(sport_val) )
do_udp_contents = true;
else
{
uint16_t p = zeek::detail::udp_content_delivery_ports_use_resp ? conn->RespPort()
: up->uh_dport;
const auto& port_val = zeek::val_mgr->Port(ntohs(p), TRANSPORT_UDP);
if ( is_orig )
{
auto result = udp_content_delivery_ports_orig->FindOrDefault(port_val);
if ( zeek::detail::udp_content_deliver_all_orig || (result && result->AsBool()) )
do_udp_contents = true;
}
else
{
auto result = udp_content_delivery_ports_resp->FindOrDefault(port_val);
if ( zeek::detail::udp_content_deliver_all_resp || (result && result->AsBool()) )
do_udp_contents = true;
}
}
if ( do_udp_contents )
ta->EnqueueConnEvent(udp_contents,
ta->ConnVal(),
val_mgr->Bool(is_orig),
make_intrusive<StringVal>(len, (const char*) data));
}
if ( is_orig )
{
conn->CheckHistory(HIST_ORIG_DATA_PKT, 'D');
ta->UpdateLength(is_orig, ulen);
ta->Event(udp_request);
}
else
{
conn->CheckHistory(HIST_RESP_DATA_PKT, 'd');
ta->UpdateLength(is_orig, ulen);
ta->Event(udp_reply);
}
// Send the packet back into the packet analysis framework.
ForwardPacket(len, data, pkt);
// Also try sending it into session analysis.
if ( remaining >= len )
ta->ForwardPacket(len, data, is_orig, -1, ip.get(), remaining);
conn = nullptr;
}
bool UDPAnalyzer::ValidateChecksum(const IP_Hdr* ip, const udphdr* up, int len)
{
auto sum = detail::ip_in_cksum(ip->IP4_Hdr(), ip->SrcAddr(), ip->DstAddr(),
IPPROTO_UDP,
reinterpret_cast<const uint8_t*>(up), len);
return sum == 0xffff;
}
void UDPAnalyzer::ChecksumEvent(bool is_orig, uint32_t threshold)
{
conn->HistoryThresholdEvent(udp_multiple_checksum_errors, is_orig, threshold);
}
void UDPTransportAnalyzer::AddExtraAnalyzers(Connection* conn)
{
static analyzer::Tag analyzer_connsize = analyzer_mgr->GetComponentTag("CONNSIZE");
if ( analyzer_mgr->IsEnabled(analyzer_connsize) )
// Add ConnSize analyzer. Needs to see packets, not stream.
AddChildAnalyzer(new analyzer::conn_size::ConnSize_Analyzer(conn));
}
void UDPTransportAnalyzer::UpdateConnVal(RecordVal* conn_val)
{
auto orig_endp = conn_val->GetField("orig");
auto resp_endp = conn_val->GetField("resp");
UpdateEndpointVal(orig_endp, true);
UpdateEndpointVal(resp_endp, false);
// Call children's UpdateConnVal
Analyzer::UpdateConnVal(conn_val);
}
void UDPTransportAnalyzer::UpdateEndpointVal(const ValPtr& endp_arg, bool is_orig)
{
bro_int_t size = is_orig ? request_len : reply_len;
auto endp = endp_arg->AsRecordVal();
if ( size < 0 )
{
endp->Assign(0, val_mgr->Count(0));
endp->Assign(1, UDP_INACTIVE);
}
else
{
endp->Assign(0, static_cast<uint64_t>(size));
endp->Assign(1, UDP_ACTIVE);
}
}
void UDPTransportAnalyzer::UpdateLength(bool is_orig, int len)
{
if ( is_orig )
{
if ( request_len < 0 )
request_len = len;
else
{
request_len += len;
#ifdef DEBUG
if ( request_len < 0 )
reporter->Warning("wrapping around for UDP request length");
#endif
}
}
else
{
if ( reply_len < 0 )
reply_len = len;
else
{
reply_len += len;
#ifdef DEBUG
if ( reply_len < 0 )
reporter->Warning("wrapping around for UDP reply length");
#endif
}
}
}

View file

@ -8,7 +8,7 @@
namespace zeek::packet_analysis::UDP {
class UDPAnalyzer : public IP::IPBasedAnalyzer {
class UDPAnalyzer final : public IP::IPBasedAnalyzer {
public:
UDPAnalyzer();
~UDPAnalyzer() override;
@ -23,6 +23,13 @@ public:
void CreateTransportAnalyzer(Connection* conn, IP::IPBasedTransportAnalyzer*& root,
analyzer::pia::PIA*& pia, bool& check_port) override;
/**
* Initialize the analyzer. This method is called after the configuration
* was read. Derived classes can override this method to implement custom
* initialization.
*/
void Initialize() override;
protected:
/**
@ -39,6 +46,54 @@ protected:
*/
bool WantConnection(uint16_t src_port, uint16_t dst_port,
const u_char* data, bool& flip_roles) const override;
void ContinueProcessing(Connection* c, double t, bool is_orig, int remaining,
Packet* pkt) override;
private:
// Returns true if the checksum is valid, false if not
static bool ValidateChecksum(const IP_Hdr* ip, const struct udphdr* up,
int len);
void ChecksumEvent(bool is_orig, uint32_t threshold);
Connection* conn;
std::vector<uint16_t> vxlan_ports;
};
class UDPTransportAnalyzer final : public IP::IPBasedTransportAnalyzer {
public:
UDPTransportAnalyzer(Connection* conn) :
IP::IPBasedTransportAnalyzer("UDP", conn) { }
static zeek::analyzer::Analyzer* Instantiate(Connection* conn)
{
return new UDPTransportAnalyzer(conn);
}
void AddExtraAnalyzers(Connection* conn) override;
void UpdateConnVal(RecordVal* conn_val) override;
void UpdateLength(bool is_orig, int len);
// For tracking checksum history. These are connection-specific so they
// need to be stored in the transport analyzer created for each
// connection.
uint32_t req_chk_cnt = 0;
uint32_t req_chk_thresh = 1;
uint32_t rep_chk_cnt = 0;
uint32_t rep_chk_thresh = 1;
private:
void UpdateEndpointVal(const ValPtr& endp_arg, bool is_orig);
bro_int_t request_len = -1;
bro_int_t reply_len = -1;
};
}

View file

@ -0,0 +1,51 @@
## Generated for each packet sent by a UDP flow's originator. This a potentially
## expensive event due to the volume of UDP traffic and should be used with
## care.
##
## u: The connection record for the corresponding UDP flow.
##
## .. zeek:see:: udp_contents udp_reply udp_session_done
event udp_request%(u: connection%);
## Generated for each packet sent by a UDP flow's responder. This a potentially
## expensive event due to the volume of UDP traffic and should be used with
## care.
##
## u: The connection record for the corresponding UDP flow.
##
## .. zeek:see:: udp_contents udp_request udp_session_done
event udp_reply%(u: connection%);
## Generated for UDP packets to pass on their payload. As the number of UDP
## packets can be very large, this event is normally raised only for those on
## ports configured in :zeek:id:`udp_content_delivery_ports_orig` (for packets
## sent by the flow's originator) or :zeek:id:`udp_content_delivery_ports_resp`
## (for packets sent by the flow's responder). However, delivery can be enabled
## for all UDP request and reply packets by setting
## :zeek:id:`udp_content_deliver_all_orig` or
## :zeek:id:`udp_content_deliver_all_resp`, respectively. Note that this
## event is also raised for all matching UDP packets, including empty ones.
##
## u: The connection record for the corresponding UDP flow.
##
## is_orig: True if the event is raised for the originator side.
##
## contents: TODO.
##
## .. zeek:see:: udp_reply udp_request udp_session_done
## udp_content_deliver_all_orig udp_content_deliver_all_resp
## udp_content_delivery_ports_orig udp_content_delivery_ports_resp
event udp_contents%(u: connection, is_orig: bool, contents: string%);
## Generated if a UDP flow crosses a checksum-error threshold, per
## 'C'/'c' history reporting.
##
## u: The connection record for the corresponding UDP flow.
##
## is_orig: True if the event is raised for the originator side.
##
## threshold: the threshold that was crossed
##
## .. zeek:see:: udp_reply udp_request udp_session_done
## tcp_multiple_checksum_errors
event udp_multiple_checksum_errors%(u: connection, is_orig: bool, threshold: count%);