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 using id_t = std::uint32_t;
50 using net_time_t = std::uint32_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 return pid_.str() + 'L';
159 }
160
161 private:
163};
164
165/*
166 * This is the data structure we keep on the receiving side,
167 * to check that no packet-id (i.e. sequence number + optional timestamp)
168 * is accepted more than once.
169 *
170 * Replay window sizing in bytes = 2^REPLAY_WINDOW_ORDER.
171 * PKTID_RECV_EXPIRE is backtrack expire in seconds.
172 */
173template <unsigned int REPLAY_WINDOW_ORDER,
174 unsigned int PKTID_RECV_EXPIRE>
176{
177 public:
178 static constexpr unsigned int REPLAY_WINDOW_BYTES = 1 << REPLAY_WINDOW_ORDER;
179 static constexpr unsigned int REPLAY_WINDOW_SIZE = REPLAY_WINDOW_BYTES * 8;
180
181 OPENVPN_SIMPLE_EXCEPTION(packet_id_not_initialized);
182
183 void init(const char *name_arg,
184 const int unit_arg,
185 const SessionStats::Ptr &stats_arg)
186 {
187 initialized_ = true;
188 base = 0;
189 extent = 0;
190 expire = 0;
191 id_high = 0;
192 time_high = 0;
193 id_floor = 0;
194 max_backtrack = 0;
195 unit = unit_arg;
196 name = name_arg;
197 stats = stats_arg;
198 std::memset(history, 0, sizeof(history));
199 }
200
201 [[nodiscard]] bool initialized() const
202 {
203 return initialized_;
204 }
205
206 bool test_add(const PacketIDControl &pin,
207 const PacketIDControl::time_t now,
208 const bool mod) // don't modify history unless mod is true
209 {
210 const Error::Type err = do_test_add(pin, now, mod);
211 if (unlikely(err != Error::SUCCESS))
212 {
213 stats->error(err);
214 return false;
215 }
216 return true;
217 }
218
220 const PacketIDControl::time_t now,
221 const bool mod) // don't modify history unless mod is true
222 {
223 // make sure we were initialized
225 throw packet_id_not_initialized();
226
227 // expire backtracks at or below id_floor after PKTID_RECV_EXPIRE time
228 if (unlikely(now >= expire))
230 expire = now + PKTID_RECV_EXPIRE;
231
232 // ID must not be zero
233 if (unlikely(!pin.is_valid()))
235
236 // time changed?
237 if (unlikely(pin.time != time_high))
238 {
239 if (pin.time > time_high)
240 {
241 // time moved forward, accept
242 if (!mod)
243 return Error::SUCCESS;
244 base = 0;
245 extent = 0;
246 id_high = 0;
247 time_high = pin.time;
248 id_floor = 0;
249 }
250 else
251 {
252 // time moved backward, reject
254 }
255 }
256
257 if (likely(pin.id == id_high + 1))
258 {
259 // well-formed ID sequence (incremented by 1)
260 if (!mod)
261 return Error::SUCCESS;
262 base = REPLAY_INDEX(-1);
263 history[base / 8] |= static_cast<uint8_t>(1 << (base % 8));
265 ++extent;
266 id_high = pin.id;
267 }
268 else if (pin.id > id_high)
269 {
270 // ID jumped forward by more than one
271 if (!mod)
272 return Error::SUCCESS;
273 const unsigned int delta = pin.id - id_high;
274 if (delta < REPLAY_WINDOW_SIZE)
275 {
276 base = REPLAY_INDEX(-delta);
277 history[base / 8] |= static_cast<uint8_t>(1 << (base % 8));
278 extent += delta;
281 for (unsigned i = 1; i < delta; ++i)
282 {
283 const unsigned int newbase = REPLAY_INDEX(i);
284 history[newbase / 8] &= static_cast<uint8_t>(~(1 << (newbase % 8)));
285 }
286 }
287 else
288 {
289 base = 0;
291 std::memset(history, 0, sizeof(history));
292 history[0] = 1;
293 }
294 id_high = pin.id;
295 }
296 else
297 {
298 // ID backtrack
299 const unsigned int delta = id_high - pin.id;
300 if (delta > max_backtrack)
301 max_backtrack = delta;
302 if (delta < extent)
303 {
304 if (pin.id > id_floor)
305 {
306 const unsigned int ri = REPLAY_INDEX(delta);
307 std::uint8_t *p = &history[ri / 8];
308 const std::uint8_t mask = static_cast<uint8_t>(1 << (ri % 8));
309 if (*p & mask)
310 return Error::PKTID_REPLAY;
311 if (!mod)
312 return Error::SUCCESS;
313 *p |= mask;
314 }
315 else
316 return Error::PKTID_EXPIRE;
317 }
318 else
320 }
321
322 return Error::SUCCESS;
323 }
324
326 {
327 if (!initialized_)
328 throw packet_id_not_initialized();
329 PacketIDControl pid{};
330 pid.read(buf);
331 return pid;
332 }
333
334 std::string str() const
335 {
336 std::ostringstream os;
337 os << "[e=" << extent << " f=" << id_floor << " h=" << time_high << '/' << id_high << ']';
338 return os.str();
339 }
340
341 private:
342 unsigned int REPLAY_INDEX(const int i) const
343 {
344 return (base + i) & (REPLAY_WINDOW_SIZE - 1);
345 }
346
347 bool initialized_ = false;
348
349 unsigned int base = 0; // bit position of deque base in history
350 unsigned int extent = 0; // extent (in bits) of deque in history
351 PacketIDControl::time_t expire = 0; // expiration of history
352 PacketIDControl::id_t id_high = 0; // highest sequence number received
353 PacketIDControl::time_t time_high = 0; // highest time stamp received
354 PacketIDControl::id_t id_floor = 0; // we will only accept backtrack IDs > id_floor
355 unsigned int max_backtrack = 0;
356
357 int unit = -1; // unit number of this object (for debugging)
358 std::string name; // name of this object (for debugging)
359
361
362 std::uint8_t history[REPLAY_WINDOW_BYTES]; /* "sliding window" bitmask of recent packet IDs received */
363};
364
365// Our standard packet ID window with order=8 (window size=2048).
366// and recv expire=30 seconds.
368
369} // 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
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)
#define likely(x)
Definition likely.hpp:21
#define unlikely(x)
Definition likely.hpp:22
@ PKTID_TIME_BACKTRACK
Definition error.hpp:109
static constexpr size_t size()
static constexpr size_t idsize
void write(Buffer &buf, const bool prepend) const
std::string ret
std::ostringstream os