NetworkInspector.Sessions 0.16.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package NetworkInspector.Sessions --version 0.16.0
                    
NuGet\Install-Package NetworkInspector.Sessions -Version 0.16.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NetworkInspector.Sessions" Version="0.16.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NetworkInspector.Sessions" Version="0.16.0" />
                    
Directory.Packages.props
<PackageReference Include="NetworkInspector.Sessions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NetworkInspector.Sessions --version 0.16.0
                    
#r "nuget: NetworkInspector.Sessions, 0.16.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NetworkInspector.Sessions@0.16.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NetworkInspector.Sessions&version=0.16.0
                    
Install as a Cake Addin
#tool nuget:?package=NetworkInspector.Sessions&version=0.16.0
                    
Install as a Cake Tool

NetworkInspector.Sessions

NuGet

Session orchestration library for NetworkInspector.

What This Is

NetworkInspector.Sessions coordinates frame sources, the protocol stack, pull-based listeners, and background jobs. It never retains parsed packets: every TryGetPacket reparses the captured frame. Stream sources are wrapped in CachedFrameSource so frames stay readable after NextFrame. Random-access sources are left unwrapped unless FrameSourceAddOptions.CacheRandomAccess is set; that wrap holds the inner Frame and does not copy payload bytes.

Each frame source runs on a dedicated thread. Listeners pull by packet id on notification instead of receiving pushed copies.

How to embed a session in an application (setup order, threads, reading, recipes for packet lists, filtered lists, value caches and field observers, restart, shutdown): SESSION_GUIDE.md.

Lifecycle

graph TD
    Idle["Idle<br/>sources/listeners registered"]
    Running["Running<br/>source jobs active"]
    Restarting["Restarting<br/>stack swap + reparse"]
    Stopped["Stopped<br/>all sources finished"]
    ShuttingDown["ShuttingDown<br/>teardown in progress"]
    Idle -->|"TryStart()"| Running
    Running -->|"last source completes"| Stopped
    Running -->|"Restart()"| Restarting
    Stopped -->|"Restart()"| Restarting
    Restarting -->|"reparse done, sources active"| Running
    Restarting -->|"reparse done, no sources"| Stopped
    Running -->|"Shutdown()"| ShuttingDown
    Stopped -->|"Shutdown()"| ShuttingDown
    ShuttingDown -->|"cleanup complete"| Stopped

Typical flow:

  1. Create Session with a Stack.
  2. TryAddFrameSource (Idle only) and TryAddListener (Idle, Running, or Stopped; refused while a restart runs).
  3. TryStart() — launches source and listener threads.
  4. WaitForCompletion() — blocks until all source jobs finish.
  5. Shutdown() or Dispose() — cancels listeners, disposes jobs and sources.

Key Types

Type Role
Session Lifecycle orchestration; frames via CachedFrameSource wrap; packets reparsed on read
ISession Mutable session API (TryAddFrameSource, TryAddListener, TryStart, Restart, Shutdown)
ISessionReader Read-only view for listeners (PacketCount, TryGetPacket, GetJobs)
ISessionListener Pull-based notification callbacks
JobInfo Public view of a background job (source, listener, or user job)
ListenerInfo Public view of a listener subscription
ValueCacheRequest Name-based request for an ingest or runtime value cache
IValueCacheListener Pull-based value-cache notifications (OnNewRows)
ValueCacheInfo Public view of a value-cache subscription (Cache is a ReadOnlyValueCache)
FrameSourceInfo Public view of a registered frame source
CachedFrameSource (Sources) Auto-wrapper for non-random-access frame sources; optional wrap for random-access via FrameSourceAddOptions.CacheRandomAccess
FrameSourceAddOptions Per-source CacheRandomAccess opt-in
PacketRef A PacketId paired with its packet, so a filtered pull can report gapped ids
PacketReadMode All or Matching — whether a pull applies the listener's filter
PacketIdLayout Contiguous or Gapped — whether returned ids are consecutive
SessionFailure Why an operational Try* call returned false (Code, Message)
SessionException Caller bugs and hard limits, typed with SessionErrorCode

Pull-Based Listeners

Producers set atomic NotifyFlags on each ListenerSlot and wake the listener thread via ManualResetEventSlim. The listener clears flags, then pulls data from ISessionReader:

Flag Callback
NewPackets OnNewPackets(session, fromIndex, toIndexExclusive)
SourceAdded / SourceCompleted OnSourcesChanged
AllSourcesCompleted OnAllSourcesCompleted
JobAdded / JobStatusChanged / JobRemoved OnJobsChanged
StackChanged OnStackChanged — discard cached protocol state
PhaseChanged OnPhaseChanged
ShuttingDown OnShuttingDown

Multiple events between two wake cycles coalesce into a single flag read.

Value caches

