[Spicy] Let zeek::protocol_handle_close() send a TCP EOF.

Zeek's analyzer API makes it hard to determine during analyzer
shutdown whether a regular end-of-data has been reached, or if we're
aborting in the middle of a session (e.g., because Zeek missed the
remaining packets): the corresponding analyzer method, `EndOfData()`
gets called in both cases.

In an earlier change, we had stopped signaling Spicy analyzers a
regular finish when that `EndOfData()` method executes, because doing
so could trigger a parse error if it wasn't a regular shutdown—-which
isn't desired, a user request was to just silently stop processing in
this case.

However, that behavior now seems unfortunate in the case that one
deliberately calls `zeek::protocol_handle_close()` to terminate an
analyzer: this feels like a regular shutdown that should just
immediately happen. We achieve this now in this function by
additionally signaling the shutdown at the TCP layer as an "end of
file", which, for Spicy analyzers, happens to run the final, orderly
tear-down.

Not exactly great, but ti seems to thread the needle to achieve the
desired semantics in both cases.
This commit is contained in:
Benjamin Bannier 2025-07-04 13:05:13 +02:00 committed by Robin Sommer
parent 92868804b1
commit d6c22295bd
No known key found for this signature in database
GPG key ID: D8187293B3FFE5D0
3 changed files with 62 additions and 1 deletions

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
F, S, 1
T, S, 1

View file

@ -0,0 +1,51 @@
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: spicyz -d -o x.hlto x.spicy x.evt
# @TEST-EXEC: zeek -r ${TRACES}/ssh/single-conn.trace Zeek::Spicy x.hlto x.zeek >output 2>&1
# @TEST-EXEC: btest-diff output
#
# @TEST-DOC: Checks that a analyzer is properly finished when a protocol handle is closed.
# We use a child analyzer since this particular issue does not trigger for the root analyzer.
# @TEST-START-FILE x.spicy
module Foo;
import zeek;
public type X = unit {
data: bytes &size=2 {
local h = zeek::protocol_handle_get_or_create("Y");
zeek::protocol_data_in(zeek::is_orig(), $$, h);
zeek::protocol_handle_close(h);
}
};
public type Y = unit {
a: bytes &size=1;
b: bytes &eod;
};
# @TEST-END-FILE
# @TEST-START-FILE x.evt
import Foo;
protocol analyzer X over TCP:
parse with Foo::X;
protocol analyzer Y over TCP:
parse with Foo::Y;
export Foo::Y;
on Foo::Y -> event foo($is_orig, self);
# @TEST-END-FILE
# @TEST-START-FILE x.zeek
event zeek_init() {
Analyzer::register_for_port(Analyzer::ANALYZER_X, 22/tcp);
}
event foo(is_orig: bool, y: Foo::Y) {
print is_orig, y$a, |y$b|;
}
# @TEST-END-FILE