Skip to content

Commit 6b8c2b8

Browse files
leogrc2ndevgeraldcombsirozzo-1Aekoops
committed
new(userspace/falco): add capture.max_file_size_mb global hard limit
Co-authored-by: Alessandro Cannarella <cannarella.dev@gmail.com> Co-authored-by: Gerald Combs <gerald@wireshark.org> Co-authored-by: Iacopo Rozzo <iacopo@sysdig.com> Co-authored-by: Leonardo Di Giovanna <leonardodigiovanna1@gmail.com> Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
1 parent 6f5274c commit 6b8c2b8

6 files changed

Lines changed: 97 additions & 10 deletions

File tree

falco.yaml

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -461,11 +461,29 @@ engine:
461461
# 2. `all_rules`: Captures events when any enabled rule is triggered.
462462
#
463463
# When a capture starts, Falco records events from the moment the triggering rule
464-
# fires until the deadline is reached. The deadline is determined by the rule's
465-
# `capture_duration` if specified, otherwise the `default_duration` is used.
466-
# If additional rules trigger during an active capture, the deadline is extended
467-
# accordingly. Once the deadline expires, the capture stops and data is written
468-
# to a file. Subsequent captures create new files with unique names.
464+
# fires until a stop condition is reached. Two stop conditions are available:
465+
#
466+
# - Time (per-rule, soft): determined by the rule's `capture_duration` if
467+
# specified, otherwise `default_duration` is used. If additional rules trigger
468+
# during an active capture, the deadline is extended accordingly (the longest
469+
# deadline wins). For this reason, the time limit has "at least" semantics:
470+
# the capture is guaranteed to last at least that long, but can last longer if
471+
# other rules keep matching.
472+
#
473+
# - File size (global, hard): `max_file_size_mb` applies to any capture,
474+
# regardless of which rule triggered it. Unlike the time limit, it cannot be
475+
# overridden or extended by rules: if a capture reaches this size, it stops.
476+
# N.B. The size check uses the dumper's compressed on-disk counter, which is
477+
# updated in chunks as zlib flushes its internal buffers. The effective file
478+
# size is therefore approximate and may overshoot the configured value by up
479+
# to one flush window. For this reason, avoid very small values (under a few
480+
# MB), which may be inaccurate, and consider tuning with a healthy margin.
481+
#
482+
# The first stop condition met wins (OR semantics). Once a stop condition is
483+
# met, the capture stops and data is written to a file. Subsequent captures
484+
# create new files with unique names. When a capture stops because of
485+
# `max_file_size_mb`, Falco emits an internal INFO message so the truncation
486+
# is visible in the configured outputs.
469487
#
470488
# Captured data is stored in files with a `.scap` extension, which can be
471489
# analyzed later using:
@@ -483,7 +501,8 @@ engine:
483501
# Use `capture.mode` to choose between `rules` and `all_rules` modes.
484502
#
485503
# Set `capture.default_duration` to define the default capture duration
486-
# in milliseconds.
504+
# in milliseconds. Optionally, set `capture.max_file_size_mb` to enforce a
505+
# hard upper bound on the capture file size in MB (applies to any capture).
487506
#
488507
# --- [Suggestions]
489508
#
@@ -512,6 +531,10 @@ capture:
512531
mode: rules
513532
# -- Default capture duration in milliseconds if not specified in the rule.
514533
default_duration: 5000
534+
# -- Global hard cap on capture file size in MB (0 = unlimited).
535+
# This limit applies to any capture and cannot be overridden or extended by rules.
536+
# The check is approximate (see the section above): prefer values of at least a few MB.
537+
# max_file_size_mb: 100
515538

516539
#################
517540
# Falco plugins #

userspace/falco/app/actions/helpers.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,33 @@ void check_for_ignored_events(falco::app::state& s);
3535
void format_plugin_info(std::shared_ptr<sinsp_plugin> p, std::ostream& os);
3636
void format_described_rules_as_text(const nlohmann::json& v, std::ostream& os);
3737

