PacketAnalyzer::Geneve: Add get_options()

Allow to extract Geneve options on-demand, for example during a
new_connection() event.
This commit is contained in:
Arne Welzel 2025-02-14 17:50:28 -08:00
parent 2dc98acd1f
commit 776c003033
16 changed files with 168 additions and 2 deletions

3
NEWS
View file

@ -24,6 +24,9 @@ New Functionality
encrypted session information from a Kerberos response, including the cipher
and encrypted data.
- Geneve tunnel options of the current packet can be extracted from scripts
using the new PacketAnalyzer::Geneve::get_options() builtin function.
Changed Functionality
---------------------

View file

@ -6,6 +6,18 @@ export {
## if you customize this, you may still want to manually ensure that
## :zeek:see:`likely_server_ports` also gets populated accordingly.
const geneve_ports: set[port] = { 6081/udp } &redef;
## A Geneve option.
type Option: record {
## The class of the option.
class: count;
## The critical bit of the type.
critical: bool;
## The type field of the option with the critical bit masked.
typ: count;
## The data field of the option.
data: string;
};
}
redef likely_server_ports += { geneve_ports };
@ -25,3 +37,8 @@ event zeek_init() &priority=20
PacketAnalyzer::register_packet_analyzer(PacketAnalyzer::ANALYZER_GENEVE, 0x08DD, PacketAnalyzer::ANALYZER_IP);
PacketAnalyzer::register_packet_analyzer(PacketAnalyzer::ANALYZER_GENEVE, 0x0806, PacketAnalyzer::ANALYZER_ARP);
}
module GLOBAL;
type geneve_options_vec: vector of PacketAnalyzer::Geneve::Option;
type geneve_options_vec_vec: vector of geneve_options_vec;

View file

@ -5,4 +5,5 @@ zeek_add_plugin(
Geneve.cc
Plugin.cc
BIFS
events.bif)
events.bif
functions.bif)

View file

