feat(topology): SNMP LLDP/CDP and NetFlow/IPFIX/sFlow v5 collectors with topology functions#21702
feat(topology): SNMP LLDP/CDP and NetFlow/IPFIX/sFlow v5 collectors with topology functions#21702ktsaou wants to merge 36 commits intonetdata:masterfrom
Conversation
There was a problem hiding this comment.
3 issues found across 218 files
Confidence score: 3/5
- The maxBuckets cap in
src/go/plugin/go.d/collector/netflow/flow_aggregator.gocan be bypassed for older buckets, risking unbounded map growth and memory pressure. src/go/plugin/go.d/collector/netflow/collector.goignores caller contexts in Init/Cleanup, so framework cancellation/timeout may not propagate, impacting shutdown reliability.- Score reflects concrete behavioral risks (resource growth and shutdown propagation), though fixes are localized.
- Pay close attention to
src/go/plugin/go.d/collector/netflow/flow_aggregator.go,src/go/plugin/go.d/collector/netflow/collector.go- bucket limit enforcement and context propagation.
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="src/go/plugin/go.d/collector/netflow/collector.go">
<violation number="1" location="src/go/plugin/go.d/collector/netflow/collector.go:117">
P2: Init discards the caller context and always uses context.Background(), so module cancellation from the framework won't propagate to the collector goroutines.</violation>
<violation number="2" location="src/go/plugin/go.d/collector/netflow/collector.go:158">
P2: Cleanup ignores the caller’s context and uses context.Background(), which can block shutdowns when the framework expects cancellation/timeout to be honored.</violation>
</file>
<file name="src/go/plugin/go.d/collector/netflow/flow_aggregator.go">
<violation number="1" location="src/go/plugin/go.d/collector/netflow/flow_aggregator.go:255">
P2: maxBuckets enforcement can fail when the incoming bucket is older than all existing buckets, allowing the map to exceed the configured limit.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
3 issues found across 128 files (changes from recent commits).
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="src/crates/netdata-netflow/netflow-plugin/src/network_sources.rs">
<violation number="1" location="src/crates/netdata-netflow/netflow-plugin/src/network_sources.rs:178">
P2: Silent failure on lock poisoning: if `state.by_source.write()` fails due to lock poisoning, records are silently dropped without logging. Consider at minimum logging a warning when the lock cannot be acquired.</violation>
</file>
<file name="src/crates/netdata-netflow/netflow-plugin/src/plugin_config.rs">
<violation number="1" location="src/crates/netdata-netflow/netflow-plugin/src/plugin_config.rs:984">
P2: The gRPC address validation only checks for any ':' when no scheme is provided, so IPv6 literals without a port pass validation even though the error message requires host:port. This lets invalid configs through and will fail later at connection time.</violation>
</file>
<file name="src/go/plugin/go.d/collector/snmp/topology_cache_test.go">
<violation number="1" location="src/go/plugin/go.d/collector/snmp/topology_cache_test.go:203">
P3: The helper treats any non-nil attribute type as a valid non-empty list, so the test can pass even when the attribute is the wrong type or empty. To keep the test meaningful, only return true for non-empty list types and treat unknown types as missing.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
src/crates/netdata-netflow/netflow-plugin/src/network_sources.rs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="src/web/api/functions/function-streaming.c">
<violation number="1" location="src/web/api/functions/function-streaming.c:18">
P1: Avoid unbounded variable-length stack allocations from the user-controlled `function` string; a large request can exhaust the stack and crash the agent. Use a bounded buffer with a length check or heap allocation with proper cleanup.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Add optional entry timestamp overrides while preserving strict monotonic ordering (last + 1us floor) for realtime and monotonic values. Seed monotonic baseline from journal tail on startup (same boot only) so override writes remain monotonic across process restarts. Keep existing write_entry() behavior unchanged and cover new behavior with integration tests.
ee1bb5a to
70cb670
Compare
There was a problem hiding this comment.
2 issues found across 2 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="src/go/plugin/go.d/collector/snmp/integrations/snmp_devices.md">
<violation number="1" location="src/go/plugin/go.d/collector/snmp/integrations/snmp_devices.md:208">
P2: The security description for `Snmp:topology` incorrectly claims it only exposes interface names and traffic counters, but this function returns LLDP/CDP topology data (devices, links, management addresses). This is misleading for users reading the documentation.</violation>
<violation number="2" location="src/go/plugin/go.d/collector/snmp/integrations/snmp_devices.md:209">
P2: The availability description references interface data caching instead of LLDP/CDP topology data, which doesn’t match this function’s purpose. It should reference topology cache readiness.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
3 issues found across 10 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/crates/journalctl/src/main.rs">
<violation number="1" location="src/crates/journalctl/src/main.rs:152">
P2: Guard against relative times that underflow the UNIX epoch; the current subtraction can go negative and wrap when cast to u64.</violation>
</file>
<file name="src/crates/journal-session/src/cursor.rs">
<violation number="1" location="src/crates/journal-session/src/cursor.rs:103">
P2: Broken rustdoc intra-doc link: `Cursor::fields` does not exist. The method is `payloads()`, so the link target should be `Cursor::payloads`.</violation>
<violation number="2" location="src/crates/journal-session/src/cursor.rs:475">
P2: This `expect()` panics in library code, which violates the project convention ("Avoid panics in library paths; return typed errors instead"). Additionally, the panic message references `fields()` but the method is named `payloads()`. Return a typed `SessionError` variant instead.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| }; | ||
|
|
||
| let now = chrono::Utc::now().timestamp_micros(); | ||
| return Ok((now - offset_usec) as u64); |
There was a problem hiding this comment.
P2: Guard against relative times that underflow the UNIX epoch; the current subtraction can go negative and wrap when cast to u64.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/crates/journalctl/src/main.rs, line 152:
<comment>Guard against relative times that underflow the UNIX epoch; the current subtraction can go negative and wrap when cast to u64.</comment>
<file context>
@@ -0,0 +1,432 @@
+ };
+
+ let now = chrono::Utc::now().timestamp_micros();
+ return Ok((now - offset_usec) as u64);
+ }
+
</file context>
| /// | ||
| /// Must be called after [`step()`](Cursor::step) returns `true`. | ||
| pub fn payloads(&mut self) -> Result<Payloads<'_>, SessionError> { | ||
| let entry_offset = self.entry_offset.expect("fields() called without step()"); |
There was a problem hiding this comment.
P2: This expect() panics in library code, which violates the project convention ("Avoid panics in library paths; return typed errors instead"). Additionally, the panic message references fields() but the method is named payloads(). Return a typed SessionError variant instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/crates/journal-session/src/cursor.rs, line 475:
<comment>This `expect()` panics in library code, which violates the project convention ("Avoid panics in library paths; return typed errors instead"). Additionally, the panic message references `fields()` but the method is named `payloads()`. Return a typed `SessionError` variant instead.</comment>
<file context>
@@ -0,0 +1,517 @@
+ ///
+ /// Must be called after [`step()`](Cursor::step) returns `true`.
+ pub fn payloads(&mut self) -> Result<Payloads<'_>, SessionError> {
+ let entry_offset = self.entry_offset.expect("fields() called without step()");
+ let jf = self.journal_file.as_ref().unwrap();
+ let iter = jf.entry_data_objects(entry_offset)?;
</file context>
journal-session provides a cursor-based API for iterating journal entries
across multiple files. Files are discovered, sorted, and traversed
sequentially with transparent cross-file transitions. The two-phase
iteration API (step() + payloads()) returns raw data object payloads as
byte slices, letting callers pay only for the fields they need.
Key features:
- CursorBuilder with match filters, time bounds (since/until), direction
- Binary search on archived file timestamps to skip irrelevant files
- Error recovery: bad files are skipped, caller can retry
- Lending iterator (Payloads) with internal decompression buffer reuse
jctl is a journalctl-like CLI built on journal-session:
- Scans /var/log/journal by default, or use --directory/-D, --file
- Supports --since/--until with absolute, relative ("1 day ago"), and
symbolic (today, yesterday, now) timestamps
- Match filters (FIELD=VALUE), --grep, --priority
- Output formats: short, verbose, json, cat
- Single-file introspection: --header, --fields, -F
journal-core changes:
- Re-export EntryDataIterator for external field iteration
- Add JournalReader::build_filter() to extract FilterExpr for use
with JournalCursor directly
301482a to
e033503
Compare
There was a problem hiding this comment.
2 issues found across 9 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/go/plugin/go.d/agent/jobmgr/manager.go">
<violation number="1" location="src/go/plugin/go.d/agent/jobmgr/manager.go:174">
P2: The new topology alias registration isn't mirrored in cleanup; `topology:*` aliases remain registered after shutdown, leaving stale handlers if the manager is re-initialized. Add corresponding unregistration for the alias names.</violation>
</file>
<file name="src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_table.go">
<violation number="1" location="src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_table.go:330">
P1: Caching auxiliary tables (tables with no symbols, used only for cross-table tags) prevents them from being re-walked. However, dependent tables that *do* need walking (e.g., due to cache expiry or first run) rely on `walkedData` to resolve cross-table tags. Since `walkedData` only contains the results of the *current* walk, skipping the auxiliary table means its data is missing from `walkedData`, causing tag resolution to fail for the dependent table.
The fix is to ensure auxiliary tables are always present in `walkedData` when needed. The safest way with the current architecture is to NOT cache them (remove the `cacheData` call), forcing them to be walked every cycle. Alternatively, the cross-table resolution logic would need to be updated to fallback to reading from the cache.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| // We still cache their presence so they are not re-walked on every cycle. | ||
| if len(result.config.Symbols) == 0 { | ||
| if result.pdus != nil { | ||
| tc.tableCache.cacheData(result.config, nil, nil, nil) |
There was a problem hiding this comment.
P1: Caching auxiliary tables (tables with no symbols, used only for cross-table tags) prevents them from being re-walked. However, dependent tables that do need walking (e.g., due to cache expiry or first run) rely on walkedData to resolve cross-table tags. Since walkedData only contains the results of the current walk, skipping the auxiliary table means its data is missing from walkedData, causing tag resolution to fail for the dependent table.
The fix is to ensure auxiliary tables are always present in walkedData when needed. The safest way with the current architecture is to NOT cache them (remove the cacheData call), forcing them to be walked every cycle. Alternatively, the cross-table resolution logic would need to be updated to fallback to reading from the cache.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_table.go, line 330:
<comment>Caching auxiliary tables (tables with no symbols, used only for cross-table tags) prevents them from being re-walked. However, dependent tables that *do* need walking (e.g., due to cache expiry or first run) rely on `walkedData` to resolve cross-table tags. Since `walkedData` only contains the results of the *current* walk, skipping the auxiliary table means its data is missing from `walkedData`, causing tag resolution to fail for the dependent table.
The fix is to ensure auxiliary tables are always present in `walkedData` when needed. The safest way with the current architecture is to NOT cache them (remove the `cacheData` call), forcing them to be walked every cycle. Alternatively, the cross-table resolution logic would need to be updated to fallback to reading from the cache.</comment>
<file context>
@@ -322,12 +322,43 @@ func (tc *tableCollector) buildTableNameMap(walkResults []tableWalkResult) map[s
+ // We still cache their presence so they are not re-walked on every cycle.
+ if len(result.config.Symbols) == 0 {
+ if result.pdus != nil {
+ tc.tableCache.cacheData(result.config, nil, nil, nil)
+ } else if tc.tableCache.isConfigCached(result.config) {
+ stats.SNMP.TablesCached++
</file context>
| // Register default module-scoped function name. | ||
| funcNames := []string{fmt.Sprintf("%s:%s", name, method.ID)} | ||
| // Topology methods may also be exposed with topology:* aliases for UI grouping. | ||
| if method.ResponseType == "topology" && strings.HasPrefix(method.ID, "topology:") { |
There was a problem hiding this comment.
P2: The new topology alias registration isn't mirrored in cleanup; topology:* aliases remain registered after shutdown, leaving stale handlers if the manager is re-initialized. Add corresponding unregistration for the alias names.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/plugin/go.d/agent/jobmgr/manager.go, line 174:
<comment>The new topology alias registration isn't mirrored in cleanup; `topology:*` aliases remain registered after shutdown, leaving stale handlers if the manager is re-initialized. Add corresponding unregistration for the alias names.</comment>
<file context>
@@ -168,10 +168,13 @@ func (m *Manager) Run(ctx context.Context, in chan []*confgroup.Group) {
+ // Register default module-scoped function name.
+ funcNames := []string{fmt.Sprintf("%s:%s", name, method.ID)}
+ // Topology methods may also be exposed with topology:* aliases for UI grouping.
+ if method.ResponseType == "topology" && strings.HasPrefix(method.ID, "topology:") {
+ funcNames = append(funcNames, method.ID)
+ }
</file context>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/go/tools/topology-parity-evidence/main.go">
<violation number="1">
P2: Assertion detection uses `rawLine`, so `assert*` tokens in comments or string literals are counted as real assertions, which can inflate the inventory and create false parity gaps. Scan `cleanLine` (the comment/string-stripped version) instead.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| @@ -0,0 +1,2806 @@ | |||
| // SPDX-License-Identifier: GPL-3.0-or-later | |||
There was a problem hiding this comment.
P2: Assertion detection uses rawLine, so assert* tokens in comments or string literals are counted as real assertions, which can inflate the inventory and create false parity gaps. Scan cleanLine (the comment/string-stripped version) instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/tools/topology-parity-evidence/main.go, line 2286:
<comment>Assertion detection uses `rawLine`, so `assert*` tokens in comments or string literals are counted as real assertions, which can inflate the inventory and create false parity gaps. Scan `cleanLine` (the comment/string-stripped version) instead.</comment>
<file context>
@@ -0,0 +1,2806 @@
+ inBlockComment = nextInBlockComment
+ trimmed := strings.TrimSpace(cleanLine)
+
+ if matches := assertionCallRE.FindAllStringSubmatchIndex(rawLine, -1); len(matches) > 0 {
+ for _, m := range matches {
+ if len(m) < 4 {
</file context>
There was a problem hiding this comment.
3 issues found across 25 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/go/pkg/topology/engine/parity/evidence/office-live-reliability-report.md">
<violation number="1" location="src/go/pkg/topology/engine/parity/evidence/office-live-reliability-report.md:6">
P2: Avoid committing real SNMP community strings in documentation; replace with a redacted placeholder to prevent leaking credentials.</violation>
</file>
<file name="src/go/plugin/go.d/collector/snmp/topology_registry.go">
<violation number="1" location="src/go/plugin/go.d/collector/snmp/topology_registry.go:177">
P2: L3 topology responses skip augmentLocalActorFromCache, so local device attributes (management addresses, capabilities, sys_descr, etc.) are missing compared with L2. Consider augmenting the L3 data before returning.</violation>
<violation number="2" location="src/go/plugin/go.d/collector/snmp/topology_registry.go:182">
P2: Merged topology responses skip augmentLocalActorFromCache, so local device attributes are dropped in the combined L2/L3 output. Consider augmenting the merged data before returning.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| ## Scope | ||
| - Gate: `T4.5` + `T4.6` | ||
| - Office seed: `10.20.4.0/24` | ||
| - Community: `atadteN` |
There was a problem hiding this comment.
P2: Avoid committing real SNMP community strings in documentation; replace with a redacted placeholder to prevent leaking credentials.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/pkg/topology/engine/parity/evidence/office-live-reliability-report.md, line 6:
<comment>Avoid committing real SNMP community strings in documentation; replace with a redacted placeholder to prevent leaking credentials.</comment>
<file context>
@@ -0,0 +1,60 @@
+## Scope
+- Gate: `T4.5` + `T4.6`
+- Office seed: `10.20.4.0/24`
+- Community: `atadteN`
+- Function under validation: `topology:snmp` (`topology_view:l2|l3|merged`)
+
</file context>
| case topologyViewMerged: | ||
| data, ok := mergeSNMPTopologyData(l2Data, l2OK, l3Data, l3OK, agentID, collectedAt) | ||
| if !ok { | ||
| return topologyData{}, false | ||
| } | ||
| return data, true |
There was a problem hiding this comment.
P2: Merged topology responses skip augmentLocalActorFromCache, so local device attributes are dropped in the combined L2/L3 output. Consider augmenting the merged data before returning.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/plugin/go.d/collector/snmp/topology_registry.go, line 182:
<comment>Merged topology responses skip augmentLocalActorFromCache, so local device attributes are dropped in the combined L2/L3 output. Consider augmenting the merged data before returning.</comment>
<file context>
@@ -157,6 +166,42 @@ func (r *topologyRegistry) snapshot() (topologyData, bool) {
+ return topologyData{}, false
+ }
+ return l3Data, true
+ case topologyViewMerged:
+ data, ok := mergeSNMPTopologyData(l2Data, l2OK, l3Data, l3OK, agentID, collectedAt)
+ if !ok {
</file context>
| case topologyViewMerged: | |
| data, ok := mergeSNMPTopologyData(l2Data, l2OK, l3Data, l3OK, agentID, collectedAt) | |
| if !ok { | |
| return topologyData{}, false | |
| } | |
| return data, true | |
| case topologyViewMerged: | |
| data, ok := mergeSNMPTopologyData(l2Data, l2OK, l3Data, l3OK, agentID, collectedAt) | |
| if !ok { | |
| return topologyData{}, false | |
| } | |
| for _, snapshot := range snapshots { | |
| augmentLocalActorFromCache(&data, snapshot.localDevice) | |
| } | |
| return data, true |
| case topologyViewL3: | ||
| if !l3OK { | ||
| return topologyData{}, false | ||
| } | ||
| return l3Data, true |
There was a problem hiding this comment.
P2: L3 topology responses skip augmentLocalActorFromCache, so local device attributes (management addresses, capabilities, sys_descr, etc.) are missing compared with L2. Consider augmenting the L3 data before returning.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/go/plugin/go.d/collector/snmp/topology_registry.go, line 177:
<comment>L3 topology responses skip augmentLocalActorFromCache, so local device attributes (management addresses, capabilities, sys_descr, etc.) are missing compared with L2. Consider augmenting the L3 data before returning.</comment>
<file context>
@@ -157,6 +166,42 @@ func (r *topologyRegistry) snapshot() (topologyData, bool) {
+ selectedView = topologyViewL2
+ }
+ switch selectedView {
+ case topologyViewL3:
+ if !l3OK {
+ return topologyData{}, false
</file context>
| case topologyViewL3: | |
| if !l3OK { | |
| return topologyData{}, false | |
| } | |
| return l3Data, true | |
| case topologyViewL3: | |
| if !l3OK { | |
| return topologyData{}, false | |
| } | |
| data := l3Data | |
| for _, snapshot := range snapshots { | |
| augmentLocalActorFromCache(&data, snapshot.localDevice) | |
| } | |
| return data, true |
There was a problem hiding this comment.
1 issue found across 7 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/go/pkg/topology/engine/topology_adapter.go">
<violation number="1">
P1: Segment actors are always appended to the output regardless of identity-key deduplication. Unlike device and endpoint actors which `continue` (skip) when keys are empty or already present in `actorIndex`, this code only gates `addTopologyIdentityKeys` but not the `append`. This will produce duplicate actors when identity keys overlap.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
1 issue found across 12 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="TODO-topology-library-phase2-direct-port.md">
<violation number="1">
P1: Do not commit real SNMP community strings in documentation. Replace `atadteN` with a redacted placeholder (e.g., `<community>`) everywhere it appears to avoid leaking credentials.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| @@ -0,0 +1,2276 @@ | |||
| # TODO-topology-library-phase2-direct-port | |||
There was a problem hiding this comment.
P1: Do not commit real SNMP community strings in documentation. Replace atadteN with a redacted placeholder (e.g., <community>) everywhere it appears to avoid leaking credentials.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At TODO-topology-library-phase2-direct-port.md:
<comment>Do not commit real SNMP community strings in documentation. Replace `atadteN` with a redacted placeholder (e.g., `<community>`) everywhere it appears to avoid leaking credentials.</comment>
Summary
topologyandflows) for dashboard integrationMilestone 1: Data Collection ✅
This PR completes Milestone 1 — collecting topology and flow data at the agent level.
SNMP Topology (L2)
topologyfunction returning JSON with devices and linksNetFlow Collector (L3)
flowsfunction returning top talkers with bytes/packets/connectionsAggregation Tool
topology-flow-mergethat aggregates topology/flows from multiple Netdata agentsTesting
Roadmap
Test plan
go test ./plugin/go.d/collector/snmp/...— all LLDP/CDP tests passgo test ./plugin/go.d/collector/netflow/...— all flow decoder tests passSummary by cubic
Adds SNMP LLDP/CDP L2 and expanded OSPF/ISIS L3 topology via a new Go topology engine, live L7 topology from local sockets, and a Rust NetFlow/IPFIX/sFlow collector with journal-backed storage and fast multi-file queries. Updates CI, function API, and packaging to ship the netflow plugin and new journal tools.
New Features
Migration
Written for commit 28c7a6e. Summary will update on new commits.