A session can fill a RAM ValueCache during the first parse (SessionOptions.ValueCache) and/or add dedicated caches at runtime through TryAddValueCache. Listeners receive OnNewRows on a dedicated slot thread with the same coalesced packet-id window as OnNewPackets, then pull columns from ReadOnlyValueCache. Runtime caches never record on the parse thread.

How to snapshot, watermark, find, and SIMD-scan: VALUECACHE_GUIDE.md. Design: docs/value-cache-design.md.

Ingest always skip-parses (FieldTreeMode.Skip): the throwaway packet is recycled on the source thread. Listeners that call TryGetPacket still get a Build tree from the frame. On-demand TryAddValueCache fills by skip-record from TryGetFrame (recordOnReplay: true); it does not walk stored FieldBodies via RecordPacket.

sealed class UdpPortCacheListener : IValueCacheListener
{
    public string UiName => "udp src ports";
    public void OnNewRows(ISessionReader session, ReadOnlyValueCache cache, int fromIndex, int toIndexExclusive)
    {
        if (!cache.TryGetSeries<ulong>("udp.srcport", out ReadOnlyValueCacheSeries<ulong> series))
        {
            return;
        }

        ValueCacheSeriesHandle<ulong> handle = series.Handle;
        foreach (ValueCacheRow<ulong> row in handle.EnumerateFrom(_RowWatermark))
        {
            _ = row.PacketId;
        }

        _RowWatermark = handle.Count;
        _ = fromIndex;
        _ = toIndexExclusive;
    }

    private int _RowWatermark;
}

session.TryAddValueCache(
    new UdpPortCacheListener(),
    new ValueCacheRequest { FieldNames = ["udp.srcport"] },
    out ValueCacheInfo? info,
    out SessionFailure? failure);
// info.Cache is a ReadOnlyValueCache — no RecordPacket

Construction-time ingest:

using Session session = new(stack, new SessionOptions
{
    ValueCache = new ValueCacheRequest { FieldNames = ["udp.srcport"] },
});

session.IngestValueCache is the read-only view filled by Packet.TryParse with ParseOptions.Cache. Restart abandons the previous writer and rebinds surviving runtime slots. Field and group names are validated with NameValidation.IsValidName when the request is bound (construction, TryAddValueCache, Restart). Restart builds the new ingest cache before it changes anything, so a field the new stack lacks throws ValueCacheUnknownField and leaves the session as it was. A runtime cache whose field the new stack lacks fails only its own slot. A read refused while a restart runs only pauses a runtime fill; it does not fail the slot.

Per-Listener Filters

A listener can register a filter, which then applies to that listener's pulls only:

session.TryAddListener(listener, "tcp.port == 443", out ListenerInfo? info, out FilterError? filterFailure, out SessionFailure? failure);

// Or hand over a NetworkInspector.Filter.Filter you compiled yourself:
session.TryAddListener(listener, myFilter, out info, out failure);

// No filter at all — every packet:
session.TryAddListener(listener, out info, out failure);

An empty or whitespace-only expression compiles to the always-match filter. A bad expression leaves the session untouched: no listener is registered and filterFailure explains why. A filter compiled against another stack (another session, or this session before a restart) is re-bound to the session stack with TryDerive; the session installs that copy and leaves your instance untouched. A filter that cannot be re-bound throws ArgumentException and registers nothing. Each listener filter is single-threaded. TryReadPackets in Matching mode and restart rebind share that listener's filter gate. All reads do not take the gate.

Filtered Pulls

ISessionReader offers three read shapes. All of them fill a caller-owned buffer and allocate nothing:

// 1. Packets only, contiguous ids implied by the start index.
int n = reader.ReadPackets(fromIndex, packetBuffer);

// 2. Packets paired with their ids; always contiguous.
PacketRef[] buffer = new PacketRef[256];
int n = reader.ReadPackets(startId, buffer, out PacketIdLayout layout);

// 3. Listener-bound, optionally filtered.
bool read = reader.TryReadPackets(
    listenerId,
    startId,
    buffer,
    PacketReadMode.Matching,
    out int count,
    out PacketIdLayout layout,
    out FilterError? filterFailure,
    out SessionFailure? sessionFailure);

Matching scans from startId to the current PacketCount and keeps only what the filter accepts, so layout becomes Gapped as soon as an id in the range is skipped. A listener without a filter, or one whose filter is always-match, takes the unfiltered fast path and does no per-packet work. Otherwise the filter's presence-index candidate set prunes the range first, and only the survivors are evaluated.

TryReadPackets returns false with count == 0 and filterFailure set when the filter refuses to produce a verdict: it is poisoned by an earlier failure, a packet failed to evaluate, or the filter could not be re-bound after a stack swap. All reads keep working in every one of those cases. An unknown ListenerId makes TryReadPackets return false with sessionFailure.Code == SessionErrorCode.ListenerNotFound; disabled queries (restart, shutdown) return false with SessionErrorCode.QueriesDisabled.