@ -2,11 +2,50 @@
#include "zeek/packet_analysis/protocol/geneve/Geneve.h"
#include "zeek/Span.h"
#include "zeek/packet_analysis/protocol/geneve/events.bif.h"
#include "zeek/packet_analysis/protocol/iptunnel/IPTunnel.h"
using namespace zeek::packet_analysis::Geneve;
void zeek::packet_analysis::Geneve::detail::parse_options(zeek::Span<const uint8_t> data, detail::Callback cb) {
size_t remaining = data.size();
if ( remaining < 8 )
return;
remaining -= 8;
uint8_t all_opt_len = (data[0] & 0x3F) * 4;
if ( remaining < all_opt_len )
return;
const uint8_t* p = &data[8];
const uint8_t* const end = &data[8] + all_opt_len;
while ( p < end ) {
auto remaining = end - p;
if ( remaining < 4 )
break;
uint16_t opt_class = ntohs(reinterpret_cast<const uint16_t*>(p)[0]);
bool opt_critical = (p[2] & 0x80) == 0x80;
uint8_t opt_type = p[2] & 0x7F;
uint8_t opt_len = (p[3] & 0x1F) * 4;
remaining -= 4;
p += 4;
if ( remaining < opt_len )
break;
cb(opt_class, opt_critical, opt_type, zeek::Span{p, opt_len});
p += opt_len;
}
}
GeneveAnalyzer::GeneveAnalyzer() : zeek::packet_analysis::Analyzer("Geneve") {}
bool GeneveAnalyzer::AnalyzePacket(size_t len, const uint8_t* data, Packet* packet) {

View file

@ -2,11 +2,34 @@
#pragma once
#include <functional>
#include "zeek/Span.h"
#include "zeek/packet_analysis/Analyzer.h"
#include "zeek/packet_analysis/Component.h"
namespace zeek::packet_analysis::Geneve {
namespace detail {
/**
* Callback for parse_options(), passing the individual option pieces.
*/
using Callback =
std::function<void(uint16_t opt_class, bool opt_critical, uint8_t opt_type, zeek::Span<const uint8_t> opt_data)>;
/**
* Parse Geneve options from the header data.
*
* For each option, the given callback is invoked.
*
* @param data The data span to treat as a Geneve header.
* @param cb The callback to invoke with each parsed option.
*/
void parse_options(zeek::Span<const uint8_t> data, Callback cb);
} // namespace detail
class GeneveAnalyzer : public zeek::packet_analysis::Analyzer {
public:
GeneveAnalyzer();

View file

@ -0,0 +1,44 @@
module PacketAnalyzer::Geneve;
%%{
#include "zeek/packet_analysis/Manager.h"
#include "zeek/packet_analysis/protocol/geneve/Geneve.h"
%%}
## Returns all Geneve options from all layers of the current packet.
##
## The last entry in the outer vector are the options of the most
## inner Geneve header.
##
## Returns a vector of vector of zeek::see:`PacketAnalyzer::Geneve::Option` records.
function get_options%(%): geneve_options_vec_vec
%{
static const auto& analyzer = zeek::packet_mgr->GetAnalyzer("Geneve");
static const auto& rvtype = zeek::id::find_type<zeek::VectorType>("geneve_options_vec_vec");
static const auto& vtype = zeek::id::find_type<zeek::VectorType>("geneve_options_vec");
static const auto& rtype = zeek::id::find_type<zeek::RecordType>("PacketAnalyzer::Geneve::Option");
auto result = zeek::make_intrusive<zeek::VectorVal>(rvtype);
auto spans = zeek::packet_mgr->GetAnalyzerData(analyzer);
result->Reserve(spans.size());
for ( const auto& span : spans ) {
auto v = zeek::make_intrusive<zeek::VectorVal>(vtype);
auto cb = [&v](uint16_t opt_class, bool opt_critical, uint8_t opt_type, zeek::Span<const uint8_t> opt_data) -> void {
auto rv = zeek::make_intrusive<zeek::RecordVal>(rtype);
rv->Assign(0, zeek::val_mgr->Count(opt_class));
rv->Assign(1, zeek::val_mgr->Bool(opt_critical));
rv->Assign(2, zeek::val_mgr->Count(opt_type));
auto sv = zeek::make_intrusive<zeek::StringVal>(opt_data.size(), reinterpret_cast<const char*>(opt_data.data()));
rv->Assign(3, std::move(sv));
v->Append(std::move(rv));
};
zeek::packet_analysis::Geneve::detail::parse_options(span, cb);
result->Append(std::move(v));
}
return result;
%}

View file

@ -112,6 +112,7 @@ static std::unordered_map<std::string, unsigned int> func_attrs = {
{"Option::any_set_to_any_vec", ATTR_FOLDABLE},
{"Option::set_change_handler", ATTR_NO_SCRIPT_SIDE_EFFECTS},
{"PacketAnalyzer::GTPV1::remove_gtpv1_connection", ATTR_NO_SCRIPT_SIDE_EFFECTS},
{"PacketAnalyzer::Geneve::get_options", ATTR_NO_SCRIPT_SIDE_EFFECTS},
{"PacketAnalyzer::TEREDO::remove_teredo_connection", ATTR_NO_SCRIPT_SIDE_EFFECTS},
{"PacketAnalyzer::__disable_analyzer", ATTR_NO_SCRIPT_SIDE_EFFECTS},
{"PacketAnalyzer::__enable_analyzer", ATTR_NO_SCRIPT_SIDE_EFFECTS},

View file

@ -0,0 +1,7 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
new_connection, ClEkJM2Vm5giqnMf4h, geneve-many-options.pcap
opt, [class=256, critical=F, typ=1, data=1234567890abcde\x00]
opt, [class=256, critical=F, typ=2, data=0123456789abcdef0123456789abcdef0\x00\x00\x00]
opt, [class=256, critical=F, typ=3, data=0123456789\x00\x00]
new_connection, ClEkJM2Vm5giqnMf4h, geneve-tagged-udp-packet.pcap
opt, [class=65394, critical=F, typ=127, data=/home/esk/src/zeek//testing/btest/Traces/udp-packet.pcap]

View file

@ -264,6 +264,7 @@ scripts/base/init-frameworks-and-bifs.zeek
build/scripts/base/bif/plugins/Zeek_UDP.events.bif.zeek
build/scripts/base/bif/plugins/Zeek_ICMP.events.bif.zeek
build/scripts/base/bif/plugins/Zeek_Geneve.events.bif.zeek
build/scripts/base/bif/plugins/Zeek_Geneve.functions.bif.zeek
build/scripts/base/bif/plugins/Zeek_VXLAN.events.bif.zeek
build/scripts/base/bif/plugins/Zeek_FileEntropy.events.bif.zeek
build/scripts/base/bif/plugins/Zeek_FileExtract.events.bif.zeek

View file

@ -264,6 +264,7 @@ scripts/base/init-frameworks-and-bifs.zeek
build/scripts/base/bif/plugins/Zeek_UDP.events.bif.zeek
build/scripts/base/bif/plugins/Zeek_ICMP.events.bif.zeek
build/scripts/base/bif/plugins/Zeek_Geneve.events.bif.zeek
build/scripts/base/bif/plugins/Zeek_Geneve.functions.bif.zeek
build/scripts/base/bif/plugins/Zeek_VXLAN.events.bif.zeek
build/scripts/base/bif/plugins/Zeek_FileEntropy.events.bif.zeek
build/scripts/base/bif/plugins/Zeek_FileExtract.events.bif.zeek

View file

@ -1,2 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
543 seen BiFs, 0 unseen BiFs (), 0 new BiFs ()
544 seen BiFs, 0 unseen BiFs (), 0 new BiFs ()

View file

@ -360,6 +360,7 @@
0.000000 MetaHookPost LoadFile(0, ./Zeek_GTPv1.events.bif.zeek, <...>/Zeek_GTPv1.events.bif.zeek) -> -1
0.000000 MetaHookPost LoadFile(0, ./Zeek_GTPv1.functions.bif.zeek, <...>/Zeek_GTPv1.functions.bif.zeek) -> -1
0.000000 MetaHookPost LoadFile(0, ./Zeek_Geneve.events.bif.zeek, <...>/Zeek_Geneve.events.bif.zeek) -> -1
0.000000 MetaHookPost LoadFile(0, ./Zeek_Geneve.functions.bif.zeek, <...>/Zeek_Geneve.functions.bif.zeek) -> -1
0.000000 MetaHookPost LoadFile(0, ./Zeek_Gnutella.events.bif.zeek, <...>/Zeek_Gnutella.events.bif.zeek) -> -1
0.000000 MetaHookPost LoadFile(0, ./Zeek_HTTP.events.bif.zeek, <...>/Zeek_HTTP.events.bif.zeek) -> -1
0.000000 MetaHookPost LoadFile(0, ./Zeek_HTTP.functions.bif.zeek, <...>/Zeek_HTTP.functions.bif.zeek) -> -1
@ -667,6 +668,7 @@
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_GTPv1.events.bif.zeek, <...>/Zeek_GTPv1.events.bif.zeek) -> (-1, <no content>)
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_GTPv1.functions.bif.zeek, <...>/Zeek_GTPv1.functions.bif.zeek) -> (-1, <no content>)
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_Geneve.events.bif.zeek, <...>/Zeek_Geneve.events.bif.zeek) -> (-1, <no content>)
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_Geneve.functions.bif.zeek, <...>/Zeek_Geneve.functions.bif.zeek) -> (-1, <no content>)
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_Gnutella.events.bif.zeek, <...>/Zeek_Gnutella.events.bif.zeek) -> (-1, <no content>)
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_HTTP.events.bif.zeek, <...>/Zeek_HTTP.events.bif.zeek) -> (-1, <no content>)
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_HTTP.functions.bif.zeek, <...>/Zeek_HTTP.functions.bif.zeek) -> (-1, <no content>)
@ -1307,6 +1309,7 @@
0.000000 MetaHookPre LoadFile(0, ./Zeek_GTPv1.events.bif.zeek, <...>/Zeek_GTPv1.events.bif.zeek)
0.000000 MetaHookPre LoadFile(0, ./Zeek_GTPv1.functions.bif.zeek, <...>/Zeek_GTPv1.functions.bif.zeek)
0.000000 MetaHookPre LoadFile(0, ./Zeek_Geneve.events.bif.zeek, <...>/Zeek_Geneve.events.bif.zeek)
0.000000 MetaHookPre LoadFile(0, ./Zeek_Geneve.functions.bif.zeek, <...>/Zeek_Geneve.functions.bif.zeek)
0.000000 MetaHookPre LoadFile(0, ./Zeek_Gnutella.events.bif.zeek, <...>/Zeek_Gnutella.events.bif.zeek)
0.000000 MetaHookPre LoadFile(0, ./Zeek_HTTP.events.bif.zeek, <...>/Zeek_HTTP.events.bif.zeek)
0.000000 MetaHookPre LoadFile(0, ./Zeek_HTTP.functions.bif.zeek, <...>/Zeek_HTTP.functions.bif.zeek)
@ -1614,6 +1617,7 @@
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_GTPv1.events.bif.zeek, <...>/Zeek_GTPv1.events.bif.zeek)
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_GTPv1.functions.bif.zeek, <...>/Zeek_GTPv1.functions.bif.zeek)
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_Geneve.events.bif.zeek, <...>/Zeek_Geneve.events.bif.zeek)
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_Geneve.functions.bif.zeek, <...>/Zeek_Geneve.functions.bif.zeek)
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_Gnutella.events.bif.zeek, <...>/Zeek_Gnutella.events.bif.zeek)
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_HTTP.events.bif.zeek, <...>/Zeek_HTTP.events.bif.zeek)
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_HTTP.functions.bif.zeek, <...>/Zeek_HTTP.functions.bif.zeek)
@ -2253,6 +2257,7 @@
0.000000 | HookLoadFile ./Zeek_GTPv1.events.bif.zeek <...>/Zeek_GTPv1.events.bif.zeek
0.000000 | HookLoadFile ./Zeek_GTPv1.functions.bif.zeek <...>/Zeek_GTPv1.functions.bif.zeek
0.000000 | HookLoadFile ./Zeek_Geneve.events.bif.zeek <...>/Zeek_Geneve.events.bif.zeek
0.000000 | HookLoadFile ./Zeek_Geneve.functions.bif.zeek <...>/Zeek_Geneve.functions.bif.zeek
0.000000 | HookLoadFile ./Zeek_Gnutella.events.bif.zeek <...>/Zeek_Gnutella.events.bif.zeek
0.000000 | HookLoadFile ./Zeek_HTTP.events.bif.zeek <...>/Zeek_HTTP.events.bif.zeek
0.000000 | HookLoadFile ./Zeek_HTTP.functions.bif.zeek <...>/Zeek_HTTP.functions.bif.zeek
@ -2560,6 +2565,7 @@
0.000000 | HookLoadFileExtended ./Zeek_GTPv1.events.bif.zeek <...>/Zeek_GTPv1.events.bif.zeek
0.000000 | HookLoadFileExtended ./Zeek_GTPv1.functions.bif.zeek <...>/Zeek_GTPv1.functions.bif.zeek
0.000000 | HookLoadFileExtended ./Zeek_Geneve.events.bif.zeek <...>/Zeek_Geneve.events.bif.zeek
0.000000 | HookLoadFileExtended ./Zeek_Geneve.functions.bif.zeek <...>/Zeek_Geneve.functions.bif.zeek
0.000000 | HookLoadFileExtended ./Zeek_Gnutella.events.bif.zeek <...>/Zeek_Gnutella.events.bif.zeek
0.000000 | HookLoadFileExtended ./Zeek_HTTP.events.bif.zeek <...>/Zeek_HTTP.events.bif.zeek
0.000000 | HookLoadFileExtended ./Zeek_HTTP.functions.bif.zeek <...>/Zeek_HTTP.functions.bif.zeek

