OpenVPN 3 Core Library
Loading...
Searching...
No Matches
packet_id_control.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
14#pragma once
15
16#include <string>
17#include <cstring>
18#include <sstream>
19#include <cstdint> // for std::uint32_t
20
21#include <openvpn/io/io.hpp>
22
28#include <openvpn/time/time.hpp>
31
32namespace openvpn {
33/*
34 * Control channel Packet ID. These IDs have the format
35 *
36 * | 32 bit integer timestamp in BE | 32 bit packet counter in BE |
37 *
38 * This format of long packet-ids is also used as IVs for CFB/OFB ciphers
39 * in OpenVPN 2.x but OpenVPN 3.x supports only CBC and AEAD ciphers, so
40 * it is only used for control channel and control chanel authentication/encryption
41 * schemes like tls-auth/tls-crypt.
42 *
43 * This data structure is always sent over the net in network byte order,
44 * by calling htonl, ntohl, on the 32-bit data elements, id_t and
45 * net_time_t, to change them to and from network order.
46 */
48{
49 typedef std::uint32_t id_t;
50 typedef std::uint32_t net_time_t;
52
53 id_t id; // legal values are 1 through 2^32-1
54 time_t time; // converted to PacketID::net_time_t before transmission
55
56 static constexpr size_t size()
57 {
58 return idsize;
59 }
60
61 constexpr static size_t idsize = sizeof(id_t) + sizeof(net_time_t);
62
63 bool is_valid() const
64 {
65 return id != 0;
66 }
67
68 void reset()
69 {
70 id = id_t(0);
71 time = time_t(0);
72 }
73
74 template <typename BufType> // so it can take a Buffer or a ConstBuffer
75 void read(BufType &buf)
76 {
77 id_t net_id;
78 net_time_t net_time;
79
80 buf.read((unsigned char *)&net_id, sizeof(net_id));
81 id = ntohl(net_id);
82
83 buf.read((unsigned char *)&net_time, sizeof(net_time));
84 time = ntohl(net_time);
85 }
86
87 void write(Buffer &buf, const bool prepend) const
88 {
89 const id_t net_id = htonl(id);
90 const net_time_t net_time = htonl(static_cast<uint32_t>(time & 0x00000000FFFFFFFF));
91 // TODO: [OVPN3-931] Make our code handle rollover of this value gracefully as possible
92 // since at the current time this will probably force a reconnect.
93
94 if (prepend)
95 {
96 buf.prepend((unsigned char *)&net_time, sizeof(net_time));
97 buf.prepend((unsigned char *)&net_id, sizeof(net_id));
98 }
99 else
100 {
101 buf.write((unsigned char *)&net_id, sizeof(net_id));
102 buf.write((unsigned char *)&net_time, sizeof(net_time));
103 }
104 }
105
106 std::string str() const
107 {
108 std::ostringstream os;
109 os << std::hex << "[0x" << time << ", 0x" << id << "]";
110 return os.str();
111 }
112};
113
114
116{
117 public:
119
120
122 {
123 init(start_at);
124 }
125
129 void init(PacketIDControl::id_t start_at = 0)
130 {
131 pid_.id = start_at;
133 }
134
136 {
138 if (!pid_.time)
139 pid_.time = now;
140 ret.id = ++pid_.id;
141 if (unlikely(!pid_.id)) // wraparound
142 {
143 pid_.time = now;
144 ret.id = pid_.id = 1;
145 }
146 ret.time = pid_.time;
147 return ret;
148 }
149
150 void write_next(Buffer &buf, const bool prepend, const PacketIDControl::time_t now)
151 {
152 const PacketIDControl pid = next(now);
153 pid.write(buf, prepend);
154 }
155
156 std::string str() const
157 {
158 std::string ret;
159 ret = pid_.str();
160 ret += 'L';
161 return ret;
162 }
163
164 private:
166};
167
168/*
169 * This is the data structure we keep on the receiving side,
170 * to check that no packet-id (i.e. sequence number + optional timestamp)
171 * is accepted more than once.
172 *
173 * Replay window sizing in bytes = 2^REPLAY_WINDOW_ORDER.
174 * PKTID_RECV_EXPIRE is backtrack expire in seconds.
175 */
176template <unsigned int REPLAY_WINDOW_ORDER,
177 unsigned int PKTID_RECV_EXPIRE>
179{
180 public:
181 static constexpr unsigned int REPLAY_WINDOW_BYTES = 1 << REPLAY_WINDOW_ORDER;
182 static constexpr unsigned int REPLAY_WINDOW_SIZE = REPLAY_WINDOW_BYTES * 8;
183
184 OPENVPN_SIMPLE_EXCEPTION(packet_id_not_initialized);
185
186 // TODO: [OVPN3-933] Consider RAII'ifying this code
190
191 void init(const char *name_arg,
192 const int unit_arg,
193 const SessionStats::Ptr &stats_arg)
194 {
195 initialized_ = true;
196 base = 0;
197 extent = 0;
198 expire = 0;
199 id_high = 0;
200 time_high = 0;
201 id_floor = 0;
202 max_backtrack = 0;
203 unit = unit_arg;
204 name = name_arg;
205 stats = stats_arg;
206 std::memset(history, 0, sizeof(history));
207 }
208
209 [[nodiscard]] bool initialized() const
210 {
211 return initialized_;
212 }
213
214 bool test_add(const PacketIDControl &pin,
215 const PacketIDControl::time_t now,
216 const bool mod) // don't modify history unless mod is true
217 {
218 const Error::Type err = do_test_add(pin, now, mod);
219 if (unlikely(err != Error::SUCCESS))
220 {
221 stats->error(err);
222 return false;
223 }
224 else
225 return true;
226 }
227
229 const PacketIDControl::time_t now,
230 const bool mod) // don't modify history unless mod is true
231 {
232 // make sure we were initialized
234 throw packet_id_not_initialized();
235
236 // expire backtracks at or below id_floor after PKTID_RECV_EXPIRE time
237 if (unlikely(now >= expire))
239 expire = now + PKTID_RECV_EXPIRE;
240
241 // ID must not be zero
242 if (unlikely(!pin.is_valid()))
244
245 // time changed?
246 if (unlikely(pin.time != time_high))
247 {
248 if (pin.time > time_high)
249 {
250 // time moved forward, accept
251 if (!mod)
252 return Error::SUCCESS;
253 base = 0;
254 extent = 0;
255 id_high = 0;
256 time_high = pin.time;
257 id_floor = 0;
258 }
259 else
260 {
261 // time moved backward, reject
263 }
264 }
265
266 if (likely(pin.id == id_high + 1))
267 {
268 // well-formed ID sequence (incremented by 1)
269 if (!mod)
270 return Error::SUCCESS;
271 base = REPLAY_INDEX(-1);
272 history[base / 8] |= static_cast<uint8_t>(1 << (base % 8));
274 ++extent;
275 id_high = pin.id;
276 }
277 else if (pin.id > id_high)
278 {
279 // ID jumped forward by more than one
280 if (!mod)
281 return Error::SUCCESS;
282 const unsigned int delta = pin.id - id_high;
283 if (delta < REPLAY_WINDOW_SIZE)
284 {
285 base = REPLAY_INDEX(-delta);
286 history[base / 8] |= static_cast<uint8_t>(1 << (base % 8));
287 extent += delta;
290 for (unsigned i = 1; i < delta; ++i)
291 {
292 const unsigned int newbase = REPLAY_INDEX(i);
293 history[newbase / 8] &= static_cast<uint8_t>(~(1 << (newbase % 8)));
294 }
295 }
296 else
297 {
298 base = 0;
300 std::memset(history, 0, sizeof(history));
301 history[0] = 1;
302 }
303 id_high = pin.id;
304 }
305 else
306 {
307 // ID backtrack
308 const unsigned int delta = id_high - pin.id;
309 if (delta > max_backtrack)
310 max_backtrack = delta;
311 if (delta < extent)
312 {
313 if (pin.id > id_floor)
314 {
315 const unsigned int ri = REPLAY_INDEX(delta);
316 std::uint8_t *p = &history[ri / 8];
317 const std::uint8_t mask = static_cast<uint8_t>(1 << (ri % 8));
318 if (*p & mask)
319 return Error::PKTID_REPLAY;
320 if (!mod)
321 return Error::SUCCESS;
322 *p |= mask;
323 }
324 else
325 return Error::PKTID_EXPIRE;
326 }
327 else
329 }
330
331 return Error::SUCCESS;
332 }
333
335 {
336 if (!initialized_)
337 throw packet_id_not_initialized();
338 PacketIDControl pid{};
339 pid.read(buf);
340 return pid;
341 }
342
343 std::string str() const
344 {
345 std::ostringstream os;
346 os << "[e=" << extent << " f=" << id_floor << " h=" << time_high << '/' << id_high << ']';
347 return os.str();
348 }
349
350 private:
351 unsigned int REPLAY_INDEX(const int i) const
352 {
353 return (base + i) & (REPLAY_WINDOW_SIZE - 1);
354 }
355
356 bool initialized_ = false;
357
358 unsigned int base = 0; // bit position of deque base in history
359 unsigned int extent = 0; // extent (in bits) of deque in history
360 PacketIDControl::time_t expire = 0; // expiration of history
361 PacketIDControl::id_t id_high = 0; // highest sequence number received
362 PacketIDControl::time_t time_high = 0; // highest time stamp received
363 PacketIDControl::id_t id_floor = 0; // we will only accept backtrack IDs > id_floor
364 unsigned int max_backtrack = 0;
365
366 int unit = -1; // unit number of this object (for debugging)
367 std::string name; // name of this object (for debugging)
368
370
371 std::uint8_t history[REPLAY_WINDOW_BYTES]; /* "sliding window" bitmask of recent packet IDs received */
372};
373
374// Our standard packet ID window with order=8 (window size=2048).
375// and recv expire=30 seconds.
377
378} // namespace openvpn
void prepend(const T *data, const size_t size)
Prepend data to the buffer.
Definition buffer.hpp:1558
void write(const T *data, const size_t size)
Write data to the buffer.
Definition buffer.hpp:1546
static constexpr unsigned int REPLAY_WINDOW_BYTES
std::uint8_t history[REPLAY_WINDOW_BYTES]
Error::Type do_test_add(const PacketIDControl &pin, const PacketIDControl::time_t now, const bool mod)
void init(const char *name_arg, const int unit_arg, const SessionStats::Ptr &stats_arg)
static constexpr unsigned int REPLAY_WINDOW_SIZE
PacketIDControl read_next(Buffer &buf) const
bool test_add(const PacketIDControl &pin, const PacketIDControl::time_t now, const bool mod)
OPENVPN_SIMPLE_EXCEPTION(packet_id_not_initialized)
unsigned int REPLAY_INDEX(const int i) const
void init(PacketIDControl::id_t start_at=0)
OPENVPN_SIMPLE_EXCEPTION(packet_id_wrap)
void write_next(Buffer &buf, const bool prepend, const PacketIDControl::time_t now)
PacketIDControl next(const PacketIDControl::time_t now)
PacketIDControlSend(PacketIDControl::id_t start_at=PacketIDControl::id_t(0))
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
@ PKTID_TIME_BACKTRACK
Definition error.hpp:109
Support deferred server-side state creation when client connects.
Definition ovpncli.cpp:95
PacketIDControlReceiveType< 8, 30 > PacketIDControlReceive
static constexpr size_t size()
static constexpr size_t idsize
void write(Buffer &buf, const bool prepend) const
std::string ret