Skip to content

Commit 0c82116

Browse files
test: verify probe wire parameters
1 parent 2b7aa3a commit 0c82116

4 files changed

Lines changed: 90 additions & 28 deletions

File tree

WinMTR.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,6 +1416,7 @@
14161416
<ClInclude Include="WinMTRMain.h" />
14171417
<ClInclude Include="WinMTRNetworkData.h" />
14181418
<ClInclude Include="WinMTRProperties.h" />
1419+
<ClInclude Include="WinMTRProbeParameters.h" />
14191420
<ClInclude Include="WinMTRRoutePolicy.h" />
14201421
<ClInclude Include="WinMTRSerialization.h" />
14211422
<ClCompile Include="WinMTRUtils.ixx">

WinMTRNet-Tracing.cpp

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module;
1313
#pragma warning (disable : 4005)
1414
#include "targetver.h"
1515
#include "WinMTRRoutePolicy.h"
16+
#include "WinMTRProbeParameters.h"
1617
#define WIN32_LEAN_AND_MEAN
1718
#define VC_EXTRALEAN
1819
#define NOMCX
@@ -167,27 +168,6 @@ struct parsed_reply final {
167168
return reply_header + sizeof(IO_STATUS_BLOCK) + request_size + 8u;
168169
}
169170

170-
[[nodiscard]] std::vector<std::byte> make_payload(const WinMTRTraceOptions& options,
171-
std::uint64_t& random_state)
172-
{
173-
std::vector<std::byte> payload(options.packet_size);
174-
if (options.payload_pattern >= 0) {
175-
std::fill(payload.begin(), payload.end(),
176-
static_cast<std::byte>(options.payload_pattern));
177-
return payload;
178-
}
179-
180-
// Small xorshift generator: no runtime dependency and random mode changes
181-
// every byte without claiming cryptographic randomness.
182-
for (auto& value : payload) {
183-
random_state ^= random_state << 13u;
184-
random_state ^= random_state >> 7u;
185-
random_state ^= random_state << 17u;
186-
value = static_cast<std::byte>(random_state & 0xffu);
187-
}
188-
return payload;
189-
}
190-
191171
[[nodiscard]] unique_icmp_handle create_icmp_handle(ADDRESS_FAMILY family) noexcept
192172
{
193173
if (family == AF_INET) {
@@ -209,11 +189,12 @@ void issue_probe(pending_probe& probe, HANDLE icmp_handle,
209189
}
210190

211191
probe.reply_buffer.resize(reply_buffer_size(destination.si_family, payload.size()));
212-
probe.ip_options.Ttl = static_cast<UCHAR>(probe.ttl);
213-
probe.ip_options.Tos = static_cast<UCHAR>(options.tos);
214-
probe.ip_options.Flags = destination.si_family == AF_INET && options.dont_fragment
215-
? IP_FLAG_DF
216-
: 0;
192+
const auto wire_options = winmtr::probe_parameters::make_wire_options(
193+
probe.ttl, options.tos, options.dont_fragment,
194+
destination.si_family == AF_INET);
195+
probe.ip_options.Ttl = wire_options.ttl;
196+
probe.ip_options.Tos = wire_options.tos;
197+
probe.ip_options.Flags = wire_options.dont_fragment ? IP_FLAG_DF : 0;
217198

218199
DWORD result = 0;
219200
SetLastError(ERROR_SUCCESS);
@@ -321,7 +302,8 @@ void issue_probe(pending_probe& probe, HANDLE icmp_handle,
321302
probe.ttl = 1;
322303
std::uint64_t random_state = GetTickCount64()
323304
^ static_cast<std::uint64_t>(candidate.si_family);
324-
const auto payload = make_payload(options, random_state);
305+
const auto payload = winmtr::probe_parameters::make_payload(
306+
options.packet_size, options.payload_pattern, random_state);
325307
issue_probe(probe, handle.get(), candidate, options, payload, timeout_ms);
326308
if (stop_token.stop_requested()) return false;
327309
const auto parsed = parse_reply(probe, candidate.si_family);
@@ -845,7 +827,8 @@ WinMTRTraceResult WinMTRNet::DoTrace(std::stop_token stop_token, SOCKADDR_INET a
845827
request->icmp_handle = create_icmp_handle(address.si_family);
846828
request->destination = address;
847829
request->options = trace_options;
848-
request->payload = make_payload(trace_options, random_state);
830+
request->payload = winmtr::probe_parameters::make_payload(
831+
trace_options.packet_size, trace_options.payload_pattern, random_state);
849832
request->timeout_ms = trace_options.timeout_ms;
850833
request->completions = &completions;
851834
if (!request->icmp_handle) {

WinMTRProbeParameters.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#pragma once
2+
3+
#include <algorithm>
4+
#include <cstddef>
5+
#include <cstdint>
6+
#include <vector>
7+
8+
namespace winmtr::probe_parameters {
9+
10+
struct WireOptions final {
11+
std::uint8_t ttl = 0;
12+
std::uint8_t tos = 0;
13+
bool dont_fragment = false;
14+
};
15+
16+
[[nodiscard]] constexpr WireOptions make_wire_options(unsigned ttl, unsigned tos,
17+
bool request_dont_fragment, bool ipv4) noexcept
18+
{
19+
return WireOptions{
20+
.ttl = static_cast<std::uint8_t>(std::min(ttl, 255u)),
21+
.tos = static_cast<std::uint8_t>(std::min(tos, 255u)),
22+
.dont_fragment = ipv4 && request_dont_fragment,
23+
};
24+
}
25+
26+
[[nodiscard]] inline std::vector<std::byte> make_payload(unsigned packet_size,
27+
int payload_pattern, std::uint64_t& random_state)
28+
{
29+
std::vector<std::byte> payload(packet_size);
30+
if (payload_pattern >= 0) {
31+
std::fill(payload.begin(), payload.end(),
32+
static_cast<std::byte>(static_cast<unsigned>(payload_pattern) & 0xffu));
33+
return payload;
34+
}
35+
36+
// Deterministic xorshift is sufficient for varying diagnostic payload bytes;
37+
// this is intentionally not a cryptographic random-number generator.
38+
for (auto& value : payload) {
39+
random_state ^= random_state << 13u;
40+
random_state ^= random_state >> 7u;
41+
random_state ^= random_state << 17u;
42+
value = static_cast<std::byte>(random_state & 0xffu);
43+
}
44+
return payload;
45+
}
46+
47+
} // namespace winmtr::probe_parameters

tests/ProbeSchedulerTests.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "WinMTRProbeScheduler.h"
2+
#include "WinMTRProbeParameters.h"
23
#include "WinMTRAddressPolicy.h"
34
#include "WinMTRJson.h"
45
#include "WinMTRSerialization.h"
@@ -441,6 +442,35 @@ void test_route_policy_scripted_changes()
441442
"minimum TTL was not enforced after an early destination");
442443
}
443444

445+
void test_probe_packet_parameters()
446+
{
447+
using winmtr::probe_parameters::make_payload;
448+
using winmtr::probe_parameters::make_wire_options;
449+
std::uint64_t fixedState = 123;
450+
require(make_payload(0, 32, fixedState).empty(), "zero-byte payload was not supported");
451+
const auto maximum = make_payload(4'096, 0xab, fixedState);
452+
require(maximum.size() == 4'096
453+
&& std::ranges::all_of(maximum, [](std::byte value) { return value == std::byte{ 0xab }; }),
454+
"fixed maximum-size payload did not preserve its pattern");
455+
456+
std::uint64_t firstState = 0x123456789abcdef0ull;
457+
std::uint64_t secondState = firstState;
458+
const auto randomFirst = make_payload(64, -1, firstState);
459+
const auto randomSecond = make_payload(64, -1, secondState);
460+
require(randomFirst == randomSecond && firstState == secondState,
461+
"random payload was not reproducible from its seed");
462+
require(std::ranges::any_of(randomFirst,
463+
[head = randomFirst.front()](std::byte value) { return value != head; }),
464+
"random payload did not vary its bytes");
465+
466+
const auto ipv4 = make_wire_options(64, 255, true, true);
467+
require(ipv4.ttl == 64 && ipv4.tos == 255 && ipv4.dont_fragment,
468+
"IPv4 TTL/ToS/DF parameters changed");
469+
const auto ipv6 = make_wire_options(64, 255, true, false);
470+
require(ipv6.ttl == 64 && ipv6.tos == 255 && !ipv6.dont_fragment,
471+
"IPv4-only DF flag leaked into IPv6");
472+
}
473+
444474
void fuzz_json_parser_offline()
445475
{
446476
std::uint64_t state = 0x243f6a8885a308d3ull;
@@ -477,6 +507,7 @@ int main()
477507
test_grace_cancellation_is_not_loss();
478508
test_deterministic_scheduler_resource_budget();
479509
test_route_policy_scripted_changes();
510+
test_probe_packet_parameters();
480511
fuzz_json_parser_offline();
481512
std::cout << "All probe scheduler tests passed.\n";
482513
return 0;

0 commit comments

Comments
 (0)