View file

@ -46,3 +46,5 @@ Trace Index/Sources:
VirusTotal reports that this file contains malware. The PE analyzer was originally added
to decode info for malware, so this is expected. See
https://zeekorg.slack.com/archives/CSZBXF6TH/p1738261449655049
- tunnels/geneve-tagged-udp-packet.pcap
Provided by Eldon Koyle Corelight for testing.

View file

@ -0,0 +1,20 @@
# @TEST-EXEC: zeek -C -b -r $TRACES/tunnels/geneve-many-options.pcap %INPUT >>out
# @TEST-EXEC: zeek -C -b -r $TRACES/tunnels/geneve-tagged-udp-packet.pcap %INPUT >>out
# @TEST-EXEC: btest-diff out
@load base/frameworks/tunnels
@load base/protocols/conn
event new_connection(c: connection)
{
if ( ! c?$tunnel )
return;
print "new_connection", c$uid, split_string(packet_source()$path, /\//)[-1];
for ( _, layer in PacketAnalyzer::Geneve::get_options() )
for ( _, opt in layer )
print "opt", opt;
}

View file

@ -141,6 +141,7 @@ global known_BiFs = set(
"Option::set",
"Option::set_change_handler",
"PacketAnalyzer::GTPV1::remove_gtpv1_connection",
"PacketAnalyzer::Geneve::get_options",
"PacketAnalyzer::TEREDO::remove_teredo_connection",
"PacketAnalyzer::__disable_analyzer",
"PacketAnalyzer::__enable_analyzer",