Skip to content

Commit

Permalink
[epoxy] TLS support
Browse files Browse the repository at this point in the history
* Epoxy connections can now be secured using TLS. Only server authentication
  is currently supported.
* epoxy:// parsing updated to support epoxys://
* Mutual client/server authentication supported.
* `EpoxySocket` renamed to `EpoxyNetworkStream` and refactored to handle
   stream establishment.

Closes #200
  • Loading branch information
chwarr committed Aug 19, 2016
1 parent 6a0e167 commit dffdeb1
Show file tree
Hide file tree
Showing 29 changed files with 1,046 additions and 196 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ different versioning scheme, following the Haskell community's
is configured with `EpoxyTransportBuilder.SetResolver`.
* Bond-generated Errors now give clients opaque GUIDs. These GUIDs can be
matched against emitted metrics for debugging.
* Epoxy can now be configured to use TLS to secure the connection.
* TLS configuration is set via
`EpoxyTransportBuilder.SetClientTlsConfig`/`EpoxyTransportBuilder.SetServerTlsConfig`.
* See the
[TLS example](https://github.com/Microsoft/bond/tree/master/examples/cs/comm/tls/)
for even more details.

## 4.2.1: 2016-06-02 ##

Expand Down
31 changes: 30 additions & 1 deletion cs/cs.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Attributes", "src\attributes\Attributes.csproj", "{92915BD9-8AB1-4E4D-A2AC-95BBF4F82D89}"
EndProject
Expand Down Expand Up @@ -217,6 +217,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "metrics", "..\examples\cs\c
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "layers", "src\comm\layers\layers.csproj", "{5F6CBC77-8FB5-4644-BAB5-F8E62792266E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tls", "..\examples\cs\comm\tls\tls.csproj", "{9C176B2F-6518-4469-8B3D-762F1EC053AE}"
ProjectSection(ProjectDependencies) = postProject
{C687C52C-0A5B-4F10-8CB3-DBAF9A72D042} = {C687C52C-0A5B-4F10-8CB3-DBAF9A72D042}
{79D2423A-87C8-44A2-89C2-2FA94521747E} = {79D2423A-87C8-44A2-89C2-2FA94521747E}
{FF056B62-225A-47BC-B177-550FADDA4B41} = {FF056B62-225A-47BC-B177-550FADDA4B41}
{5F6CBC77-8FB5-4644-BAB5-F8E62792266E} = {5F6CBC77-8FB5-4644-BAB5-F8E62792266E}
{45EFB397-298A-4A32-A178-A2BDF8ABBBD9} = {45EFB397-298A-4A32-A178-A2BDF8ABBBD9}
{92915BD9-8AB1-4E4D-A2AC-95BBF4F82D89} = {92915BD9-8AB1-4E4D-A2AC-95BBF4F82D89}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -848,6 +858,24 @@ Global
{5F6CBC77-8FB5-4644-BAB5-F8E62792266E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{5F6CBC77-8FB5-4644-BAB5-F8E62792266E}.Release|Win32.ActiveCfg = Release|Any CPU
{5F6CBC77-8FB5-4644-BAB5-F8E62792266E}.Release|Win32.Build.0 = Release|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Debug|Win32.ActiveCfg = Debug|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Debug|Win32.Build.0 = Debug|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Fields|Any CPU.ActiveCfg = Debug|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Fields|Any CPU.Build.0 = Debug|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Fields|Mixed Platforms.ActiveCfg = Debug|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Fields|Mixed Platforms.Build.0 = Debug|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Fields|Win32.ActiveCfg = Debug|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Fields|Win32.Build.0 = Debug|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Release|Any CPU.Build.0 = Release|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Release|Win32.ActiveCfg = Release|Any CPU
{9C176B2F-6518-4469-8B3D-762F1EC053AE}.Release|Win32.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -888,5 +916,6 @@ Global
{12279366-F646-4FEC-8CAA-B62A8EC477BB} = {621A2166-EEE0-4A27-88AA-5BE5AC996452}
{72ECA320-121F-4F3A-B455-B9EDE3364D9D} = {621A2166-EEE0-4A27-88AA-5BE5AC996452}
{5F6CBC77-8FB5-4644-BAB5-F8E62792266E} = {ED161076-6BB8-4A13-83ED-0E9C01461D5E}
{9C176B2F-6518-4469-8B3D-762F1EC053AE} = {621A2166-EEE0-4A27-88AA-5BE5AC996452}
EndGlobalSection
EndGlobal
122 changes: 21 additions & 101 deletions cs/src/comm/epoxy-transport/EpoxyConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private enum State
readonly EpoxyListener parentListener;
readonly ServiceHost serviceHost;

readonly EpoxySocket netSocket;
readonly EpoxyNetworkStream networkStream;

readonly ResponseMap responseMap;

Expand All @@ -75,26 +75,22 @@ private EpoxyConnection(
EpoxyTransport parentTransport,
EpoxyListener parentListener,
ServiceHost serviceHost,
Socket socket,
EpoxyNetworkStream networkStream,
Logger logger,
Metrics metrics)
{
Debug.Assert(parentTransport != null);
Debug.Assert(connectionType != ConnectionType.Server || parentListener != null, "Server connections must have a listener");
Debug.Assert(serviceHost != null);
Debug.Assert(socket != null);
Debug.Assert(networkStream != null);

this.connectionType = connectionType;

this.parentTransport = parentTransport;
this.parentListener = parentListener;
this.serviceHost = serviceHost;

netSocket = new EpoxySocket(socket, logger);

// cache these so we can use them after the socket has been shutdown
LocalEndPoint = (IPEndPoint) socket.LocalEndPoint;
RemoteEndPoint = (IPEndPoint) socket.RemoteEndPoint;
this.networkStream = networkStream;

responseMap = new ResponseMap();

Expand All @@ -115,7 +111,7 @@ private EpoxyConnection(

internal static EpoxyConnection MakeClientConnection(
EpoxyTransport parentTransport,
Socket clientSocket,
EpoxyNetworkStream networkStream,
Logger logger,
Metrics metrics)
{
Expand All @@ -126,7 +122,7 @@ internal static EpoxyConnection MakeClientConnection(
parentTransport,
parentListener,
new ServiceHost(logger),
clientSocket,
networkStream,
logger,
metrics);
}
Expand All @@ -135,7 +131,7 @@ internal static EpoxyConnection MakeServerConnection(
EpoxyTransport parentTransport,
EpoxyListener parentListener,
ServiceHost serviceHost,
Socket socket,
EpoxyNetworkStream networkStream,
Logger logger,
Metrics metrics)
{
Expand All @@ -144,20 +140,20 @@ internal static EpoxyConnection MakeServerConnection(
parentTransport,
parentListener,
serviceHost,
socket,
networkStream,
logger,
metrics);
}

/// <summary>
/// Get this connection's local endpoint.
/// </summary>
public IPEndPoint LocalEndPoint { get; private set; }
public IPEndPoint LocalEndPoint => networkStream.LocalEndpoint;

/// <summary>
/// Get this connection's remote endpoint.
/// </summary>
public IPEndPoint RemoteEndPoint { get; private set; }
public IPEndPoint RemoteEndPoint => networkStream.RemoteEndpoint;

public override string ToString()
{
Expand Down Expand Up @@ -374,19 +370,19 @@ private async Task<bool> SendFrameAsync(Frame frame)
{
try
{
Stream networkStream = netSocket.NetworkStream;
Stream stream = this.networkStream.Stream;

await netSocket.WriteLock.WaitAsync();
await networkStream.WriteLock.WaitAsync();
try
{
await frame.WriteAsync(networkStream);
await frame.WriteAsync(stream);
}
finally
{
netSocket.WriteLock.Release();
networkStream.WriteLock.Release();
}

await networkStream.FlushAsync();
await stream.FlushAsync();
return true;
}
catch (Exception ex) when (ex is IOException || ex is ObjectDisposedException || ex is SocketException)
Expand Down Expand Up @@ -542,8 +538,8 @@ private async Task<State> DoSendConfigAsync()

private async Task<State> DoExpectConfigAsync()
{
Stream networkStream = netSocket.NetworkStream;
Frame frame = await Frame.ReadAsync(networkStream, shutdownTokenSource.Token, logger);
Stream stream = networkStream.Stream;
Frame frame = await Frame.ReadAsync(stream, shutdownTokenSource.Token, logger);
if (frame == null)
{
logger.Site().Information("{0} EOS encountered while waiting for config, so disconnecting.", this);
Expand Down Expand Up @@ -581,8 +577,8 @@ private async Task<State> DoConnectedAsync()

try
{
Stream networkStream = netSocket.NetworkStream;
frame = await Frame.ReadAsync(networkStream, shutdownTokenSource.Token, logger);
Stream stream = networkStream.Stream;
frame = await Frame.ReadAsync(stream, shutdownTokenSource.Token, logger);
if (frame == null)
{
logger.Site().Information("{0} EOS encountered, so disconnecting.", this);
Expand Down Expand Up @@ -667,7 +663,7 @@ private State DoDisconnect()
{
logger.Site().Debug("{0} Shutting down.", this);

netSocket.Shutdown();
networkStream.Shutdown();

if (connectionType == ConnectionType.Server)
{
Expand Down Expand Up @@ -850,7 +846,7 @@ public override Task StopAsync()
{
EnsureCorrectState(State.All);
shutdownTokenSource.Cancel();
netSocket.Shutdown();
networkStream.Shutdown();

return stopTask.Task;
}
Expand All @@ -869,81 +865,5 @@ public Task FireEventAsync<TPayload>(string methodName, IMessage<TPayload> messa
EnsureCorrectState(State.Connected);
return SendEventAsync(methodName, message);
}

/// <summary>
/// Epoxy-private wrapper around <see cref="Socket"/>. Provides idempotent shutdown.
/// </summary>
private class EpoxySocket
{
Socket socket;
NetworkStream stream;
int isShutdown;
Logger logger;

public EpoxySocket(Socket sock, Logger logger)
{
socket = sock;
stream = new NetworkStream(sock, ownsSocket: false);
WriteLock = new SemaphoreSlim(1, 1);
isShutdown = 0;
this.logger = logger;
}

public Stream NetworkStream
{
get
{
if (isShutdown != 0)
{
throw new ObjectDisposedException(nameof(EpoxySocket));
}

return stream;
}
}

// It looks like we don't need to .Dispose this SemaphoreSlim. The
// current implementation of SemaphoreSlim only does interesting
// stuff during .Dispose if there's an allocated
// AvailableWaitHandle. We never call that, so there shouldn't be
// anything needing disposal. If we do end up allocating a wait
// handle somehow, its finalizer will save us.
public SemaphoreSlim WriteLock { get; }

public void Shutdown()
{
int oldIsShutdown = Interlocked.CompareExchange(ref isShutdown, 1, 0);
if (oldIsShutdown == 0)
{
// we are responsible for shutdown
try
{
stream.Dispose();

try
{
socket.Shutdown(SocketShutdown.Send);
socket.Close();
}
catch (ObjectDisposedException)
{
// ignore, as we're shutting down anyway
}

// We cannot call socket.Disconnect, as that will block
// for longer than we want. So, we just forcible close
// the socket with Dispose
socket.Dispose();
}
catch (Exception ex) when (ex is IOException || ex is SocketException)
{
logger.Site().Error(ex, "Exception during connection shutdown");
}

stream = null;
socket = null;
}
}
}
}
}
Loading

0 comments on commit dffdeb1

Please sign in to comment.