Reduce core dependencies, move https binding to optional assembly. (#1157)

* implement transport binding factory
* split https transport into seperate nuget package
* new Opc.Ua.Binding.Https assembly
* Https binding is loaded on demand, if available
* reduce core dependencies to kestrel and others
fixes #600
This commit is contained in:
Martin Regen
2020-10-21 00:03:37 +02:00
committed by GitHub
parent 3514e4d5fe
commit 62eedc19be
43 changed files with 1386 additions and 827 deletions
@@ -13,7 +13,7 @@
namespace Opc.Ua.Bindings
{
/// <summary>
/// The <b>Opc.Ua.Bindings</b> namespace contains classes that implement the WCF bindings for the
/// The <b>Opc.Ua.Bindings</b> namespace contains classes that implement the bindings for the
/// mappings described in Part 6 of the UA specification. It also includes an implementation for
/// the UA TCP protocol.
/// </summary>
-17
View File
@@ -16,10 +16,6 @@
<PackageId>$(PackageId).Debug</PackageId>
</PropertyGroup>
<PropertyGroup>
<DefineConstants Condition=" '$(NoHttps)' == 'true' ">$(DefineConstants);NO_HTTPS</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(SignAssembly)' == 'true'">
<DefineConstants>$(DefineConstants);SIGNASSEMBLY</DefineConstants>
</PropertyGroup>
@@ -47,33 +43,20 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
<Reference Include="System.IdentityModel" />
<Reference Include="System.IdentityModel.Selectors" />
<Reference Include="System.Security" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Condition="$(DefineConstants.Contains('NO_HTTPS'))=='false'">
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" Version="2.2.1" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="2.2.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Data.Common" Version="4.3.0" />
<PackageReference Include="System.ServiceModel.Primitives" Version="4.5.3" />
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.4.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
<PackageReference Include="System.Data.Common" Version="4.3.0" />
<PackageReference Include="System.ServiceModel.Primitives" Version="4.7.0" />
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
<ItemGroup>
@@ -102,7 +102,12 @@ namespace Opc.Ua
/// Communicates with UA TCP, UA Security and UA Binary.
/// </summary>
public const string UaTcpTransport = "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary";
/// <summary>
/// Communicates with UA TCP over secure Websockets, UA Security and UA Binary.
/// </summary>
public const string UaWssTransport = "http://opcfoundation.org/UA-Profile/Transport/uawss-uasc-uabinary";
/// <summary>
/// Communicates with UA Binary over HTTPS.
/// </summary>
@@ -10,14 +10,12 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System.ServiceModel.Channels;
namespace Opc.Ua.Bindings
{
/// <summary>
/// The binding for the UA native stack
/// </summary>
public abstract class BaseBinding : Binding
public abstract class BaseBinding
{
#region Constructors
/// <summary>
@@ -0,0 +1,118 @@
/* Copyright (c) 1996-2020 The OPC Foundation. All rights reserved.
The source code in this file is covered under a dual-license scenario:
- RCL: for OPC Foundation members in good-standing
- GPL V2: everybody else
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
GNU General Public License as published by the Free Software Foundation;
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace Opc.Ua.Bindings
{
/// <summary>
/// This is an interface to the scheme of a transport binding.
/// </summary>
public interface ITransportBindingScheme
{
/// <summary>
/// The protocol scheme supported by the binding.
/// </summary>
string UriScheme { get; }
}
/// <summary>
/// This is an interface to the factory of a transport binding.
/// </summary>
public interface ITransportBindingFactory<T>
: ITransportBindingScheme
{
/// <summary>
/// The factory to create a new transport.
/// </summary>
/// <returns>The transport.</returns>
T Create();
}
/// <summary>
/// The interface to manage transport bindings.
/// </summary>
public interface ITransportBindings<T>
{
/// <summary>
/// Get a transport binding for a uri scheme.
/// </summary>
/// <param name="uriScheme">The uri scheme.</param>
T GetBinding(string uriScheme);
/// <summary>
/// Return if there is a transport listener for a uri scheme.
/// </summary>
/// <param name="uriScheme">The uri scheme.</param>
bool HasBinding(string uriScheme);
/// <summary>
/// Set the transport factory to the binding.
/// Overrides other bindings with the same uri scheme.
/// </summary>
void SetBinding(T binding);
/// <summary>
/// Add all bindings with interface <see cref="T"/> exported from a assembly.
/// </summary>
/// <param name="assembly">The assembly with the bindings.</param>
IEnumerable<Type> AddBindings(Assembly assembly);
/// <summary>
/// Add all bindings with interface <see cref="T"/> from a list.
/// </summary>
/// <param name="bindings">The array of binding types with interface <see cref="T"/>.</param>
IEnumerable<Type> AddBindings(IEnumerable<Type> bindings);
}
/// <summary>
/// This is the transport listener factory interface for a binding (server).
/// </summary>
public interface ITransportListenerFactory :
ITransportBindingFactory<ITransportListener>
{
/// <summary>
/// Create the service host for a server using <see cref="ServerBase"/>
/// </summary>
/// <param name="serverBase">The server base.</param>
/// <param name="hosts">The service hosts are added to this list.</param>
/// <param name="configuration">The application configuration.</param>
/// <param name="baseAddresses">The base addreses for the service host.</param>
/// <param name="serverDescription">The server description.</param>
/// <param name="securityPolicies">The list of supported security policies.</param>
/// <param name="instanceCertificate">The server certificate.</param>
/// <param name="instanceCertificateChain">The cert cahin of the server certificate.</param>
List<EndpointDescription> CreateServiceHost(
ServerBase serverBase,
IDictionary<string, Task> hosts,
ApplicationConfiguration configuration,
IList<string> baseAddresses,
ApplicationDescription serverDescription,
List<ServerSecurityPolicy> securityPolicies,
X509Certificate2 instanceCertificate,
X509Certificate2Collection instanceCertificateChain
);
}
/// <summary>
/// This is the transport channel factory interface for a binding (client).
/// </summary>
public interface ITransportChannelFactory :
ITransportBindingFactory<ITransportChannel>
{
}
}
@@ -0,0 +1,88 @@
/* Copyright (c) 1996-2020 The OPC Foundation. All rights reserved.
The source code in this file is covered under a dual-license scenario:
- RCL: for OPC Foundation members in good-standing
- GPL V2: everybody else
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
GNU General Public License as published by the Free Software Foundation;
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System;
namespace Opc.Ua.Bindings
{
/// <summary>
/// The transpaort bindings available for teh stack.
/// </summary>
public static class TransportBindings
{
static TransportBindings()
{
Channels = new TransportChannelBindings(new Type[] { typeof(TcpTransportChannelFactory) });
Listeners = new TransportListenerBindings(new Type[] { typeof(TcpTransportListenerFactory) });
}
/// <summary>
/// The bindings for transport channels (client).
/// </summary>
public static TransportChannelBindings Channels { get; private set; }
/// <summary>
/// The bindings for transport listeners (server).
/// </summary>
public static TransportListenerBindings Listeners { get; private set; }
/// <summary>
/// The bindings for the transport channels.
/// </summary>
public class TransportChannelBindings :
TransportBindingsBase<ITransportChannelFactory>
{
/// <summary>
/// Initialize the transport listener.
/// </summary>
/// <param name="defaultBindings">List of known default bindings.</param>
public TransportChannelBindings(Type[] defaultBindings) : base(defaultBindings)
{
}
/// <summary>
/// Get a transport channel for the specified uri scheme.
/// </summary>
/// <param name="uriScheme">The uri scheme of the transport.</param>
public ITransportChannel GetChannel(string uriScheme)
{
var binding = GetBinding(uriScheme);
return binding?.Create();
}
}
/// <summary>
/// The bindings for the transport listeners.
/// </summary>
public class TransportListenerBindings :
TransportBindingsBase<ITransportListenerFactory>
{
/// <summary>
/// Initialize the transport listener.
/// </summary>
/// <param name="defaultBindings">List of known default bindings.</param>
public TransportListenerBindings(Type[] defaultBindings) : base(defaultBindings)
{
}
/// <summary>
/// Get a transport listener for the specified uri scheme.
/// </summary>
/// <param name="uriScheme">The uri scheme of the transport.</param>
public ITransportListener GetListener(string uriScheme)
{
var binding = GetBinding(uriScheme);
return binding?.Create();
}
}
}
}
@@ -0,0 +1,174 @@
/* Copyright (c) 1996-2020 The OPC Foundation. All rights reserved.
The source code in this file is covered under a dual-license scenario:
- RCL: for OPC Foundation members in good-standing
- GPL V2: everybody else
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
GNU General Public License as published by the Free Software Foundation;
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Opc.Ua.Bindings
{
/// <summary>
/// The bindings for the transport listeners.
/// </summary>
public class TransportBindingsBase<T> :
ITransportBindings<T> where T : class, ITransportBindingScheme
{
/// <summary>
/// Implement the default constructor.
/// </summary>
/// <remarks>
/// The default constructor adds all interfaces T.
/// </remarks>
protected TransportBindingsBase()
{
Bindings = new Dictionary<string, T>();
AddBindings(typeof(TransportBindingsBase<T>).Assembly);
}
/// <summary>
/// Initialize object with default list of bindings.
/// </summary>
protected TransportBindingsBase(Type[] defaultBindings)
{
Bindings = new Dictionary<string, T>();
AddBindings(defaultBindings);
}
#region Public Properties
/// <summary>
/// Dictionary of bindings of type <see cref="T"/>.
/// </summary>
protected Dictionary<string, T> Bindings { get; private set; }
#endregion
#region ITransportBindings
/// <inheritdoc/>
public T GetBinding(string uriScheme)
{
T binding;
if (!Bindings.TryGetValue(uriScheme, out binding))
{
TryAddDefaultTransportBindings(uriScheme);
if (!Bindings.TryGetValue(uriScheme, out binding))
{
return default(T);
}
}
return binding;
}
/// <inheritdoc/>
public bool HasBinding(string uriScheme)
{
T binding;
if (Bindings.TryGetValue(uriScheme, out binding))
{
return true;
}
return false;
}
/// <inheritdoc/>
public void SetBinding(T binding)
{
Bindings[binding.UriScheme] = binding;
}
/// <inheritdoc/>
public IEnumerable<Type> AddBindings(Assembly assembly)
{
var bindings = assembly.GetExportedTypes().Where(type => IsBindingType(type));
return AddBindings(bindings);
}
/// <inheritdoc/>
public IEnumerable<Type> AddBindings(IEnumerable<Type> bindings)
{
var result = new List<Type>();
foreach (Type bindingType in bindings)
{
var binding = Activator.CreateInstance(bindingType) as T;
if (binding != null)
{
Bindings[binding.UriScheme] = binding;
result.Add(bindingType);
}
}
return result;
}
#endregion
#region Private Methods
/// <summary>
/// Validate the type is a transport listener.
/// </summary>
protected static bool IsBindingType(System.Type bindingType)
{
if (bindingType == null)
{
return false;
}
var bindingTypeInfo = bindingType.GetTypeInfo();
if (bindingTypeInfo.IsAbstract ||
!typeof(T).GetTypeInfo().IsAssignableFrom(bindingTypeInfo))
{
return false;
}
var listener = Activator.CreateInstance(bindingType) as T;
if (listener == null)
{
return false;
}
return true;
}
/// <summary>
/// Try to load a binding from well known assemblies at runtime.
/// </summary>
/// <param name="scheme">The uri scheme of the binding.</param>
private bool TryAddDefaultTransportBindings(string scheme)
{
string assemblyName;
if (Utils.DefaultBindings.TryGetValue(scheme, out assemblyName))
{
Assembly assembly = null;
string fullName = Utils.DefaultOpcUaCoreAssemblyFullName.Replace(Utils.DefaultOpcUaCoreAssemblyName, assemblyName);
try
{
assembly = Assembly.Load(fullName);
}
catch
{
Utils.Trace(Utils.TraceMasks.Error, "Failed to load the assembly {0} for transport binding {1}.",
fullName, scheme
);
}
if (assembly != null)
{
var listeners = AddBindings(assembly);
return listeners.Count() > 0;
}
}
else
{
Utils.Trace(Utils.TraceMasks.Error, "The transport binding {0} is unsupported.", scheme);
}
return false;
}
#endregion
}
}
@@ -1,96 +0,0 @@
/* Copyright (c) 1996-2020 The OPC Foundation. All rights reserved.
The source code in this file is covered under a dual-license scenario:
- RCL: for OPC Foundation members in good-standing
- GPL V2: everybody else
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
GNU General Public License as published by the Free Software Foundation;
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Opc.Ua.Bindings
{
/// <summary>
/// The binding for the transport listeners.
/// </summary>
public static class TransportListenerBindings
{
static TransportListenerBindings()
{
TransportListeners = new Dictionary<string, Type>();
var listeners = typeof(TransportListenerBindings).Assembly.GetExportedTypes().Where(type => IsTransportListenerType(type));
foreach (var listenerType in listeners)
{
Add(listenerType);
}
}
#region Public Properties
/// <summary>
/// Dictionary of transported listeners.
/// </summary>
public static Dictionary<string, Type> TransportListeners { get; private set; }
/// <summary>
/// Get a transport listener for a uri scheme.
/// </summary>
/// <param name="uriScheme">The uri scheme.</param>
public static ITransportListener GetTransportListener(string uriScheme)
{
Type listenerType;
if (TransportListeners.TryGetValue(uriScheme, out listenerType))
{
return Activator.CreateInstance(listenerType) as ITransportListener;
}
return null;
}
/// <summary>
/// Add a transport listener to the binding.
/// </summary>
public static void Add(Type listenerType)
{
if (IsTransportListenerType(listenerType))
{
var listener = Activator.CreateInstance(listenerType) as ITransportListener;
TransportListeners.Add(listener.UriScheme, listenerType);
}
}
/// <summary>
/// Validate the type is a transport listener.
/// </summary>
private static bool IsTransportListenerType(System.Type systemType)
{
if (systemType == null)
{
return false;
}
var systemTypeInfo = systemType.GetTypeInfo();
if (systemTypeInfo.IsAbstract ||
!typeof(ITransportListener).GetTypeInfo().IsAssignableFrom(systemTypeInfo))
{
return false;
}
var listener = Activator.CreateInstance(systemType) as ITransportListener;
if (listener == null)
{
return false;
}
listener.Dispose();
return true;
}
#endregion
}
}
@@ -32,9 +32,13 @@ namespace Opc.Ua
{
if (url == null) throw new ArgumentNullException(nameof(url));
var listener = TransportListenerBindings.GetTransportListener(url.Scheme);
if (listener == null) throw new ArgumentException(nameof(url), "No suitable listener found.");
var listener = TransportBindings.Listeners.GetListener(url.Scheme);
if (listener == null)
{
throw ServiceResultException.Create(
StatusCodes.BadProtocolVersionUnsupported,
"Unsupported transport profile for scheme {0}\r\n", url.Scheme);
}
m_listener = listener;
Url = url;
+15 -34
View File
@@ -692,28 +692,14 @@ namespace Opc.Ua
X509Certificate2Collection clientCertificateChain,
ServiceMessageContext messageContext)
{
bool useUaTcp = description.EndpointUrl.StartsWith(Utils.UriSchemeOpcTcp);
bool useHttps = description.EndpointUrl.StartsWith(Utils.UriSchemeHttps);
// initialize the channel which will be created with the server.
ITransportChannel channel = null;
if (useUaTcp)
{
channel = new TcpTransportChannel();
}
#if !NO_HTTPS
else if (useHttps)
{
channel = new HttpsTransportChannel();
}
#endif
string uriScheme = new Uri(description.EndpointUrl).Scheme;
ITransportChannel channel = TransportBindings.Channels.GetChannel(uriScheme);
if (channel == null)
{
throw ServiceResultException.Create(
StatusCodes.BadProtocolVersionUnsupported,
"Unsupported transport profile\r\n");
"Unsupported transport profile for scheme {0}\r\n", uriScheme);
}
// create a UA channel.
@@ -780,42 +766,37 @@ namespace Opc.Ua
X509Certificate2Collection clientCertificateChain,
ServiceMessageContext messageContext)
{
bool useUaTcp = description.EndpointUrl.StartsWith(Utils.UriSchemeOpcTcp);
bool useHttps = description.EndpointUrl.StartsWith(Utils.UriSchemeHttps);
string uriScheme = new Uri(description.EndpointUrl).Scheme;
switch (description.TransportProfileUri)
{
case Profiles.UaTcpTransport:
{
useUaTcp = true;
uriScheme = Utils.UriSchemeOpcTcp;
break;
}
case Profiles.HttpsBinaryTransport:
{
useHttps = true;
uriScheme = Utils.UriSchemeHttps;
break;
}
case Profiles.UaWssTransport:
{
uriScheme = Utils.UriSchemeOpcWss;
break;
}
}
// initialize the channel which will be created with the server.
ITransportChannel channel = null;
if (useUaTcp)
{
channel = new TcpTransportChannel();
}
#if !NO_HTTPS
else if (useHttps)
{
channel = new HttpsTransportChannel();
}
#endif
ITransportChannel channel = TransportBindings.Channels.GetChannel(uriScheme);
if (channel == null)
{
throw ServiceResultException.Create(
StatusCodes.BadProtocolVersionUnsupported,
"Unsupported transport profile\r\n");
"Unsupported transport profile for scheme {0}\r\n", uriScheme);
}
// create a UA-TCP channel.
@@ -1,385 +0,0 @@
/* ========================================================================
* Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved.
*
* OPC Reciprocal Community License ("RCL") Version 1.00
*
* Unless explicitly acquired and licensed from Licensor under another
* license, the contents of this file are subject to the Reciprocal
* Community License ("RCL") Version 1.00, or subsequent versions
* as allowed by the RCL, and You may not copy or use this file in either
* source code or executable form, except in compliance with the terms and
* conditions of the RCL.
*
* All software distributed under the RCL is provided strictly on an
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* AND LICENSOR HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
* LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RCL for specific
* language governing rights and limitations under the RCL.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/RCL/1.00/
* ======================================================================*/
#if !NO_HTTPS
using System;
using System.IO;
using System.Net;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Https;
namespace Opc.Ua.Bindings
{
/// <summary>
/// Implements the startup of the Https listener.
/// </summary>
public class Startup
{
/// <summary>
/// Get the Https listener.
/// </summary>
public static UaHttpsChannelListener Listener { get; set; }
/// <summary>
/// Configure the request pipeline for the listener.
/// </summary>
/// <param name="appBuilder">The application builder.</param>
public void Configure(IApplicationBuilder appBuilder)
{
appBuilder.Run(async context => {
if (context.Request.Method != "POST")
{
context.Response.ContentLength = 0;
context.Response.ContentType = "text/plain";
context.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
await context.Response.WriteAsync(string.Empty);
}
else
{
await Listener.SendAsync(context);
}
});
}
}
/// <summary>
/// Manages the connections for a UA HTTPS server.
/// </summary>
public partial class UaHttpsChannelListener : ITransportListener
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="UaHttpsChannelListener"/> class.
/// </summary>
public UaHttpsChannelListener()
{
}
#endregion
#region IDisposable Members
/// <summary>
/// Frees any unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// An overrideable version of the Dispose.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "m_simulator")]
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
ConnectionStatusChanged = null;
ConnectionWaiting = null;
Utils.SilentDispose(m_host);
m_host = null;
}
}
#endregion
#region ITransportListener Members
/// <summary>
/// The URI scheme handled by the listener.
/// </summary>
public string UriScheme => Utils.UriSchemeHttps;
/// <summary>
/// Opens the listener and starts accepting connection.
/// </summary>
/// <param name="baseAddress">The base address.</param>
/// <param name="settings">The settings to use when creating the listener.</param>
/// <param name="callback">The callback to use when requests arrive via the channel.</param>
/// <exception cref="ArgumentNullException">Thrown if any parameter is null.</exception>
/// <exception cref="ServiceResultException">Thrown if any communication error occurs.</exception>
public void Open(
Uri baseAddress,
TransportListenerSettings settings,
ITransportListenerCallback callback)
{
// assign a unique guid to the listener.
m_listenerId = Guid.NewGuid().ToString();
m_uri = baseAddress;
m_descriptions = settings.Descriptions;
var configuration = settings.Configuration;
// initialize the quotas.
m_quotas = new ChannelQuotas();
m_quotas.MaxBufferSize = configuration.MaxBufferSize;
m_quotas.MaxMessageSize = configuration.MaxMessageSize;
m_quotas.ChannelLifetime = configuration.ChannelLifetime;
m_quotas.SecurityTokenLifetime = configuration.SecurityTokenLifetime;
m_quotas.MessageContext = new ServiceMessageContext();
m_quotas.MessageContext.MaxArrayLength = configuration.MaxArrayLength;
m_quotas.MessageContext.MaxByteStringLength = configuration.MaxByteStringLength;
m_quotas.MessageContext.MaxMessageSize = configuration.MaxMessageSize;
m_quotas.MessageContext.MaxStringLength = configuration.MaxStringLength;
m_quotas.MessageContext.NamespaceUris = settings.NamespaceUris;
m_quotas.MessageContext.ServerUris = new StringTable();
m_quotas.MessageContext.Factory = settings.Factory;
m_quotas.CertificateValidator = settings.CertificateValidator;
// save the callback to the server.
m_callback = callback;
m_serverCert = settings.ServerCertificate;
// start the listener
Start();
}
/// <summary>
/// Closes the listener and stops accepting connection.
/// </summary>
/// <exception cref="ServiceResultException">Thrown if any communication error occurs.</exception>
public void Close()
{
Stop();
}
/// <summary>
/// Raised when a new connection is waiting for a client.
/// </summary>
public event ConnectionWaitingHandlerAsync ConnectionWaiting;
/// <summary>
/// Raised when a monitored connection's status changed.
/// </summary>
public event EventHandler<ConnectionStatusEventArgs> ConnectionStatusChanged;
/// <inheritdoc/>
/// <remarks>
/// Reverse connect for the https transport listener is not implemeted.
/// </remarks>
public void CreateReverseConnection(Uri url, int timeout)
{
// suppress warnings
ConnectionWaiting = null;
ConnectionWaiting?.Invoke(null, null);
ConnectionStatusChanged = null;
ConnectionStatusChanged?.Invoke(null, null);
throw new NotImplementedException();
}
#endregion
#region Public Methods
/// <summary>
/// Gets the URL for the listener's endpoint.
/// </summary>
/// <value>The URL for the listener's endpoint.</value>
public Uri EndpointUrl => m_uri;
/// <summary>
/// Starts listening at the specified port.
/// </summary>
public void Start()
{
Startup.Listener = this;
m_hostBuilder = new WebHostBuilder();
HttpsConnectionAdapterOptions httpsOptions = new HttpsConnectionAdapterOptions();
httpsOptions.CheckCertificateRevocation = false;
httpsOptions.ClientCertificateMode = ClientCertificateMode.NoCertificate;
httpsOptions.ServerCertificate = m_serverCert;
httpsOptions.SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
m_hostBuilder.UseKestrel(options => {
options.Listen(IPAddress.Any, m_uri.Port, listenOptions => {
listenOptions.NoDelay = true;
listenOptions.UseHttps(httpsOptions);
});
});
m_hostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
m_hostBuilder.UseStartup<Startup>();
m_host = m_hostBuilder.Start(Utils.ReplaceLocalhost(m_uri.ToString()));
}
/// <summary>
/// Stops listening.
/// </summary>
public void Stop()
{
Dispose();
}
#endregion
#region Private Methods
/// <summary>
/// Handles requests arriving from a channel.
/// </summary>
public async Task SendAsync(HttpContext context)
{
IAsyncResult result = null;
try
{
if (m_callback == null)
{
context.Response.ContentLength = 0;
context.Response.ContentType = "text/plain";
context.Response.StatusCode = (int)HttpStatusCode.NotImplemented;
await context.Response.WriteAsync(string.Empty);
return;
}
if (context.Request.ContentType != "application/octet-stream")
{
context.Response.ContentLength = 0;
context.Response.ContentType = "text/plain";
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("HTTPSLISTENER - Unsupported content type.");
return;
}
int length = (int)context.Request.ContentLength;
byte[] buffer = await ReadBodyAsync(context.Request);
if (buffer.Length != length)
{
context.Response.ContentLength = 0;
context.Response.ContentType = "text/plain";
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("HTTPSLISTENER - Couldn't decode buffer.");
return;
}
IServiceRequest input = (IServiceRequest)BinaryDecoder.DecodeMessage(buffer, null, m_quotas.MessageContext);
// extract the JWT token from the HTTP headers.
if (input.RequestHeader == null)
{
input.RequestHeader = new RequestHeader();
}
if (NodeId.IsNull(input.RequestHeader.AuthenticationToken) && input.TypeId != DataTypeIds.CreateSessionRequest)
{
if (context.Request.Headers.Keys.Contains("Authorization"))
{
foreach (string value in context.Request.Headers["Authorization"])
{
if (value.StartsWith("Bearer"))
{
input.RequestHeader.AuthenticationToken = new NodeId(value.Substring("Bearer ".Length).Trim());
}
}
}
}
EndpointDescription endpoint = null;
foreach (var ep in m_descriptions)
{
if (ep.EndpointUrl.StartsWith(Utils.UriSchemeHttps))
{
endpoint = ep;
break;
}
}
result = m_callback.BeginProcessRequest(
m_listenerId,
endpoint,
input as IServiceRequest,
null,
null);
IServiceResponse output = m_callback.EndProcessRequest(result);
byte[] response = BinaryEncoder.EncodeMessage(output, m_quotas.MessageContext);
context.Response.ContentLength = response.Length;
context.Response.ContentType = context.Request.ContentType;
context.Response.StatusCode = (int)HttpStatusCode.OK;
await context.Response.Body.WriteAsync(response, 0, response.Length);
}
catch (Exception e)
{
Utils.Trace(e, "HTTPSLISTENER - Unexpected error processing request.");
context.Response.ContentLength = e.Message.Length;
context.Response.ContentType = "text/plain";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await context.Response.WriteAsync(e.Message);
}
}
/// <summary>
/// Called when a UpdateCertificate event occured.
/// </summary>
public void CertificateUpdate(
ICertificateValidator validator,
X509Certificate2 serverCertificate,
X509Certificate2Collection serverCertificateChain)
{
Stop();
m_quotas.CertificateValidator = validator;
m_serverCert = serverCertificate;
foreach (var description in m_descriptions)
{
if (description.ServerCertificate != null)
{
description.ServerCertificate = serverCertificate.RawData;
}
}
Start();
}
private async Task<byte[]> ReadBodyAsync(HttpRequest req)
{
using (var memory = new MemoryStream())
using (var reader = new StreamReader(req.Body))
{
await reader.BaseStream.CopyToAsync(memory);
return memory.ToArray();
}
}
#endregion
#region Private Fields
private string m_listenerId;
private Uri m_uri;
private EndpointDescriptionCollection m_descriptions;
private ChannelQuotas m_quotas;
private ITransportListenerCallback m_callback;
private IWebHostBuilder m_hostBuilder;
private IWebHost m_host;
private X509Certificate2 m_serverCert;
#endregion
}
}
#endif
@@ -1,348 +0,0 @@
/* Copyright (c) 1996-2020 The OPC Foundation. All rights reserved.
The source code in this file is covered under a dual-license scenario:
- RCL: for OPC Foundation members in good-standing
- GPL V2: everybody else
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
GNU General Public License as published by the Free Software Foundation;
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#if !NO_HTTPS
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
namespace Opc.Ua.Bindings
{
/// <summary>
/// Wraps the HttpsTransportChannel and provides an ITransportChannel implementation.
/// </summary>
public class HttpsTransportChannel : ITransportChannel
{
/// <inheritdoc/>
public void Dispose()
{
}
/// <inheritdoc/>
public TransportChannelFeatures SupportedFeatures
{
get { return TransportChannelFeatures.Open | TransportChannelFeatures.Reconnect | TransportChannelFeatures.BeginSendRequest; }
}
/// <inheritdoc/>
public EndpointDescription EndpointDescription
{
get { return m_settings.Description; }
}
/// <inheritdoc/>
public EndpointConfiguration EndpointConfiguration
{
get { return m_settings.Configuration; }
}
/// <inheritdoc/>
public ServiceMessageContext MessageContext
{
get { return m_quotas.MessageContext; }
}
/// <inheritdoc/>
public ChannelToken CurrentToken
{
get { return null; }
}
/// <inheritdoc/>
public int OperationTimeout
{
get { return m_operationTimeout; }
set { m_operationTimeout = value; }
}
/// <inheritdoc/>
public void Initialize(
Uri url,
TransportChannelSettings settings)
{
SaveSettings(url, settings);
}
/// <summary>
/// Initializes a secure channel with a waiting reverse connection.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="settings">The settings to use when creating the channel.</param>
/// <exception cref="ServiceResultException">Thrown if any communication error occurs.</exception>
public void Initialize(
ITransportWaitingConnection connection,
TransportChannelSettings settings)
{
SaveSettings(connection.EndpointUrl, settings);
}
/// <inheritdoc/>
public void Open()
{
try
{
// auto validate server cert, if supported
// if unsupported, the TLS server cert must be trusted by a root CA
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
// send client certificate for servers that require TLS client authentication
if (m_settings.ClientCertificate != null)
{
handler.ClientCertificates.Add(m_settings.ClientCertificate);
}
// OSX platform cannot auto validate certs and throws
// on PostAsync, do not set validation handler
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
try
{
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, chain, policyErrors) =>
{
try
{
m_quotas.CertificateValidator?.Validate(cert);
return true;
}
catch (Exception ex)
{
Utils.Trace("HTTPS: Failed to validate server cert: " + cert.Subject);
Utils.Trace("HTTPS: Exception:" + ex.Message);
}
return false;
};
}
catch (PlatformNotSupportedException)
{
// client may throw if not supported (e.g. UWP)
handler.ServerCertificateCustomValidationCallback = null;
}
}
m_client = new HttpClient(handler);
}
catch (Exception ex)
{
Utils.Trace("Exception creating HTTPS Client: " + ex.Message);
throw;
}
}
/// <inheritdoc/>
public void Close()
{
if (m_client != null)
{
m_client.Dispose();
}
}
/// <summary>
/// The async result class for the Https transport.
/// </summary>
private class AsyncResult : AsyncResultBase
{
public IServiceRequest Request;
public HttpResponseMessage Response;
public AsyncResult(
AsyncCallback callback,
object callbackData,
int timeout,
IServiceRequest request,
HttpResponseMessage response)
:
base(callback, callbackData, timeout)
{
Request = request;
Response = response;
}
}
/// <inheritdoc/>
public IAsyncResult BeginSendRequest(IServiceRequest request, AsyncCallback callback, object callbackData)
{
HttpResponseMessage response = null;
try
{
ByteArrayContent content = new ByteArrayContent(BinaryEncoder.EncodeMessage(request, m_quotas.MessageContext));
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
AsyncResult result = new AsyncResult(callback, callbackData, m_operationTimeout, request, null);
Task.Run(async () =>
{
try
{
response = await m_client.PostAsync(m_url, content);
response.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
Utils.Trace("Exception sending HTTPS request: " + ex.Message);
result.Exception = ex;
response = null;
}
result.Response = response;
result.OperationCompleted();
});
return result;
}
catch (Exception ex)
{
Utils.Trace("Exception sending HTTPS request: " + ex.Message);
AsyncResult result = new AsyncResult(callback, callbackData, m_operationTimeout, request, response);
result.Exception = ex;
result.OperationCompleted();
return result;
}
}
/// <inheritdoc/>
public IServiceResponse EndSendRequest(IAsyncResult result)
{
AsyncResult result2 = result as AsyncResult;
if (result2 == null)
{
throw new ArgumentException("Invalid result object passed.", nameof(result));
}
try
{
result2.WaitForComplete();
if (result2.Response != null)
{
Stream responseContent = result2.Response.Content.ReadAsStreamAsync().Result;
return BinaryDecoder.DecodeMessage(responseContent, null, m_quotas.MessageContext) as IServiceResponse;
}
}
catch (Exception ex)
{
Utils.Trace("Exception reading HTTPS response: " + ex.Message);
result2.Exception = ex;
}
return result2 as IServiceResponse;
}
/// <inheritdoc/>
/// <remarks>Not implemented here.</remarks>
public IAsyncResult BeginOpen(AsyncCallback callback, object callbackData)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
/// <remarks>Not implemented here.</remarks>
public void EndOpen(IAsyncResult result)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
/// <remarks>Not implemented here.</remarks>
public void Reconnect()
{
Utils.Trace("HttpsTransportChannel RECONNECT: Reconnecting to {0}.", m_url);
}
/// <inheritdoc/>
/// <remarks>Not implemented here.</remarks>
void ITransportChannel.Reconnect(ITransportWaitingConnection connection)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
/// <remarks>Not implemented here.</remarks>
public IAsyncResult BeginReconnect(AsyncCallback callback, object callbackData)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
/// <remarks>Not implemented here.</remarks>
public void EndReconnect(IAsyncResult result)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
/// <remarks>Not implemented here.</remarks>
public IAsyncResult BeginClose(AsyncCallback callback, object callbackData)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
/// <remarks>Not implemented here.</remarks>
public void EndClose(IAsyncResult result)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
public IServiceResponse SendRequest(IServiceRequest request)
{
IAsyncResult result = BeginSendRequest(request, null, null);
return EndSendRequest(result);
}
/// <summary>
/// Save the settings for a connection.
/// </summary>
/// <param name="url">The server url.</param>
/// <param name="settings">The settings for the transport channel.</param>
private void SaveSettings(Uri url, TransportChannelSettings settings)
{
m_url = new Uri(url.ToString());
m_settings = settings;
m_operationTimeout = settings.Configuration.OperationTimeout;
// initialize the quotas.
m_quotas = new ChannelQuotas();
m_quotas.MaxBufferSize = m_settings.Configuration.MaxBufferSize;
m_quotas.MaxMessageSize = m_settings.Configuration.MaxMessageSize;
m_quotas.ChannelLifetime = m_settings.Configuration.ChannelLifetime;
m_quotas.SecurityTokenLifetime = m_settings.Configuration.SecurityTokenLifetime;
m_quotas.MessageContext = new ServiceMessageContext();
m_quotas.MessageContext.MaxArrayLength = m_settings.Configuration.MaxArrayLength;
m_quotas.MessageContext.MaxByteStringLength = m_settings.Configuration.MaxByteStringLength;
m_quotas.MessageContext.MaxMessageSize = m_settings.Configuration.MaxMessageSize;
m_quotas.MessageContext.MaxStringLength = m_settings.Configuration.MaxStringLength;
m_quotas.MessageContext.NamespaceUris = m_settings.NamespaceUris;
m_quotas.MessageContext.ServerUris = new StringTable();
m_quotas.MessageContext.Factory = m_settings.Factory;
m_quotas.CertificateValidator = settings.CertificateValidator;
}
private Uri m_url;
private int m_operationTimeout;
private TransportChannelSettings m_settings;
private ChannelQuotas m_quotas;
private HttpClient m_client;
}
}
#endif
+40 -262
View File
@@ -699,282 +699,60 @@ namespace Opc.Ua
}
/// <summary>
/// Create a new service host for UA TCP.
/// Create the transport listener for the service host endpoint.
/// </summary>
protected List<EndpointDescription> CreateUaTcpServiceHost(
IDictionary<string, Task> hosts,
ApplicationConfiguration configuration,
IList<string> baseAddresses,
ApplicationDescription serverDescription,
List<ServerSecurityPolicy> securityPolicies)
/// <param name="endpointUri">The endpoint Uri.</param>
/// <param name="endpoints">The description of the endpoints.</param>
/// <param name="endpointConfiguration">The configuration of the endpoints.</param>
/// <param name="listener">The transport listener.</param>
/// <param name="certificateValidator">The certificate validator for the transport.</param>
public virtual void CreateServiceHostEndpoint(
Uri endpointUri,
EndpointDescriptionCollection endpoints,
EndpointConfiguration endpointConfiguration,
ITransportListener listener,
ICertificateValidator certificateValidator
)
{
// generate a unique host name.
string hostName = String.Empty;
if (hosts.ContainsKey(hostName))
// create the stack listener.
try
{
hostName = "/Tcp";
}
TransportListenerSettings settings = new TransportListenerSettings();
if (hosts.ContainsKey(hostName))
settings.Descriptions = endpoints;
settings.Configuration = endpointConfiguration;
settings.ServerCertificate = InstanceCertificate;
settings.CertificateValidator = certificateValidator;
settings.NamespaceUris = MessageContext.NamespaceUris;
settings.Factory = MessageContext.Factory;
listener.Open(
endpointUri,
settings,
GetEndpointInstance(this));
TransportListeners.Add(listener);
listener.ConnectionStatusChanged += OnConnectionStatusChanged;
}
catch (Exception e)
{
hostName += Utils.Format("/{0}", hosts.Count);
string message = $"Could not load {endpointUri.Scheme} Stack Listener.";
if (e.InnerException != null)
{
message += (" " + e.InnerException.Message);
}
Utils.Trace(e, message);
}
// build list of uris.
List<Uri> uris = new List<Uri>();
EndpointDescriptionCollection endpoints = new EndpointDescriptionCollection();
// create the endpoint configuration to use.
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration);
string computerName = Utils.GetHostName();
for (int ii = 0; ii < baseAddresses.Count; ii++)
{
// UA TCP and HTTPS endpoints support multiple policies.
if (!baseAddresses[ii].StartsWith(Utils.UriSchemeOpcTcp, StringComparison.Ordinal))
{
continue;
}
UriBuilder uri = new UriBuilder(baseAddresses[ii]);
if (String.Compare(uri.Host, "localhost", StringComparison.OrdinalIgnoreCase) == 0)
{
uri.Host = computerName;
}
uris.Add(uri.Uri);
foreach (ServerSecurityPolicy policy in securityPolicies)
{
// create the endpoint description.
EndpointDescription description = new EndpointDescription();
description.EndpointUrl = uri.ToString();
description.Server = serverDescription;
description.SecurityMode = policy.SecurityMode;
description.SecurityPolicyUri = policy.SecurityPolicyUri;
description.SecurityLevel = ServerSecurityPolicy.CalculateSecurityLevel(policy.SecurityMode, policy.SecurityPolicyUri);
description.UserIdentityTokens = GetUserTokenPolicies(configuration, description);
description.TransportProfileUri = Profiles.UaTcpTransport;
bool requireEncryption = RequireEncryption(description);
if (requireEncryption)
{
description.ServerCertificate = InstanceCertificate.RawData;
// check if complete chain should be sent.
if (configuration.SecurityConfiguration.SendCertificateChain &&
InstanceCertificateChain != null &&
InstanceCertificateChain.Count > 0)
{
List<byte> serverCertificateChain = new List<byte>();
for (int i = 0; i < InstanceCertificateChain.Count; i++)
{
serverCertificateChain.AddRange(InstanceCertificateChain[i].RawData);
}
description.ServerCertificate = serverCertificateChain.ToArray();
}
}
endpoints.Add(description);
}
// create the UA-TCP stack listener.
try
{
TransportListenerSettings settings = new TransportListenerSettings();
settings.Descriptions = endpoints;
settings.Configuration = endpointConfiguration;
settings.CertificateValidator = configuration.CertificateValidator.GetChannelValidator();
settings.NamespaceUris = this.MessageContext.NamespaceUris;
settings.Factory = this.MessageContext.Factory;
settings.ServerCertificate = this.InstanceCertificate;
if (configuration.SecurityConfiguration.SendCertificateChain)
{
settings.ServerCertificateChain = this.InstanceCertificateChain;
}
ITransportListener listener = new Opc.Ua.Bindings.TcpListener();
listener.Open(
uri.Uri,
settings,
GetEndpointInstance(this));
TransportListeners.Add(listener);
listener.ConnectionStatusChanged += OnConnectionStatusChanged;
}
catch (Exception e)
{
Utils.Trace(e, "Could not load UA-TCP Stack Listener.");
throw;
}
}
return endpoints;
}
#if !NO_HTTPS
/// <summary>
/// Create a new service host for UA HTTPS.
/// </summary>
protected List<EndpointDescription> CreateHttpsServiceHost(
IDictionary<string, Task> hosts,
ApplicationConfiguration configuration,
IList<string> baseAddresses,
ApplicationDescription serverDescription,
List<ServerSecurityPolicy> securityPolicies)
{
// generate a unique host name.
string hostName = String.Empty;
if (hosts.ContainsKey(hostName))
{
hostName = "/Https";
}
if (hosts.ContainsKey(hostName))
{
hostName += Utils.Format("/{0}", hosts.Count);
}
// build list of uris.
List<Uri> uris = new List<Uri>();
EndpointDescriptionCollection endpoints = new EndpointDescriptionCollection();
// create the endpoint configuration to use.
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration);
string computerName = Utils.GetHostName();
for (int ii = 0; ii < baseAddresses.Count; ii++)
{
if (!baseAddresses[ii].StartsWith(Utils.UriSchemeHttps, StringComparison.Ordinal))
{
continue;
}
UriBuilder uri = new UriBuilder(baseAddresses[ii]);
if (uri.Path[uri.Path.Length - 1] != '/')
{
uri.Path += "/";
}
if (String.Compare(uri.Host, "localhost", StringComparison.OrdinalIgnoreCase) == 0)
{
uri.Host = computerName;
}
uris.Add(uri.Uri);
if (uri.Scheme == Utils.UriSchemeHttps)
{
// Can only support one policy with HTTPS
// So pick the first policy with security mode sign and encrypt
ServerSecurityPolicy bestPolicy = null;
foreach (ServerSecurityPolicy policy in securityPolicies)
{
if (policy.SecurityMode != MessageSecurityMode.SignAndEncrypt)
{
continue;
}
bestPolicy = policy;
break;
}
// Pick the first policy from the list if no policies with sign and encrypt defined
if (bestPolicy == null)
{
bestPolicy = securityPolicies[0];
}
EndpointDescription description = new EndpointDescription();
description.EndpointUrl = uri.ToString();
description.Server = serverDescription;
if (InstanceCertificate != null)
{
description.ServerCertificate = InstanceCertificate.RawData;
// check if complete chain should be sent.
if (configuration.SecurityConfiguration.SendCertificateChain && InstanceCertificateChain != null && InstanceCertificateChain.Count > 0)
{
List<byte> serverCertificateChain = new List<byte>();
for (int i = 0; i < InstanceCertificateChain.Count; i++)
{
serverCertificateChain.AddRange(InstanceCertificateChain[i].RawData);
}
description.ServerCertificate = serverCertificateChain.ToArray();
}
}
description.SecurityMode = bestPolicy.SecurityMode;
description.SecurityPolicyUri = bestPolicy.SecurityPolicyUri;
description.SecurityLevel = ServerSecurityPolicy.CalculateSecurityLevel(bestPolicy.SecurityMode, bestPolicy.SecurityPolicyUri);
description.UserIdentityTokens = GetUserTokenPolicies(configuration, description);
description.TransportProfileUri = Profiles.HttpsBinaryTransport;
endpoints.Add(description);
}
// create the stack listener.
try
{
TransportListenerSettings settings = new TransportListenerSettings();
settings.Descriptions = endpoints;
settings.Configuration = endpointConfiguration;
settings.ServerCertificate = this.InstanceCertificate;
settings.CertificateValidator = configuration.CertificateValidator.GetChannelValidator();
settings.NamespaceUris = this.MessageContext.NamespaceUris;
settings.Factory = this.MessageContext.Factory;
ITransportListener listener = new Opc.Ua.Bindings.UaHttpsChannelListener();
listener.Open(
uri.Uri,
settings,
GetEndpointInstance(this));
TransportListeners.Add(listener);
listener.ConnectionStatusChanged += OnConnectionStatusChanged;
}
catch (Exception e)
{
string message = "Could not load HTTPS Stack Listener.";
if (e.InnerException != null)
{
message += (" " + e.InnerException.Message);
}
Utils.Trace(e, message);
}
}
return endpoints;
}
#endif
/// <summary>
/// Returns the UserTokenPolicies supported by the server.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="description">The description.</param>
/// <returns>Returns a collection of UserTokenPolicy objects, the return type is <seealso cref="UserTokenPolicyCollection"/> . </returns>
protected virtual UserTokenPolicyCollection GetUserTokenPolicies(ApplicationConfiguration configuration, EndpointDescription description)
public virtual UserTokenPolicyCollection GetUserTokenPolicies(ApplicationConfiguration configuration, EndpointDescription description)
{
UserTokenPolicyCollection policies = new UserTokenPolicyCollection();
@@ -10,8 +10,6 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System.IdentityModel.Selectors;
namespace Opc.Ua.Bindings
{
/// <summary>
@@ -39,6 +39,11 @@ namespace Opc.Ua.Bindings
/// </summary>
public class TcpTransportChannelFactory : ITransportChannelFactory
{
/// <summary>
/// The protocol supported by the channel.
/// </summary>
public string UriScheme => Utils.UriSchemeOpcTcp;
/// <summary>
/// The method creates a new instance of a TCP transport channel
/// </summary>
@@ -311,7 +316,6 @@ namespace Opc.Ua.Bindings
/// <value>The socket handle.</value>
public int Handle => m_socket != null ? m_socket.GetHashCode() : -1;
/// <summary>
/// Gets the transport channel features implemented by this message socket.
/// </summary>
@@ -0,0 +1,142 @@
/* Copyright (c) 1996-2020 The OPC Foundation. All rights reserved.
The source code in this file is covered under a dual-license scenario:
- RCL: for OPC Foundation members in good-standing
- GPL V2: everybody else
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
GNU General Public License as published by the Free Software Foundation;
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace Opc.Ua.Bindings
{
/// <summary>
/// Creates a new Tcp service host.
/// </summary>
/// <remarks>
/// This class can be used by a transport which
/// implements the <see cref="ITransportListenerFactory"/>
/// </remarks>
public abstract class TcpServiceHost : ITransportListenerFactory
{
/// <inheritdoc/>
public abstract string UriScheme { get; }
/// <inheritdoc/>
public abstract ITransportListener Create();
/// <inheritdoc/>
/// <summary>
/// Create a new service host for UA TCP.
/// </summary>
public List<EndpointDescription> CreateServiceHost(
ServerBase serverBase,
IDictionary<string, Task> hosts,
ApplicationConfiguration configuration,
IList<string> baseAddresses,
ApplicationDescription serverDescription,
List<ServerSecurityPolicy> securityPolicies,
X509Certificate2 instanceCertificate,
X509Certificate2Collection instanceCertificateChain)
{
// generate a unique host name.
string hostName = String.Empty;
if (hosts.ContainsKey(hostName))
{
hostName = "/Tcp";
}
if (hosts.ContainsKey(hostName))
{
hostName += Utils.Format("/{0}", hosts.Count);
}
// build list of uris.
List<Uri> uris = new List<Uri>();
EndpointDescriptionCollection endpoints = new EndpointDescriptionCollection();
// create the endpoint configuration to use.
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration);
string computerName = Utils.GetHostName();
for (int ii = 0; ii < baseAddresses.Count; ii++)
{
// UA TCP and HTTPS endpoints support multiple policies.
if (!baseAddresses[ii].StartsWith(Utils.UriSchemeOpcTcp, StringComparison.Ordinal))
{
continue;
}
UriBuilder uri = new UriBuilder(baseAddresses[ii]);
if (String.Compare(uri.Host, "localhost", StringComparison.OrdinalIgnoreCase) == 0)
{
uri.Host = computerName;
}
ITransportListener listener = this.Create();
if (listener != null)
{
uris.Add(uri.Uri);
foreach (ServerSecurityPolicy policy in securityPolicies)
{
// create the endpoint description.
EndpointDescription description = new EndpointDescription();
description.EndpointUrl = uri.ToString();
description.Server = serverDescription;
description.SecurityMode = policy.SecurityMode;
description.SecurityPolicyUri = policy.SecurityPolicyUri;
description.SecurityLevel = ServerSecurityPolicy.CalculateSecurityLevel(policy.SecurityMode, policy.SecurityPolicyUri);
description.UserIdentityTokens = serverBase.GetUserTokenPolicies(configuration, description);
description.TransportProfileUri = Profiles.UaTcpTransport;
bool requireEncryption = ServerBase.RequireEncryption(description);
if (requireEncryption)
{
description.ServerCertificate = instanceCertificate.RawData;
// check if complete chain should be sent.
if (configuration.SecurityConfiguration.SendCertificateChain &&
instanceCertificateChain != null &&
instanceCertificateChain.Count > 0)
{
List<byte> serverCertificateChain = new List<byte>();
for (int i = 0; i < instanceCertificateChain.Count; i++)
{
serverCertificateChain.AddRange(instanceCertificateChain[i].RawData);
}
description.ServerCertificate = serverCertificateChain.ToArray();
}
}
endpoints.Add(description);
}
serverBase.CreateServiceHostEndpoint(uri.Uri, endpoints, endpointConfiguration, listener,
configuration.CertificateValidator.GetChannelValidator()
);
}
else
{
Utils.Trace(Utils.TraceMasks.Error, "Failed to create endpoint {0} because the transport profile is unsupported.", uri);
}
}
return endpoints;
}
}
}
@@ -20,9 +20,24 @@ using System.Threading.Tasks;
namespace Opc.Ua.Bindings
{
/// <summary>
/// Manages the connections for a UA TCP server.
/// Creates a new TcpTransportListener with ITransportListener interface.
/// </summary>
public class TcpListener : ITransportListener, ITcpChannelListener
public class TcpTransportListenerFactory : TcpServiceHost
{
/// <inheritdoc/>
public override string UriScheme => Utils.UriSchemeOpcTcp;
/// <inheritdoc/>
public override ITransportListener Create()
{
return new TcpTransportListener();
}
}
/// <summary>
/// Manages the transport for a UA TCP server.
/// </summary>
public class TcpTransportListener : ITransportListener, ITcpChannelListener
{
#region IDisposable Members
/// <summary>
@@ -15,18 +15,6 @@ using Opc.Ua.Bindings;
namespace Opc.Ua
{
/// <summary>
/// This is an interface to a channel which supports a factory
/// </summary>
public interface ITransportChannelFactory
{
/// <summary>
/// The method creates a new transport channel
/// </summary>
/// <returns> the transport channel</returns>
ITransportChannel Create();
}
/// <summary>
/// This is an interface to a channel which supports
/// </summary>
@@ -10,7 +10,6 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System.IdentityModel.Selectors;
using System.Security.Cryptography.X509Certificates;
namespace Opc.Ua
@@ -55,7 +55,7 @@ namespace Opc.Ua
public EncodeableFactory(bool shared)
{
m_encodeableTypes = new Dictionary<ExpandedNodeId, System.Type>();
AddEncodeableTypes("Opc.Ua.Core");
AddEncodeableTypes(Utils.DefaultOpcUaCoreAssemblyFullName);
#if DEBUG
m_instanceId = Interlocked.Increment(ref m_globalInstanceCount);
+37 -7
View File
@@ -25,7 +25,7 @@ using System.Threading.Tasks;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Net;
using System.Collections.ObjectModel;
namespace Opc.Ua
{
@@ -50,11 +50,30 @@ namespace Opc.Ua
/// </summary>
public const string UriSchemeOpcTcp = "opc.tcp";
/// <summary>
/// The URI scheme for the UA TCP protocol over Secure WebSockets.
/// </summary>
public const string UriSchemeOpcWss = "opc.wss";
/// <summary>
/// The URI schemes which are supported in the core server.
/// </summary>
public static readonly string[] DefaultUriSchemes = new string []
{
Utils.UriSchemeOpcTcp,
Utils.UriSchemeHttps
};
/// <summary>
/// The default port for the UA TCP protocol.
/// </summary>
public const int UaTcpDefaultPort = 4840;
/// <summary>
/// The default port for the UA TCP protocol over WebSockets.
/// </summary>
public const int UaWebSocketsDefaultPort = 4843;
/// <summary>
/// The urls of the discovery servers on a node.
/// </summary>
@@ -67,11 +86,6 @@ namespace Opc.Ua
"http://{0}/UADiscovery/Default.svc"
};
/// <summary>
/// The class that provides the default implementation for the UA TCP protocol.
/// </summary>
public const string UaTcpBindingDefault = "Opc.Ua.Bindings.UaTcpBinding";
/// <summary>
/// The default certificate store's type.
/// </summary>
@@ -87,6 +101,23 @@ namespace Opc.Ua
/// </summary>
public static string DefaultLocalFolder = Directory.GetCurrentDirectory();
/// <summary>
/// The full name of the Opc.Ua.Core assembly.
/// </summary>
public static readonly string DefaultOpcUaCoreAssemblyFullName = typeof(Utils).Assembly.GetName().FullName;
/// <summary>
/// The name of the Opc.Ua.Core assembly.
/// </summary>
public static readonly string DefaultOpcUaCoreAssemblyName = typeof(Utils).Assembly.GetName().Name;
/// <summary>
/// List of known default bindings hosted in other assemblies.
/// </summary>
public static ReadOnlyDictionary<string, string> DefaultBindings = new ReadOnlyDictionary<string, string>(
new Dictionary<string, string>() {
{ Utils.UriSchemeHttps, "Opc.Ua.Bindings.Https"}
});
#endregion
#region Trace Support
@@ -854,7 +885,6 @@ namespace Opc.Ua
#region String, Object and Data Convienence Functions
private const int MAX_MESSAGE_LENGTH = 1024;
private const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
private const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;