Merge remote-tracking branch 'origin/topic/robin/gh-3561-forward-to-udp'

* origin/topic/robin/gh-3561-forward-to-udp:
  Update docs.
  Add explicit children life-cycle management method to analyzers.
  Spicy: Support UDP in Spicy's `protocol_*` runtime functions.
  Add method to analyzer to retrieve direct child by name.
  Extend PIA's `FirstPacket` API.
  Spicy: Prepare for supporting forwarding to protocols other than TCP.
This commit is contained in:
Robin Sommer 2024-05-10 11:08:47 +02:00
commit 82be6425e6
No known key found for this signature in database
GPG key ID: D8187293B3FFE5D0
18 changed files with 678 additions and 190 deletions

54
CHANGES
View file

@ -1,3 +1,57 @@
7.0.0-dev.244 | 2024-05-10 11:08:47 +0200
* GH-3725: Spicy: Fix service reporting for replaced analyzers. (Robin Sommer, Corelight)
We accidentally applied analyzer mappings when looking up an
analyzer's name from scriptland.
* GH-3561: Spicy: Support UDP in Spicy's `protocol_*` runtime functions. (Robin Sommer, Corelight)
This extends the ability to feed new payload back into Zeek's analyzer
pipeline from TCP to now also UDP.
Note: We don't extend this further to ICMP because the ICMP analyzer
cannot be dynamically instantiated (Zeek aborts when trying so). As
ICMP isn't very interesting from use case perspective anyways, that
seems fine.
* Add method to analyzer to retrieve direct child by name. (Robin Sommer, Corelight)
* Add explicit children life-cycle management method to analyzers. (Robin Sommer, Corelight)
* Extend PIA's `FirstPacket` API. (Robin Sommer, Corelight)
`FirstPacket()` so far supported only TCP. To extend this to UDP, we
move the method into the PIA base class; give it a protocol parameter
for the case that there's no actual packet is available; and add the
ability to create fake UDP packets as well, not just TCP.
This whole thing is pretty ugly to begin with, and this doesn't make
it nicer, but we need this extension that so we can feed UDP data into
the signature engine that's tunneled over other protocols. Without the
fake packets, DPD signatures in particular wouldn't have anything to
match on.
* Spicy: Prepare for supporting forwarding to protocols other than TCP. (Robin Sommer, Corelight)
So far the Spicy runtime supported forwarding data into other
analyzers only for TCP analyzers. This puts branching logic in place
that let the relevant runtime functions dispatch differently based on
the target transport-layer protocol. We don't implement anything else
than TCP yet; that will come next.
Along with the internal changes, this also updates the user-visible
runtime function to pass protocol information in. For now, this
likewise remains limited to TCP. The function signatures are chosen so
that they stay backwards-compatible to previous Spicy version. In
particular, they default to TCP where not otherwise specified.
* Fix include of private file in public header (Benjamin Bannier, Corelight)
* Update package-manager submodule [nomail] (Tim Wojtulewicz, Corelight)
* Update zeekctl submodule [nomail] (Tim Wojtulewicz, Corelight)
7.0.0-dev.231 | 2024-05-08 13:09:25 -0700
* enable ZAM operation specifications to reside in multiple files (not yet used) (Vern Paxson, Corelight)

View file

@ -1 +1 @@
7.0.0-dev.234
7.0.0-dev.244

2
doc

@ -1 +1 @@
Subproject commit 3ecd2d62d0c369810eef5c6ffaedc17efbff6095
Subproject commit 0253bce437cde767b4fe66988defc8fa26935a30

View file

