Skip to content

Commit 7ab5d6f

Browse files
feat: harden route discovery and session exports
1 parent a8cfe8e commit 7ab5d6f

6 files changed

Lines changed: 219 additions & 16 deletions

WinMTRDialog-exporter.cpp

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ namespace {
5353
case WinMTRProbeOutcome::scheduler_skipped: return L"scheduler_skipped";
5454
case WinMTRProbeOutcome::cached: return L"cached";
5555
case WinMTRProbeOutcome::late_discarded: return L"late_discarded";
56+
case WinMTRProbeOutcome::post_destination_discarded:
57+
return L"post_destination_discarded";
5658
}
5759
return L"none";
5860
}
@@ -88,6 +90,20 @@ struct ExportRow final {
8890
return result;
8991
}
9092

93+
[[nodiscard]] std::wstring isoUtcTimestamp(std::uint64_t unixMilliseconds)
94+
{
95+
if (unixMilliseconds == 0) return {};
96+
constexpr std::uint64_t windowsEpochOffset = 116'444'736'000'000'000ull;
97+
ULARGE_INTEGER ticks{};
98+
ticks.QuadPart = windowsEpochOffset + unixMilliseconds * 10'000ull;
99+
FILETIME fileTime{ ticks.LowPart, ticks.HighPart };
100+
SYSTEMTIME utc{};
101+
if (!FileTimeToSystemTime(&fileTime, &utc)) return {};
102+
return std::format(L"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
103+
utc.wYear, utc.wMonth, utc.wDay, utc.wHour, utc.wMinute, utc.wSecond,
104+
utc.wMilliseconds);
105+
}
106+
91107
[[nodiscard]] std::wstring primaryHost(const s_nethost& hop)
92108
{
93109
auto name = hop.getName();
@@ -138,7 +154,13 @@ struct ExportRow final {
138154
const auto headers = exportHeaders();
139155
const CString targetLabel = localized(IDS_EXPORT_TARGET_LABEL);
140156
std::wostringstream out;
141-
out << targetLabel.GetString() << L"" << snapshot.target << L"\r\n";
157+
out << targetLabel.GetString() << L"" << snapshot.target << L"\r\n"
158+
<< L"Started UTC\t" << isoUtcTimestamp(snapshot.started_at_unix_ms) << L"\r\n"
159+
<< L"Ended UTC\t";
160+
if (snapshot.ended_at_unix_ms != 0) {
161+
out << isoUtcTimestamp(snapshot.ended_at_unix_ms);
162+
}
163+
out << L"\r\nDuration (ms)\t" << snapshot.duration_ms << L"\r\n";
142164
for (size_t index = 0; index < headers.size(); ++index) {
143165
if (index != 0) out << L'\t';
144166
out << headers[index];
@@ -186,7 +208,14 @@ struct ExportRow final {
186208
}
187209
out << L"<h1>" << htmlEscape(reportTitle.GetString()) << L"</h1><p>"
188210
<< htmlEscape(targetLabel.GetString()) << L":<code>" << htmlEscape(snapshot.target)
189-
<< L"</code></p><table><thead><tr>";
211+
<< L"</code></p><dl><dt>Started UTC</dt><dd>"
212+
<< isoUtcTimestamp(snapshot.started_at_unix_ms)
213+
<< L"</dd><dt>Ended UTC</dt><dd>";
214+
if (snapshot.ended_at_unix_ms != 0) {
215+
out << isoUtcTimestamp(snapshot.ended_at_unix_ms);
216+
}
217+
out << L"</dd><dt>Duration (ms)</dt><dd>" << snapshot.duration_ms
218+
<< L"</dd></dl><table><thead><tr>";
190219
for (const auto header : headers) out << L"<th>" << header << L"</th>";
191220
out << L"</tr></thead><tbody>";
192221
for (const auto& row : makeRows(snapshot)) {
@@ -215,12 +244,18 @@ struct ExportRow final {
215244
{
216245
const auto headers = exportHeaders();
217246
std::wostringstream out;
247+
out << L"started_at_utc,ended_at_utc,duration_ms,";
218248
for (size_t index = 0; index < headers.size(); ++index) {
219249
if (index != 0) out << L',';
220250
out << csvCell(headers[index]);
221251
}
222252
out << L"\r\n";
223253
for (const auto& row : makeRows(snapshot)) {
254+
out << csvCell(isoUtcTimestamp(snapshot.started_at_unix_ms)) << L',';
255+
if (snapshot.ended_at_unix_ms != 0) {
256+
out << csvCell(isoUtcTimestamp(snapshot.ended_at_unix_ms));
257+
}
258+
out << L',' << snapshot.duration_ms << L',';
224259
for (size_t index = 0; index < row.cells.size(); ++index) {
225260
if (index != 0) out << L',';
226261
out << csvCell(row.cells[index]);
@@ -258,7 +293,14 @@ struct ExportRow final {
258293
<< L"\r\n \"statistics\": {\"loss\":\"timed_out/completed\","
259294
<< L"\"stddev\":\"sample_standard_deviation\","
260295
<< L"\"jitter\":\"ewma_absolute_consecutive_delta_alpha_1_16\"},"
261-
<< L"\r\n \"target\": \"" << jsonEscape(snapshot.target) << L"\",\r\n \"hops\": [";
296+
<< L"\r\n \"target\": \"" << jsonEscape(snapshot.target) << L"\","
297+
<< L"\r\n \"started_at_utc\": \""
298+
<< isoUtcTimestamp(snapshot.started_at_unix_ms) << L"\","
299+
<< L"\r\n \"ended_at_utc\": ";
300+
if (snapshot.ended_at_unix_ms == 0) out << L"null";
301+
else out << L'\"' << isoUtcTimestamp(snapshot.ended_at_unix_ms) << L'\"';
302+
out << L",\r\n \"duration_ms\": " << snapshot.duration_ms << L','
303+
<< L"\r\n \"hops\": [";
262304
for (size_t hopIndex = 0; hopIndex < snapshot.hops.size(); ++hopIndex) {
263305
const auto& hop = snapshot.hops[hopIndex];
264306
if (hopIndex != 0) out << L',';
@@ -276,6 +318,8 @@ struct ExportRow final {
276318
<< L"\"scheduler_skipped\":" << hop.scheduler_skipped << L','
277319
<< L"\"cache_skipped\":" << hop.cache_skipped << L','
278320
<< L"\"late_completions\":" << hop.late_completions << L','
321+
<< L"\"post_destination_completions\":"
322+
<< hop.post_destination_completions << L','
279323
<< L"\"scheduler_late_slots\":" << hop.scheduler_late_slots << L','
280324
<< L"\"scheduler_lateness_total_ms\":"
281325
<< hop.scheduler_lateness_total_ms << L','
@@ -296,7 +340,11 @@ struct ExportRow final {
296340
for (size_t responderIndex = 0; responderIndex < hop.responders.size(); ++responderIndex) {
297341
const auto& responder = hop.responders[responderIndex];
298342
if (responderIndex != 0) out << L',';
299-
out << L"{\"host\":\"" << jsonEscape(responder.getName()) << L"\","
343+
out << L"{\"id\":\"" << std::format(L"{:016x}", responder.stable_id)
344+
<< L"\","
345+
<< L"\"hit_count\":" << responder.hit_count << L','
346+
<< L"\"last_seen_sequence\":" << responder.last_seen_sequence << L','
347+
<< L"\"host\":\"" << jsonEscape(responder.getName()) << L"\","
300348
<< L"\"ip\":\"" << jsonEscape(addr_to_string(responder.addr)) << L"\","
301349
<< L"\"country\":\"" << jsonEscape(responder.country) << L"\","
302350
<< L"\"asn\":\"" << jsonEscape(responder.asn) << L"\","

WinMTRNet-ClassDef.ixx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ export struct WinMTRTraceSnapshot final {
5454
std::uint64_t session_id = 0;
5555
std::uint64_t data_epoch = 0;
5656
std::uint64_t revision = 0;
57+
std::uint64_t started_at_unix_ms = 0;
58+
std::uint64_t ended_at_unix_ms = 0;
59+
std::uint64_t duration_ms = 0;
5760
std::wstring target;
5861
SOCKADDR_INET target_address = {};
5962
ADDRESS_FAMILY address_family = AF_UNSPEC;
@@ -151,6 +154,10 @@ private:
151154
std::uint64_t reply_sequence = 0;
152155
std::uint64_t completed_cycles = 0;
153156
std::uint64_t data_revision = 0;
157+
std::uint64_t session_started_at_unix_ms = 0;
158+
std::uint64_t session_ended_at_unix_ms = 0;
159+
std::uint64_t session_started_tick = 0;
160+
std::uint64_t session_ended_tick = 0;
154161
unsigned session_start_ttl = WinMTRUtils::DEFAULT_START_TTL;
155162
unsigned display_max_ttl = 0;
156163
WinMTRTraceOptions session_options;
@@ -182,6 +189,8 @@ private:
182189
void commitSchedulerSkipped(unsigned ttl, std::uint64_t expected_epoch) noexcept;
183190
void commitCacheSkipped(unsigned ttl, std::uint64_t expected_epoch) noexcept;
184191
void commitLateCompletion(unsigned ttl, std::uint64_t expected_epoch) noexcept;
192+
void commitPostDestinationCompletion(unsigned ttl,
193+
std::uint64_t expected_epoch) noexcept;
185194
void commitReply(unsigned ttl, const SOCKADDR_INET& responder, unsigned round_trip_ms,
186195
std::uint64_t cycle, std::uint64_t tick, std::uint64_t expected_session,
187196
std::uint64_t expected_epoch, WinMTRProbeOutcome outcome,

WinMTRNet-Getters.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,20 @@ WinMTRTraceSnapshot WinMTRNet::getTraceSnapshot() const
4646
snapshot.session_id = session_id.load(std::memory_order_relaxed);
4747
snapshot.data_epoch = data_epoch.load(std::memory_order_relaxed);
4848
snapshot.revision = data_revision;
49+
snapshot.started_at_unix_ms = session_started_at_unix_ms;
50+
snapshot.ended_at_unix_ms = session_ended_at_unix_ms;
4951
snapshot.target = target_name;
5052
snapshot.target_address = last_remote_addr;
5153
snapshot.address_family = last_remote_addr.si_family;
5254
snapshot.start_ttl = session_start_ttl;
5355
snapshot.display_max_ttl = display_max_ttl;
5456
snapshot.tracing = tracing.load(std::memory_order_relaxed);
57+
const auto duration_end = snapshot.tracing
58+
? GetTickCount64()
59+
: session_ended_tick;
60+
if (session_started_tick != 0 && duration_end >= session_started_tick) {
61+
snapshot.duration_ms = duration_end - session_started_tick;
62+
}
5563

5664
if (display_max_ttl < session_start_ttl || display_max_ttl == 0) {
5765
return snapshot;

WinMTRNet-Tracing.cpp

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ struct parsed_reply final {
111111
std::chrono::steady_clock::now().time_since_epoch()).count();
112112
}
113113

114+
[[nodiscard]] std::uint64_t unix_now_ms() noexcept
115+
{
116+
return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
117+
std::chrono::system_clock::now().time_since_epoch()).count());
118+
}
119+
114120
[[nodiscard]] WinMTRProbeOutcome classify_probe_outcome(DWORD status) noexcept
115121
{
116122
switch (status) {
@@ -144,6 +150,13 @@ struct parsed_reply final {
144150
&& outcome != WinMTRProbeOutcome::local_error;
145151
}
146152

153+
[[nodiscard]] bool is_terminal_destination_outcome(
154+
WinMTRProbeOutcome outcome) noexcept
155+
{
156+
return outcome == WinMTRProbeOutcome::echo_reply
157+
|| outcome == WinMTRProbeOutcome::destination_unreachable;
158+
}
159+
147160
[[nodiscard]] std::size_t reply_buffer_size(ADDRESS_FAMILY family, std::size_t request_size) noexcept
148161
{
149162
const auto reply_header = family == AF_INET
@@ -409,7 +422,7 @@ void CALLBACK probe_work_callback(PTP_CALLBACK_INSTANCE, void* context, PTP_WORK
409422
if (is_usable_trace_reply(request->outcome)
410423
&& isValidAddress(request->parsed.address)) {
411424
request->completion_kind = winmtr::probe::CompletionKind::reply;
412-
request->destination_reply = request->parsed.status == IP_SUCCESS
425+
request->destination_reply = is_terminal_destination_outcome(request->outcome)
413426
&& same_network_address(request->parsed.address, request->destination);
414427
}
415428
else if (request->probe.issued
@@ -515,8 +528,12 @@ WinMTRTraceResult WinMTRNet::DoTrace(std::stop_token stop_token, SOCKADDR_INET a
515528
bool session_had_usable_reply = false;
516529
bool stopping = false;
517530
bool exploration_cycle = true;
531+
bool initial_discovery = true;
518532
unsigned highest_response_ttl = 0;
519533
unsigned destination_ttl = 0;
534+
unsigned stable_ceiling = initial_ceiling;
535+
unsigned shrink_candidate = 0;
536+
unsigned shrink_confirmations = 0;
520537

521538
const auto notify_changed = [this] {
522539
if (options != nullptr) options->notifyTraceDataChanged();
@@ -540,15 +557,42 @@ WinMTRTraceResult WinMTRNet::DoTrace(std::stop_token stop_token, SOCKADDR_INET a
540557
? std::max(mandatory_ttl, destination_ttl)
541558
: std::min(trace_options.max_hops,
542559
std::max(mandatory_ttl, tail_origin + trace_options.unknown_host_limit));
543-
scheduler.set_last_ttl(normal_ceiling, monotonic_now());
560+
if (initial_discovery) {
561+
stable_ceiling = normal_ceiling;
562+
initial_discovery = false;
563+
shrink_candidate = 0;
564+
shrink_confirmations = 0;
565+
}
566+
else if (normal_ceiling < stable_ceiling) {
567+
if (shrink_candidate == normal_ceiling) {
568+
++shrink_confirmations;
569+
}
570+
else {
571+
shrink_candidate = normal_ceiling;
572+
shrink_confirmations = 1;
573+
}
574+
if (shrink_confirmations >= WinMTRUtils::PATH_SHRINK_CONFIRMATIONS) {
575+
stable_ceiling = normal_ceiling;
576+
shrink_candidate = 0;
577+
shrink_confirmations = 0;
578+
}
579+
}
580+
else {
581+
stable_ceiling = normal_ceiling;
582+
shrink_candidate = 0;
583+
shrink_confirmations = 0;
584+
}
585+
scheduler.set_last_ttl(stable_ceiling, monotonic_now());
544586
exploration_cycle = false;
545587
}
546588
if (reported_cycles % WinMTRUtils::PATH_EXPLORATION_PERIOD == 0
547-
&& scheduler.active_last_ttl() < trace_options.max_hops) {
589+
&& stable_ceiling < trace_options.max_hops) {
548590
exploration_cycle = true;
549591
highest_response_ttl = 0;
550592
destination_ttl = 0;
551-
scheduler.set_last_ttl(trace_options.max_hops, monotonic_now());
593+
const auto frontier_ceiling = std::min(trace_options.max_hops,
594+
stable_ceiling + WinMTRUtils::PATH_EXPLORATION_FRONTIER_TTLS);
595+
scheduler.set_last_ttl(frontier_ceiling, monotonic_now());
552596
}
553597
}
554598
};
@@ -562,8 +606,12 @@ WinMTRTraceResult WinMTRNet::DoTrace(std::stop_token stop_token, SOCKADDR_INET a
562606
session_reached_destination = false;
563607
session_had_usable_reply = false;
564608
exploration_cycle = true;
609+
initial_discovery = true;
565610
highest_response_ttl = 0;
566611
destination_ttl = 0;
612+
stable_ceiling = initial_ceiling;
613+
shrink_candidate = 0;
614+
shrink_confirmations = 0;
567615
scheduler.restart(current_epoch, now);
568616
scheduler.set_last_ttl(initial_ceiling, now);
569617
}
@@ -573,6 +621,23 @@ WinMTRTraceResult WinMTRNet::DoTrace(std::stop_token stop_token, SOCKADDR_INET a
573621
if (found == requests.end()) continue;
574622
const auto disposition = scheduler.complete(request->token,
575623
request->completion_kind, request->completed_at);
624+
const auto accepted_after_destination = destination_ttl != 0
625+
&& request->token.ttl > std::max(mandatory_ttl, destination_ttl)
626+
&& (disposition == winmtr::probe::CompletionDisposition::accepted_reply
627+
|| disposition == winmtr::probe::CompletionDisposition::accepted_timeout
628+
|| disposition == winmtr::probe::CompletionDisposition::accepted_local_error
629+
|| disposition
630+
== winmtr::probe::CompletionDisposition::late_discarded_after_timeout);
631+
if (accepted_after_destination) {
632+
commitPostDestinationCompletion(request->token.ttl, request->token.epoch);
633+
if (disposition
634+
== winmtr::probe::CompletionDisposition::late_discarded_after_timeout) {
635+
commitLateCompletion(request->token.ttl, request->token.epoch);
636+
}
637+
notify_changed();
638+
requests.erase(found);
639+
continue;
640+
}
576641
switch (disposition) {
577642
case winmtr::probe::CompletionDisposition::accepted_reply: {
578643
commitReply(request->token.ttl, request->parsed.address,
@@ -626,7 +691,13 @@ WinMTRTraceResult WinMTRNet::DoTrace(std::stop_token stop_token, SOCKADDR_INET a
626691
}
627692
if (!stopping) {
628693
for (const auto& expired : scheduler.expire(now)) {
629-
commitTimeout(expired.ttl, expired.epoch);
694+
if (destination_ttl != 0
695+
&& expired.ttl > std::max(mandatory_ttl, destination_ttl)) {
696+
commitPostDestinationCompletion(expired.ttl, expired.epoch);
697+
}
698+
else {
699+
commitTimeout(expired.ttl, expired.epoch);
700+
}
630701
notify_changed();
631702
}
632703

@@ -733,6 +804,10 @@ void WinMTRNet::beginSession(const SOCKADDR_INET& address, std::wstring target,
733804
display_max_ttl = 0;
734805
reply_sequence = 0;
735806
completed_cycles = 0;
807+
session_started_at_unix_ms = unix_now_ms();
808+
session_ended_at_unix_ms = 0;
809+
session_started_tick = GetTickCount64();
810+
session_ended_tick = 0;
736811
++data_revision;
737812
for (unsigned index = 0; index < host.size(); ++index) {
738813
host[index].reset(index + 1);
@@ -741,9 +816,12 @@ void WinMTRNet::beginSession(const SOCKADDR_INET& address, std::wstring target,
741816

742817
void WinMTRNet::finishSession(std::uint64_t expected_session) noexcept
743818
{
744-
if (session_id.load(std::memory_order_acquire) == expected_session) {
745-
tracing.store(false, std::memory_order_release);
746-
}
819+
std::scoped_lock lock(ghMutex);
820+
if (session_id.load(std::memory_order_relaxed) != expected_session) return;
821+
session_ended_at_unix_ms = unix_now_ms();
822+
session_ended_tick = GetTickCount64();
823+
tracing.store(false, std::memory_order_release);
824+
++data_revision;
747825
}
748826

749827
void WinMTRNet::ResetHops() noexcept
@@ -876,6 +954,16 @@ void WinMTRNet::commitLateCompletion(unsigned ttl,
876954
++data_revision;
877955
}
878956

957+
void WinMTRNet::commitPostDestinationCompletion(unsigned ttl,
958+
std::uint64_t expected_epoch) noexcept
959+
{
960+
if (ttl == 0 || ttl > host.size()) return;
961+
std::scoped_lock lock(ghMutex);
962+
if (data_epoch.load(std::memory_order_relaxed) != expected_epoch) return;
963+
host[ttl - 1].notePostDestinationCompletion();
964+
++data_revision;
965+
}
966+
879967
void WinMTRNet::commitReply(unsigned ttl, const SOCKADDR_INET& responder,
880968
unsigned round_trip_ms, std::uint64_t cycle, std::uint64_t tick,
881969
std::uint64_t expected_session, std::uint64_t expected_epoch,

0 commit comments

Comments
 (0)