mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-29 07:07:24 +08:00
Hey everyone! 👋 Just wanted to contribute! I learned about the OPC Foundation during my last internship and got introduced to open source coding, so here we are! I noticed issue #4863 while exploring the codebase and wanted to help address it. --- # Description ## Problem In `ReverseConnectServer.OnReverseConnect` (`src/Opc.Ua.Server/Server/ReverseConnectServer.cs`), reverse connection admission was evaluated using a combination of `MaxSessionCount` and `ServerInternal.SessionManager.GetSessions().Count`: ```csharp if (reverseConnection.Enabled && ( reverseConnection.MaxSessionCount == 0 || ( reverseConnection.MaxSessionCount == 1 && reverseConnection.LastState == ReverseConnectState.Closed ) || reverseConnection.MaxSessionCount > ServerInternal.SessionManager .GetSessions() .Count)) ``` As reported in #4863, this logic causes several issues: 1. **Redundant connection attempts:** When `MaxSessionCount == 0` (the default if omitted or unconfigured), `MaxSessionCount == 0` is always true. Every 15-second timer tick dispatches another `CreateConnection` call, even if the connection is already `Connected` or `Connecting`. 2. **Unrelated server sessions block reverse connect:** When `MaxSessionCount >= 2`, `ServerInternal.SessionManager.GetSessions().Count` queries the global session count across all endpoints (including local/direct OPC UA clients). Once direct sessions reach `MaxSessionCount`, reverse connections are permanently prevented from establishing even when in state `Closed`. 3. **Premature connections under `MaxSessionCount == 1`:** If a reverse connection is established (`Connected`) but the client has not yet activated an OPC UA session (`GetSessions().Count == 0`), `1 > 0` evaluates to true, triggering duplicate connection attempts. 4. **Stuck `Errored` state:** If `CreateConnection` throws an exception, `LastState` is set to `Errored`. If other sessions exist on the server, `1 > 1` is false, leaving the connection permanently stuck in `Errored` without retrying. --- ## Code Explanation Following maintainer guidance on #4863 to remove session-count coupling from connection admission: In `src/Opc.Ua.Server/Server/ReverseConnectServer.cs`: ```csharp // try the reverse connect if (reverseConnection.Enabled && (reverseConnection.LastState == ReverseConnectState.Closed || reverseConnection.LastState == ReverseConnectState.Errored)) ``` - **Connection state drives admission:** A reverse connection is only initiated if it is enabled and currently `Closed` (needs connection) or `Errored` (retrying after a previous failure). - **Prevents duplicate sockets:** When already `Connected` or `Connecting`, no redundant connection attempts are dispatched. - **Respects reject backoff:** When in state `Rejected`, the connection remains idle until `m_rejectTimeout` elapses and recharges the state to `Closed`. - **Decoupled from global sessions:** Unrelated client sessions on the server no longer interfere with reverse connection establishment. - **Zero API breakage:** Kept changes minimal and surgical—no public signatures were changed, ensuring full backward compatibility. --- ## Commits Breakdown The changes were structured into two focused commits: 1. **`31fc2fd95` — `test(server): add regression tests for reverse connect session accounting (#4863)`** - Adds unit regression tests in `tests/Opc.Ua.Server.Tests/ReverseConnectServerSessionAccountingTests.cs` covering all edge cases (`MaxSessionCount == 0`, `MaxSessionCount == 1`, `MaxSessionCount >= 2`, `Errored` retry, reject timeout expiry, and disabled connections) using `FakeTimeProvider`. 2. **`b820dbfed` — `fix(server): decouple reverse connect connection admission from global sessions (#4863)`** - Applies the surgical fix to `ReverseConnectServer.OnReverseConnect` to evaluate `Closed` and `Errored` states directly. --- ## Testing & Verification - **New regression suite:** All 7 tests in `ReverseConnectServerSessionAccountingTests` pass on `net10.0` (98 ms). - **Existing suite:** All 43 tests in category `ReverseConnect` pass on `net10.0` (12 s). - **Analyzers:** Zero compiler warnings and zero Roslynator analyzer warnings (`TreatWarningsAsErrors=true`). --- ## Related Issues Fixes #4863 --- ## Checklist - [] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [x] I ran the affected tests locally against .NET 10 and all passed. - [] I have addressed all PR feedback received. --------- Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> Co-authored-by: Marc <marcschier@hotmail.com>
144 lines
6.2 KiB
XML
144 lines
6.2 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<ApplicationConfiguration
|
|
xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd"
|
|
xmlns:ua="http://opcfoundation.org/UA/2008/02/Types.xsd">
|
|
<ApplicationName>Full Server Test</ApplicationName>
|
|
<ApplicationUri>urn:localhost:fullservertest</ApplicationUri>
|
|
<ProductUri>http://test.org/fullserver</ProductUri>
|
|
<ApplicationType>Server_0</ApplicationType>
|
|
<SecurityConfiguration>
|
|
<ApplicationCertificate>
|
|
<StoreType>Directory</StoreType>
|
|
<StorePath>%LocalApplicationData%/OPC/fullserver</StorePath>
|
|
<SubjectName>CN=FullServer, O=Test</SubjectName>
|
|
</ApplicationCertificate>
|
|
<TrustedIssuerCertificates>
|
|
<StoreType>Directory</StoreType>
|
|
<StorePath>%LocalApplicationData%/OPC/issuers</StorePath>
|
|
</TrustedIssuerCertificates>
|
|
<TrustedPeerCertificates>
|
|
<StoreType>Directory</StoreType>
|
|
<StorePath>%LocalApplicationData%/OPC/trusted</StorePath>
|
|
</TrustedPeerCertificates>
|
|
<NonceLength>32</NonceLength>
|
|
<RejectSHA1SignedCertificates>true</RejectSHA1SignedCertificates>
|
|
<MinimumCertificateKeySize>2048</MinimumCertificateKeySize>
|
|
</SecurityConfiguration>
|
|
<ServerConfiguration>
|
|
<BaseAddresses>
|
|
<ua:String>opc.tcp://localhost:4840</ua:String>
|
|
<ua:String>https://localhost:4843</ua:String>
|
|
</BaseAddresses>
|
|
<AlternateBaseAddresses>
|
|
<ua:String>opc.tcp://myhost:4840</ua:String>
|
|
</AlternateBaseAddresses>
|
|
<SecurityPolicies>
|
|
<ServerSecurityPolicy>
|
|
<SecurityMode>SignAndEncrypt_3</SecurityMode>
|
|
<SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256</SecurityPolicyUri>
|
|
</ServerSecurityPolicy>
|
|
<ServerSecurityPolicy>
|
|
<SecurityMode>Sign_2</SecurityMode>
|
|
<SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Aes128_Sha256_RsaOaep</SecurityPolicyUri>
|
|
</ServerSecurityPolicy>
|
|
</SecurityPolicies>
|
|
<MinRequestThreadCount>5</MinRequestThreadCount>
|
|
<MaxRequestThreadCount>50</MaxRequestThreadCount>
|
|
<MaxQueuedRequestCount>500</MaxQueuedRequestCount>
|
|
<UserTokenPolicies>
|
|
<ua:UserTokenPolicy>
|
|
<ua:TokenType>Anonymous_0</ua:TokenType>
|
|
</ua:UserTokenPolicy>
|
|
<ua:UserTokenPolicy>
|
|
<ua:TokenType>UserName_1</ua:TokenType>
|
|
</ua:UserTokenPolicy>
|
|
</UserTokenPolicies>
|
|
<DiagnosticsEnabled>true</DiagnosticsEnabled>
|
|
<MaxSessionCount>200</MaxSessionCount>
|
|
<MaxChannelCount>500</MaxChannelCount>
|
|
<MinSessionTimeout>5000</MinSessionTimeout>
|
|
<MaxSessionTimeout>1800000</MaxSessionTimeout>
|
|
<MaxBrowseContinuationPoints>20</MaxBrowseContinuationPoints>
|
|
<MaxQueryContinuationPoints>15</MaxQueryContinuationPoints>
|
|
<MaxHistoryContinuationPoints>50</MaxHistoryContinuationPoints>
|
|
<MaxRequestAge>300000</MaxRequestAge>
|
|
<MinPublishingInterval>50</MinPublishingInterval>
|
|
<MaxPublishingInterval>1800000</MaxPublishingInterval>
|
|
<PublishingResolution>50</PublishingResolution>
|
|
<MaxSubscriptionLifetime>1800000</MaxSubscriptionLifetime>
|
|
<MaxMessageQueueSize>20</MaxMessageQueueSize>
|
|
<AvailableSamplingRates>
|
|
<SamplingRateGroup>
|
|
<Start>5</Start>
|
|
<Increment>5</Increment>
|
|
<Count>20</Count>
|
|
</SamplingRateGroup>
|
|
<SamplingRateGroup>
|
|
<Start>100</Start>
|
|
<Increment>100</Increment>
|
|
<Count>4</Count>
|
|
</SamplingRateGroup>
|
|
</AvailableSamplingRates>
|
|
<MaxNotificationQueueSize>200</MaxNotificationQueueSize>
|
|
<MaxNotificationsPerPublish>500</MaxNotificationsPerPublish>
|
|
<MinSupportedSamplingInterval>2500</MinSupportedSamplingInterval>
|
|
<MaxRegistrationInterval>60000</MaxRegistrationInterval>
|
|
<MinSubscriptionLifetime>5000</MinSubscriptionLifetime>
|
|
<MaxPublishRequestCount>50</MaxPublishRequestCount>
|
|
<MaxSubscriptionCount>200</MaxSubscriptionCount>
|
|
<MaxEventQueueSize>5000</MaxEventQueueSize>
|
|
<ServerProfileArray>
|
|
<ua:String>http://opcfoundation.org/UA-Profile/Server/StandardUA2017</ua:String>
|
|
<ua:String>http://opcfoundation.org/UA-Profile/Server/DataAccess</ua:String>
|
|
</ServerProfileArray>
|
|
<ShutdownDelay>10</ShutdownDelay>
|
|
<ServerCapabilities>
|
|
<ua:String>DA</ua:String>
|
|
<ua:String>HD</ua:String>
|
|
</ServerCapabilities>
|
|
<SupportedPrivateKeyFormats>
|
|
<ua:String>PFX</ua:String>
|
|
<ua:String>PEM</ua:String>
|
|
</SupportedPrivateKeyFormats>
|
|
<MaxTrustListSize>65536</MaxTrustListSize>
|
|
<MultiCastDnsEnabled>true</MultiCastDnsEnabled>
|
|
<ReverseConnect>
|
|
<Clients>
|
|
<ReverseConnectClient>
|
|
<EndpointUrl>opc.tcp://client1:65300</EndpointUrl>
|
|
<Timeout>5000</Timeout>
|
|
<Enabled>true</Enabled>
|
|
</ReverseConnectClient>
|
|
<ReverseConnectClient>
|
|
<EndpointUrl>opc.tcp://client2:65301</EndpointUrl>
|
|
<Timeout>10000</Timeout>
|
|
<Enabled>false</Enabled>
|
|
</ReverseConnectClient>
|
|
</Clients>
|
|
<ConnectInterval>20000</ConnectInterval>
|
|
<ConnectTimeout>45000</ConnectTimeout>
|
|
<RejectTimeout>90000</RejectTimeout>
|
|
</ReverseConnect>
|
|
<OperationLimits>
|
|
<MaxNodesPerRead>1000</MaxNodesPerRead>
|
|
<MaxNodesPerHistoryReadData>500</MaxNodesPerHistoryReadData>
|
|
<MaxNodesPerHistoryReadEvents>500</MaxNodesPerHistoryReadEvents>
|
|
<MaxNodesPerWrite>1000</MaxNodesPerWrite>
|
|
<MaxNodesPerHistoryUpdateData>200</MaxNodesPerHistoryUpdateData>
|
|
<MaxNodesPerHistoryUpdateEvents>200</MaxNodesPerHistoryUpdateEvents>
|
|
<MaxNodesPerMethodCall>100</MaxNodesPerMethodCall>
|
|
<MaxNodesPerBrowse>2000</MaxNodesPerBrowse>
|
|
<MaxNodesPerRegisterNodes>1500</MaxNodesPerRegisterNodes>
|
|
<MaxNodesPerTranslateBrowsePathsToNodeIds>1500</MaxNodesPerTranslateBrowsePathsToNodeIds>
|
|
<MaxNodesPerNodeManagement>500</MaxNodesPerNodeManagement>
|
|
<MaxMonitoredItemsPerCall>3000</MaxMonitoredItemsPerCall>
|
|
</OperationLimits>
|
|
<AuditingEnabled>true</AuditingEnabled>
|
|
<HttpsMutualTls>true</HttpsMutualTls>
|
|
<DurableSubscriptionsEnabled>true</DurableSubscriptionsEnabled>
|
|
<MaxDurableNotificationQueueSize>100000</MaxDurableNotificationQueueSize>
|
|
<MaxDurableEventQueueSize>50000</MaxDurableEventQueueSize>
|
|
<MaxDurableSubscriptionLifetimeInHours>24</MaxDurableSubscriptionLifetimeInHours>
|
|
</ServerConfiguration>
|
|
</ApplicationConfiguration>
|