38+
enum class capture_stop_reason {
39+
NONE,
40+
TIME_DEADLINE,
41+
SIZE_LIMIT,
42+
};
43+
44+
// Returns the reason why an active capture should stop, given the current
45+
// event timestamp, the dump deadline, and the number of bytes written so far.
46+
// Stop conditions combine with OR semantics (first met wins); time is checked
47+
// before size, so when both trip on the same event, TIME_DEADLINE is reported.
48+
// `max_file_size_mb == 0` means "unlimited".
49+
// Note: `written_bytes` is the dumper's on-disk counter (compressed, buffered
50+
// by zlib), so the check is approximate — the effective file size may overshoot
51+
// by up to one flush window.
52+
inline capture_stop_reason check_capture_stop(uint64_t evt_ts,
53+
uint64_t dump_deadline_ts,
54+
uint64_t written_bytes,
55+
uint64_t max_file_size_mb) {
56+
if(evt_ts >= dump_deadline_ts) {
57+
return capture_stop_reason::TIME_DEADLINE;
58+
}
59+
if(max_file_size_mb > 0 && written_bytes >= max_file_size_mb * 1024 * 1024) {
60+
return capture_stop_reason::SIZE_LIMIT;
61+
}
62+
return capture_stop_reason::NONE;
63+
}
64+
3865
inline std::string generate_scap_file_path(const std::string& prefix,
3966
uint64_t timestamp,
4067
uint64_t evt_num) {

userspace/falco/app/actions/process_events.cpp

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ static falco::app::run_result do_inspect(
149149
auto dumper = std::make_unique<sinsp_dumper>();
150150
uint64_t dump_started_ts = 0;
151151
uint64_t dump_deadline_ts = 0;
152+
// Global hard cap on capture file size (MB). 0 means unlimited.
153+
const uint64_t dump_max_file_size_mb = s.config->m_capture_max_file_size_mb;
154+
// Pre-built notification strings for the size-limit branch (constant per
155+
// do_inspect call), hoisted to avoid per-event allocations in the hot path.
156+
const std::string size_cap_rule = "Falco internal: capture size limit reached";
157+
const std::string size_cap_msg =
158+
size_cap_rule + ". Configured limit: " + std::to_string(dump_max_file_size_mb) + " MB.";
152159

153160
//
154161
// Start capture
@@ -347,14 +354,35 @@ static falco::app::run_result do_inspect(
347354
}
348355

349356
// Save events when a dump is in progress.
350-
// If the deadline is reached, close the dump.
357+
// If any stop condition is reached, close the dump.
358+
// Stop conditions (first one met wins):
359+
// - per-rule time deadline (soft, extended by matching rules)
360+
// - global size cap (hard, applies to any capture)
351361
if(dump_started_ts != 0) {
352362
dumper->dump(ev);
353-
if(ev->get_ts() > dump_deadline_ts) {
363+
auto reason = check_capture_stop(ev->get_ts(),
364+
dump_deadline_ts,
365+
dumper->written_bytes(),
366+
dump_max_file_size_mb);
367+
if(reason != capture_stop_reason::NONE) {
354368
dumper->flush();
369+
uint64_t written = dumper->written_bytes();
355370
dumper->close();
356371
dump_started_ts = 0;
357372
dump_deadline_ts = 0;
373+
if(reason == capture_stop_reason::SIZE_LIMIT) {
374+
nlohmann::json fields;
375+
fields["max_file_size_mb"] = dump_max_file_size_mb;
376+
fields["written_bytes"] = written;
377+
auto now = std::chrono::duration_cast<std::chrono::nanoseconds>(
378+
std::chrono::system_clock::now().time_since_epoch())
379+
.count();
380+
s.outputs->handle_msg(now,
381+
falco_common::PRIORITY_INFORMATIONAL,
382+
size_cap_msg,
383+
size_cap_rule,
384+
fields);
385+
}
358386
}
359387
}
360388

userspace/falco/config_json_schema.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,13 @@ const char config_schema_string[] = LONG_STRING_CONST(
326326
]
327327
},
328328
"default_duration": {
329-
"type": "integer"
329+
"type": "integer",
330+
"minimum": 0
331+
},
332+
"max_file_size_mb": {
333+
"type": "integer",
334+
"minimum": 0,
335+
"maximum": 1048576
330336
}
331337
},
332338
"title": "Capture"

userspace/falco/configuration.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ falco_configuration::falco_configuration():
100100
m_capture_enabled(false),
101101
m_capture_path_prefix("/tmp/falco"),
102102
m_capture_mode(capture_mode_t::RULES),
103-
m_capture_default_duration_ns(5000 * 1000000LL) {
103+
m_capture_default_duration_ns(5000 * 1000000LL),
104+
m_capture_max_file_size_mb(0) {
104105
m_config_schema = nlohmann::json::parse(config_schema_string);
105106
}
106107

@@ -621,6 +622,7 @@ void falco_configuration::load_yaml(const std::string &config_name) {
621622
// Convert to nanoseconds
622623
m_capture_default_duration_ns =
623624
m_config.get_scalar<uint32_t>("capture.default_duration", 5000) * 1000000LL;
625+
m_capture_max_file_size_mb = m_config.get_scalar<uint64_t>("capture.max_file_size_mb", 0);
624626

625627
m_plugins_hostinfo = m_config.get_scalar<bool>("plugins_hostinfo", true);
626628

userspace/falco/configuration.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ class falco_configuration {
199199
std::string m_capture_path_prefix;
200200
capture_mode_t m_capture_mode = capture_mode_t::RULES;
201201
uint64_t m_capture_default_duration_ns;
202+
uint64_t m_capture_max_file_size_mb;
202203

203204
// Falco engine
204205
engine_kind_t m_engine_mode = engine_kind_t::KMOD;

0 commit comments

Comments
 (0)