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 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 // TODO: [OVPN3-933] Consider RAII'ifying this code
185
186 void init(const char *name_arg,
187 const int unit_arg,
188 const SessionStats::Ptr &stats_arg)
189 {
190 initialized_ = true;
191 base = 0;
192 extent = 0;
193 expire = 0;
194 id_high = 0;
195 time_high = 0;
196 id_floor = 0;
197 max_backtrack = 0;
198 unit = unit_arg;
199 name = name_arg;
200 stats = stats_arg;
201 std::memset(history, 0, sizeof(history));
202 }
203
204 [[nodiscard]] bool initialized() const
205 {
206 return initialized_;
207 }
208
209 bool test_add(const PacketIDControl &pin,
210 const PacketIDControl::time_t now,
211 const bool mod) // don't modify history unless mod is true
212 {
213 const Error::Type err = do_test_add(pin, now, mod);
214 if (unlikely(err != Error::SUCCESS))
215 {
216 stats->error(err);
217 return false;
218 }
219 else
220 return true;
221 }
222
224 const PacketIDControl::time_t now,
225 const bool mod) // don't modify history unless mod is true
226 {
227 // make sure we were initialized
229 throw packet_id_not_initialized();
230
231 // expire backtracks at or below id_floor after PKTID_RECV_EXPIRE time
232 if (unlikely(now >= expire))
234 expire = now + PKTID_RECV_EXPIRE;
235
236 // ID must not be zero
237 if (unlikely(!pin.is_valid()))
239
240 // time changed?
241 if (unlikely(pin.time != time_high))
242 {
243 if (pin.time > time_high)
244 {
245 // time moved forward, accept
246 if (!mod)
247 return Error::SUCCESS;
248 base = 0;
249 extent = 0;
250 id_high = 0;
251 time_high = pin.time;
252 id_floor = 0;
253 }
254 else
255 {
256 // time moved backward, reject
258 }
259 }
260
261 if (likely(pin.id == id_high + 1))
262 {
263 // well-formed ID sequence (incremented by 1)
264 if (!mod)
265 return Error::SUCCESS;
266 base = REPLAY_INDEX(-1);
267 history[base / 8] |= static_cast<uint8_t>(1 << (base % 8));
269 ++extent;
270 id_high = pin.id;
271 }
272 else if (pin.id > id_high)
273 {
274 // ID jumped forward by more than one
275 if (!mod)
276 return Error::SUCCESS;
277 const unsigned int delta = pin.id - id_high;
278 if (delta < REPLAY_WINDOW_SIZE)
279 {
280 base = REPLAY_INDEX(-delta);
281 history[base / 8] |= static_cast<uint8_t>(1 << (base % 8));
282 extent += delta;
285 for (unsigned i = 1; i < delta; ++i)
286 {
287 const unsigned int newbase = REPLAY_INDEX(i);
288 history[newbase / 8] &= static_cast<uint8_t>(~(1 << (newbase % 8)));
289 }
290 }
291 else
292 {
293 base = 0;
295 std::memset(history, 0, sizeof(history));
296 history[0] = 1;
297 }
298 id_high = pin.id;
299 }
300 else
301 {
302 // ID backtrack
303 const unsigned int delta = id_high - pin.id;
304 if (delta > max_backtrack)
305 max_backtrack = delta;
306 if (delta < extent)
307 {
308 if (pin.id > id_floor)
309 {
310 const unsigned int ri = REPLAY_INDEX(delta);
311 std::uint8_t *p = &history[ri / 8];
312 const std::uint8_t mask = static_cast<uint8_t>(1 << (ri % 8));
313 if (*p & mask)
314 return Error::PKTID_REPLAY;
315 if (!mod)
316 return Error::SUCCESS;
317 *p |= mask;
318 }
319 else
320 return Error::PKTID_EXPIRE;
321 }
322 else
324 }
325
326 return Error::SUCCESS;
327 }
328
330 {
331 if (!initialized_)
332 throw packet_id_not_initialized();
333 PacketIDControl pid{};
334 pid.read(buf);
335 return pid;
336 }
337
338 std::string str() const
339 {
340 std::ostringstream os;
341 os << "[e=" << extent << " f=" << id_floor << " h=" << time_high << '/' << id_high << ']';
342 return os.str();
343 }
344
345 private:
346 unsigned int REPLAY_INDEX(const int i) const
347 {
348 return (base + i) & (REPLAY_WINDOW_SIZE - 1);
349 }
350
351 bool initialized_ = false;
352
353 unsigned int base = 0; // bit position of deque base in history
354 unsigned int extent = 0; // extent (in bits) of deque in history
355 PacketIDControl::time_t expire = 0; // expiration of history
356 PacketIDControl::id_t id_high = 0; // highest sequence number received
357 PacketIDControl::time_t time_high = 0; // highest time stamp received
358 PacketIDControl::id_t id_floor = 0; // we will only accept backtrack IDs > id_floor
359 unsigned int max_backtrack = 0;
360
361 int unit = -1; // unit number of this object (for debugging)
362 std::string name; // name of this object (for debugging)
363
365
366 std::uint8_t history[REPLAY_WINDOW_BYTES]; /* "sliding window" bitmask of recent packet IDs received */
367};
368
369// Our standard packet ID window with order=8 (window size=2048).
370// and recv expire=30 seconds.
372
373} // namespace openvpn
void prepend(const T *data, const size_t size)
Prepend data to the buffer.
Definition buffer.hpp:1575
void write(const T *data, const size_t size)
Write data to the buffer.
Definition buffer.hpp:1563
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
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
std::ostringstream os