@ -2,6 +2,8 @@
module zeek;
import spicy;
# Note: Retain the formatting here, doc/scripts/autogen-spicy-lib is picking up on that.
%cxx-include = "zeek/spicy/runtime-support.h";
@ -48,46 +50,82 @@ public type ProtocolHandle = __library_type("zeek::spicy::rt::ProtocolHandle");
## Adds a Zeek-side child protocol analyzer to the current connection.
##
## If the same analyzer was added previously with protocol_handle_get_or_create or
## protocol_begin with same argument, and not closed with protocol_handle_close
## or protocol_end, no new analyzer will be added.
## If the same analyzer was added previously with `protocol_handle_get_or_create` or
## `protocol_begin` with same argument, and not closed with `protocol_handle_close`
## or `protocol_end`, no new analyzer will be added.
##
## See `protocol_handle_get_or_create` for the error semantics of this function.
##
## analyzer: type of analyzer to instantiate, specified through its Zeek-side
## name (similar to what Zeek's signature action `enable` takes); if not
## specified, Zeek will perform its usual dynamic protocol detection to figure
## out how to parse the data (the latter will work only for TCP protocols, though.)
public function protocol_begin(analyzer: optional<string> = Null) : void &cxxname="zeek::spicy::rt::protocol_begin";
## name (similar to what Zeek's signature action `enable` takes)
##
## protocol: the transport-layer protocol that the analyzer uses; only TCP is
## currently supported here
##
## Note: For backwards compatibility, the analyzer argument can be left unset to add
## a DPD analyzer. This use is deprecated, though; use the single-argument version of
## `protocol_begin` for that instead.
public function protocol_begin(analyzer: optional<string>, protocol: spicy::Protocol = spicy::Protocol::TCP) : void &cxxname="zeek::spicy::rt::protocol_begin";
## Adds a Zeek-side DPD child protocol analyzer performing dynamic protocol detection
## on subsequently provided data.
##
## If the same DPD analyzer was added previously with `protocol_handle_get_or_create` or
## `protocol_begin` with same argument, and not closed with `protocol_handle_close`
## or `protocol_end`, no new analyzer will be added.
##
## See `protocol_handle_get_or_create` for the error semantics of this function.
##
## protocol: the transport-layer protocol on which to perform protocol detection;
## only TCP is currently supported here
public function protocol_begin(protocol: spicy::Protocol = spicy::Protocol::TCP) : void &cxxname="zeek::spicy::rt::protocol_begin";
## Gets a handle to a Zeek-side child protocol analyzer for the current connection.
##
## If no such child exists it will be added; otherwise a handle to the
## If no such child exists yet it will be added; otherwise a handle to the
## existing child protocol analyzer will be returned.
##
## This function will return an error
## This function will return an error if:
##
## - if not called from a protocol analyzer, or
## - the requested child protocol analyzer is unknown, or
## - not called from a protocol analyzer, or
## - the requested child protocol analyzer is of unknown type or not support by the requested transport protocol, or
## - creation of a child analyzer of the requested type was prevented by a
## previous call of `disable_analyzer` with `prevent=T`
##
## analyzer: type of analyzer to instantiate, specified through its Zeek-side
## analyzer: type of analyzer to get or instantiate, specified through its Zeek-side
## name (similar to what Zeek's signature action `enable` takes).
public function protocol_handle_get_or_create(analyzer: string) : ProtocolHandle &cxxname="zeek::spicy::rt::protocol_handle_get_or_create";
##
## protocol: the transport-layer protocol that the analyser uses; only TCP is
## currently supported here
##
public function protocol_handle_get_or_create(analyzer: string, protocol: spicy::Protocol = spicy::Protocol::TCP) : ProtocolHandle &cxxname="zeek::spicy::rt::protocol_handle_get_or_create";
## Forwards protocol data to all previously instantiated Zeek-side child protocol analyzers.
## Forwards protocol data to all previously instantiated Zeek-side child protocol analyzers of a given transport-layer.
##
## is_orig: true to feed the data to the child's originator side, false for the responder
##
## data: chunk of data to forward to child analyzer
## h: optional handle to the child analyzer to forward data into, else forward to all child analyzers
public function protocol_data_in(is_orig: bool, data: bytes, h: optional<ProtocolHandle> = Null) : void &cxxname="zeek::spicy::rt::protocol_data_in";
##
## protocol: the transport-layer protocol of the children to forward to; only TCP is currently supported here
public function protocol_data_in(is_orig: bool, data: bytes, protocol: spicy::Protocol = spicy::Protocol::TCP) : void &cxxname="zeek::spicy::rt::protocol_data_in";
## Forwards protocol data to a specific previously instantiated Zeek-side child analyzer.
##
## is_orig: true to feed the data to the child's originator side, false for the responder
##
## data: chunk of data to forward to child analyzer
##
## h: handle to the child analyzer to forward data into
public function protocol_data_in(is_orig: bool, data: bytes, h: ProtocolHandle) : void &cxxname="zeek::spicy::rt::protocol_data_in";
## Signals a gap in input data to all previously instantiated Zeek-side child protocol analyzers.
##
## is_orig: true to signal gap to the child's originator side, false for the responder
##
## offset: start offset of gap in input stream
##
## len: size of gap
##
## h: optional handle to the child analyzer signal a gap to, else signal to all child analyzers
public function protocol_gap(is_orig: bool, offset: uint64, len: uint64, h: optional<ProtocolHandle> = Null) : void &cxxname="zeek::spicy::rt::protocol_gap";

View file

