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 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) * 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; module zeek;
import spicy;
# Note: Retain the formatting here, doc/scripts/autogen-spicy-lib is picking up on that. # Note: Retain the formatting here, doc/scripts/autogen-spicy-lib is picking up on that.
%cxx-include = "zeek/spicy/runtime-support.h"; %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. ## 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 ## 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 ## `protocol_begin` with same argument, and not closed with `protocol_handle_close`
## or protocol_end, no new analyzer will be added. ## or `protocol_end`, no new analyzer will be added.
## ##
## See `protocol_handle_get_or_create` for the error semantics of this function. ## See `protocol_handle_get_or_create` for the error semantics of this function.
## ##
## analyzer: type of analyzer to instantiate, specified through its Zeek-side ## analyzer: type of analyzer to instantiate, specified through its Zeek-side
## name (similar to what Zeek's signature action `enable` takes); if not ## name (similar to what Zeek's signature action `enable` takes)
## 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.) ## protocol: the transport-layer protocol that the analyzer uses; only TCP is
public function protocol_begin(analyzer: optional<string> = Null) : void &cxxname="zeek::spicy::rt::protocol_begin"; ## 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. ## 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. ## 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 ## - not called from a protocol analyzer, or
## - the requested child protocol analyzer is unknown, 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 ## - creation of a child analyzer of the requested type was prevented by a
## previous call of `disable_analyzer` with `prevent=T` ## 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). ## 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 ## 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 ## 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. ## 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 ## is_orig: true to signal gap to the child's originator side, false for the responder
##
## offset: start offset of gap in input stream ## offset: start offset of gap in input stream
##
## len: size of gap ## len: size of gap
##
## h: optional handle to the child analyzer signal a gap to, else signal to all child analyzers ## 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"; 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; 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) { Analyzer* Analyzer::FindChild(ID arg_id) {
if ( id == arg_id && ! (removing || finished) ) if ( id == arg_id && ! (removing || finished) )
return this; return this;
@ -465,6 +477,17 @@ Analyzer* Analyzer::FindChild(const char* name) {
return tag ? FindChild(tag) : nullptr; 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_list::iterator Analyzer::DeleteChild(analyzer_list::iterator i) {
Analyzer* child = *i; Analyzer* child = *i;

View file

@ -451,6 +451,19 @@ public:
*/ */
Analyzer* GetChildAnalyzer(const zeek::Tag& tag) const; 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 * Recursively searches all (direct or indirect) children of the
* analyzer for an analyzer with a specific ID. * analyzer for an analyzer with a specific ID.
@ -494,6 +507,13 @@ public:
*/ */
const analyzer_list& GetChildren() { return children; } 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 * Returns a pointer to the parent analyzer, or null if this instance
* has not yet been added to an analyzer tree. * 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()); pia = new analyzer::pia::PIA_TCP(Conn());
if ( AddChildAnalyzer(pia) ) { if ( AddChildAnalyzer(pia) ) {
pia->FirstPacket(true, nullptr); pia->FirstPacket(true, TransportProto::TRANSPORT_TCP);
pia->FirstPacket(false, nullptr); pia->FirstPacket(false, TransportProto::TRANSPORT_TCP);
int remaining_in_content_line = content_line_resp->GetDeliverStreamRemainingLength(); int remaining_in_content_line = content_line_resp->GetDeliverStreamRemainingLength();
if ( remaining_in_content_line > 0 ) { if ( remaining_in_content_line > 0 ) {
@ -1396,8 +1396,8 @@ void HTTP_Analyzer::HTTP_Upgrade() {
upgrade_protocol.c_str()); upgrade_protocol.c_str());
pia = new analyzer::pia::PIA_TCP(Conn()); pia = new analyzer::pia::PIA_TCP(Conn());
if ( AddChildAnalyzer(pia) ) { if ( AddChildAnalyzer(pia) ) {
pia->FirstPacket(true, nullptr); pia->FirstPacket(true, TransportProto::TRANSPORT_TCP);
pia->FirstPacket(false, nullptr); pia->FirstPacket(false, TransportProto::TRANSPORT_TCP);
} }
} }

View file

@ -169,26 +169,37 @@ 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 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 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 && proto ) { // proto needed here to avoid GCC warning that it may be used uninitialized
if ( ! ip ) {
// Create a dummy packet. Not very elegant, but everything // Create a dummy packet. Not very elegant, but everything
// else would be *really* ugly ... // else would be *really* ugly ...
if ( ! ip4_hdr ) { switch ( *proto ) {
ip4 = (struct ip*)dummy_packet; case TransportProto::TRANSPORT_TCP: {
DBG_LOG(DBG_ANALYZER, "PIA/TCP FirstPacket(%s)", (is_orig ? "T" : "F"));
if ( ! ip4_tcp_hdr ) {
ip4_tcp = (struct ip*)dummy_packet;
tcp4 = (struct tcphdr*)(dummy_packet + sizeof(struct ip)); tcp4 = (struct tcphdr*)(dummy_packet + sizeof(struct ip));
ip4->ip_len = sizeof(struct ip) + sizeof(struct tcphdr); ip4_tcp->ip_len = sizeof(struct ip) + sizeof(struct tcphdr);
ip4->ip_hl = sizeof(struct ip) >> 2; ip4_tcp->ip_hl = sizeof(struct ip) >> 2;
ip4->ip_p = IPPROTO_TCP;
// Cast to const so that it doesn't delete it. // Cast to const so that it doesn't delete it.
ip4_hdr = new IP_Hdr(ip4, false); ip4_tcp_hdr = new IP_Hdr(ip4_tcp, false);
} }
// Locals used to avoid potential alignment problems // Locals used to avoid potential alignment problems
@ -210,11 +221,60 @@ void PIA_TCP::FirstPacket(bool is_orig, const IP_Hdr* ip) {
tcp4->th_dport = htons(Conn()->OrigPort()); tcp4->th_dport = htons(Conn()->OrigPort());
} }
ip4->ip_src = tmp_src; ip4_tcp->ip_src = tmp_src;
ip4->ip_dst = tmp_dst; ip4_tcp->ip_dst = tmp_dst;
ip = ip4_hdr; 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");
}
}
assert(ip);
if ( ! MatcherInitialized(is_orig) ) if ( ! MatcherInitialized(is_orig) )
DoMatch((const u_char*)"", 0, is_orig, true, false, false, ip); DoMatch((const u_char*)"", 0, is_orig, true, false, false, ip);
} }

