me-web? 0.4: no network failure takes the app down; mobile data read right; the route on the path
- Every network call returns a result whatever fails, not only the exceptions .NET usually throws: on Android the HTTPS client is the platform's own stack, and a network switch mid-request throws others. The outside test as a whole cannot throw; sharing the link falls back to copying it. A last guard (the UI thread, unobserved tasks, Android's raiser) writes errors.log in the app folder and keeps the app running; nothing is sent. - On mobile data the app no longer probes the operator's gateway as if it were a home router: the router card says there is none, a private address on the phone with another public one is the operator's address sharing (CGNAT) and is drawn as its shared exit, the public IPv4 is held by that exit, and the provider's notes are labelled as being about its home lines. These were "router not answering" and "can't tell" on an Iliad phone. - The route's routers appear as stops on the path's wires: the operator's internal ones before its exit, the public ones with their network number towards the internet. - The download speed is timed after the first quarter of the sample, once the connection has ramped up; the path no longer shows the address block's registry country. - Two new strings, drafted in every language. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
1 parent
4b4b37ad8e
commit
20f8b2149f
131 files changed
+416
-37
No files matched your search
@@ -93,13 +93,13 @@ public class CheckRunner(IPlatform platform, ServerProbe server, KnowledgeBase k
|
||||
run.NetworkV4 = table?.Find(Addresses.Parse(run.PublicV4));
|
||||
run.NetworkV6 = table?.Find(Addresses.Parse(run.PublicV6));
|
||||
run.Provider = knowledge.ProviderFor((run.NetworkV4 ?? run.NetworkV6)?.Number ?? 0);
|
||||
Settle(Verdicts.Provider(knowledge, run.NetworkV4, run.NetworkV6, run.PublicV4 is not null || run.PublicV6 is not null));
|
||||
Settle(Verdicts.Provider(knowledge, run.NetworkV4, run.NetworkV6, run.PublicV4 is not null || run.PublicV6 is not null, run.Local?.Active?.Kind == LinkKind.Mobile));
|
||||
});
|
||||
|
||||
var router = Step(CheckId.Router, async () =>
|
||||
{
|
||||
await connection;
|
||||
run.Router = await Gateway.Inspect(platform, run.Local.GatewayV4, ct);
|
||||
run.Router = run.Local?.Active?.Kind == LinkKind.Mobile ? new RouterReport() : await Gateway.Inspect(platform, run.Local?.GatewayV4, ct);
|
||||
Settle(Verdicts.Router(run.Router, run.Local));
|
||||
});
|
||||
|
||||
|
||||
@@ -33,6 +33,18 @@ public class Reach(ServerProbe server, Leftovers leftovers)
|
||||
static readonly int[] Wellknown = [80, 443];
|
||||
|
||||
public async Task<ReachReport> Run(RunReport run, ReachChoice choice, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Attempt(run, choice, ct);
|
||||
}
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
return new ReachReport { Choice = choice, V4Refusal = run.PublicV4 is null ? null : "unreachable", V6Refusal = run.PublicV6 is null ? null : "unreachable" };
|
||||
}
|
||||
}
|
||||
|
||||
async Task<ReachReport> Attempt(RunReport run, ReachChoice choice, CancellationToken ct)
|
||||
{
|
||||
var report = new ReachReport { Choice = choice };
|
||||
if (choice == ReachChoice.Skip) return report;
|
||||
@@ -118,7 +130,7 @@ public class Reach(ServerProbe server, Leftovers leftovers)
|
||||
var read = await stream.ReadAsync(buffer, timeout.Token);
|
||||
return Encoding.ASCII.GetString(buffer, 0, read).Trim() == ProbeProof.For(Convert.FromBase64String(ticket.Key), ticket.Nonce, port);
|
||||
}
|
||||
catch (Exception ex) when (ex is SocketException or IOException or OperationCanceledException && !ct.IsCancellationRequested)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class ServerProbe(HttpClient https, HttpClient plain)
|
||||
var answer = await https.GetFromJsonAsync(origin + Endpoints.Server.GET_Ip, MeWebJsonContext.Default.ViewIp, timeout.Token);
|
||||
return answer?.Address;
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or System.Text.Json.JsonException && !ct.IsCancellationRequested)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public class ServerProbe(HttpClient https, HttpClient plain)
|
||||
using var response = await https.SendAsync(request, timeout.Token);
|
||||
return response.IsSuccessStatusCode || response.StatusCode == HttpStatusCode.NotFound;
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException && !ct.IsCancellationRequested)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -108,7 +108,7 @@ public class ServerProbe(HttpClient https, HttpClient plain)
|
||||
: null;
|
||||
return (null, refusal ?? new ViewRefusal { Reason = $"http-{(int)response.StatusCode}" });
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or System.Text.Json.JsonException && !ct.IsCancellationRequested)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
return (null, new ViewRefusal { Reason = "unreachable" });
|
||||
}
|
||||
@@ -121,7 +121,7 @@ public class ServerProbe(HttpClient https, HttpClient plain)
|
||||
var addresses = await Dns.GetHostAddressesAsync(Endpoints.HostV4, AddressFamily.InterNetwork, ct);
|
||||
return addresses.FirstOrDefault();
|
||||
}
|
||||
catch (SocketException)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -133,7 +133,7 @@ public class ServerProbe(HttpClient https, HttpClient plain)
|
||||
{
|
||||
return (await Dns.GetHostAddressesAsync(host, ct)).Length > 0;
|
||||
}
|
||||
catch (SocketException)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -147,7 +147,7 @@ public class ServerProbe(HttpClient https, HttpClient plain)
|
||||
timeout.CancelAfter(Patience);
|
||||
return await https.GetFromJsonAsync(Endpoints.Base + Endpoints.Server.GET_Info, MeWebJsonContext.Default.ViewInfo, timeout.Token);
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or System.Text.Json.JsonException && !ct.IsCancellationRequested)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -170,7 +170,7 @@ public class ServerProbe(HttpClient https, HttpClient plain)
|
||||
if (response.IsSuccessStatusCode && response.Content.Headers.ContentType?.MediaType == "application/json") return (true, null);
|
||||
return (false, null);
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException && !ct.IsCancellationRequested)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ public static class Verdicts
|
||||
{
|
||||
finding.With("FactPublicV4", v4);
|
||||
var own = local.Active?.Addresses.Any(a => Net.Addresses.SameAddress(a, v4)) == true;
|
||||
finding.WithKey("FactWhoHasIt", own ? "ValueOnThisDevice" : "ValueSharedAtHome");
|
||||
var shared = local.Active?.Kind == LinkKind.Mobile || Net.Addresses.Parse(local.LocalV4) is { } address && Net.Addresses.Classify(address) == AddressKind.Shared;
|
||||
finding.WithKey("FactWhoHasIt", own ? "ValueOnThisDevice" : shared ? "ValueSharedProvider" : "ValueSharedAtHome");
|
||||
}
|
||||
if (v6 is not null)
|
||||
{
|
||||
@@ -92,7 +93,7 @@ public static class Verdicts
|
||||
return finding;
|
||||
}
|
||||
|
||||
public static Finding Provider(KnowledgeBase knowledge, AsnEntry v4, AsnEntry v6, bool anyAddress)
|
||||
public static Finding Provider(KnowledgeBase knowledge, AsnEntry v4, AsnEntry v6, bool anyAddress, bool mobile)
|
||||
{
|
||||
var finding = new Finding { Id = CheckId.Provider };
|
||||
if (!anyAddress)
|
||||
@@ -116,7 +117,7 @@ public static class Verdicts
|
||||
}
|
||||
var profile = knowledge.ProviderFor(main.Number);
|
||||
finding.Outcome = Outcome.Info;
|
||||
finding.Summary = profile is null ? "ProviderUnlisted" : "ProviderListed";
|
||||
finding.Summary = profile is null ? "ProviderUnlisted" : mobile ? "ProviderListedMobile" : "ProviderListed";
|
||||
finding.Args = [profile?.Name ?? Display(main)];
|
||||
finding.With("FactNetwork", Describe(main));
|
||||
if (v6 is not null && v4 is not null && v6.Number != v4.Number) finding.With("FactNetworkIpv6", Describe(v6));
|
||||
@@ -191,6 +192,7 @@ public static class Verdicts
|
||||
if (router?.Gateway is null && localV4 is not null && Net.Addresses.Classify(localV4) is AddressKind.Shared or AddressKind.Private && local.Active?.Kind == LinkKind.Mobile)
|
||||
return NatKind.Shared;
|
||||
var wan = Net.Addresses.Parse(router?.WanAddress);
|
||||
if (wan is null && localV4 is not null && Net.Addresses.Classify(localV4) == AddressKind.Shared) return NatKind.Shared;
|
||||
if (wan is null) return NatKind.Unknown;
|
||||
if (Net.Addresses.SameAddress(router.WanAddress, v4)) return NatKind.HomeRouter;
|
||||
return Net.Addresses.Classify(wan) switch
|
||||
|
||||
@@ -98,7 +98,7 @@ public static partial class MailChecks
|
||||
}
|
||||
anyAnswer = true;
|
||||
}
|
||||
catch (Exception ex) when (ex is SocketException or IOException or OperationCanceledException && !ct.IsCancellationRequested)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public static class Route
|
||||
IsDestination = reply.Status == IPStatus.Success,
|
||||
};
|
||||
}
|
||||
catch (Exception ex) when (ex is PingException or PlatformNotSupportedException or InvalidOperationException or NotSupportedException)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -34,10 +34,22 @@ public static class SpeedCheck
|
||||
response.EnsureSuccessStatusCode();
|
||||
await using var body = await response.Content.ReadAsStreamAsync(timeout.Token);
|
||||
var buffer = new byte[64 * 1024];
|
||||
var warm = report.DownBytes / 4;
|
||||
long total = 0;
|
||||
long warmBytes = 0;
|
||||
var warmAt = TimeSpan.Zero;
|
||||
int read;
|
||||
while ((read = await body.ReadAsync(buffer, timeout.Token)) > 0) total += read;
|
||||
report.DownMbps = Mbps(total, clock.Elapsed);
|
||||
while ((read = await body.ReadAsync(buffer, timeout.Token)) > 0)
|
||||
{
|
||||
total += read;
|
||||
if (warmBytes > 0 || total < warm) continue;
|
||||
warmBytes = total;
|
||||
warmAt = clock.Elapsed;
|
||||
}
|
||||
var steady = clock.Elapsed - warmAt;
|
||||
report.DownMbps = warmBytes > 0 && total > warmBytes && steady > TimeSpan.FromMilliseconds(50)
|
||||
? Mbps(total - warmBytes, steady)
|
||||
: Mbps(total, clock.Elapsed);
|
||||
}
|
||||
|
||||
var payload = new byte[report.UpBytes];
|
||||
@@ -48,7 +60,7 @@ public static class SpeedCheck
|
||||
if (response.IsSuccessStatusCode) report.UpMbps = Mbps(payload.Length, clock.Elapsed);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or IOException && !ct.IsCancellationRequested)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
}
|
||||
return report;
|
||||
|
||||
@@ -57,7 +57,7 @@ public static class Gateway
|
||||
report.UpnpService = service.Type;
|
||||
await ExternalAddress(control, service.Type, report, ct);
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or XmlException or TaskCanceledException && !ct.IsCancellationRequested)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public static class PortMapper
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or XmlException or SocketException or FormatException or TaskCanceledException && !ct.IsCancellationRequested)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,7 @@ public static class PortMapper
|
||||
}, 0);
|
||||
if (error != 725) return (null, error);
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or XmlException or TaskCanceledException && !ct.IsCancellationRequested)
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
return (null, -1);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user