mirror of
https://github.com/Azure/DotNetty.git
synced 2026-10-20 07:37:46 +08:00
395 lines
17 KiB
C#
395 lines
17 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
|
|
namespace DotNetty.Handlers.Tests
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Net.Security;
|
|
using System.Security.Authentication;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Threading.Tasks;
|
|
using DotNetty.Buffers;
|
|
using DotNetty.Common.Concurrency;
|
|
using DotNetty.Handlers.Tls;
|
|
using DotNetty.Tests.Common;
|
|
using DotNetty.Transport.Channels;
|
|
using DotNetty.Transport.Channels.Embedded;
|
|
using Xunit;
|
|
using Xunit.Abstractions;
|
|
|
|
public class TlsHandlerTest : TestBase
|
|
{
|
|
static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(10);
|
|
|
|
public TlsHandlerTest(ITestOutputHelper output)
|
|
: base(output)
|
|
{
|
|
}
|
|
|
|
public static IEnumerable<object[]> GetTlsReadTestData()
|
|
{
|
|
var random = new Random(Environment.TickCount);
|
|
var lengthVariations =
|
|
new[]
|
|
{
|
|
new[] { 1 },
|
|
new[] { 2, 8000, 300 },
|
|
new[] { 100, 0, 1000 },
|
|
new[] { 4 * 1024 - 10, 1, 0, 1 },
|
|
new[] { 0, 24000, 0, 1000 },
|
|
new[] { 0, 4000, 0 },
|
|
new[] { 16 * 1024 - 100 },
|
|
Enumerable.Repeat(0, 30).Select(_ => random.Next(0, 17000)).ToArray()
|
|
};
|
|
var boolToggle = new[] { false, true };
|
|
// TLS 1.0 and TLS 1.1 are deprecated and disabled by default in .NET 6+ and modern Windows.
|
|
// Only test with TLS 1.2 and TLS 1.3 which are supported on all modern platforms.
|
|
var protocols = new[]
|
|
{
|
|
Tuple.Create(SslProtocols.Tls12, SslProtocols.Tls12),
|
|
#if NET6_0_OR_GREATER
|
|
Tuple.Create(SslProtocols.Tls13, SslProtocols.Tls13),
|
|
Tuple.Create(SslProtocols.Tls12 | SslProtocols.Tls13, SslProtocols.Tls12 | SslProtocols.Tls13),
|
|
#endif
|
|
};
|
|
var writeStrategyFactories = new Func<IWriteStrategy>[]
|
|
{
|
|
() => new AsIsWriteStrategy(),
|
|
() => new BatchingWriteStrategy(1, TimeSpan.FromMilliseconds(20), true),
|
|
() => new BatchingWriteStrategy(4096, TimeSpan.FromMilliseconds(20), true),
|
|
() => new BatchingWriteStrategy(32 * 1024, TimeSpan.FromMilliseconds(20), false)
|
|
};
|
|
|
|
return
|
|
from frameLengths in lengthVariations
|
|
from isClient in boolToggle
|
|
from writeStrategyFactory in writeStrategyFactories
|
|
from protocol in protocols
|
|
select new object[] { frameLengths, isClient, writeStrategyFactory(), protocol.Item1, protocol.Item2 };
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(GetTlsReadTestData))]
|
|
public async Task TlsRead(int[] frameLengths, bool isClient, IWriteStrategy writeStrategy, SslProtocols serverProtocol, SslProtocols clientProtocol)
|
|
{
|
|
this.Output.WriteLine($"frameLengths: {string.Join(", ", frameLengths)}");
|
|
this.Output.WriteLine($"isClient: {isClient}");
|
|
this.Output.WriteLine($"writeStrategy: {writeStrategy}");
|
|
this.Output.WriteLine($"serverProtocol: {serverProtocol}");
|
|
this.Output.WriteLine($"clientProtocol: {clientProtocol}");
|
|
|
|
var executor = new SingleThreadEventLoop("test executor", TimeSpan.FromMilliseconds(10));
|
|
|
|
try
|
|
{
|
|
var writeTasks = new List<Task>();
|
|
var pair = await SetupStreamAndChannelAsync(isClient, executor, writeStrategy, serverProtocol, clientProtocol, writeTasks).WithTimeout(TimeSpan.FromSeconds(10));
|
|
IEmbeddedChannel ch = pair.Item1;
|
|
SslStream driverStream = pair.Item2;
|
|
|
|
int randomSeed = Environment.TickCount;
|
|
var random = new Random(randomSeed);
|
|
IByteBuffer expectedBuffer = Unpooled.Buffer(16 * 1024);
|
|
foreach (int len in frameLengths)
|
|
{
|
|
var data = new byte[len];
|
|
random.NextBytes(data);
|
|
expectedBuffer.WriteBytes(data);
|
|
await driverStream.WriteAsync(data, 0, data.Length).WithTimeout(TimeSpan.FromSeconds(5));
|
|
}
|
|
await Task.WhenAll(writeTasks).WithTimeout(TimeSpan.FromSeconds(5));
|
|
IByteBuffer finalReadBuffer = Unpooled.Buffer(16 * 1024);
|
|
|
|
await ReadOutboundAsync(async () => ch.ReadInbound<IByteBuffer>(), expectedBuffer.ReadableBytes, finalReadBuffer, TestTimeout);
|
|
bool isEqual = ByteBufferUtil.Equals(expectedBuffer, finalReadBuffer);
|
|
if (!isEqual)
|
|
{
|
|
Assert.True(isEqual, $"---Expected:\n{ByteBufferUtil.PrettyHexDump(expectedBuffer)}\n---Actual:\n{ByteBufferUtil.PrettyHexDump(finalReadBuffer)}");
|
|
}
|
|
driverStream.Dispose();
|
|
|
|
// Drain any remaining inbound/outbound messages before finishing the channel
|
|
// This can happen due to deferred processing of pending data packets
|
|
while (ch.ReadInbound<object>() != null) { }
|
|
while (ch.ReadOutbound<object>() != null) { }
|
|
|
|
// Finish the channel - may have pending messages due to deferred processing
|
|
Assert.False(ch.Finish());
|
|
}
|
|
finally
|
|
{
|
|
await executor.ShutdownGracefullyAsync(TimeSpan.Zero, TimeSpan.Zero);
|
|
}
|
|
}
|
|
|
|
public static IEnumerable<object[]> GetTlsWriteTestData()
|
|
{
|
|
var random = new Random(Environment.TickCount);
|
|
var lengthVariations =
|
|
new[]
|
|
{
|
|
new[] { 1 },
|
|
new[] { 2, 8000, 300 },
|
|
new[] { 100, 0, 1000 },
|
|
new[] { 4 * 1024 - 10, 1, -1, 0, -1, 1 },
|
|
new[] { 0, 24000, 0, -1, 1000 },
|
|
new[] { 0, 4000, 0 },
|
|
new[] { 16 * 1024 - 100 },
|
|
Enumerable.Repeat(0, 30).Select(_ => random.Next(0, 10) < 2 ? -1 : random.Next(0, 17000)).ToArray()
|
|
};
|
|
var boolToggle = new[] { false, true };
|
|
// TLS 1.0 and TLS 1.1 are deprecated and disabled by default in .NET 6+ and modern Windows.
|
|
// Only test with TLS 1.2 and TLS 1.3 which are supported on all modern platforms.
|
|
var protocols = new[]
|
|
{
|
|
Tuple.Create(SslProtocols.Tls12, SslProtocols.Tls12),
|
|
#if NET6_0_OR_GREATER
|
|
Tuple.Create(SslProtocols.Tls13, SslProtocols.Tls13),
|
|
Tuple.Create(SslProtocols.Tls12 | SslProtocols.Tls13, SslProtocols.Tls12 | SslProtocols.Tls13),
|
|
#endif
|
|
};
|
|
|
|
return
|
|
from frameLengths in lengthVariations
|
|
from isClient in boolToggle
|
|
from protocol in protocols
|
|
select new object[] { frameLengths, isClient, protocol.Item1, protocol.Item2 };
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(GetTlsWriteTestData))]
|
|
public async Task TlsWrite(int[] frameLengths, bool isClient, SslProtocols serverProtocol, SslProtocols clientProtocol)
|
|
{
|
|
this.Output.WriteLine($"frameLengths: {string.Join(", ", frameLengths)}");
|
|
this.Output.WriteLine($"isClient: {isClient}");
|
|
this.Output.WriteLine($"serverProtocol: {serverProtocol}");
|
|
this.Output.WriteLine($"clientProtocol: {clientProtocol}");
|
|
|
|
var writeStrategy = new AsIsWriteStrategy();
|
|
this.Output.WriteLine($"writeStrategy: {writeStrategy}");
|
|
|
|
var executor = new SingleThreadEventLoop("test executor", TimeSpan.FromMilliseconds(10));
|
|
|
|
try
|
|
{
|
|
var writeTasks = new List<Task>();
|
|
var pair = await SetupStreamAndChannelAsync(isClient, executor, writeStrategy, serverProtocol, clientProtocol, writeTasks);
|
|
IEmbeddedChannel ch = pair.Item1;
|
|
SslStream driverStream = pair.Item2;
|
|
|
|
int randomSeed = Environment.TickCount;
|
|
var random = new Random(randomSeed);
|
|
IByteBuffer expectedBuffer = Unpooled.Buffer(16 * 1024);
|
|
foreach (IEnumerable<int> lengths in frameLengths.Split(x => x < 0))
|
|
{
|
|
ch.WriteOutbound(lengths.Select(len =>
|
|
{
|
|
var data = new byte[len];
|
|
random.NextBytes(data);
|
|
expectedBuffer.WriteBytes(data);
|
|
return (object)Unpooled.WrappedBuffer(data);
|
|
}).ToArray());
|
|
}
|
|
|
|
IByteBuffer finalReadBuffer = Unpooled.Buffer(16 * 1024);
|
|
var readBuffer = new byte[16 * 1024 * 10];
|
|
await ReadOutboundAsync(
|
|
async () =>
|
|
{
|
|
int read = await driverStream.ReadAsync(readBuffer, 0, readBuffer.Length);
|
|
return Unpooled.WrappedBuffer(readBuffer, 0, read);
|
|
},
|
|
expectedBuffer.ReadableBytes, finalReadBuffer, TestTimeout);
|
|
bool isEqual = ByteBufferUtil.Equals(expectedBuffer, finalReadBuffer);
|
|
if (!isEqual)
|
|
{
|
|
Assert.True(isEqual, $"---Expected:\n{ByteBufferUtil.PrettyHexDump(expectedBuffer)}\n---Actual:\n{ByteBufferUtil.PrettyHexDump(finalReadBuffer)}");
|
|
}
|
|
driverStream.Dispose();
|
|
|
|
// Drain any remaining inbound/outbound messages before finishing the channel
|
|
// This can happen due to deferred processing of pending data packets
|
|
while (ch.ReadInbound<object>() != null) { }
|
|
while (ch.ReadOutbound<object>() != null) { }
|
|
|
|
// Finish the channel - may have pending messages due to deferred processing
|
|
Assert.False(ch.Finish());
|
|
}
|
|
finally
|
|
{
|
|
await executor.ShutdownGracefullyAsync(TimeSpan.Zero, TimeSpan.Zero);
|
|
}
|
|
}
|
|
|
|
static async Task<Tuple<IEmbeddedChannel, SslStream>> SetupStreamAndChannelAsync(bool isClient, IEventLoop executor, IWriteStrategy writeStrategy, SslProtocols serverProtocol, SslProtocols clientProtocol, List<Task> writeTasks)
|
|
{
|
|
X509Certificate2 tlsCertificate = TestResourceHelper.GetTestCertificate();
|
|
string targetHost = tlsCertificate.GetNameInfo(X509NameType.DnsName, false);
|
|
TlsHandler tlsHandler = isClient ?
|
|
new TlsHandler(stream => new SslStream(stream, true, (sender, certificate, chain, errors) => true), new ClientTlsSettings(clientProtocol, false, new List<X509Certificate>(), targetHost)) :
|
|
new TlsHandler(new ServerTlsSettings(tlsCertificate, false, false, serverProtocol));
|
|
//var ch = new EmbeddedChannel(new LoggingHandler("BEFORE"), tlsHandler, new LoggingHandler("AFTER"));
|
|
|
|
#if NET5_0_OR_GREATER
|
|
IEmbeddedChannel ch = new SingleThreadedEmbeddedChannel(executor, tlsHandler);
|
|
#else
|
|
IEmbeddedChannel ch = new EmbeddedChannel(tlsHandler);
|
|
#endif
|
|
|
|
IByteBuffer readResultBuffer = Unpooled.Buffer(4 * 1024);
|
|
Func<ArraySegment<byte>, Task<int>> readDataFunc = async output =>
|
|
{
|
|
if (writeTasks.Count > 0)
|
|
{
|
|
await Task.WhenAll(writeTasks).WithTimeout(TestTimeout);
|
|
writeTasks.Clear();
|
|
}
|
|
|
|
if (readResultBuffer.ReadableBytes < output.Count)
|
|
{
|
|
if (ch.Active)
|
|
{
|
|
await ReadOutboundAsync(
|
|
async () => ch.ReadOutbound<IByteBuffer>(),
|
|
output.Count - readResultBuffer.ReadableBytes,
|
|
readResultBuffer,
|
|
TestTimeout,
|
|
readResultBuffer.ReadableBytes != 0 ? 0 : 1
|
|
);
|
|
}
|
|
}
|
|
int read = Math.Min(output.Count, readResultBuffer.ReadableBytes);
|
|
readResultBuffer.ReadBytes(output.Array, output.Offset, read);
|
|
return read;
|
|
};
|
|
var mediationStream = new MediationStream(
|
|
output =>
|
|
{
|
|
Task<int> task = executor.SubmitAsync(
|
|
() => readDataFunc(output)
|
|
).Unwrap();
|
|
return task;
|
|
},
|
|
input =>
|
|
{
|
|
Task task = executor.SubmitAsync(
|
|
() => writeStrategy.WriteToChannelAsync(ch, input)
|
|
).Unwrap();
|
|
writeTasks.Add(task);
|
|
return task;
|
|
},
|
|
() =>
|
|
{
|
|
ch.CloseAsync();
|
|
});
|
|
|
|
var driverStream = new SslStream(mediationStream, true, (_1, _2, _3, _4) => true);
|
|
if (isClient)
|
|
{
|
|
await Task.Run(() => driverStream.AuthenticateAsServerAsync(tlsCertificate, false, serverProtocol, false)).WithTimeout(TimeSpan.FromSeconds(5));
|
|
}
|
|
else
|
|
{
|
|
await Task.Run(() => driverStream.AuthenticateAsClientAsync(targetHost, null, clientProtocol, false)).WithTimeout(TimeSpan.FromSeconds(5));
|
|
}
|
|
writeTasks.Clear();
|
|
|
|
return Tuple.Create(ch, driverStream);
|
|
}
|
|
|
|
static Task ReadOutboundAsync(Func<Task<IByteBuffer>> readFunc, int expectedBytes, IByteBuffer result, TimeSpan timeout, int minBytes = -1)
|
|
{
|
|
Stopwatch stopwatch = Stopwatch.StartNew();
|
|
int remaining = expectedBytes;
|
|
if (minBytes < 0) minBytes = expectedBytes;
|
|
if (minBytes > expectedBytes) throw new ArgumentOutOfRangeException("minBytes can not greater than expectedBytes");
|
|
return AssertEx.EventuallyAsync(
|
|
async () =>
|
|
{
|
|
TimeSpan readTimeout = timeout - stopwatch.Elapsed;
|
|
if (readTimeout <= TimeSpan.Zero)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
IByteBuffer output;
|
|
while(true)
|
|
{
|
|
output = await readFunc().WithTimeout(readTimeout);//inbound ? ch.ReadInbound<IByteBuffer>() : ch.ReadOutbound<IByteBuffer>();
|
|
if (output == null)
|
|
break;
|
|
|
|
if (!output.IsReadable())
|
|
{
|
|
output.Release();
|
|
return true;
|
|
}
|
|
|
|
remaining -= output.ReadableBytes;
|
|
minBytes -= output.ReadableBytes;
|
|
result.WriteBytes(output);
|
|
output.Release();
|
|
|
|
if (remaining <= 0)
|
|
return true;
|
|
}
|
|
return minBytes <= 0;
|
|
},
|
|
TimeSpan.FromMilliseconds(10),
|
|
timeout);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void NoAutoReadHandshakeProgresses(bool dropChannelActive)
|
|
{
|
|
var readHandler = new ReadRegisterHandler();
|
|
var ch = new EmbeddedChannel(EmbeddedChannelId.Instance, false, false,
|
|
readHandler,
|
|
TlsHandler.Client("dotnetty.com"),
|
|
new ActivatingHandler(dropChannelActive)
|
|
);
|
|
|
|
ch.Configuration.AutoRead = false;
|
|
ch.Register();
|
|
Assert.False(ch.Configuration.AutoRead);
|
|
Assert.True(ch.WriteOutbound(Unpooled.Empty));
|
|
Assert.True(readHandler.ReadIssued);
|
|
ch.CloseAsync();
|
|
}
|
|
|
|
class ReadRegisterHandler : ChannelHandlerAdapter
|
|
{
|
|
public bool ReadIssued { get; private set; }
|
|
|
|
public override void Read(IChannelHandlerContext context)
|
|
{
|
|
this.ReadIssued = true;
|
|
base.Read(context);
|
|
}
|
|
}
|
|
|
|
class ActivatingHandler : ChannelHandlerAdapter
|
|
{
|
|
bool dropChannelActive;
|
|
|
|
public ActivatingHandler(bool dropChannelActive)
|
|
{
|
|
this.dropChannelActive = dropChannelActive;
|
|
}
|
|
|
|
public override void ChannelActive(IChannelHandlerContext context)
|
|
{
|
|
if (!dropChannelActive)
|
|
{
|
|
context.FireChannelActive();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|