OpenVPN 3 Core Library
Loading...
Searching...
No Matches
packet_id_data.hpp
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) 2012- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12// Manage OpenVPN protocol Packet IDs for packet replay detection
13#pragma once
14
15#include <algorithm>
16#include <string>
17#include <cstring>
18#include <sstream>
19#include <cstdint> // for std::uint32_t
20
21#include <openvpn/io/io.hpp>
22
30#include <openvpn/time/time.hpp>
33
34namespace openvpn {
51{
52 using data_id_t = std::uint64_t;
53
54 /* the part of the packet id that represents the PID, the first 16 bits
55 * are used by the Epoch*/
56 static constexpr inline data_id_t epoch_packet_id_mask = 0x0000ffffffffffffULL;
57
58
59 data_id_t id = 0; // legal values are 1 through 2^64-1
60 bool wide = false;
61
66 [[nodiscard]] constexpr std::size_t size() const
67 {
68 return size(wide);
69 }
70
71 static constexpr size_t size(bool wide)
72 {
73 if (wide)
74 return long_id_size;
75 return short_id_size;
76 }
77
78
79 explicit PacketIDData(bool wide_arg)
80 : wide(wide_arg)
81 {
82 }
83
84 explicit PacketIDData(bool wide_arg, data_id_t id_arg)
85 : id(id_arg), wide(wide_arg)
86 {
87 }
88
89 constexpr static std::size_t short_id_size = sizeof(std::uint32_t);
90 constexpr static std::size_t long_id_size = sizeof(std::uint64_t);
91
92 [[nodiscard]] bool is_valid() const
93 {
94 return id != 0;
95 }
96
97 void reset()
98 {
99 id = data_id_t(0);
100 }
101
102 uint16_t get_epoch()
103 {
104 return static_cast<uint16_t>((id & ~epoch_packet_id_mask) >> 48ULL);
105 }
106
111 void read(ConstBuffer &buf)
112 {
113 if (wide)
114 {
115 std::uint64_t net_id;
116 buf.read(reinterpret_cast<unsigned char *>(&net_id), sizeof(net_id));
117 id = Endian::rev64(net_id);
118 }
119 else
120 {
121 std::uint32_t net_id;
122 buf.read(reinterpret_cast<unsigned char *>(&net_id), sizeof(net_id));
123 id = ntohl(net_id);
124 }
125 }
126
128 void write(Buffer &buf) const
129 {
130 if (wide)
131 {
132 const std::uint64_t net_id = Endian::rev64(id);
133 buf.write(reinterpret_cast<const unsigned char *>(&net_id), sizeof(net_id));
134 }
135 else
136 {
137 const std::uint32_t net_id = htonl(static_cast<std::uint32_t>(id));
138 buf.write(reinterpret_cast<const unsigned char *>(&net_id), sizeof(net_id));
139 }
140 }
141
143 void write_prepend(Buffer &buf) const
144 {
145 if (wide)
146 {
147 const std::uint64_t net_id = Endian::rev64(id);
148 buf.prepend(reinterpret_cast<const unsigned char *>(&net_id), sizeof(net_id));
149 }
150 else
151 {
152 const std::uint32_t net_id = htonl(static_cast<std::uint32_t>(id));
153 buf.prepend(reinterpret_cast<const unsigned char *>(&net_id), sizeof(net_id));
154 }
155 }
156
157
158 [[nodiscard]] std::string str() const
159 {
160 std::ostringstream os;
161 os << std::hex << "[0x" << id << "]";
162 return os.str();
163 }
164};
165
167{
168 public:
170
172 static constexpr inline PacketIDData::data_id_t epoch_packet_id_max = 0x0000ffffffffffffULL;
173
174
175 explicit PacketIDDataSend(bool wide_arg, uint16_t epoch)
176 : pid_(wide_arg, static_cast<PacketIDData::data_id_t>(epoch) << 48ULL)
177 {
178 }
179
181 : pid_(false)
182 {
183 }
189 [[nodiscard]] PacketIDData next()
190 {
191 ++pid_.id;
193 if (at_limit())
194 {
195 throw packet_id_wrap();
196 }
197 return ret;
198 }
199
205 {
206 const PacketIDData pid = next();
207 pid.write(buf);
208 }
209
215 {
216 const PacketIDData pid = next();
217 pid.write_prepend(buf);
218 }
219
220 [[nodiscard]] std::string str() const
221 {
222 std::string ret;
223 ret = pid_.str();
224 if (pid_.wide)
225 ret += 'L';
226 return ret;
227 }
228
233 [[nodiscard]] constexpr std::size_t length() const
234 {
235 return pid_.size();
236 }
237
249 bool wrap_warning() const
250 {
251 if (pid_.wide)
252 return false;
253
254 const PacketIDData::data_id_t wrap_at = 0xFF000000;
255 return pid_.id >= wrap_at;
256 }
257
258 bool at_limit()
259 {
260 if (!pid_.wide && unlikely(pid_.id == std::numeric_limits<std::uint32_t>::max())) // wraparound
261 {
262 return true;
263 }
265 {
266 return true;
267 }
268 return false;
269 }
270
271
272 protected:
274};
275
276/*
277 * This is the data structure we keep on the receiving side,
278 * to check that no packet-id is accepted more than once.
279 *
280 * Replay window sizing in bytes = 2^REPLAY_WINDOW_ORDER.
281 * PKTID_RECV_EXPIRE is backtrack expire in seconds.
282 */
283template <unsigned int REPLAY_WINDOW_ORDER,
284 unsigned int PKTID_RECV_EXPIRE>
286{
287 public:
288 static constexpr unsigned int REPLAY_WINDOW_BYTES = 1U << REPLAY_WINDOW_ORDER;
289 static constexpr unsigned int REPLAY_WINDOW_SIZE = REPLAY_WINDOW_BYTES * 8;
290
291#if defined(__GNUC__) && (__GNUC__ < 11)
292 /*
293 * For some reason g++ versions 10.x are regenerating a move constructor
294 * and move assignment operators that g++ itself then complains about them
295 * with "error: writing 16 bytes into a region of size 0 [-Werror=stringop-overflow=]
296 *
297 * So we manually define these to avoid this behaviour
298 */
299
300 PacketIDDataReceiveType() = default;
301 PacketIDDataReceiveType(const PacketIDDataReceiveType &other) = default;
303 {
304 wide = other.wide;
305 base = other.base;
306 extent = other.extent;
307 expire = other.expire;
308 id_high = other.id_high;
309 id_floor = other.id_floor;
310 unit = other.unit;
311 name = other.name;
312 memcpy(history, other.history, sizeof(history));
313 }
314
315 PacketIDDataReceiveType &operator=(PacketIDDataReceiveType &&other) noexcept
316 {
317 wide = other.wide;
318 base = other.base;
319 extent = other.extent;
320 expire = other.expire;
321 id_high = other.id_high;
322 id_floor = other.id_floor;
323 unit = other.unit;
324 name = other.name;
325 memcpy(history, other.history, sizeof(history));
326 return *this;
327 }
328 PacketIDDataReceiveType &operator=(const PacketIDDataReceiveType &other) = default;
329#endif
330
331 void init(const char *name_arg,
332 const int unit_arg,
333 bool wide_arg)
334 {
335 wide = wide_arg;
336 base = 0;
337 extent = 0;
338 expire = 0;
339 id_high = 0;
340 id_floor = 0;
341 unit = unit_arg;
342 name = name_arg;
343 std::memset(history, 0, sizeof(history));
344 }
345
346
358 [[nodiscard]] bool test_add(const PacketIDData &pin,
359 const Time::base_type now,
360 const SessionStats::Ptr &stats)
361 {
362 const Error::Type err = do_test_add(pin, now);
363 if (unlikely(err != Error::SUCCESS))
364 {
365 stats->error(err);
366 return false;
367 }
368 return true;
369 }
370
380 [[nodiscard]] Error::Type do_test_add(const PacketIDData &pin,
381 const Time::base_type now)
382 {
383 // expire backtracks at or below id_floor after PKTID_RECV_EXPIRE time
384 if (unlikely(now >= expire))
386 expire = now + PKTID_RECV_EXPIRE;
387
388 // ID must not be zero
389 if (unlikely(!pin.is_valid()))
391
392
393 if (likely(pin.id == id_high + 1))
394 {
395 // well-formed ID sequence (incremented by 1)
396 base = replay_index(-1);
397 history[base / 8] |= static_cast<uint8_t>(1 << (base % 8));
399 ++extent;
400 id_high = pin.id;
401 }
402 else if (pin.id > id_high)
403 {
404 // ID jumped forward by more than one
405
406 const auto delta = pin.id - id_high;
407 if (delta < REPLAY_WINDOW_SIZE)
408 {
409 base = replay_index(-delta);
410 history[base / 8] |= static_cast<uint8_t>(1U << (base % 8));
411 extent += static_cast<std::size_t>(delta);
414 for (unsigned i = 1; i < delta; ++i)
415 {
416 const auto newbase = replay_index(i);
417 history[newbase / 8] &= static_cast<uint8_t>(~(1U << (newbase % 8)));
418 }
419 }
420 else
421 {
422 base = 0;
424 std::memset(history, 0, sizeof(history));
425 history[0] = 1;
426 }
427 id_high = pin.id;
428 }
429 else
430 {
431 // ID backtrack
432 const auto delta = id_high - pin.id;
433 if (delta < extent)
434 {
435 if (pin.id > id_floor)
436 {
437 const auto ri = replay_index(delta);
438 std::uint8_t *p = &history[ri / 8];
439 const std::uint8_t mask = static_cast<uint8_t>(1U << (ri % 8));
440 if (*p & mask)
441 return Error::PKTID_REPLAY;
442 *p |= mask;
443 }
444 else
445 return Error::PKTID_EXPIRE;
446 }
447 else
449 }
450
451 return Error::SUCCESS;
452 }
453
455 {
456 PacketIDData pid{wide};
457 pid.read(buf);
458 return pid;
459 }
460
461 [[nodiscard]] std::string str() const
462 {
463 std::ostringstream os;
464 os << "[e=" << extent << " f=" << id_floor << id_high << ']';
465 return os.str();
466 }
467
468 [[nodiscard]] std::size_t constexpr length() const
469 {
470 return PacketIDData::size(wide);
471 }
472
473 private:
474 [[nodiscard]] constexpr std::size_t replay_index(PacketIDData::data_id_t i) const
475 {
476 return (base + i) & (REPLAY_WINDOW_SIZE - 1);
477 }
478
479 std::size_t base = 0; // bit position of deque base in history
480 std::size_t extent = 0; // extent (in bits) of deque in history
481 Time::base_type expire = 0; // expiration of history
482 PacketIDData::data_id_t id_high = 0; // highest sequence number received
483 PacketIDData::data_id_t id_floor = 0; // we will only accept backtrack IDs > id_floor
484
486 bool wide = false;
487 int unit = -1; // unit number of this object (for debugging)
488 std::string name{"not initialised"}; // name of this object (for debugging)
489
492};
493
494// Our standard packet ID window with order=8 (window size=2048).
495// and recv expire=30 seconds.
497
498} // namespace openvpn
void prepend(const T *data, const size_t size)
Prepend data to the buffer.
Definition buffer.hpp:1572
void write(const T *data, const size_t size)
Write data to the buffer.
Definition buffer.hpp:1560
void read(NCT *data, const size_t size)
Read data from the buffer into the specified memory location.
Definition buffer.hpp:1330
static constexpr unsigned int REPLAY_WINDOW_BYTES
PacketIDData read_next(Buffer &buf) const
PacketIDData::data_id_t id_high
void init(const char *name_arg, const int unit_arg, bool wide_arg)
static constexpr unsigned int REPLAY_WINDOW_SIZE
std::size_t constexpr length() const
bool test_add(const PacketIDData &pin, const Time::base_type now, const SessionStats::Ptr &stats)
std::uint8_t history[REPLAY_WINDOW_BYTES]
"sliding window" bitmask of recent packet IDs received *‍/
bool wide
< 32 or 64 bit packet counter
constexpr std::size_t replay_index(PacketIDData::data_id_t i) const
PacketIDData::data_id_t id_floor
Error::Type do_test_add(const PacketIDData &pin, const Time::base_type now)
constexpr std::size_t length() const
PacketIDDataSend(bool wide_arg, uint16_t epoch)
OPENVPN_SIMPLE_EXCEPTION(packet_id_wrap)
void prepend_next(Buffer &buf)
static constexpr PacketIDData::data_id_t epoch_packet_id_max
virtual void error(const size_t type, const std::string *text=nullptr)
::time_t base_type
Definition time.hpp:64
#define likely(x)
Definition likely.hpp:21
#define unlikely(x)
Definition likely.hpp:22
std::uint64_t rev64(const std::uint64_t value)
Definition endian64.hpp:32
void write(Buffer &buf) const
static constexpr std::size_t short_id_size
void write_prepend(Buffer &buf) const
void read(ConstBuffer &buf)
std::string str() const
PacketIDData(bool wide_arg, data_id_t id_arg)
PacketIDData(bool wide_arg)
constexpr std::size_t size() const
static constexpr data_id_t epoch_packet_id_mask
static constexpr std::size_t long_id_size
static constexpr size_t size(bool wide)
std::string ret
std::ostringstream os