Documentation
¶
Overview ¶
Package collect is the agent's flow collection loop: a Source observes connections on the host, an Aggregator folds them into one record per (source, destination, port, protocol, decision, rule) over the reporting window the control plane configures, holding at most a bounded number of distinct keys, a bounded Buffer holds closed windows until the Reporter ships them over ReportFlows, and overflow at either bound drops what does not fit and records the loss as an evidence gap. Per-connection records never leave the host (ADR-0009); the conntrack source is the first implementation behind the Source interface, and an eBPF source is a later additive one (ADR-0003).
Index ¶
- Constants
- func Batches(w *Window, batchMax int) []*innerwallv1.ReportFlowsRequest
- type Aggregator
- type Buffer
- type Collector
- type Dialer
- type Gap
- type GapKind
- type GapSource
- type Gapped
- type Gaps
- func (g *Gaps) Close(source GapSource, to time.Time)
- func (g *Gaps) CutOpen(to time.Time)
- func (g *Gaps) Held() int
- func (g *Gaps) Open(source GapSource, kind GapKind, from time.Time)
- func (g *Gaps) Overrun()
- func (g *Gaps) Overruns() uint64
- func (g *Gaps) Pending() []Gap
- func (g *Gaps) Record(gap Gap)
- func (g *Gaps) Restore(gaps []Gap)
- func (g *Gaps) Take() []Gap
- type Key
- type Observation
- type Reporter
- type Source
- type Sources
- type Window
Constants ¶
const DefaultBatchMax = 1000
DefaultBatchMax caps the records in one ReportFlowsRequest before the control plane has said otherwise in HelloAck.
const DefaultBufferRecords = 50_000
DefaultBufferRecords is the buffer capacity in records unless configured otherwise: an hour of busy windows in memory, and a bound the agent enforces on itself before it would ever burden the host (ADR-0011).
const DefaultGapCapacity = 1024
DefaultGapCapacity bounds the gaps held until they are shipped. Past it, gaps of the same kind and source are merged into wider intervals, so the bound costs precision, never the fact of a loss (ADR-0011).
const DefaultNetlinkBuffer = 4 << 20
DefaultNetlinkBuffer is the receive buffer a flow source asks for on its netlink socket unless configured otherwise: enough for a burst of tens of thousands of events while the source is busy, at a few megabytes of kernel memory per socket (ADR-0011).
const DefaultWindow = 60 * time.Second
DefaultWindow is the aggregation window before the control plane has said otherwise in HelloAck.
const DefaultWindowKeys = DefaultBufferRecords
DefaultWindowKeys bounds the distinct keys one open window holds unless configured otherwise: the closed-window buffer's figure, so one window can never hold more than the buffer would keep (ADR-0011).
Variables ¶
This section is empty.
Functions ¶
func Batches ¶
func Batches(w *Window, batchMax int) []*innerwallv1.ReportFlowsRequest
Batches splits a window into requests of at most batchMax records, each carrying the window's bounds; an empty window is one request with none.
Types ¶
type Aggregator ¶
type Aggregator struct {
// Gaps receives the overflow gap when a window closes; nil records
// nothing.
Gaps *Gaps
// contains filtered or unexported fields
}
Aggregator folds observations into the open window. The window holds at most a bounded number of distinct keys: an observation for a new key past the bound is dropped, and the window records the loss as a window_overflow gap from the first drop to the window's close, with the observations dropped. Observations for keys already held still fold in.
func NewAggregator ¶
func NewAggregator(start time.Time) *Aggregator
NewAggregator opens a window starting at start, holding at most DefaultWindowKeys keys.
func NewBoundedAggregator ¶
func NewBoundedAggregator(start time.Time, maxKeys int) *Aggregator
NewBoundedAggregator opens a window starting at start holding at most maxKeys distinct keys; DefaultWindowKeys when maxKeys is not positive.
func (*Aggregator) Add ¶
func (a *Aggregator) Add(o Observation)
Add folds one observation into the open window, or drops it when its key is new and the window is full.
func (*Aggregator) Close ¶
func (a *Aggregator) Close(end time.Time) *Window
Close ends the open window at end, returns it, and opens the next one starting at end. A window that dropped observations records its window_overflow gap now: from the first drop to end, because every new key in that interval was lost.
func (*Aggregator) Cut ¶ added in v0.2.0
func (a *Aggregator) Cut(end, next time.Time) *Window
Cut ends the open window at end and opens the next one at next, for a window the wall clock stepped back under (see closeBounds): end is where the window's own clock reads at its close, and next is the stepped clock. Every observation in the window happened between its bounds on the window's own clock, whatever the stepped clock stamped on it, so each record's first and last seen, and the first drop of a window overflow, are held within the bounds.
func (*Aggregator) Dropped ¶
func (a *Aggregator) Dropped() uint64
Dropped returns the observations the open window has dropped so far.
func (*Aggregator) Len ¶
func (a *Aggregator) Len() int
Len returns the number of distinct keys in the open window.
func (*Aggregator) Start ¶ added in v0.2.0
func (a *Aggregator) Start() time.Time
Start returns the open window's start.
type Buffer ¶
type Buffer struct {
// Gaps records every overflow as a buffer_overflow gap over the
// dropped window's bounds, with the records dropped; nil records
// nothing.
Gaps *Gaps
// contains filtered or unexported fields
}
Buffer holds closed windows between the collector and the reporter. It is bounded in records; when a window does not fit, the oldest windows are dropped until it does and every dropped record is counted, so the heartbeat can tell the operator the flow map is incomplete.
func NewBuffer ¶
NewBuffer returns a buffer holding at most capacity records; DefaultBufferRecords when capacity is not positive.
func (*Buffer) Carry ¶
Carry queues an empty window, unless a window is already queued, so that gaps waiting to be shipped ride it when no records are.
func (*Buffer) Dropped ¶
Dropped returns the number of records dropped to overflow since the buffer was created. It is what the heartbeat reports.
func (*Buffer) Pop ¶
Pop removes and returns the oldest window, waiting until one is queued or ctx ends.
func (*Buffer) Push ¶
Push appends w as the newest window. An empty window is not queued; Carry queues one when gaps need it.
type Collector ¶
type Collector struct {
Source Source
Buffer *Buffer
// Gaps holds the evidence gaps waiting to be shipped: a window that
// closes empty is still queued while any are, so they ride it.
Gaps *Gaps
Log *slog.Logger
// Now is the clock; time.Now if nil.
Now func() time.Time
// Monotonic reads a clock that never steps, as the time since any
// fixed origin; the runtime's monotonic clock if nil. A window's
// length is measured on it, so a wall clock stepped while a window was
// open is seen when the window closes.
Monotonic func() time.Duration
// WindowKeys bounds the distinct keys one open window holds;
// DefaultWindowKeys when zero.
WindowKeys int
// BackoffBase and BackoffCap bound the wait before a failed source is
// restarted: one second and one minute when zero.
BackoffBase time.Duration
BackoffCap time.Duration
// contains filtered or unexported fields
}
Collector runs a Source, aggregates what it emits, and closes a window into the Buffer every window length. The window length follows SyncConfig.flow_aggregation_window_seconds and takes effect at the next rotation.
func (*Collector) Run ¶
Run collects until ctx ends. The source is restarted with jittered backoff whenever it fails, because a host without a working source still holds its policy and its stream; what it loses is telemetry, and that is reported, not fatal. The window open when ctx ends is closed and queued so a clean shutdown loses nothing that was observed.
func (*Collector) SetConfig ¶
func (c *Collector) SetConfig(cfg *innerwallv1.SyncConfig)
SetConfig adopts the control plane's aggregation window.
type Dialer ¶
type Dialer func(ctx context.Context) (innerwallv1.AgentServiceClient, io.Closer, error)
Dialer opens a connection to the control plane with the workload credential. The reporter dials for itself so that telemetry never shares a connection's fate with the sync stream, and reconnects with its own backoff (ADR-0015).
type Gap ¶
Gap is one interval of known evidence loss, [From, To). Count is the number of records or entries lost when HasCount is set; a kernel-side loss has no count.
func (Gap) Wire ¶
func (gap Gap) Wire() *innerwallv1.EvidenceGap
Wire returns the gap as ReportFlows carries it.
type GapKind ¶
type GapKind string
GapKind says how evidence was lost.
const ( // GapSourceOverrun: the kernel dropped events because the source's // socket receive buffer was full. How many is unknown. GapSourceOverrun GapKind = "source_overrun" // GapSourceRestart: the source was down between a failure and its // next successful subscribe. How many events is unknown. GapSourceRestart GapKind = "source_restart" // GapBufferOverflow: closed windows were dropped because the buffer // was full. The record count is known. GapBufferOverflow GapKind = "buffer_overflow" // GapDumpTruncated: the table dump at subscribe held more entries than // the source processes; the count of skipped entries is known. GapDumpTruncated GapKind = "dump_truncated" // GapWindowOverflow: the open aggregation window held as many distinct // keys as it may, and observations for new keys were dropped until it // closed. The count of dropped observations is known. GapWindowOverflow GapKind = "window_overflow" // GapWindowRejected: the control plane refused a closed window as // malformed, and it was dropped rather than retried. The record count // is known. GapWindowRejected GapKind = "window_rejected" // GapTableRemoved: the owned table was gone from the kernel, and with // it the observation and classification that depend on it. It stays // open until the table is installed again, and is cut and shipped with // every window that closes meanwhile, so a removal that lasts is never // read as a quiet host (issue #64). No count. GapTableRemoved GapKind = "table_removed" )
The ways a source or the buffer can lose evidence. Each is an interval in which the flow map is known to be incomplete; what was lost inside it cannot be recovered, only bounded in time.
type Gapped ¶
Gapped is a source that records the evidence it loses. When such a source fails, the restart loop opens a restart gap for it at the failure; the source closes it when it next subscribes, so the gap spans exactly the time nothing was observed.
type Gaps ¶
type Gaps struct {
Log *slog.Logger
// Capacity bounds the held gaps; DefaultGapCapacity when zero.
Capacity int
// contains filtered or unexported fields
}
Gaps records evidence gaps for the agent: closed intervals, and at most one open interval per source between a failure and the subscribe that ends it. It also counts kernel-side overruns per source. Gaps are logged as they are recorded and held, bounded, until they are taken.
func (*Gaps) Close ¶
Close ends the open interval for source at to, the instant the source is observing again, and records it. Nothing happens when none is open.
func (*Gaps) CutOpen ¶ added in v0.2.0
CutOpen records the part up to to of every open interval of a kind that can last indefinitely, and keeps it open from to. The collector calls it as each window closes, so the window carries the loss so far. A nil Gaps records nothing.
func (*Gaps) Open ¶
Open starts an interval of loss for source at from, unless one is already open: the earlier start stands, because nothing was observed since. An open interval of another kind is closed at from and the new kind takes over, so each recorded gap names what happened. A nil Gaps records nothing.
func (*Gaps) Overrun ¶
func (g *Gaps) Overrun()
Overrun counts one kernel-side overrun. A nil Gaps counts nothing.
type Key ¶
type Key struct {
Src netip.Addr
Dst netip.Addr
DstPort uint16
Protocol innerwallv1.Protocol
Decision innerwallv1.PolicyDecision
RuleID string
}
Key is what records aggregate on within a window. The ephemeral source port is deliberately absent (ADR-0015).
type Observation ¶
type Observation struct {
At time.Time
Src netip.Addr
Dst netip.Addr
DstPort uint16
Protocol innerwallv1.Protocol
Decision innerwallv1.PolicyDecision
RuleID string
Connections uint64
Bytes uint64
ProcessName string
}
Observation is one event from a Source: a connection seen inbound to this host. A source reports the start of a connection with Connections set and its byte total, once known, with Bytes set; an aggregator adds both into the record for the connection's key.
type Reporter ¶
type Reporter struct {
Buffer *Buffer
// Gaps are taken and shipped with each window, and put back when its
// stream fails; nil ships none.
Gaps *Gaps
Dial Dialer
Log *slog.Logger
// BackoffBase and BackoffCap bound the wait after a failed report:
// one second and five minutes when zero.
BackoffBase time.Duration
BackoffCap time.Duration
// SendTimeout bounds one window's stream; 30 seconds when zero.
SendTimeout time.Duration
// Now is the clock; time.Now if nil.
Now func() time.Time
// contains filtered or unexported fields
}
Reporter ships closed windows over ReportFlows: one stream per window, one request per batch of at most the configured records, each stamped with the agent's clock as it is sent, closed for the accepted count, with the evidence gaps recorded since the last delivery on the first request. A window whose stream fails is requeued, its gaps put back, and both retried after a jittered backoff; the buffer's bound decides what is dropped if the control plane stays away, and what it drops becomes a gap of its own. A window the control plane refuses as malformed is the exception: resending it can only be refused again, and as the oldest window it would hold every later one behind it, so it is dropped, its loss recorded as a window_rejected gap, and its gaps put back to ride the next window.
func (*Reporter) SetConfig ¶
func (r *Reporter) SetConfig(cfg *innerwallv1.SyncConfig)
SetConfig adopts the control plane's batch cap.
type Source ¶
type Source interface {
Run(ctx context.Context, emit func(Observation)) error
}
Source emits observations until ctx ends. It returns an error when it cannot observe at all (the collector restarts it with backoff); an observation that cannot be classified is dropped by the source, never reported as an error.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package conntrack observes inbound connections through the kernel's connection tracking over netlink (ADR-0003).
|
Package conntrack observes inbound connections through the kernel's connection tracking over netlink (ADR-0003). |
|
Package nflog observes the packets the terminal rule of the owned table logs: what the policy did not permit.
|
Package nflog observes the packets the terminal rule of the owned table logs: what the policy did not permit. |