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