0
0
mirror of https://github.com/OpenVPN/openvpn3.git synced 2024-09-20 12:12:15 +02:00
openvpn3/openvpn/crypto/packet_id.hpp
James Yonan 52c42fb5d2 Moved time source files to openvpn/time.
Added search/replace tool smod.
2011-11-05 17:02:16 +00:00

540 lines
13 KiB
C++

#ifndef OPENVPN_CRYPTO_PACKET_ID_H
#define OPENVPN_CRYPTO_PACKET_ID_H
#include <string>
#include <sstream>
#include <boost/cstdint.hpp>
#include <boost/asio.hpp>
#include <openvpn/common/types.hpp>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/log.hpp>
#include <openvpn/common/circ_list.hpp>
#include <openvpn/time/now.hpp>
#include <openvpn/buffer/buffer.hpp>
namespace openvpn {
/*
* Communicate packet-id over the wire.
* A short packet-id is just a 32 bit
* sequence number. A long packet-id
* includes a timestamp as well.
*
* Long packet-ids are used as IVs for
* CFB/OFB ciphers.
*
* This data structure is always sent
* over the net in network byte order,
* by calling htonpid, ntohpid,
* htontime, and ntohtime on the
* data elements to change them
* to and from standard sizes.
*
* In addition, time is converted to
* a PacketID::net_time_t before sending,
* since openvpn always
* uses a 32-bit time_t but some
* 64 bit platforms use a
* 64 bit time_t.
*/
struct PacketID
{
typedef boost::uint32_t id_t;
typedef boost::uint32_t net_time_t;
typedef Time::base_type time_t;
enum {
SHORT_FORM = 0,
LONG_FORM = 1
};
id_t id; /* legal values are 1 through 2^32-1 */
time_t time; /* converted to PacketID::net_time_t before transmission */
void reset()
{
id = id_t(0);
time = time_t(0);
}
void read(Buffer& buf, const int form)
{
id_t net_id;
net_time_t net_time;
buf.read ((unsigned char *)&net_id, sizeof (net_id));
id = ntohl (net_id);
if (form == LONG_FORM)
{
buf.read ((unsigned char *)&net_time, sizeof (net_time));
time = ntohl (net_time);
}
else
time = time_t(0);
}
void write(Buffer& buf, const int form, const bool prepend) const
{
const id_t net_id = htonl(id);
const net_time_t net_time = htonl(time);
if (prepend)
{
if (form == LONG_FORM)
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));
if (form == LONG_FORM)
buf.write ((unsigned char *)&net_time, sizeof (net_time));
}
}
#ifdef PACKET_ID_EXTRA_LOG_INFO
std::string str() const
{
std::ostringstream os;
os << "[" << time << "," << id << "]";
return os.str();
}
#endif
};
struct PacketIDConstruct : public PacketID
{
PacketIDConstruct(const PacketID::time_t v_time = PacketID::time_t(0), const PacketID::id_t v_id = PacketID::id_t(0))
{
id = v_id;
time = v_time;
}
};
class PacketIDSend
{
public:
OPENVPN_SIMPLE_EXCEPTION(packet_id_wrap);
PacketIDSend()
{
init(PacketID::SHORT_FORM);
}
void init(const int form) // PacketID::LONG_FORM or PacketID::SHORT_FORM
{
pid_.id = PacketID::id_t(0);
pid_.time = PacketID::time_t(0);
form_ = form;
}
PacketID next()
{
PacketID ret;
if (!pid_.time)
pid_.time = now;
ret.id = ++pid_.id;
if (!pid_.id) // wraparound
{
if (form_ != PacketID::LONG_FORM)
throw packet_id_wrap();
pid_.time = now;
ret.id = pid_.id = 1;
}
ret.time = pid_.time;
return ret;
}
void write_next(Buffer& buf, const bool prepend)
{
const PacketID pid = next();
pid.write(buf, form_, prepend);
}
/*
* In TLS mode, when a packet ID gets to this level,
* start thinking about triggering a new
* SSL/TLS handshake.
*/
bool wrap_trigger() const
{
const PacketID::id_t wrap_at = 0xFF000000;
return pid_.id >= wrap_at;
}
#ifdef PACKET_ID_EXTRA_LOG_INFO
std::string str() const
{
std::string ret;
ret = pid_.str();
if (form_ == PacketID::LONG_FORM)
ret += 'L';
return ret;
}
#endif
private:
PacketID pid_;
int form_;
};
/*
* 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.
*/
class PacketIDReceive
{
public:
OPENVPN_SIMPLE_EXCEPTION(packet_id_backtrack_out_of_range);
OPENVPN_SIMPLE_EXCEPTION(packet_id_not_initialized);
/*
* Maximum allowed backtrack in
* sequence number due to packets arriving
* out of order.
*/
enum {
MIN_SEQ_BACKTRACK = 0,
MAX_SEQ_BACKTRACK = 65536,
DEFAULT_SEQ_BACKTRACK = 64
};
/*
* Maximum allowed backtrack in
* seconds due to packets arriving
* out of order.
*/
enum {
MIN_TIME_BACKTRACK = 0,
MAX_TIME_BACKTRACK = 600,
DEFAULT_TIME_BACKTRACK = 15
};
/*
* Special PacketID::time_t value that indicates that
* sequence number has expired.
*/
enum {
SEQ_UNSEEN = 0,
SEQ_EXPIRED = 1
};
/*
* Do a reap pass through the sequence number
* array once every n seconds in order to
* expire sequence numbers which can no longer
* be accepted because they would violate
* TIME_BACKTRACK.
*/
enum {
SEQ_REAP_PERIOD = 5
};
/*
* Debug levels.
*/
enum {
DEBUG_QUIET=0,
DEBUG_LOW,
DEBUG_MEDIUM,
DEBUG_HIGH,
DEBUG_VERBOSE,
};
/* mode */
enum {
UDP_MODE = 0,
TCP_MODE = 1
};
PacketIDReceive() : initialized_(false) {}
bool initialized() const { return initialized_; }
void init(const int mode, const int form,
const int seq_backtrack, const int time_backtrack,
const char *name, const int unit, int debug_level)
{
initialized_ = false;
form_ = form;
debug_level_ = debug_level;
id_ = 0;
time_ = 0;
last_reap_ = 0;
seq_backtrack_ = 0;
max_backtrack_stat_ = 0;
time_backtrack_ = 0;
name_ = name;
unit_ = unit;
if (seq_backtrack && mode == UDP_MODE)
{
if (MIN_SEQ_BACKTRACK <= seq_backtrack
&& seq_backtrack <= MAX_SEQ_BACKTRACK
&& MIN_TIME_BACKTRACK <= time_backtrack
&& time_backtrack <= MAX_TIME_BACKTRACK)
{
seq_backtrack_ = seq_backtrack;
time_backtrack_ = time_backtrack;
seq_list_.init(seq_backtrack);
}
else
throw packet_id_backtrack_out_of_range();
}
else
seq_list_.init(0);
initialized_ = true;
}
/*
* Return true if packet id is ok, or false if
* it's a replay.
*/
bool test(const PacketID& pin)
{
// make sure we were initialized
if (!initialized_)
throw packet_id_not_initialized();
// see if we should do an expiration reap pass
if (last_reap_ + SEQ_REAP_PERIOD <= now)
reap();
// packet ID==0 is invalid
if (!pin.id)
{
debug_log (DEBUG_LOW, pin, "ID is 0", 0);
return false;
}
if (seq_list_.defined())
{
/*
* In backtrack mode, we allow packet reordering subject
* to the seq_backtrack and time_backtrack constraints.
*
* This mode is used with UDP.
*/
if (pin.time == time_)
{
/* is packet-id greater than any one we've seen yet? */
if (pin.id > id_)
return true;
/* check packet-id sliding window for original/replay status */
const PacketID::id_t diff = id_ - pin.id;
/* keep track of maximum backtrack seen for debugging purposes */
if (diff > max_backtrack_stat_)
{
max_backtrack_stat_ = diff;
debug_log (DEBUG_LOW, pin, "UDP replay-window backtrack occurred", max_backtrack_stat_);
}
if (diff >= seq_list_.size())
{
debug_log (DEBUG_LOW, pin, "UDP large diff", diff);
return false;
}
{
const PacketID::time_t v = seq_list_[diff];
if (v == PacketID::time_t(SEQ_UNSEEN))
return true;
else
{
/* raised from DEBUG_LOW to reduce verbosity */
debug_log (DEBUG_MEDIUM, pin, "UDP replay", diff);
return false;
}
}
}
else if (pin.time < time_) /* if time goes back, reject */
{
debug_log (DEBUG_LOW, pin, "UDP time backtrack", 0);
return false;
}
else /* time moved forward */
return true;
}
else
{
/*
* In non-backtrack mode, all sequence number series must
* begin at some number n > 0 and must increment linearly without gaps.
*
* This mode is used with TCP.
*/
if (pin.time == time_)
{
if (pin.id == id_ + 1)
return true;
else
{
debug_log (DEBUG_MEDIUM, pin, "TCP packet ID out of sequence", 0);
return false;
}
}
else if (pin.time < time_) /* if time goes back, reject */
{
debug_log (DEBUG_LOW, pin, "TCP time backtrack", 0);
return false;
}
else /* time moved forward */
{
if (pin.id == 1)
return true;
else
{
debug_log (DEBUG_LOW, pin, "bad initial TCP packet ID", pin.id);
return false;
}
}
}
}
void
add(const PacketID& pin)
{
if (!initialized_)
throw packet_id_not_initialized();
if (seq_list_.defined())
{
// UDP mode. Decide if we should reset sequence number history list.
if (!seq_list_.size() // indicates first pass
|| pin.time > time_ // if time value increases, must reset
|| (pin.id >= seq_backtrack_ // also, big jumps in pin.id require us to reset
&& pin.id - seq_backtrack_ > id_))
{
time_ = pin.time;
id_ = 0;
if (pin.id > seq_backtrack_) // if pin.id is large, fast-forward
id_ = pin.id - seq_backtrack_;
seq_list_.reset();
}
while (id_ < pin.id) // should never iterate more than seq_backtrack_ steps
{
seq_list_.push(PacketID::time_t(SEQ_UNSEEN));
++id_;
}
// remember timestamp of packet ID
{
const PacketID::time_t local_now = now;
const size_t diff = id_ - pin.id;
if (diff < seq_list_.size() && local_now > PacketID::time_t(SEQ_EXPIRED))
seq_list_[diff] = local_now;
}
}
else
{
// TCP mode
time_ = pin.time;
id_ = pin.id;
}
}
PacketID read_next(Buffer& buf) const
{
if (!initialized_)
throw packet_id_not_initialized();
PacketID pid;
pid.read(buf, form_);
return pid;
}
#ifdef PACKET_ID_EXTRA_LOG_INFO
std::string str() const
{
if (!initialized_)
throw packet_id_not_initialized();
std::ostringstream os;
const PacketID::time_t local_now = now;
os << name_ << "-" << unit_ << " [";
for (size_t i = 0; i < seq_list_.size(); ++i)
{
char c;
const PacketID::time_t v = seq_list_[i];
if (v == PacketID::time_t(SEQ_UNSEEN))
c = '_';
else if (v == PacketID::time_t(SEQ_EXPIRED))
c = 'E';
else
{
const int diff = int(local_now - v);
if (diff < 0)
c = 'N';
else if (diff < 10)
c = '0' + diff;
else
c = '>';
}
os << c;
}
os << "] " << time_ << ":" << id_;
return os.str();
}
#endif
private:
/*
* Expire sequence numbers which can no longer
* be accepted because they would violate
* time_backtrack.
*/
void reap()
{
const PacketID::time_t local_now = now;
if (time_backtrack_)
{
bool expire = false;
for (size_t i = 0; i < seq_list_.size(); ++i)
{
const PacketID::time_t t = seq_list_[i];
if (t == PacketID::time_t(SEQ_EXPIRED)) // fast path -- once we see SEQ_EXPIRED from previous run, we can stop
break;
if (!expire && t && t + time_backtrack_ < local_now)
expire = true;
if (expire)
seq_list_[i] = PacketID::time_t(SEQ_EXPIRED);
}
}
last_reap_ = local_now;
}
void debug_log (const int level, const PacketID& pin, const char *description, const PacketID::id_t info) const
{
if (debug_level_ >= level)
do_log(pin, description, info);
}
void do_log (const PacketID& pin, const char *description, const PacketID::id_t info) const
{
#ifdef PACKET_ID_EXTRA_LOG_INFO
OPENVPN_LOG("PACKET_ID: '" << description << "' pin=[" << pin.id << "," << pin.time << "] info=" << info << " state=" << str());
#else
OPENVPN_LOG("PACKET_ID: '" << description << "' pin=[" << pin.id << "," << pin.time << "] info=" << info << " state=" << name_ << "-" << unit_);
#endif
}
bool initialized_; /* true if packet_id_init was called */
int debug_level_; /* log more when higher */
PacketID::id_t id_; /* highest sequence number received */
PacketID::time_t time_; /* highest time stamp received */
PacketID::time_t last_reap_; /* last call of packet_id_reap */
PacketID::id_t seq_backtrack_; /* maximum allowed packet ID backtrack (init parameter) */
PacketID::id_t max_backtrack_stat_; /* maximum backtrack seen so far */
int time_backtrack_; /* maximum allowed time backtrack (init parameter) */
std::string name_; /* name of this object (for debugging) */
int unit_; /* unit number of this object (for debugging) */
int form_; /* PacketID::LONG_FORM or PacketID::SHORT_FORM */
CircList<PacketID::time_t> seq_list_; /* packet-id "memory" */
};
} // namespace openvpn
#endif // OPENVPN_CRYPTO_PACKET_ID_H