/* ========================================================================
* Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Opc.Ua.Bindings.Pcap.Audit;
using Opc.Ua.Bindings.Pcap.Bindings;
using Opc.Ua.Bindings.Pcap.Capture;
using Opc.Ua.Bindings.Pcap.Capture.Sources;
using Opc.Ua.Bindings.Pcap.Formats;
using Opc.Ua.Bindings.Pcap.KeyLog;
using Opc.Ua.Bindings.Pcap.Replay;
namespace Opc.Ua.Bindings.Pcap.DependencyInjection
{
///
/// Dependency injection registration helpers for the OPC UA
/// Bindings.Pcap package.
///
public static class PcapServiceCollectionExtensions
{
///
/// Registers the default Pcap capture services AND installs the
/// Pcap transport channel binding into
/// .
/// After this call every OPC UA client channel created through
/// uses a capture-aware
/// socket; the actual recording is gated by the
/// and turned on or off by
/// a .
///
public static IServiceCollection AddOpcUaBindingsPcap(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
return services.AddOpcUaBindingsPcap(static _ => { });
}
///
/// Registers the Pcap capture services with caller-supplied
/// configuration.
///
public static IServiceCollection AddOpcUaBindingsPcap(
this IServiceCollection services,
Action configure)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configure);
var options = new PcapOptions();
configure(options);
// Shared registry: the Pcap binding installed below and every
// CaptureSessionManager created via DI talk to the SAME
// ChannelCaptureRegistry instance, so the
// CapturingMessageSocket's single volatile read of
// CurrentObserver picks up StartAsync / StopAsync writes
// without any further coordination.
var registry = new ChannelCaptureRegistry();
Opc.Ua.Bindings.Pcap.Bindings.PcapBindings.Install(registry);
services.AddSingleton(options);
services.AddSingleton();
if (options.EnableTamperEvidentAudit)
{
byte[]? tamperEvidentAuditHmacKey = options.TamperEvidentAuditHmacKey;
if (tamperEvidentAuditHmacKey is null || tamperEvidentAuditHmacKey.Length != 32)
{
throw new InvalidOperationException(
"PcapOptions.EnableTamperEvidentAudit is true but " +
"TamperEvidentAuditHmacKey is missing or not 32 bytes.");
}
string auditPath = options.TamperEvidentAuditFilePath
?? Path.Combine(options.BaseFolder, "audit.jsonl");
string? auditDirectory = Path.GetDirectoryName(auditPath);
if (!string.IsNullOrEmpty(auditDirectory))
{
Directory.CreateDirectory(auditDirectory);
}
services.AddSingleton(sp =>
new HashChainedAuditFileSink(
auditPath,
tamperEvidentAuditHmacKey,
sp.GetService>()));
}
services.TryAddSingleton();
services.AddSingleton(registry);
services.AddSingleton(provider =>
new DefaultCaptureSourceFactory(
provider.GetRequiredService()));
services.AddSingleton(provider =>
{
PcapOptions configuredOptions = provider.GetRequiredService();
ICaptureSourceFactory sourceFactory = provider.GetRequiredService();
ILoggerFactory loggerFactory = provider.GetService() ?? NullLoggerFactory.Instance;
return new CaptureSessionManager(
sourceFactory,
configuredOptions.BaseFolder,
loggerFactory,
configuredOptions.MaxActiveSessions,
provider.GetService(),
provider.GetService());
});
return services;
}
///
/// Adds trace formatters to dependency injection as a singleton
/// registry.
///
public static IServiceCollection AddOpcUaBindingsPcapFormatters(
this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
services.AddSingleton(TraceFormatterRegistry.CreateDefault());
return services;
}
///
/// Registers pcap replay session services.
///
public static IServiceCollection AddOpcUaBindingsPcapReplay(
this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
services.AddSingleton();
return services;
}
}
///
/// Options for OPC UA Pcap binding service registration.
///
public sealed class PcapOptions
{
///
/// Base directory where capture session folders are created.
///
///
/// Defaults to a per-user directory under
/// :
/// on Linux this resolves to
/// ~/.local/share/OPCFoundation/opcua-pcap, on Windows
/// %LOCALAPPDATA%\OPCFoundation\opcua-pcap. The previous
/// default () placed artifacts in
/// a shared, world-readable directory on multi-tenant Unix hosts.
///
public string BaseFolder { get; set; } =
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"OPCFoundation",
"opcua-pcap");
///
/// Maximum intended number of concurrent active sessions. Passed
/// to the constructor and
/// enforced by .
///
public int MaxActiveSessions { get; set; } = CaptureSessionManager.DefaultMaxActiveSessions;
///
/// Maximum bytes a single pcap capture file may grow to before
/// the writer rotates to a new file with a numeric suffix
/// (e.g. capture.pcap → capture.001.pcap). Defaults
/// to 256 MB. Set to 0 to disable rotation (unbounded
/// growth — legacy behavior).
///
public long MaxBytesPerCapture { get; set; } = 256L * 1024 * 1024;
///
/// Maximum bytes a single keylog file may grow to before the
/// writer rotates. Defaults to 8 MB.
///
public long MaxBytesPerKeylog { get; set; } = 8L * 1024 * 1024;
///
/// Maximum number of rotated artifact files retained per session.
/// When exceeded, the oldest rotated file is deleted. The currently-
/// active writer file is always retained regardless of count.
/// Defaults to 16. Set to 0 to disable pruning.
///
public int MaxArtifactsPerSession { get; set; } = 16;
///
/// Enables the replay_pcap tool's mock-client mode that
/// connects to a real OPC UA endpoint and emits frames from a
/// recorded capture. Off by default because this can be used as
/// a relay-attack vector if the recorded capture contains
/// authentication payloads. Combined with
/// for endpoint scoping.
///
public bool AllowMockClientReplay { get; set; }
///
/// Allow-list of endpoint hostnames against which the
/// replay_pcap mock-client mode is permitted to connect.
/// An empty list (default) blocks all endpoints even when
/// is true. Each entry is
/// matched against using
/// .
///
public IReadOnlyList AllowedReplayEndpoints { get; set; } =
Array.Empty();
///
///
/// Enables the registration of
/// as the audit-event destination. The sink writes a JSONL ledger with
/// per-line HMAC chaining so tampering is detectable. Off by default
/// because the chain HMAC key requires operator setup. Set
/// and
/// in the same options block.
///
///
public bool EnableTamperEvidentAudit { get; set; }
///
///
/// File path for the tamper-evident audit ledger when
/// is true. Defaults to
/// audit.jsonl under .
///
///
public string? TamperEvidentAuditFilePath { get; set; }
///
///
/// 32-byte HMAC key for the tamper-evident audit chain. Generate
/// with System.Security.Cryptography.RandomNumberGenerator.GetBytes(32)
/// and store securely (e.g., environment variable, Key Vault).
/// REQUIRED when is true.
///
///
public byte[]? TamperEvidentAuditHmacKey { get; set; }
///
/// Enables the registration of MCP diagnostic tools that
/// disclose symmetric channel keys
/// (dump_keys, decode_pcap_with_keys) or perform
/// privileged replay operations (replay_pcap). These tools
/// are off by default; an operator must explicitly opt in
/// because they expose the keys that protect live OPC UA traffic.
///
///
/// Setting this to true must be paired with at least one
/// of the following controls applied at the MCP transport layer:
/// authentication (Bearer token or mutual TLS) and audit logging.
/// See Docs/PacketCapture.md for the full security model.
///
public bool EnableDiagnosticsTools { get; set; }
}
}