Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,50 @@ public Task RequestResponse(bool useSsl)
return RequestResponseTest(CreateServer, CreateClient);
}

[Fact]
public async Task ConnectionClosedAfterTlsHandshake()
{
// backlog 1: the timeout sources come from a pool that keeps one
var serverAddr = new IPEndPoint(IPAddress.Loopback, 3790);
await using var server = new TcpServer(serverAddr, 1, CreateLocalMember(), NullLoggerFactory.Instance)
{
ReceiveTimeout = TimeSpan.FromMilliseconds(300),
MemoryAllocator = MemoryAllocator<byte>.Default,
TransmissionBlockSize = 65535,
GracefulShutdownTimeout = 2000,
SslOptions = CreateServerSslOptions()
};
await server.StartAsync(TestToken);

// A completes the TLS handshake and sends nothing: the handshake's timeout source goes back to the pool
using var a = new System.Net.Sockets.Socket(serverAddr.AddressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
await a.ConnectAsync(serverAddr, TestToken);
await using var tls = new SslStream(new System.Net.Sockets.NetworkStream(a, ownsSocket: false));
await tls.AuthenticateAsClientAsync(CreateClientSslOptions(), TestToken);

// B never starts its handshake: it takes the same source from the pool, times out, and the source is disposed
using var b = new System.Net.Sockets.Socket(serverAddr.AddressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
await b.ConnectAsync(serverAddr, TestToken);
Equal(0, await b.ReceiveAsync(new byte[1], TestToken)); // closed by the server after the timeout

// A leaves before its first message: the connection must not return the handshake's source to the pool again
// (it threw ObjectDisposedException from async void HandleConnection, which ended the process)
a.Shutdown(System.Net.Sockets.SocketShutdown.Both);
a.Close();
await Task.Delay(500, TestToken);

// the server still answers
using var client = new TcpClient(CreateLocalMember(), serverAddr)
{
MemoryAllocator = MemoryAllocator<byte>.Default,
RequestTimeout = DefaultTimeout,
ConnectTimeout = DefaultTimeout,
TransmissionBlockSize = 65535,
SslOptions = CreateClientSslOptions(),
};
True((await client.As<IRaftClusterMember>().VoteAsync(42L, 1L, 56L, TestToken)).Value);
}

[Fact]
public Task StressTest()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ ValueTask<Result<PreVoteResult>> ILocalMember.PreVoteAsync(ClusterMemberId sende
int ILocalMember.Version => 0;
}

private protected static ILocalMember CreateLocalMember() => new LocalMember();

private protected delegate IServer ServerFactory(ILocalMember localMember, EndPoint address, TimeSpan timeout);
private protected delegate RaftClusterMember ClientFactory(EndPoint address, ILocalMember localMember, TimeSpan timeout);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ private async void HandleConnection(Socket remoteClient)
await timeoutSource.DisposeAsync().ConfigureAwait(false);
}

// returned to the pool above: the finally below must not return it again
timeoutSource = default;
protocol = new(ssl, MemoryAllocator, transmissionBlockSize);
}

Expand Down
Loading