@ -422,6 +422,18 @@ Analyzer* Analyzer::GetChildAnalyzer(const zeek::Tag& tag) const {
return nullptr;
}
Analyzer* Analyzer::GetChildAnalyzer(const std::string& name) const {
LOOP_OVER_CHILDREN(i)
if ( (*i)->GetAnalyzerName() == name && ! ((*i)->removing || (*i)->finished) )
return *i;
LOOP_OVER_GIVEN_CHILDREN(i, new_children)
if ( (*i)->GetAnalyzerName() == name && ! ((*i)->removing || (*i)->finished) )
return *i;
return nullptr;
}
Analyzer* Analyzer::FindChild(ID arg_id) {
if ( id == arg_id && ! (removing || finished) )
return this;
@ -465,6 +477,17 @@ Analyzer* Analyzer::FindChild(const char* name) {
return tag ? FindChild(tag) : nullptr;
}
void Analyzer::CleanupChildren() {
AppendNewChildren();
for ( auto i = children.begin(); i != children.end(); ) {
if ( ! ((*i)->finished || (*i)->removing) )
++i;
else
i = DeleteChild(i);
}
}
analyzer_list::iterator Analyzer::DeleteChild(analyzer_list::iterator i) {
Analyzer* child = *i;

View file

@ -451,6 +451,19 @@ public:
*/
Analyzer* GetChildAnalyzer(const zeek::Tag& tag) const;
/**
* Returns a pointer to a direct child analyzer of the given name.
*
* Note that the returned pointer is owned by the analyzer and may
* be deleted without notification. Do not hold on to it.
*
* @param name The name of the analyzers, as returned by its
* GetAnalyzereName() method.
*
* @return The analyzer, or null if not found.
*/
Analyzer* GetChildAnalyzer(const std::string& name) const;
/**
* Recursively searches all (direct or indirect) children of the
* analyzer for an analyzer with a specific ID.
@ -494,6 +507,13 @@ public:
*/
const analyzer_list& GetChildren() { return children; }
/**
* Removes any child analyzers that are finished or marked for deletion.
* This normally happens automatically are various times when feeding data
* to children, can be triggered explicitly through this method as needed.
*/
void CleanupChildren();
/**
* Returns a pointer to the parent analyzer, or null if this instance
* has not yet been added to an analyzer tree.

View file

@ -946,8 +946,8 @@ void HTTP_Analyzer::DeliverStream(int len, const u_char* data, bool is_orig) {
pia = new analyzer::pia::PIA_TCP(Conn());
if ( AddChildAnalyzer(pia) ) {
pia->FirstPacket(true, nullptr);
pia->FirstPacket(false, nullptr);
pia->FirstPacket(true, TransportProto::TRANSPORT_TCP);
pia->FirstPacket(false, TransportProto::TRANSPORT_TCP);
int remaining_in_content_line = content_line_resp->GetDeliverStreamRemainingLength();
if ( remaining_in_content_line > 0 ) {
@ -1396,8 +1396,8 @@ void HTTP_Analyzer::HTTP_Upgrade() {
upgrade_protocol.c_str());
pia = new analyzer::pia::PIA_TCP(Conn());
if ( AddChildAnalyzer(pia) ) {
pia->FirstPacket(true, nullptr);
pia->FirstPacket(false, nullptr);
pia->FirstPacket(true, TransportProto::TRANSPORT_TCP);
pia->FirstPacket(false, TransportProto::TRANSPORT_TCP);
}
}

View file

@ -169,52 +169,112 @@ void PIA_TCP::Init() {
}
}
void PIA_TCP::FirstPacket(bool is_orig, const IP_Hdr* ip) {
void PIA::FirstPacket(bool is_orig, TransportProto proto) { FirstPacket(is_orig, proto, nullptr); }
void PIA::FirstPacket(bool is_orig, const IP_Hdr* ip) {
assert(ip);
FirstPacket(is_orig, {}, ip);
}
void PIA::FirstPacket(bool is_orig, const std::optional<TransportProto>& proto, const IP_Hdr* ip) {
static char dummy_packet[sizeof(struct ip) + sizeof(struct tcphdr)];
static struct ip* ip4 = nullptr;
static struct ip* ip4_tcp = nullptr;
static struct ip* ip4_udp = nullptr;
static struct tcphdr* tcp4 = nullptr;
static IP_Hdr* ip4_hdr = nullptr;
static struct udphdr* udp4 = nullptr;
static IP_Hdr* ip4_tcp_hdr = nullptr;
static IP_Hdr* ip4_udp_hdr = nullptr;
DBG_LOG(DBG_ANALYZER, "PIA_TCP[%d] FirstPacket(%s)", GetID(), (is_orig ? "T" : "F"));
if ( ! ip ) {
if ( ! ip && proto ) { // proto needed here to avoid GCC warning that it may be used uninitialized
// Create a dummy packet. Not very elegant, but everything
// else would be *really* ugly ...
if ( ! ip4_hdr ) {
ip4 = (struct ip*)dummy_packet;
tcp4 = (struct tcphdr*)(dummy_packet + sizeof(struct ip));
ip4->ip_len = sizeof(struct ip) + sizeof(struct tcphdr);
ip4->ip_hl = sizeof(struct ip) >> 2;
ip4->ip_p = IPPROTO_TCP;
switch ( *proto ) {
case TransportProto::TRANSPORT_TCP: {
DBG_LOG(DBG_ANALYZER, "PIA/TCP FirstPacket(%s)", (is_orig ? "T" : "F"));
// Cast to const so that it doesn't delete it.
ip4_hdr = new IP_Hdr(ip4, false);
if ( ! ip4_tcp_hdr ) {
ip4_tcp = (struct ip*)dummy_packet;
tcp4 = (struct tcphdr*)(dummy_packet + sizeof(struct ip));
ip4_tcp->ip_len = sizeof(struct ip) + sizeof(struct tcphdr);
ip4_tcp->ip_hl = sizeof(struct ip) >> 2;
// Cast to const so that it doesn't delete it.
ip4_tcp_hdr = new IP_Hdr(ip4_tcp, false);
}
// Locals used to avoid potential alignment problems
// with some archs/compilers when grabbing the address
// of the struct member directly in the following.
in_addr tmp_src;
in_addr tmp_dst;
if ( is_orig ) {
Conn()->OrigAddr().CopyIPv4(&tmp_src);
Conn()->RespAddr().CopyIPv4(&tmp_dst);
tcp4->th_sport = htons(Conn()->OrigPort());
tcp4->th_dport = htons(Conn()->RespPort());
}
else {
Conn()->RespAddr().CopyIPv4(&tmp_src);
Conn()->OrigAddr().CopyIPv4(&tmp_dst);
tcp4->th_sport = htons(Conn()->RespPort());
tcp4->th_dport = htons(Conn()->OrigPort());
}
ip4_tcp->ip_src = tmp_src;
ip4_tcp->ip_dst = tmp_dst;
ip4_tcp->ip_p = IPPROTO_TCP;
ip = ip4_tcp_hdr;
break;
}
case TransportProto::TRANSPORT_UDP: {
DBG_LOG(DBG_ANALYZER, "PIA/UDP FirstPacket(%s)", (is_orig ? "T" : "F"));
if ( ! ip4_udp_hdr ) {
ip4_udp = (struct ip*)dummy_packet;
udp4 = (struct udphdr*)(dummy_packet + sizeof(struct ip));
ip4_udp->ip_len = sizeof(struct ip) + sizeof(struct udphdr);
ip4_udp->ip_hl = sizeof(struct ip) >> 2;
// Cast to const so that it doesn't delete it.
ip4_udp_hdr = new IP_Hdr(ip4_udp, false);
}
// Locals used to avoid potential alignment problems
// with some archs/compilers when grabbing the address
// of the struct member directly in the following.
in_addr tmp_src;
in_addr tmp_dst;
if ( is_orig ) {
Conn()->OrigAddr().CopyIPv4(&tmp_src);
Conn()->RespAddr().CopyIPv4(&tmp_dst);
udp4->uh_sport = htons(Conn()->OrigPort());
udp4->uh_dport = htons(Conn()->RespPort());
}
else {
Conn()->RespAddr().CopyIPv4(&tmp_src);
Conn()->OrigAddr().CopyIPv4(&tmp_dst);
udp4->uh_sport = htons(Conn()->RespPort());
udp4->uh_dport = htons(Conn()->OrigPort());
}
ip4_udp->ip_src = tmp_src;
ip4_udp->ip_dst = tmp_dst;
ip4_udp->ip_p = IPPROTO_UDP;
ip = ip4_udp_hdr;
break;
}
case TransportProto::TRANSPORT_ICMP: reporter->InternalError("ICMP not supported in PIA::FirstPacket");
default: reporter->InternalError("unknown transport proto in PIA::FirstPacket");
}
// Locals used to avoid potential alignment problems
// with some archs/compilers when grabbing the address
// of the struct member directly in the following.
in_addr tmp_src;
in_addr tmp_dst;
if ( is_orig ) {
Conn()->OrigAddr().CopyIPv4(&tmp_src);
Conn()->RespAddr().CopyIPv4(&tmp_dst);
tcp4->th_sport = htons(Conn()->OrigPort());
tcp4->th_dport = htons(Conn()->RespPort());
}
else {
Conn()->RespAddr().CopyIPv4(&tmp_src);
Conn()->OrigAddr().CopyIPv4(&tmp_dst);
tcp4->th_sport = htons(Conn()->RespPort());
tcp4->th_dport = htons(Conn()->OrigPort());
}
ip4->ip_src = tmp_src;
ip4->ip_dst = tmp_dst;
ip = ip4_hdr;
}
assert(ip);
if ( ! MatcherInitialized(is_orig) )
DoMatch((const u_char*)"", 0, is_orig, true, false, false, ip);
}

View file

@ -36,6 +36,32 @@ public:
void ReplayPacketBuffer(analyzer::Analyzer* analyzer);
// The first packet for each direction of a connection is passed
// in here. This initializes the signature engine state for DPD.
//
// This version of the method should be used preferably, assuming an IP
// header is available.
//
// (This API is a bit crude as it doesn't really fit nicely into the
// analyzer interface. Yet we need it for initializing the packet matcher
// in the case that we already get reassembled input; and making it part of
// the general analyzer interface seems to be unnecessary overhead.)
void FirstPacket(bool is_orig, const IP_Hdr* ip);
// The first packet for each direction of a connection is passed
// in here. This initializes the signature engine state for DPD.
//
// This version of the method should be used if no actual IP header is
// available. In that case a fake one will be created internally just for
// initializing the signature engine. The fake header's transport-layer
// protocol will be `proto`. Only TCP or UDP are supported.
//
// (Similar to the other variant of this method, this API is a bit crude as
// it doesn't really fit nicely into the analyzer interface. This version
// we need for feeding data into the matcher that's not directly top-level
// IP payload, but decapsulated out of other layers.)
void FirstPacket(bool is_orig, TransportProto proto);
// Children are also derived from Analyzer. Return this object
// as pointer to an Analyzer.
analyzer::Analyzer* AsAnalyzer() { return as_analyzer; }
@ -77,11 +103,15 @@ protected:
void DoMatch(const u_char* data, int len, bool is_orig, bool bol, bool eol, bool clear_state,
const IP_Hdr* ip = nullptr);
auto Conn() const { return conn; }
void SetConn(Connection* c) { conn = c; }
Buffer pkt_buffer;
private:
// Joint backend for the two public FirstPacket() methods.
void FirstPacket(bool is_orig, const std::optional<TransportProto>& proto, const IP_Hdr* ip);
analyzer::Analyzer* as_analyzer;
Connection* conn;
DataBlock current_packet;
@ -123,16 +153,6 @@ public:
void Init() override;
// The first packet for each direction of a connection is passed
// in here.
//
// (This is a bit crude as it doesn't really fit nicely into the
// analyzer interface. Yet we need it for initializing the packet
// matcher in the case that we already get reassembled input,
// and making it part of the general analyzer interface seems
// to be unnecessary overhead.)
void FirstPacket(bool is_orig, const IP_Hdr* ip);
void ReplayStreamBuffer(analyzer::Analyzer* analyzer);
static analyzer::Analyzer* Instantiate(Connection* conn) { return new PIA_TCP(conn); }

View file

@ -48,8 +48,8 @@ void SOCKS_Analyzer::DeliverStream(int len, const u_char* data, bool orig) {
if ( ! pia ) {
pia = new analyzer::pia::PIA_TCP(Conn());
if ( AddChildAnalyzer(pia) ) {
pia->FirstPacket(true, nullptr);
pia->FirstPacket(false, nullptr);
pia->FirstPacket(true, TransportProto::TRANSPORT_TCP);
pia->FirstPacket(false, TransportProto::TRANSPORT_TCP);
}
else
pia = nullptr;

View file

@ -357,8 +357,8 @@ void SSL_Analyzer::ForwardDecryptedData(const std::vector<u_char>& data, bool is
if ( ! pia ) {
pia = new analyzer::pia::PIA_TCP(Conn());
if ( AddChildAnalyzer(pia) ) {
pia->FirstPacket(true, nullptr);
pia->FirstPacket(false, nullptr);
pia->FirstPacket(true, TransportProto::TRANSPORT_TCP);
pia->FirstPacket(false, TransportProto::TRANSPORT_TCP);
}
else
reporter->Error("Could not initialize PIA");

View file

@ -79,8 +79,8 @@ bool WebSocket_Analyzer::Configure(zeek::RecordValPtr config) {
auto* pia = new analyzer::pia::PIA_TCP(Conn());
if ( effective_analyzer->AddChildAnalyzer(pia) ) {
pia->FirstPacket(true, nullptr);
pia->FirstPacket(false, nullptr);
pia->FirstPacket(true, TransportProto::TRANSPORT_TCP);
pia->FirstPacket(false, TransportProto::TRANSPORT_TCP);
return true;
}

View file

@ -2,7 +2,6 @@
#include "zeek/spicy/runtime-support.h"
#include <algorithm>
#include <memory>
#include <hilti/rt/exception.h>
@ -10,6 +9,7 @@
#include <hilti/rt/types/port.h>
#include <hilti/rt/util.h>
#include "net_util.h"
#include "zeek/Event.h"
#include "zeek/analyzer/Manager.h"
#include "zeek/analyzer/protocol/pia/PIA.h"
@ -485,15 +485,19 @@ void rt::weird(const std::string& id, const std::string& addl) {
throw ValueUnavailable("none of $conn, $file, or $packet available for weird reporting");
}
void rt::protocol_begin(const std::optional<std::string>& analyzer) {
void rt::protocol_begin(const std::optional<std::string>& analyzer, const ::hilti::rt::Protocol& proto) {
auto _ = hilti::rt::profiler::start("zeek/rt/protocol_begin");
if ( analyzer ) {
protocol_handle_get_or_create(*analyzer);
protocol_handle_get_or_create(*analyzer, proto);
return;
}
// Instantiate a DPD analyzer.
// Instantiate a DPD analyzer. If a direct child of this type already
// exists, we abort silently because that makes usage nicer if either side
// of the connection might end up creating the analyzer; this way the user
// doesn't need to track what the other side already did.
auto cookie = static_cast<Cookie*>(hilti::rt::context::cookie());
assert(cookie);
@ -501,36 +505,47 @@ void rt::protocol_begin(const std::optional<std::string>& analyzer) {
if ( ! c )
throw ValueUnavailable("no current connection available");
// Use a Zeek PIA stream analyzer performing DPD.
auto pia_tcp = std::make_unique<analyzer::pia::PIA_TCP>(c->analyzer->Conn());
switch ( proto.value() ) {
case ::hilti::rt::Protocol::TCP: {
// Use a Zeek PIA stream (TCP) analyzer performing DPD.
auto pia_tcp = std::make_unique<analyzer::pia::PIA_TCP>(c->analyzer->Conn());
pia_tcp->FirstPacket(true, TransportProto::TRANSPORT_TCP);
pia_tcp->FirstPacket(false, TransportProto::TRANSPORT_TCP);
// Forward empty payload to trigger lifecycle management in this analyzer tree.
c->analyzer->ForwardStream(0, reinterpret_cast<const u_char*>(c->analyzer), true);
c->analyzer->ForwardStream(0, reinterpret_cast<const u_char*>(c->analyzer), false);
c->analyzer->CleanupChildren();
// Direct child of this type already exists. We ignore this silently
// because that makes usage nicer if either side of the connection
// might end up creating the analyzer; this way the user doesn't
// need to track what the other side already did.
//
// We inspect the children directly to work around zeek/zeek#2899.
const auto& children = c->analyzer->GetChildren();
if ( auto it = std::find_if(children.begin(), children.end(),
[&](const auto& it) {
return ! it->Removing() && ! it->IsFinished() &&
it->GetAnalyzerTag() == pia_tcp->GetAnalyzerTag();
});
it != children.end() )
return;
// If the child already exists, do not add it again so this function is idempotent.
if ( auto child = c->analyzer->GetChildAnalyzer(pia_tcp->GetAnalyzerName()) )
return;
auto child = pia_tcp.release();
c->analyzer->AddChildAnalyzer(child);
auto child = pia_tcp.release();
c->analyzer->AddChildAnalyzer(child);
break;
}
child->FirstPacket(true, nullptr);
child->FirstPacket(false, nullptr);
case ::hilti::rt::Protocol::UDP: {
// Use a Zeek PIA packet (UDP) analyzer performing DPD.
auto pia_udp = std::make_unique<analyzer::pia::PIA_UDP>(c->analyzer->Conn());
pia_udp->FirstPacket(true, TransportProto::TRANSPORT_UDP);
pia_udp->FirstPacket(false, TransportProto::TRANSPORT_UDP);
c->analyzer->CleanupChildren();
auto child = pia_udp.release();
c->analyzer->AddChildAnalyzer(child);
break;
}
case ::hilti::rt::Protocol::ICMP: throw Unsupported("protocol_begin: ICMP not supported for DPD");
case ::hilti::rt::Protocol::Undef: throw InvalidValue("protocol_begin: no protocol specified for DPD");
default: throw InvalidValue("protocol_begin: unknown protocol for DPD");
}
}
rt::ProtocolHandle rt::protocol_handle_get_or_create(const std::string& analyzer) {
void rt::protocol_begin(const ::hilti::rt::Protocol& proto) { return protocol_begin(std::nullopt, proto); }
rt::ProtocolHandle rt::protocol_handle_get_or_create(const std::string& analyzer, const ::hilti::rt::Protocol& proto) {
auto _ = hilti::rt::profiler::start("zeek/rt/protocol_handle_get_or_create");
auto cookie = static_cast<Cookie*>(hilti::rt::context::cookie());
assert(cookie);
@ -539,56 +554,73 @@ rt::ProtocolHandle rt::protocol_handle_get_or_create(const std::string& analyzer
if ( ! c )
throw ValueUnavailable("no current connection available");
// Forward empty payload to trigger lifecycle management in this analyzer tree.
c->analyzer->ForwardStream(0, reinterpret_cast<const u_char*>(c->analyzer), true);
c->analyzer->ForwardStream(0, reinterpret_cast<const u_char*>(c->analyzer), false);
switch ( proto.value() ) {
case ::hilti::rt::Protocol::TCP: {
c->analyzer->CleanupChildren();
// If the child already exists, do not add it again so this function is idempotent.
//
// We inspect the children directly to work around zeek/zeek#2899.
const auto& children = c->analyzer->GetChildren();
if ( auto it = std::find_if(children.begin(), children.end(),
[&](const auto& it) {
return ! it->Removing() && ! it->IsFinished() && it->GetAnalyzerName() == analyzer;
});
it != children.end() )
return rt::ProtocolHandle((*it)->GetID());
// If the child already exists, do not add it again so this function is idempotent.
if ( auto child = c->analyzer->GetChildAnalyzer(analyzer) )
return rt::ProtocolHandle(child->GetID(), proto);
auto child = analyzer_mgr->InstantiateAnalyzer(analyzer.c_str(), c->analyzer->Conn());
if ( ! child )
throw ZeekError(::hilti::rt::fmt("unknown analyzer '%s' requested", analyzer));
auto child = analyzer_mgr->InstantiateAnalyzer(analyzer.c_str(), c->analyzer->Conn());
if ( ! child )
throw ZeekError(::hilti::rt::fmt("unknown analyzer '%s' requested", analyzer));
// If we had no such child before but cannot add it the analyzer was prevented.
//
// NOTE: We make this a hard error since returning e.g., an empty optional
// here would make it easy to incorrectly use the return value with e.g.,
// `protocol_data_in` or `protocol_gap`.
if ( ! c->analyzer->AddChildAnalyzer(child) )
throw ZeekError(::hilti::rt::fmt("creation of child analyzer %s was prevented", analyzer));
// If we had no such child before but cannot add it the analyzer was prevented.
//
// NOTE: We make this a hard error since returning e.g., an empty optional
// here would make it easy to incorrectly use the return value with e.g.,
// `protocol_data_in` or `protocol_gap`.
if ( ! c->analyzer->AddChildAnalyzer(child) )
throw ZeekError(::hilti::rt::fmt("creation of child analyzer %s was prevented", analyzer));
if ( c->analyzer->Conn()->ConnTransport() != TRANSPORT_TCP ) {
// Some TCP application analyzer may expect to have access to a TCP
// analyzer. To make that work, we'll create a fake TCP analyzer,
// just so that they have something to access. It won't
// semantically have any "TCP" to analyze obviously.
c->fake_tcp = std::make_shared<packet_analysis::TCP::TCPSessionAdapter>(c->analyzer->Conn());
static_cast<analyzer::Analyzer*>(c->fake_tcp.get())
->Done(); // will never see packets; cast to get around protected inheritance
if ( c->analyzer->Conn()->ConnTransport() != TRANSPORT_TCP ) {
// Some TCP application analyzer may expect to have access to a TCP
// analyzer. To make that work, we'll create a fake TCP analyzer,
// just so that they have something to access. It won't
// semantically have any "TCP" to analyze obviously.
c->fake_tcp = std::make_shared<packet_analysis::TCP::TCPSessionAdapter>(c->analyzer->Conn());
static_cast<analyzer::Analyzer*>(c->fake_tcp.get())
->Done(); // will never see packets; cast to get around protected inheritance
}
return rt::ProtocolHandle(child->GetID(), proto);
}
case ::hilti::rt::Protocol::UDP: {
c->analyzer->CleanupChildren();
// If the child already exists, do not add it again so this function is idempotent.
if ( auto child = c->analyzer->GetChildAnalyzer(analyzer) )
return rt::ProtocolHandle(child->GetID(), proto);
auto child = analyzer_mgr->InstantiateAnalyzer(analyzer.c_str(), c->analyzer->Conn());
if ( ! child )
throw ZeekError(::hilti::rt::fmt("unknown analyzer '%s' requested", analyzer));
// If we had no such child before but cannot add it the analyzer was prevented.
//
// NOTE: We make this a hard error since returning e.g., an empty optional
// here would make it easy to incorrectly use the return value with e.g.,
// `protocol_data_in` or `protocol_gap`.
if ( ! c->analyzer->AddChildAnalyzer(child) )
throw ZeekError(::hilti::rt::fmt("creation of child analyzer %s was prevented", analyzer));
return rt::ProtocolHandle(child->GetID(), proto);
}
case ::hilti::rt::Protocol::ICMP: throw Unsupported("protocol_handle_get_or_create: ICMP not supported");
case ::hilti::rt::Protocol::Undef: throw InvalidValue("protocol_handle_get_or_create: no protocol specified");
default: throw InvalidValue("protocol_handle_get_or_create: unknown protocol");
}
auto* child_as_tcp = dynamic_cast<analyzer::tcp::TCP_ApplicationAnalyzer*>(child);
if ( ! child_as_tcp )
throw ZeekError(
::hilti::rt::fmt("could not add analyzer '%s' to connection; not a TCP-based analyzer", analyzer));
if ( c->fake_tcp )
child_as_tcp->SetTCP(c->fake_tcp.get());
return rt::ProtocolHandle(child->GetID());
}
void rt::protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data,
const std::optional<rt::ProtocolHandle>& h) {
namespace zeek::spicy::rt {
static void protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data,
const std::optional<::hilti::rt::Protocol>& proto,
const std::optional<rt::ProtocolHandle>& h) {
auto _ = hilti::rt::profiler::start("zeek/rt/protocol_data_in");
auto cookie = static_cast<Cookie*>(hilti::rt::context::cookie());
assert(cookie);
@ -597,25 +629,85 @@ void rt::protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes
if ( ! c )
throw ValueUnavailable("no current connection available");
auto len = data.size();
auto* data_ = reinterpret_cast<const u_char*>(data.data());
::hilti::rt::Protocol protocol_to_use = ::hilti::rt::Protocol::Undef;
if ( h ) {
if ( auto* output_handler = c->analyzer->GetOutputHandler() )
output_handler->DeliverStream(len, data_, is_orig);
if ( proto ) {
if ( h && h->protocol() != *proto )
throw InvalidValue("protocol_data_in: protocol mismatches with analyzer handle");
auto* child = c->analyzer->FindChild(h->id());
if ( ! child )
throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", *h));
if ( child->IsFinished() || child->Removing() )
throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", *h));
child->NextStream(len, data_, is_orig);
protocol_to_use = *proto;
}
else if ( h )
protocol_to_use = h->protocol();
else
c->analyzer->ForwardStream(len, data_, is_orig);
if ( protocol_to_use == ::hilti::rt::Protocol::Undef )
throw InvalidValue("protocol_data_in: cannot determine protocol to use");
switch ( protocol_to_use.value() ) {
case ::hilti::rt::Protocol::TCP: {
auto len = data.size();
auto* data_ = reinterpret_cast<const u_char*>(data.data());
if ( h ) {
if ( auto* output_handler = c->analyzer->GetOutputHandler() )
output_handler->DeliverStream(len, data_, is_orig);
auto* child = c->analyzer->FindChild(h->id());
if ( ! child )
throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", *h));
if ( child->IsFinished() || child->Removing() )
throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", *h));
child->NextStream(len, data_, is_orig);
}
else
c->analyzer->ForwardStream(len, data_, is_orig);
break;
}
case ::hilti::rt::Protocol::UDP: {
auto len = data.size();
auto* data_ = reinterpret_cast<const u_char*>(data.data());
if ( h ) {
if ( auto* output_handler = c->analyzer->GetOutputHandler() )
output_handler->DeliverPacket(len, data_, is_orig, 0, nullptr, 0);
auto* child = c->analyzer->FindChild(h->id());
if ( ! child )
throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", *h));
if ( child->IsFinished() || child->Removing() )
throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", *h));
child->NextPacket(len, data_, is_orig);
}
else
c->analyzer->ForwardPacket(len, data_, is_orig, 0, nullptr, 0);
break;
}
case ::hilti::rt::Protocol::ICMP: throw Unsupported("protocol_data_in: ICMP not supported");
case ::hilti::rt::Protocol::Undef: hilti::rt::cannot_be_reached();
default: throw InvalidValue("protocol_data_in: unknown protocol");
}
}
} // namespace zeek::spicy::rt
void rt::protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data,
const ::hilti::rt::Protocol& proto) {
protocol_data_in(is_orig, data, proto, {});
}
void rt::protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data, const rt::ProtocolHandle& h) {
protocol_data_in(is_orig, data, {}, h);
}
void rt::protocol_gap(const hilti::rt::Bool& is_orig, const hilti::rt::integer::safe<uint64_t>& offset,
@ -628,22 +720,38 @@ void rt::protocol_gap(const hilti::rt::Bool& is_orig, const hilti::rt::integer::
if ( ! c )
throw ValueUnavailable("no current connection available");
if ( h ) {
if ( auto* output_handler = c->analyzer->GetOutputHandler() )
output_handler->Undelivered(offset, len, is_orig);
switch ( h->protocol().value() ) {
case ::hilti::rt::Protocol::TCP: {
if ( h ) {
if ( auto* output_handler = c->analyzer->GetOutputHandler() )
output_handler->Undelivered(offset, len, is_orig);
auto* child = c->analyzer->FindChild(h->id());
if ( ! child )
throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", *h));
auto* child = c->analyzer->FindChild(h->id());
if ( ! child )
throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", *h));
if ( child->IsFinished() || child->Removing() )
throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", *h));
if ( child->IsFinished() || child->Removing() )
throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", *h));
child->NextUndelivered(offset, len, is_orig);
child->NextUndelivered(offset, len, is_orig);
}
else
c->analyzer->ForwardUndelivered(offset, len, is_orig);
break;
}
case ::hilti::rt::Protocol::UDP: {
throw Unsupported("protocol_gap: UDP not supported");
}
case ::hilti::rt::Protocol::ICMP: throw Unsupported("protocol_gap: ICMP not supported");
case ::hilti::rt::Protocol::Undef: throw InvalidValue("protocol_gap: no protocol specified");
default: throw InvalidValue("protocol_gap: unknown protocol");
}
else
c->analyzer->ForwardUndelivered(offset, len, is_orig);
}
void rt::protocol_end() {
@ -668,17 +776,40 @@ void rt::protocol_handle_close(const ProtocolHandle& handle) {
if ( ! c )
throw ValueUnavailable("no current connection available");
auto child = c->analyzer->FindChild(handle.id());
if ( ! child )
throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", handle));
switch ( handle.protocol().value() ) {
case ::hilti::rt::Protocol::TCP: {
auto child = c->analyzer->FindChild(handle.id());
if ( ! child )
throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", handle));
if ( child->IsFinished() || child->Removing() )
throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", handle));
if ( child->IsFinished() || child->Removing() )
throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", handle));
child->NextEndOfData(true);
child->NextEndOfData(false);
child->NextEndOfData(true);
child->NextEndOfData(false);
c->analyzer->RemoveChildAnalyzer(handle.id());
c->analyzer->RemoveChildAnalyzer(handle.id());
break;
}
case ::hilti::rt::Protocol::UDP: {
auto child = c->analyzer->FindChild(handle.id());
if ( ! child )
throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", handle));
if ( child->IsFinished() || child->Removing() )
throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", handle));
c->analyzer->RemoveChildAnalyzer(handle.id());
break;
}
case ::hilti::rt::Protocol::ICMP: throw Unsupported("protocol_handle_close: ICMP not supported");
case ::hilti::rt::Protocol::Undef: throw InvalidValue("protocol_handle_close: no protocol specified");
default: throw InvalidValue("protocol_handle_close: unknown protocol");
}
}
rt::cookie::FileState* rt::cookie::FileStateStack::push(std::optional<std::string> fid_provided) {

View file

@ -294,7 +294,7 @@ void reject_protocol(const std::string& reason = "protocol rejected");
class ProtocolHandle {
public:
ProtocolHandle() {}
explicit ProtocolHandle(uint64_t id) : _id(id) {}
explicit ProtocolHandle(uint64_t id, ::hilti::rt::Protocol proto) : _id(id), _proto(proto) {}
uint64_t id() const {
if ( ! _id )
@ -303,6 +303,8 @@ public:
return *_id;
}
const auto& protocol() const { return _proto; }
friend std::string to_string(const ProtocolHandle& h, ::hilti::rt::detail::adl::tag) {
if ( ! h._id )
return "(uninitialized protocol handle)";
@ -316,38 +318,56 @@ public:
private:
std::optional<uint64_t> _id;
::hilti::rt::Protocol _proto = ::hilti::rt::Protocol::Undef;
};
/**
* Adds a Zeek-side child protocol analyzer to the current connection.
*
* @param analyzer if given, the Zeek-side name of the analyzer to instantiate;
* if not given, DPD will be used
* @param analyzer the Zeek-side name of the analyzer to instantiate; can be left unset to add a DPD analyzer
*/
void protocol_begin(const std::optional<std::string>& analyzer);
void protocol_begin(const std::optional<std::string>& analyzer, const ::hilti::rt::Protocol& proto);
/**
* Adds a Zeek-side DPD child analyzer to the current connection.
*
* @param proto the transport-layer protocol of the desired DPD analyzer; must be TCP or UDP
*/
void protocol_begin(const ::hilti::rt::Protocol& proto);
/**
* Gets a handle to a child analyzer of a given type. If a child of that type
* does not yet exist it will be created.
*
* @param analyzer the Zeek-side name of the analyzer to get (e.g., `HTTP`)
* @param proto the transport-layer protocol of the analyzer, which must match
* the type of the child analyzer that *analyzer* refers to
*
* @return a handle to the child analyzer. When done, the handle should be
* closed, either explicitly with protocol_handle_close or implicitly with
* protocol_end.
*/
ProtocolHandle protocol_handle_get_or_create(const std::string& analyzer);
rt::ProtocolHandle protocol_handle_get_or_create(const std::string& analyzer, const ::hilti::rt::Protocol& proto);
/**
* Forwards data to all previously instantiated Zeek-side child protocol
* analyzers.
* analyzers of a given transport-layer protocol.
*
* @param is_orig true to feed data to originator side, false for responder
* @param data next chunk of stream data for child analyzer to process
* @param h optional handle to the child analyzer to stream data into
* @param h optional handle to pass data to a specific child analyzer only
*/
void protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data,
const std::optional<ProtocolHandle>& h = {});
void protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data, const ::hilti::rt::Protocol& proto);
/**
* Forwards data to a specific previously instantiated Zeek-side child protocol
* analyzer.
*
* @param is_orig true to feed data to originator side, false for responder
* @param data next chunk of stream data for child analyzer to process
* @param h handle identifying the specific child analyzer only
*/
void protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data, const ProtocolHandle& h);
/**
* Signals a gap in input data to all previously instantiated Zeek-side child

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path http
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p trans_depth method host uri referrer version user_agent origin request_body_len response_body_len status_code status_msg info_code info_msg tags username password proxied orig_fuids orig_filenames orig_mime_types resp_fuids resp_filenames resp_mime_types
#types time string addr port addr port count string string string string string string string count count count string count string set[enum] string string set[string] vector[string] vector[string] vector[string] vector[string] vector[string] vector[string]
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 207.158.192.40 53 10.20.1.31 53 1 GET - /etc/passwd1 - 1.0 - - 0 0 200 OK - - (empty) - - - - - - - - -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,18 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path syslog
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto facility severity message
#types time string addr port addr port enum string string string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.150.186.169 49244 131.159.14.23 22 tcp UNSPECIFIED UNSPECIFIED A1 orig
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.150.186.169 49244 131.159.14.23 22 tcp UNSPECIFIED UNSPECIFIED A1 resp
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.150.186.169 49244 131.159.14.23 22 tcp UNSPECIFIED UNSPECIFIED A2 orig
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.150.186.169 49244 131.159.14.23 22 tcp UNSPECIFIED UNSPECIFIED A2 resp
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.150.186.169 49244 131.159.14.23 22 tcp UNSPECIFIED UNSPECIFIED B1 orig
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.150.186.169 49244 131.159.14.23 22 tcp UNSPECIFIED UNSPECIFIED B1 resp
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.150.186.169 49244 131.159.14.23 22 tcp UNSPECIFIED UNSPECIFIED C1 orig
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.150.186.169 49244 131.159.14.23 22 tcp UNSPECIFIED UNSPECIFIED C1 resp
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,34 @@
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: spicyz -d -o test.hlto dns.spicy ./dns.evt
# @TEST-EXEC: zeek -r ${TRACES}/dns53.pcap test.hlto %INPUT
# @TEST-EXEC: btest-diff http.log
# @TEST-START-FILE dns.spicy
module DNS;
import spicy;
import zeek;
public type Packet = unit {
data: bytes &eod;
};
on Packet::%done {
zeek::protocol_begin("HTTP", spicy::Protocol::TCP);
zeek::protocol_data_in(True, b"GET /etc/passwd1 ");
zeek::protocol_data_in(True, b"HTTP/1.0\r\n\r\n");
zeek::protocol_data_in(False, b"HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n");
zeek::protocol_end();
}
# @TEST-END-FILE
# @TEST-START-FILE dns.evt
import zeek;
protocol analyzer spicy::DNS over UDP:
parse originator with DNS::Packet,
replaces DNS;
# @TEST-END-FILE

View file

@ -0,0 +1,59 @@
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: spicyz -d -o test.hlto test.evt test.spicy
# @TEST-EXEC: zeek -B dpd -s test.sig -r ${TRACES}/ssh/single-conn.trace test.hlto %INPUT Spicy::enable_print=T >&2
# @TEST-EXEC: btest-diff syslog.log
event zeek_init() {
Analyzer::register_for_port(Analyzer::ANALYZER_SPICY_SSH, 22/tcp);
}
# @TEST-START-FILE test.sig
signature dpd_syslog {
payload /.*C1/
enable "Syslog"
}
# @TEST-END-FILE
# @TEST-START-FILE test.spicy
module Test;
import spicy;
import zeek;
public type Foo = unit {
};
on Foo::%init {
# Specify analyzer.
zeek::protocol_begin("Syslog", spicy::Protocol::UDP);
zeek::protocol_data_in(True, b"A1 orig", spicy::Protocol::UDP);
zeek::protocol_data_in(False, b"A1 resp", spicy::Protocol::UDP);
zeek::protocol_data_in(True, b"A2 orig", spicy::Protocol::UDP);
zeek::protocol_data_in(False, b"A2 resp", spicy::Protocol::UDP);
zeek::protocol_end();
# Use explicit handle.
local syslog = zeek::protocol_handle_get_or_create("syslog", spicy::Protocol::UDP);
zeek::protocol_data_in(True, b"B1 orig", syslog);
zeek::protocol_data_in(False, b"B1 resp", syslog);
zeek::protocol_handle_close(syslog);
# DPD.
zeek::protocol_begin(spicy::Protocol::UDP);
zeek::protocol_data_in(True, b"C1 orig", spicy::Protocol::UDP);
zeek::protocol_data_in(False, b"C1 resp", spicy::Protocol::UDP);
zeek::protocol_end();
}
# @TEST-END-FILE
# @TEST-START-FILE test.evt
import zeek;
protocol analyzer spicy::SSH over TCP:
parse originator with Test::Foo,
replaces SSH;
# @TEST-END-FILE