The Matching scan runs up to the current PacketCount, which can be past the window of the current OnNewPackets call. Keep a watermark (last returned id + 1) and start the next read at max(watermark, fromIndex); see SESSION_GUIDE.md section 7.3.

Filtering never affects notifications: OnNewPackets always reports the raw, unfiltered id window.

Restart (Stack Swap)

Restart(stackFactory) replaces the protocol stack without stopping running sources:

  • Source threads are gated on a parse gate while existing frames are re-parsed in PacketId order (0 … N−1).
  • The factory receives the session's internal FrameInterfaceRegistry; the returned stack must use the same registry instance.
  • Listeners receive OnStackChanged followed by OnNewPackets with the cursor reset to 0.
  • Every listener filter is re-bound to the new stack via TryDerive before pulls are re-enabled, yielding a fresh instance with empty flank state, an empty match cache, and no poison. A filter that cannot be re-bound — for example because the new stack no longer defines a referenced field — is dropped, and that listener's Matching pulls report the bind error instead of silently returning everything.
session.Restart(registry =>
{
    StackBuilder builder = new(newSettings, registry);
    builder.RegisterStandardProtocols();
    return builder.Build();
});
  • The ingest value cache and the packet index of the new stack are built before anything changes. A field of SessionOptions.ValueCache that the new stack lacks throws ValueCacheUnknownField and leaves the session exactly as it was (same stack, same phase, queries enabled).
  • A source thread that passed the parse gate just before the restart closed it parks again under the parse lock; it never allocates an id from the reset counter, so no frame is lost.
  • Restart blocks until every frame is re-parsed; run it off a UI thread. A frame that cannot be re-read throws FrameUnavailable and leaves queries disabled (fail closed).

TryUnsubscribe vs Shutdown

Operation Source job Listener job User job
TryUnsubscribe(job) Cancels read loop; source stays for random access until Shutdown Cancels slot, calls OnUnsubscribed, removes from registry Cancels via CancellationToken
Shutdown() Cancels all sources, waits, disposes everything Cancels all listeners, sets SessionEnded status Cancels all jobs

Convenience APIs: FrameSourceInfo.Stop() and ListenerInfo.Unsubscribe() delegate to TryUnsubscribe.

TryUnsubscribe returns false with a SessionFailure for foreign jobs, terminal jobs, or when the session is shutting down. A source that has not started can be removed during Idle. Do not unsubscribe a listener from its own callback: the call waits for that listener's thread.

TryRemoveJob

Removes a terminal job (Completed, Cancelled, or Failed) from the job list. Returns false if the job is not registered or was already removed. Throws SessionException if the job is still pending or running.

Error Handling

  • Operational Try* refusals return false and a SessionFailure. Validation faults (null arguments, empty UI names, a disposed session, exhausted ids) still throw SessionException.
  • QueriesEnabled is false while restart rewrites packets, after a restart fails closed, and after shutdown disables reads.
  • Shutdown() throws AggregateException when cleanup (dispose) fails for one or more items.
  • Dispose() captures shutdown failures in Session.ShutdownErrors instead of throwing (standard .NET dispose pattern).

Thread Safety

All public Session methods are thread-safe. Counters use Interlocked; phase and flags use Volatile. First-parse is serialised under a shared Monitor across source threads; re-parse of announced ids is lock-free.

Listeners and value caches may be added while the session is Idle, Running, or Stopped; they are refused while shutting down (InvalidPhase) and while packet queries are disabled (QueriesDisabled: restart in progress or failed, or after shutdown). User jobs may be added in every phase until Shutdown starts. A listener or value cache added after packets exist is notified for the current id range. Frame sources stay Idle only.

Shutdown waits for a running restart; later Shutdown callers wait for the first one. Both waits are kernel waits.

Each listener filter is single-threaded. TryReadPackets in Matching mode and restart rebind share that listener's filter gate. All reads do not take the gate. Do not call TryIsMatch on the session's filter from outside TryReadPackets.

Dependencies

  • NetworkInspector.Core — stack, parsing, PacketIndex
  • NetworkInspector.Filter — per-listener filters (FILTER_GUIDE.md)
  • NetworkInspector.Sources — IFrameSource implementations
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.17.0 37 10/17/2026
0.16.0 43 10/17/2026
0.15.0 86 10/9/2026
0.14.0 78 10/8/2026
0.13.0 93 9/27/2026
0.12.0 110 9/17/2026
0.11.0 101 9/10/2026
0.10.0 111 9/8/2026
0.9.0 109 9/2/2026
0.8.0 115 8/28/2026
0.7.0 120 8/27/2026
0.6.0 118 8/21/2026
0.5.0 121 7/11/2026