2 Commits
Author SHA1 Message Date
thepraandClaude Opus 5.5 5a7fbc4c52 me-web? 0.5.2: findings are offered for sharing once the last measurement is in
Build / Desktop, server and tools (push) Successful in 1m55s
Release / Every platform, the server, the release (push) Successful in 7m30s
- The offer to share now appears when the two-minute test of how long an unused connection
  stays open has finished, so a shared report carries its answer instead of "measuring".
- Starting a new check while that test is still running no longer lets its late answer
  land on the new check's card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
2026-10-15 11:51:11 +02:00
thepraandClaude Opus 5.5 6623c6f9f1 me-web? 0.5.1: the route from the server back to you arrives
Build / Desktop, server and tools (push) Successful in 1m39s
Release / Every platform, the server, the release (push) Successful in 8m9s
- With the test from outside, the route from the publisher's server back to your address
  came back empty: the trace program holds its output until it ends, and it was stopped
  first while waiting on routers that never answer. It now reports each router as soon as it
  answers and stops after three silent ones, so the routers in front of your provider's exit
  reach the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
2026-10-15 11:36:14 +02:00
3 changed files with 20 additions and 5 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>0.5.0</Version> <Version>0.5.2</Version>
<Product>me-web?</Product> <Product>me-web?</Product>
<Company>Arasaka di Hvozdikov Yevheniy</Company> <Company>Arasaka di Hvozdikov Yevheniy</Company>
<Copyright>© 2026 Arasaka di Hvozdikov Yevheniy</Copyright> <Copyright>© 2026 Arasaka di Hvozdikov Yevheniy</Copyright>
+11 -2
View File
@@ -45,6 +45,8 @@ public partial class MainViewModel : ObservableObject
readonly Leftovers leftovers; readonly Leftovers leftovers;
readonly Submissions submissions; readonly Submissions submissions;
RunReport lastRun; RunReport lastRun;
int checks;
bool sent;
bool revealing; bool revealing;
bool instant; bool instant;
@@ -198,15 +200,20 @@ public partial class MainViewModel : ObservableObject
HistoryCleared = false; HistoryCleared = false;
AskReach = false; AskReach = false;
OfferShare = false; OfferShare = false;
sent = false;
ShareResult = null; ShareResult = null;
var generation = ++checks;
foreach (var card in Cards) card.Finding = card.Id == CheckId.Reach ? null : Finding.Running(card.Id); foreach (var card in Cards) card.Finding = card.Id == CheckId.Reach ? null : Finding.Running(card.Id);
Uses.Clear(); Uses.Clear();
ResetPath(platform.Device); ResetPath(platform.Device);
try try
{ {
lastRun = await runner.Run((finding, run) => Dispatcher.UIThread.Post(() => Apply(finding, run)), ct); lastRun = await runner.Run((finding, run) => Dispatcher.UIThread.Post(() =>
{
if (generation == checks) Apply(finding, run);
}), ct);
ShowUses(lastRun); ShowUses(lastRun);
OfferShare = true; OfferShare = lastRun.Stun?.Ports?.LifetimeDone != false;
OnPropertyChanged(nameof(CanOpenPorts)); OnPropertyChanged(nameof(CanOpenPorts));
AskReach = lastRun.PublicV4 is not null || lastRun.PublicV6 is not null; AskReach = lastRun.PublicV4 is not null || lastRun.PublicV6 is not null;
if (!AskReach) SettleReach(null); if (!AskReach) SettleReach(null);
@@ -296,6 +303,7 @@ public partial class MainViewModel : ObservableObject
Shared.Add(new SubmissionViewModel(submission, Unshare)); Shared.Add(new SubmissionViewModel(submission, Unshare));
OnPropertyChanged(nameof(ShowShared)); OnPropertyChanged(nameof(ShowShared));
ShareResult = Localizer.Instance.Format("ShareSent", shared.Id, Texts.Date(shared.KeepUntil)); ShareResult = Localizer.Instance.Format("ShareSent", shared.Id, Texts.Date(shared.KeepUntil));
sent = true;
OfferShare = false; OfferShare = false;
} }
finally finally
@@ -474,6 +482,7 @@ public partial class MainViewModel : ObservableObject
var ports = run.Stun?.Ports; var ports = run.Stun?.Ports;
if (exit is not null && run.Nat == NatKind.Shared && run.PublicV4 is not null && ports?.Pattern is PortPattern.InOrder or PortPattern.Block) if (exit is not null && run.Nat == NatKind.Shared && run.PublicV4 is not null && ports?.Pattern is PortPattern.InOrder or PortPattern.Block)
exit.Details = Lines(Address($"{run.PublicV4}:{ports.Low}–{ports.High}")); exit.Details = Lines(Address($"{run.PublicV4}:{ports.Low}–{ports.High}"));
if (ports?.LifetimeDone == true && run == lastRun && !IsChecking && !sent) OfferShare = true;
break; break;
case CheckId.Addresses: case CheckId.Addresses:
var internet = Node(NodeRole.Internet); var internet = Node(NodeRole.Internet);
+8 -2
View File
@@ -95,18 +95,21 @@ public partial class ProbeService(IOptionsMonitor<ProbeOptions> options, ILogger
await tracing.WaitAsync(ct); await tracing.WaitAsync(ct);
try try
{ {
var info = new ProcessStartInfo("tracepath") { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false }; var info = new ProcessStartInfo("stdbuf") { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false };
foreach (var argument in new[] { "-n", "-m", "16", target.ToString() }) info.ArgumentList.Add(argument); foreach (var argument in new[] { "-oL", "tracepath", "-n", "-m", "16", target.ToString() }) info.ArgumentList.Add(argument);
using var process = Process.Start(info); using var process = Process.Start(info);
if (process is null) return hops; if (process is null) return hops;
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct); using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
timeout.CancelAfter(TimeSpan.FromSeconds(25)); timeout.CancelAfter(TimeSpan.FromSeconds(25));
try try
{ {
var silent = 0;
while (await process.StandardOutput.ReadLineAsync(timeout.Token) is { } line) while (await process.StandardOutput.ReadLineAsync(timeout.Token) is { } line)
{ {
if (SilentLine().IsMatch(line) && ++silent >= 3) break;
var match = HopLine().Match(line); var match = HopLine().Match(line);
if (!match.Success || !IPAddress.TryParse(match.Groups[2].Value, out var address)) continue; if (!match.Success || !IPAddress.TryParse(match.Groups[2].Value, out var address)) continue;
silent = 0;
var distance = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); var distance = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
if (hops.Any(h => h.Distance == distance)) continue; if (hops.Any(h => h.Distance == distance)) continue;
if (!IsPublic(address) || Refuse(address) is not null) continue; if (!IsPublic(address) || Refuse(address) is not null) continue;
@@ -148,6 +151,9 @@ public partial class ProbeService(IOptionsMonitor<ProbeOptions> options, ILogger
[GeneratedRegex(@"^\s*(\d+):\s+([0-9a-fA-F.:]+)\s+([\d.]+)ms")] [GeneratedRegex(@"^\s*(\d+):\s+([0-9a-fA-F.:]+)\s+([\d.]+)ms")]
internal static partial Regex HopLine(); internal static partial Regex HopLine();
[GeneratedRegex(@"^\s*\d+:\s+no reply")]
internal static partial Regex SilentLine();
async Task<ProbeOutcome> Knock(Ticket ticket, int port, CancellationToken ct) async Task<ProbeOutcome> Knock(Ticket ticket, int port, CancellationToken ct)
{ {
await outbound.WaitAsync(ct); await outbound.WaitAsync(ct);