test(integration): add deterministic NAT topology harness (#14)
quality-gate / quality (push) Failing after 1m3s

This commit is contained in:
KyuubiYoru
2026-07-16 11:50:53 +02:00
parent 7e3be2cad1
commit 2ff7cd6d9d
6 changed files with 1308 additions and 95 deletions
@@ -86,8 +86,10 @@ public sealed class UdpMediatorServiceTests
await service.StopAsync(timeout.Token);
}
[Fact]
public async Task NativeLiteNetLibRequestsIntroduceTheAuthorizedPair()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task NativeLiteNetLibRequestsIntroduceTheAuthorizedPair(bool restartMediator)
{
using CancellationTokenSource timeout = new(TimeSpan.FromSeconds(5));
using JoinAttemptFixture fixture = new();
@@ -103,18 +105,6 @@ public sealed class UdpMediatorServiceTests
fixture.Sessions.Store,
fixture.Sessions.Capabilities,
fixture.Service);
using UdpMediatorService service = new(
Options.Create(new UdpMediatorOptions
{
ListenAddress = IPAddress.Loopback.ToString(),
Port = 0,
MaxDatagramsPerPoll = 8,
PollIntervalMilliseconds = 1,
}),
NullLogger<UdpMediatorService>.Instance,
processor);
await service.StartAsync(timeout.Token);
EventBasedNetListener hostListener = new();
EventBasedNetListener clientListener = new();
NetManager host = new(hostListener) { NatPunchEnabled = true };
@@ -127,53 +117,141 @@ public sealed class UdpMediatorServiceTests
clientPunch.NatIntroductionSuccess += (_, _, ticket) => clientTickets.Add(ticket);
host.NatPunchModule.Init(hostPunch);
client.NatPunchModule.Init(clientPunch);
UdpMediatorService? service = null;
bool serviceStarted = false;
try
{
service = CreateMediator(processor, port: 0);
await service.StartAsync(timeout.Token);
serviceStarted = true;
Assert.True(host.Start(0));
Assert.True(client.Start(0));
IPEndPoint mediator = Assert.IsType<IPEndPoint>(service.LocalEndpoint);
host.NatPunchModule.SendNatIntroduceRequest(
mediator,
NatPunchRequestTokenCodec.Encode(
NatPunchPeerRole.Host,
created.MediationHandle,
hostAttempt.HostPunchCapability));
client.NatPunchModule.SendNatIntroduceRequest(
mediator,
NatPunchRequestTokenCodec.Encode(
NatPunchPeerRole.Client,
created.MediationHandle,
created.ClientPunchCapability));
while ((hostTickets.Count == 0 || clientTickets.Count == 0)
&& !timeout.IsCancellationRequested)
if (restartMediator)
{
host.PollEvents();
host.NatPunchModule.PollEvents();
client.PollEvents();
client.NatPunchModule.PollEvents();
await Task.Delay(5, timeout.Token);
await AssertNativeIntroductionAsync(
service,
host,
client,
hostTickets,
clientTickets,
created,
hostAttempt,
expectedCount: 1,
cancellationToken: timeout.Token);
created = fixture.Create(registration.ListingId, "native-litenet-after-restart");
hostAttempt = fixture.Service.BrowseForHost(
registration.ListingId,
ContractLimits.ContractVersion,
registration.LeaseToken,
ContractLimits.BrowserPageMaxItems,
null).Value!.Items.Single(item => item.AttemptId == created.AttemptId);
int boundPort = Assert.IsType<IPEndPoint>(service.LocalEndpoint).Port;
await service.StopAsync(timeout.Token);
serviceStarted = false;
service.Dispose();
service = null;
service = CreateMediator(processor, boundPort);
await service.StartAsync(timeout.Token);
serviceStarted = true;
Assert.Equal(boundPort, Assert.IsType<IPEndPoint>(service.LocalEndpoint).Port);
}
string hostTicket = Assert.Single(hostTickets.Distinct(StringComparer.Ordinal));
string clientTicket = Assert.Single(clientTickets.Distinct(StringComparer.Ordinal));
Assert.Equal(hostTicket, clientTicket);
Assert.True(NatIntroductionTokenCodec.TryDecode(
hostTicket,
out NatIntroductionToken? introduction));
Assert.NotNull(introduction);
Assert.Equal(created.AttemptId, introduction.AttemptId);
Assert.Equal(43, introduction.ConnectionTicket.Length);
await AssertNativeIntroductionAsync(
service,
host,
client,
hostTickets,
clientTickets,
created,
hostAttempt,
expectedCount: restartMediator ? 2 : 1,
cancellationToken: timeout.Token);
}
finally
{
host.Stop();
client.Stop();
await service.StopAsync(CancellationToken.None);
if (service is not null)
{
try
{
if (serviceStarted)
{
using CancellationTokenSource cleanup = new(TimeSpan.FromSeconds(5));
await service.StopAsync(cleanup.Token);
}
}
finally
{
service.Dispose();
}
}
}
}
private static async Task AssertNativeIntroductionAsync(
UdpMediatorService service,
NetManager host,
NetManager client,
List<string> hostTickets,
List<string> clientTickets,
CreateJoinAttemptResponse created,
HostJoinAttempt hostAttempt,
int expectedCount,
CancellationToken cancellationToken)
{
IPEndPoint mediator = Assert.IsType<IPEndPoint>(service.LocalEndpoint);
host.NatPunchModule.SendNatIntroduceRequest(
mediator,
NatPunchRequestTokenCodec.Encode(
NatPunchPeerRole.Host,
created.MediationHandle,
hostAttempt.HostPunchCapability));
client.NatPunchModule.SendNatIntroduceRequest(
mediator,
NatPunchRequestTokenCodec.Encode(
NatPunchPeerRole.Client,
created.MediationHandle,
created.ClientPunchCapability));
while ((hostTickets.Distinct(StringComparer.Ordinal).Count() < expectedCount
|| clientTickets.Distinct(StringComparer.Ordinal).Count() < expectedCount)
&& !cancellationToken.IsCancellationRequested)
{
host.PollEvents();
host.NatPunchModule.PollEvents();
client.PollEvents();
client.NatPunchModule.PollEvents();
await Task.Delay(5, cancellationToken);
}
List<string> distinctHostTickets = hostTickets.Distinct(StringComparer.Ordinal).ToList();
List<string> distinctClientTickets = clientTickets.Distinct(StringComparer.Ordinal).ToList();
Assert.Equal(expectedCount, distinctHostTickets.Count);
Assert.Equal(expectedCount, distinctClientTickets.Count);
string hostTicket = distinctHostTickets[^1];
string clientTicket = distinctClientTickets[^1];
Assert.Equal(hostTicket, clientTicket);
Assert.True(NatIntroductionTokenCodec.TryDecode(
hostTicket,
out NatIntroductionToken? introduction));
Assert.NotNull(introduction);
Assert.Equal(created.AttemptId, introduction.AttemptId);
Assert.Equal(43, introduction.ConnectionTicket.Length);
}
private static UdpMediatorService CreateMediator(NatMediationProcessor processor, int port) => new(
Options.Create(new UdpMediatorOptions
{
ListenAddress = IPAddress.Loopback.ToString(),
Port = port,
MaxDatagramsPerPoll = 8,
PollIntervalMilliseconds = 1,
}),
NullLogger<UdpMediatorService>.Instance,
processor);
[Fact]
public async Task FrozenV1EnvelopeIsConsumedOnTheLiteNetSocketWithinAmplificationBudget()
{
File diff suppressed because it is too large Load Diff