Table of Contents

Class ConnectionProbe

Namespace
McServerLauncher.Services
Assembly
McServerLauncher.dll

The measurements behind the connection test: a Minecraft status ping, a Bedrock (RakNet) ping, a bare TCP round trip, and a download and an upload to time the line.

public static class ConnectionProbe
Inheritance
ConnectionProbe
Inherited Members

Remarks

Each tells apart one stretch of the way a player's packets travel. The status ping to 127.0.0.1 is the server alone; the TCP round trip to Cloudflare is this machine's internet; the status ping to the tunnel's public address goes out to Playit and back in through the agent, which is the path a player's packets take. Comparing them says where the time goes.

The status ping is the one the game's server list sends (handshake with next state 1, status request, then a ping whose echo is timed), so any server that shows up in the list answers it, whatever its type. Only the echo is timed — the connection and the status JSON are not, as the game itself does.

The speed test uses Cloudflare's public endpoints (speed.cloudflare.com, the ones its own web test calls). No account and no key; what is sent up is random bytes.

Fields

FallbackProtocol

A protocol number to put in the handshake when the server's own is not known: a real one, recent (1.21.1). See PingJavaAsync(string, int, TimeSpan, int, CancellationToken) for why -1 will not do.

public const int FallbackProtocol = 767

Field Value

int

LatencyHost

public const string LatencyHost = "1.1.1.1"

Field Value

string

SpeedHost

public const string SpeedHost = "speed.cloudflare.com"

Field Value

string

Methods

DownloadAsync(long, TimeSpan, CancellationToken)

Downloads up to bytes, for at most limit.

public static Task<Throughput> DownloadAsync(long bytes, TimeSpan limit, CancellationToken ct = default)

Parameters

bytes long
limit TimeSpan
ct CancellationToken

Returns

Task<Throughput>

PingBedrockAsync(IPEndPoint, TimeSpan, CancellationToken)

A RakNet unconnected ping, which is what the Bedrock client sends to fill its server list. Milliseconds, or null when nothing answered in time. The address comes already resolved: see ResolveAsync(string, CancellationToken).

public static Task<double?> PingBedrockAsync(IPEndPoint server, TimeSpan timeout, CancellationToken ct = default)

Parameters

server IPEndPoint
timeout TimeSpan
ct CancellationToken

Returns

Task<double?>

PingJavaAsync(string, int, TimeSpan, int, CancellationToken)

A server-list ping. Null when nothing answered in time.

public static Task<SlpReply?> PingJavaAsync(string host, int port, TimeSpan timeout, int protocol = 767, CancellationToken ct = default)

Parameters

host string

Where to connect, and what the handshake says it connected to. A name, not an IP, when going through Playit: its edge routes by it, and an IP there gets the connection reset.

port int

The port to connect to, also written into the handshake.

timeout TimeSpan

For the whole exchange: connecting, the status and the ping.

protocol int

The protocol number in the handshake. Never -1, the "whatever you speak" that status tools send: Playit's Minecraft edge reads the handshake before passing it on and resets a connection whose protocol is not a real one (found on 2026-10-10 against a live tunnel: -1 was reset every time within a few milliseconds, 776 and 767 answered every time), and a server asked with -1 may report -1 as its own protocol, which is no use either. Asked with a real number, a server answers with its own, so the local ping finds out what the tunnel ping then sends.

ct CancellationToken

Stops it early; a cancellation is not reported as "no answer".

Returns

Task<SlpReply>

ResolveAsync(string, CancellationToken)

What host points to, or null when it does not resolve: asked once and without holding the thread, to be pinged as often as needed.

public static Task<IPAddress?> ResolveAsync(string host, CancellationToken ct = default)

Parameters

host string
ct CancellationToken

Returns

Task<IPAddress>

Remarks

Connect(string, int) resolves the name itself, synchronously, and the test pings eight times from the UI thread: eight lookups that froze the window while each waited on a slow DNS. TCP does not need this — its ConnectAsync resolves asynchronously, and the Java ping must keep the name, which Playit's edge routes by.

ResolveJavaAsync(string, CancellationToken)

Where a Java address really points: host:port as written, or the _minecraft._tcp SRV record, which is how Playit's domains carry their port.

public static Task<(string Host, int Port)> ResolveJavaAsync(string address, CancellationToken ct = default)

Parameters

address string
ct CancellationToken

Returns

Task<(string Host, int Port)>

Remarks

.NET has no SRV lookup of its own, so it is asked of Cloudflare's DNS over HTTPS — the same provider the speed test already talks to. Without an answer the game's default port is what a client would try, so it is what is tried here.

SampleAsync(Func<CancellationToken, Task<double?>>, int, TimeSpan, IProgress<double>?, CancellationToken)

Runs probe count times, a little apart.

public static Task<LatencyStats> SampleAsync(Func<CancellationToken, Task<double?>> probe, int count, TimeSpan gap, IProgress<double>? each = null, CancellationToken ct = default)

Parameters

probe Func<CancellationToken, Task<double?>>
count int
gap TimeSpan
each IProgress<double>
ct CancellationToken

Returns

Task<LatencyStats>

TcpRoundTripAsync(string, int, TimeSpan, CancellationToken)

How long a TCP connection takes to open: one round trip, and nothing else.

public static Task<double?> TcpRoundTripAsync(string host, int port, TimeSpan timeout, CancellationToken ct = default)

Parameters

host string
port int
timeout TimeSpan
ct CancellationToken

Returns

Task<double?>

UploadAsync(long, TimeSpan, CancellationToken)

Uploads up to bytes of random data, for at most limit.

public static Task<Throughput> UploadAsync(long bytes, TimeSpan limit, CancellationToken ct = default)

Parameters

bytes long
limit TimeSpan
ct CancellationToken

Returns

Task<Throughput>