View file

@ -36,6 +36,32 @@ public:
void ReplayPacketBuffer(analyzer::Analyzer* analyzer); 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 // Children are also derived from Analyzer. Return this object
// as pointer to an Analyzer. // as pointer to an Analyzer.
analyzer::Analyzer* AsAnalyzer() { return as_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, void DoMatch(const u_char* data, int len, bool is_orig, bool bol, bool eol, bool clear_state,
const IP_Hdr* ip = nullptr); const IP_Hdr* ip = nullptr);
auto Conn() const { return conn; }
void SetConn(Connection* c) { conn = c; } void SetConn(Connection* c) { conn = c; }
Buffer pkt_buffer; Buffer pkt_buffer;
private: 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; analyzer::Analyzer* as_analyzer;
Connection* conn; Connection* conn;
DataBlock current_packet; DataBlock current_packet;
@ -123,16 +153,6 @@ public:
void Init() override; 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); void ReplayStreamBuffer(analyzer::Analyzer* analyzer);
static analyzer::Analyzer* Instantiate(Connection* conn) { return new PIA_TCP(conn); } 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 ) { if ( ! pia ) {
pia = new analyzer::pia::PIA_TCP(Conn()); pia = new analyzer::pia::PIA_TCP(Conn());
if ( AddChildAnalyzer(pia) ) { if ( AddChildAnalyzer(pia) ) {
pia->FirstPacket(true, nullptr); pia->FirstPacket(true, TransportProto::TRANSPORT_TCP);
pia->FirstPacket(false, nullptr); pia->FirstPacket(false, TransportProto::TRANSPORT_TCP);
} }
else else
pia = nullptr; pia = nullptr;

View file

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

View file

@ -2,7 +2,6 @@
#include "zeek/spicy/runtime-support.h" #include "zeek/spicy/runtime-support.h"
#include <algorithm>
#include <memory> #include <memory>
#include <hilti/rt/exception.h> #include <hilti/rt/exception.h>
@ -10,6 +9,7 @@
#include <hilti/rt/types/port.h> #include <hilti/rt/types/port.h>
#include <hilti/rt/util.h> #include <hilti/rt/util.h>
#include "net_util.h"
#include "zeek/Event.h" #include "zeek/Event.h"
#include "zeek/analyzer/Manager.h" #include "zeek/analyzer/Manager.h"
#include "zeek/analyzer/protocol/pia/PIA.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"); 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"); auto _ = hilti::rt::profiler::start("zeek/rt/protocol_begin");
if ( analyzer ) { if ( analyzer ) {
protocol_handle_get_or_create(*analyzer); protocol_handle_get_or_create(*analyzer, proto);
return; 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()); auto cookie = static_cast<Cookie*>(hilti::rt::context::cookie());
assert(cookie); assert(cookie);
@ -501,36 +505,47 @@ void rt::protocol_begin(const std::optional<std::string>& analyzer) {
if ( ! c ) if ( ! c )
throw ValueUnavailable("no current connection available"); throw ValueUnavailable("no current connection available");
// Use a Zeek PIA stream analyzer performing DPD. 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()); 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->CleanupChildren();
c->analyzer->ForwardStream(0, reinterpret_cast<const u_char*>(c->analyzer), true);
c->analyzer->ForwardStream(0, reinterpret_cast<const u_char*>(c->analyzer), false);
// Direct child of this type already exists. We ignore this silently // If the child already exists, do not add it again so this function is idempotent.
// because that makes usage nicer if either side of the connection if ( auto child = c->analyzer->GetChildAnalyzer(pia_tcp->GetAnalyzerName()) )
// 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; return;
auto child = pia_tcp.release(); auto child = pia_tcp.release();
c->analyzer->AddChildAnalyzer(child); c->analyzer->AddChildAnalyzer(child);
break;
}
child->FirstPacket(true, nullptr); case ::hilti::rt::Protocol::UDP: {
child->FirstPacket(false, nullptr); // 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 _ = hilti::rt::profiler::start("zeek/rt/protocol_handle_get_or_create");
auto cookie = static_cast<Cookie*>(hilti::rt::context::cookie()); auto cookie = static_cast<Cookie*>(hilti::rt::context::cookie());
assert(cookie); assert(cookie);
@ -539,20 +554,13 @@ rt::ProtocolHandle rt::protocol_handle_get_or_create(const std::string& analyzer
if ( ! c ) if ( ! c )
throw ValueUnavailable("no current connection available"); throw ValueUnavailable("no current connection available");
// Forward empty payload to trigger lifecycle management in this analyzer tree. switch ( proto.value() ) {
c->analyzer->ForwardStream(0, reinterpret_cast<const u_char*>(c->analyzer), true); case ::hilti::rt::Protocol::TCP: {
c->analyzer->ForwardStream(0, reinterpret_cast<const u_char*>(c->analyzer), false); c->analyzer->CleanupChildren();
// If the child already exists, do not add it again so this function is idempotent. // If the child already exists, do not add it again so this function is idempotent.
// if ( auto child = c->analyzer->GetChildAnalyzer(analyzer) )
// We inspect the children directly to work around zeek/zeek#2899. return rt::ProtocolHandle(child->GetID(), proto);
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());
auto child = analyzer_mgr->InstantiateAnalyzer(analyzer.c_str(), c->analyzer->Conn()); auto child = analyzer_mgr->InstantiateAnalyzer(analyzer.c_str(), c->analyzer->Conn());
if ( ! child ) if ( ! child )
@ -576,18 +584,42 @@ rt::ProtocolHandle rt::protocol_handle_get_or_create(const std::string& analyzer
->Done(); // will never see packets; cast to get around protected inheritance ->Done(); // will never see packets; cast to get around protected inheritance
} }
auto* child_as_tcp = dynamic_cast<analyzer::tcp::TCP_ApplicationAnalyzer*>(child); return rt::ProtocolHandle(child->GetID(), proto);
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 ) case ::hilti::rt::Protocol::UDP: {
child_as_tcp->SetTCP(c->fake_tcp.get()); c->analyzer->CleanupChildren();
return rt::ProtocolHandle(child->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));
// 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");
}
} }
void rt::protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data, 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) { const std::optional<rt::ProtocolHandle>& h) {
auto _ = hilti::rt::profiler::start("zeek/rt/protocol_data_in"); auto _ = hilti::rt::profiler::start("zeek/rt/protocol_data_in");
auto cookie = static_cast<Cookie*>(hilti::rt::context::cookie()); auto cookie = static_cast<Cookie*>(hilti::rt::context::cookie());
@ -597,6 +629,22 @@ void rt::protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes
if ( ! c ) if ( ! c )
throw ValueUnavailable("no current connection available"); throw ValueUnavailable("no current connection available");
::hilti::rt::Protocol protocol_to_use = ::hilti::rt::Protocol::Undef;
if ( proto ) {
if ( h && h->protocol() != *proto )
throw InvalidValue("protocol_data_in: protocol mismatches with analyzer handle");
protocol_to_use = *proto;
}
else if ( h )
protocol_to_use = h->protocol();
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 len = data.size();
auto* data_ = reinterpret_cast<const u_char*>(data.data()); auto* data_ = reinterpret_cast<const u_char*>(data.data());
@ -616,6 +664,50 @@ void rt::protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes
else else
c->analyzer->ForwardStream(len, data_, is_orig); 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, void rt::protocol_gap(const hilti::rt::Bool& is_orig, const hilti::rt::integer::safe<uint64_t>& offset,
@ -628,6 +720,8 @@ void rt::protocol_gap(const hilti::rt::Bool& is_orig, const hilti::rt::integer::
if ( ! c ) if ( ! c )
throw ValueUnavailable("no current connection available"); throw ValueUnavailable("no current connection available");
switch ( h->protocol().value() ) {
case ::hilti::rt::Protocol::TCP: {
if ( h ) { if ( h ) {
if ( auto* output_handler = c->analyzer->GetOutputHandler() ) if ( auto* output_handler = c->analyzer->GetOutputHandler() )
output_handler->Undelivered(offset, len, is_orig); output_handler->Undelivered(offset, len, is_orig);
@ -644,6 +738,20 @@ void rt::protocol_gap(const hilti::rt::Bool& is_orig, const hilti::rt::integer::
else else
c->analyzer->ForwardUndelivered(offset, len, is_orig); 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");
}
} }
void rt::protocol_end() { void rt::protocol_end() {
@ -668,6 +776,8 @@ void rt::protocol_handle_close(const ProtocolHandle& handle) {
if ( ! c ) if ( ! c )
throw ValueUnavailable("no current connection available"); throw ValueUnavailable("no current connection available");
switch ( handle.protocol().value() ) {
case ::hilti::rt::Protocol::TCP: {
auto child = c->analyzer->FindChild(handle.id()); auto child = c->analyzer->FindChild(handle.id());
if ( ! child ) if ( ! child )
throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", handle)); throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", handle));
@ -679,6 +789,27 @@ void rt::protocol_handle_close(const ProtocolHandle& handle) {
child->NextEndOfData(false); 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) { 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 { class ProtocolHandle {
public: public:
ProtocolHandle() {} 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 { uint64_t id() const {
if ( ! _id ) if ( ! _id )
@ -303,6 +303,8 @@ public:
return *_id; return *_id;
} }
const auto& protocol() const { return _proto; }
friend std::string to_string(const ProtocolHandle& h, ::hilti::rt::detail::adl::tag) { friend std::string to_string(const ProtocolHandle& h, ::hilti::rt::detail::adl::tag) {
if ( ! h._id ) if ( ! h._id )
return "(uninitialized protocol handle)"; return "(uninitialized protocol handle)";
@ -316,38 +318,56 @@ public:
private: private:
std::optional<uint64_t> _id; std::optional<uint64_t> _id;
::hilti::rt::Protocol _proto = ::hilti::rt::Protocol::Undef;
}; };
/** /**
* Adds a Zeek-side child protocol analyzer to the current connection. * Adds a Zeek-side child protocol analyzer to the current connection.
* *
* @param analyzer if given, the Zeek-side name of the analyzer to instantiate; * @param analyzer the Zeek-side name of the analyzer to instantiate; can be left unset to add a DPD analyzer
* if not given, DPD will be used
*/ */
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 * 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. * does not yet exist it will be created.
* *
* @param analyzer the Zeek-side name of the analyzer to get (e.g., `HTTP`) * @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 * @return a handle to the child analyzer. When done, the handle should be
* closed, either explicitly with protocol_handle_close or implicitly with * closed, either explicitly with protocol_handle_close or implicitly with
* protocol_end. * 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 * 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 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 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, void protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data, const ::hilti::rt::Protocol& proto);
const std::optional<ProtocolHandle>& h = {});
/**
* 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 * 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