fix(opcua): don't register with a discovery server unless configured (#876)
v2-ci / unit-tests (push) Has been cancelled
v2-ci / unit-tests (push) Has been cancelled
OpcUaApplicationHost left ServerConfiguration.MaxRegistrationInterval at the SDK default (30000) with a null RegistrationEndpoint, so SDK 2.0.1's StandardServer armed its registration timer against opc.tcp://localhost:4840, which on an OtOpcUa node is the server itself (no LDS). It answered BadServiceUnsupported and the SDK retried forever, visible since #874. Registration is now opt-in: ApplyDiscoveryRegistration sets MaxRegistrationInterval = 0 (the value that keeps StandardServer from arming the timer) unless OpcUa:DiscoveryServerUrl is set, in which case the server registers against that URL every OpcUa:DiscoveryRegistrationIntervalMs (default 30000) over SignAndEncrypt/Basic256Sha256, like the SDK's own default endpoint. The validator requires an absolute opc.tcp:// URL and a positive interval when the URL is set. Documented in Configuration.md and OpcUaServer.md. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+12
@@ -30,6 +30,18 @@ public sealed class OpcUaApplicationHostOptionsValidator : OptionsValidatorBase<
|
||||
// directly. ToList() bridges to the shared primitive while preserving the count (and message).
|
||||
builder.MinCount(o.EnabledSecurityProfiles?.ToList(), 1, "OpcUa:EnabledSecurityProfiles");
|
||||
|
||||
// #876: discovery registration is opt-in; when configured, the URL must be an absolute opc.tcp URI and the
|
||||
// interval positive (0 would silently disable the registration that was asked for).
|
||||
if (!string.IsNullOrWhiteSpace(o.DiscoveryServerUrl))
|
||||
{
|
||||
builder.RequireThat(
|
||||
Uri.TryCreate(o.DiscoveryServerUrl.Trim(), UriKind.Absolute, out var discoveryUri)
|
||||
&& discoveryUri.Scheme == "opc.tcp",
|
||||
"OpcUa:DiscoveryServerUrl must be an absolute opc.tcp:// URL (or empty to disable discovery registration)");
|
||||
builder.RequireThat(o.DiscoveryRegistrationIntervalMs > 0,
|
||||
"OpcUa:DiscoveryRegistrationIntervalMs must be greater than 0 while OpcUa:DiscoveryServerUrl is set");
|
||||
}
|
||||
|
||||
// #833 review: an enabled credential lockout needs positive durations — a zero LockoutSeconds would lock
|
||||
// nothing, a zero FailureWindowSeconds would never accumulate. MaxFailedAttempts <= 0 disables it.
|
||||
var lockout = o.CredentialLockout;
|
||||
|
||||
@@ -107,6 +107,25 @@ public sealed class OpcUaApplicationHostOptions
|
||||
/// </summary>
|
||||
public IList<string> PeerApplicationUris { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// URL of an OPC UA discovery server (LDS / GDS, e.g. <c>opc.tcp://lds-host:4840</c>) this server
|
||||
/// registers with via <c>RegisterServer2</c> (<c>OpcUa:DiscoveryServerUrl</c>, lmxopcua#876).
|
||||
/// <see langword="null"/> or blank (the default) disables discovery registration entirely.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The SDK's own default is to register every 30 s with an LDS at <c>opc.tcp://localhost:4840</c>, which on
|
||||
/// an OtOpcUa node is this server itself (no LDS) — it answered <c>BadServiceUnsupported</c> and the SDK
|
||||
/// retried forever. Registration is therefore opt-in.
|
||||
/// </remarks>
|
||||
public string? DiscoveryServerUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum interval in milliseconds between <c>RegisterServer2</c> calls while
|
||||
/// <see cref="DiscoveryServerUrl"/> is set (<c>OpcUa:DiscoveryRegistrationIntervalMs</c>; default 30000, the
|
||||
/// SDK's own default). Ignored when no discovery server is configured.
|
||||
/// </summary>
|
||||
public int DiscoveryRegistrationIntervalMs { get; set; } = 30_000;
|
||||
|
||||
/// <summary>
|
||||
/// SDK request-processing and transport quotas (<c>OpcUa:Quotas:*</c>, review 01 P6). The defaults are
|
||||
/// exactly the values that used to be hard-coded (5 / 100 / 200 request threads and queue) and the SDK's
|
||||
@@ -614,6 +633,7 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable
|
||||
};
|
||||
ApplyRequestQuotas(serverConfig, _options.Quotas);
|
||||
DisableSdkAuthenticationLockout(serverConfig);
|
||||
ApplyDiscoveryRegistration(serverConfig, _options);
|
||||
|
||||
serverConfig.SecurityPolicies = BuildSecurityPolicies(_options.EnabledSecurityProfiles).ToArray();
|
||||
serverConfig.UserTokenPolicies = BuildUserTokenPolicies(_options.AllowAnonymous).ToArray();
|
||||
@@ -658,6 +678,36 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable
|
||||
internal static void DisableSdkAuthenticationLockout(ServerConfiguration serverConfig) =>
|
||||
serverConfig.MaxFailedAuthenticationAttempts = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Configures discovery-server registration (lmxopcua#876). Without <see cref="OpcUaApplicationHostOptions.DiscoveryServerUrl"/>
|
||||
/// registration is OFF: <c>MaxRegistrationInterval = 0</c> is what makes the SDK (2.0.1
|
||||
/// <c>StandardServer.StartApplicationAsync</c>) skip its registration timer, and the endpoint stays null.
|
||||
/// With a URL, the server registers against it every
|
||||
/// <see cref="OpcUaApplicationHostOptions.DiscoveryRegistrationIntervalMs"/>; the endpoint mirrors the SDK's own
|
||||
/// default (SignAndEncrypt / Basic256Sha256, refreshed from the discovery server before connecting).
|
||||
/// </summary>
|
||||
/// <param name="serverConfig">The configuration to set.</param>
|
||||
/// <param name="options">The host options carrying the discovery settings.</param>
|
||||
internal static void ApplyDiscoveryRegistration(ServerConfiguration serverConfig, OpcUaApplicationHostOptions options)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(options.DiscoveryServerUrl))
|
||||
{
|
||||
serverConfig.MaxRegistrationInterval = 0;
|
||||
serverConfig.RegistrationEndpoint = null;
|
||||
return;
|
||||
}
|
||||
|
||||
serverConfig.MaxRegistrationInterval = options.DiscoveryRegistrationIntervalMs;
|
||||
var endpoint = new EndpointDescription
|
||||
{
|
||||
EndpointUrl = options.DiscoveryServerUrl.Trim(),
|
||||
SecurityMode = MessageSecurityMode.SignAndEncrypt,
|
||||
SecurityPolicyUri = SecurityPolicies.Basic256Sha256,
|
||||
};
|
||||
endpoint.Server.ApplicationType = ApplicationType.DiscoveryServer;
|
||||
serverConfig.RegistrationEndpoint = endpoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the request-processing quotas (<c>OpcUa:Quotas:*</c>, review 01 P6 — they were hard-coded at
|
||||
/// 5 / 100 / 200) to the SDK server configuration.
|
||||
|
||||
Reference in New Issue
Block a user