OpenVPN 3 Core Library
Loading...
Searching...
No Matches
test_oob_probe.cpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2026- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12#include "test_common.hpp"
13
14#include <cstdint>
15
17
18using namespace openvpn;
19
20namespace {
21
22// Build a deterministic ProtoSessionID from 8 known bytes.
23ProtoSessionID make_psid(const unsigned char (&bytes)[ProtoSessionID::SIZE])
24{
26 b.write(bytes, ProtoSessionID::SIZE);
27 return ProtoSessionID(b);
28}
29
30} // namespace
31
32// SERVER_PROBE round-trips: what we write, we read back identically.
33TEST(oob_probe, server_probe_roundtrip)
34{
35 const oob::ProbeParameter in{.timestamp = 0x1122334455667788ULL, .flags = 0};
36
37 BufferAllocated buf(128);
38 ASSERT_TRUE(oob::server_probe_write(buf, in));
39
40 const auto out = oob::server_probe_read(buf);
41 ASSERT_TRUE(out);
42 EXPECT_EQ(out->timestamp, in.timestamp);
43 EXPECT_EQ(out->flags, in.flags);
44}
45
46// PROBE_REPLY round-trips, session id and all fields preserved.
47TEST(oob_probe, probe_reply_roundtrip)
48{
49 const unsigned char sid[ProtoSessionID::SIZE] = {1, 2, 3, 4, 5, 6, 7, 8};
50 const oob::ProbeReply in{.peer_session_id = make_psid(sid),
51 .priority = 100,
52 .weight = 50,
53 .max_latency_diff = 25,
54 .connect_lifetime = 60,
56
57 BufferAllocated buf(128);
58 ASSERT_TRUE(oob::client_reply_write(buf, in));
59
60 const auto out = oob::client_reply_read(buf);
61 ASSERT_TRUE(out);
62 EXPECT_TRUE(out->peer_session_id.match(in.peer_session_id));
63 EXPECT_EQ(out->priority, in.priority);
64 EXPECT_EQ(out->weight, in.weight);
65 EXPECT_EQ(out->connect_lifetime, in.connect_lifetime);
66 EXPECT_EQ(out->flags, in.flags);
67 EXPECT_EQ(out->max_latency_diff, in.max_latency_diff);
68}
69
70// Wire format is locked (big-endian) so it stays interoperable with the C impl.
71TEST(oob_probe, server_probe_wire_format)
72{
73 const oob::ProbeParameter in{.timestamp = 0x1122334455667788ULL, .flags = 0};
74
75 BufferAllocated buf(128);
76 ASSERT_TRUE(oob::server_probe_write(buf, in));
77
78 const unsigned char expected[] = {
79 0x01,
80 0x00, // msg type SERVER_PROBE (0x100)
81 0x02,
82 0x00, // TLV type 0x200 (not optional)
83 0x00,
84 0x0c, // TLV value length = 12
85 0x11,
86 0x22,
87 0x33,
88 0x44,
89 0x55,
90 0x66,
91 0x77,
92 0x88, // timestamp (BE)
93 0x00,
94 0x00,
95 0x00,
96 0x00, // flags (BE)
97 };
98 ASSERT_EQ(buf.size(), sizeof(expected));
99 EXPECT_EQ(std::memcmp(buf.c_data(), expected, sizeof(expected)), 0);
100}
101
102// PROBE_REPLY wire format is locked to the spec's field order
103// (priority, weight, max_latency_diff, connect_lifetime, flags).
104TEST(oob_probe, probe_reply_wire_format)
105{
106 const unsigned char sid[ProtoSessionID::SIZE] = {1, 2, 3, 4, 5, 6, 7, 8};
107 const oob::ProbeReply in{.peer_session_id = make_psid(sid),
108 .priority = 100,
109 .weight = 50,
110 .max_latency_diff = 25,
111 .connect_lifetime = 60,
113
114 BufferAllocated buf(128);
115 ASSERT_TRUE(oob::client_reply_write(buf, in));
116
117 const unsigned char expected[] = {
118 0x01,
119 0x01, // msg type PROBE_REPLY (0x101)
120 0x02,
121 0x01, // TLV type 0x201 (not optional)
122 0x00,
123 0x14, // TLV value length = 20
124 0x01,
125 0x02,
126 0x03,
127 0x04,
128 0x05,
129 0x06,
130 0x07,
131 0x08, // peer_session_id
132 0x00,
133 0x64, // priority = 100 (BE)
134 0x00,
135 0x32, // weight = 50 (BE)
136 0x00,
137 0x19, // max_latency_diff = 25 (BE)
138 0x00,
139 0x3c, // connect_lifetime = 60 (BE)
140 0x00,
141 0x00,
142 0x00,
143 0x01, // flags = REPLY_FLAG_RESEND_WKC (BE)
144 };
145 ASSERT_EQ(buf.size(), sizeof(expected));
146 EXPECT_EQ(std::memcmp(buf.c_data(), expected, sizeof(expected)), 0);
147}
148
149// Unknown *optional* TLVs before the wanted one are skipped (forward compatibility).
150TEST(oob_probe, skips_unknown_optional_tlv)
151{
152 BufferAllocated buf(128);
154 // an unknown TLV (type 0x7ff, 3 bytes of value) marked optional, so skippable
155 ASSERT_TRUE(oob::tlv_write_header(buf, 0x7ff, true, 3));
156 ASSERT_TRUE(oob::write_u16(buf, 0xdead));
157 buf.push_back(0xaa);
158 // the real probe_parameter TLV
159 const oob::ProbeParameter in{.timestamp = 42, .flags = 0};
160 ASSERT_TRUE(in.write(buf));
161
162 const auto out = oob::server_probe_read(buf);
163 ASSERT_TRUE(out);
164 EXPECT_EQ(out->timestamp, 42u);
165}
166
167// The same message is rejected when that TLV is not marked optional: we cannot
168// act on a message carrying something mandatory we do not understand.
169TEST(oob_probe, rejects_unknown_mandatory_tlv)
170{
171 BufferAllocated buf(128);
173 ASSERT_TRUE(oob::tlv_write_header(buf, 0x7ff, false, 3));
174 ASSERT_TRUE(oob::write_u16(buf, 0xdead));
175 buf.push_back(0xaa);
176 const oob::ProbeParameter in{.timestamp = 42, .flags = 0};
177 ASSERT_TRUE(in.write(buf));
178
179 EXPECT_FALSE(oob::server_probe_read(buf));
180}
181
182// A forward-compatible longer TLV value (extra trailing bytes) still parses.
183TEST(oob_probe, tolerates_longer_tlv_value)
184{
185 BufferAllocated buf(128);
187 ASSERT_TRUE(oob::tlv_write_header(buf, oob::TLV_PROBE_PARAMETER, false,
188 oob::ProbeParameter::WIRE_LEN + 4)); // 4 extra bytes
189 ASSERT_TRUE(oob::write_u64(buf, 7));
190 ASSERT_TRUE(oob::write_u32(buf, 0));
191 ASSERT_TRUE(oob::write_u32(buf, 0xffffffff)); // trailing bytes to skip
192
193 const auto out = oob::server_probe_read(buf);
194 ASSERT_TRUE(out);
195 EXPECT_EQ(out->timestamp, 7u);
196}
197
198// Truncated input never crashes and fails cleanly (untrusted-network safety).
199TEST(oob_probe, truncated_input_fails)
200{
201 const unsigned char sid[ProtoSessionID::SIZE] = {9, 9, 9, 9, 9, 9, 9, 9};
202 const oob::ProbeReply in{.peer_session_id = make_psid(sid)};
203 BufferAllocated full(128);
204 ASSERT_TRUE(oob::client_reply_write(full, in));
205
206 // every prefix shorter than the full message must fail, not throw
207 for (size_t n = 0; n < full.size(); ++n)
208 {
209 BufferAllocated trunc(128);
210 trunc.write(full.c_data(), n);
211 EXPECT_FALSE(oob::client_reply_read(trunc)) << "prefix len " << n;
212 }
213}
214
215// A TLV whose declared length exceeds the buffer must fail, not over-read.
216TEST(oob_probe, oversized_tlv_length_fails)
217{
218 BufferAllocated buf(128);
220 ASSERT_TRUE(oob::tlv_write_header(buf, oob::TLV_PROBE_PARAMETER, false, 0xffff));
221 ASSERT_TRUE(oob::write_u64(buf, 1)); // only a little body, far less than 0xffff
222
223 EXPECT_FALSE(oob::server_probe_read(buf));
224}
225
226// Wrong message type is rejected.
227TEST(oob_probe, wrong_message_type_fails)
228{
229 const oob::ProbeParameter in{.timestamp = 1};
230 BufferAllocated buf(128);
231 ASSERT_TRUE(oob::server_probe_write(buf, in)); // this is a SERVER_PROBE
232
233 EXPECT_FALSE(oob::client_reply_read(buf)); // read as PROBE_REPLY -> fail
234}
const T * c_data() const
Returns a const pointer to the start of the buffer.
Definition buffer.hpp:1193
void push_back(const T &value)
Append a T object to the end of the array, resizing the array if necessary.
Definition buffer.hpp:1480
size_t size() const
Returns the size of the buffer in T objects.
Definition buffer.hpp:1241
void write(const T *data, const size_t size)
Write data to the buffer.
Definition buffer.hpp:1561
@ REPLY_FLAG_RESEND_WKC
Definition oob_probe.hpp:77
bool server_probe_write(Buffer &buf, const ProbeParameter &param)
Write a complete SERVER_PROBE (message header + probe_parameter TLV). Client.
bool client_reply_write(Buffer &buf, const ProbeReply &reply)
Write a complete PROBE_REPLY (message header + probe_reply TLV). Server.
std::optional< ProbeReply > client_reply_read(Buffer &buf)
std::optional< ProbeParameter > server_probe_read(Buffer &buf)
bool msg_write_header(Buffer &buf, std::uint16_t msg_type)
Write an OOB message-type header (the 16-bit type preceding the TLVs).
Definition oob_probe.hpp:83
bool tlv_write_header(Buffer &buf, std::uint16_t type, bool optional, std::uint16_t value_len)
Write a TLV header (type + optional flag + value length) to buf.
probe parameter TLV (sent by the client in a SERVER_PROBE).
static constexpr std::size_t WIRE_LEN
std::uint64_t timestamp
client clock as a UNIX timestamp
probe reply TLV (sent by the server in a PROBE_REPLY).
ProtoSessionID peer_session_id
echoes the session id of the request
const std::string expected
TEST(oob_probe, server_probe_roundtrip)
static std::stringstream out
Definition test_path.cpp:10