/* ======================================================================== * 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; } } }