Measure RTT

This commit is contained in:
Théophile Bastian 2020-06-28 23:29:39 +02:00
parent c5541d1e79
commit a08808344e
8 changed files with 253 additions and 17 deletions

View file

@ -1,5 +1,6 @@
#include "UdpVpn.hpp"
#include <chrono>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
@ -18,6 +19,8 @@ UdpVpn::UdpVpn()
: _stopped(false), _dump_requested(false), _vpn_mtu(VPN_MTU),
_tun_dev("cvpn%d"), _peer(nullptr)
{
_last_control_sent =
std::chrono::steady_clock::now() - std::chrono::seconds(1);
_tun_dev.set_mtu(VpnPacket::get_tunnelled_mtu(_vpn_mtu));
_socket = socket(AF_INET6, SOCK_DGRAM, 0);
if(_socket < 0)
@ -49,7 +52,7 @@ void UdpVpn::run() {
_dump_requested = false;
}
rc = poll(poll_fds, nfds, -1);
rc = poll(poll_fds, nfds, 100); // timeout every 100ms
if(rc < 0) {
if(errno == EINTR) // Interrupt.
@ -57,7 +60,18 @@ void UdpVpn::run() {
throw UdpVpn::NetError(
"Error polling from interface", errno, true);
}
else if(rc == 0) // Nothing to read
// ## Check periodic actions
if(_peer) {
if(std::chrono::steady_clock::now() - _last_control_sent
> std::chrono::milliseconds(100))
{
if(_peer->send_control_packet())
_last_control_sent = std::chrono::steady_clock::now();
}
}
if(rc == 0) // Nothing to read -- timeout
continue;
cur_fd = start_at_fd;
@ -181,6 +195,14 @@ void UdpVpn::receive_from_udp() {
tlv.seek_next_tlv())
{
switch(tlv.get_type()) {
case VpnPacketTLV::PAYLOAD_TYPE_RTTQ:
if(_peer)
_peer->make_rtta_for(VpnTlvRTTQ(tlv));
break;
case VpnPacketTLV::PAYLOAD_TYPE_RTTA:
if(_peer)
_peer->log_rtta(VpnTlvRTTA(tlv));
break;
case VpnPacketTLV::PAYLOAD_TYPE_UNDEF:
default:
debugf("#%d+%lu: ignoring TLV with bad type %d.\n",
@ -197,6 +219,11 @@ void UdpVpn::receive_from_udp() {
}
void UdpVpn::receive_tunnelled_tlv(VpnDataPacket& packet) {
if(!packet.parse_as_ipv6()) {
debugf("Reinjection: dropping packet with bad IPv6 header.\n");
return;
}
// Reinject into tun
kdebugf("Reinjecting tunnelled packet of size %d [#%ld, TS=%ld μs]\n",
packet.get_payload_size(),
@ -209,5 +236,12 @@ void UdpVpn::dump_state() const {
printf("====== State dump ======\n");
printf("Packet loss rate: %.0lf%%\n",
round(_peer->get_loss_logger().get_loss_rate() * 100));
printf("RTT: %.02lf ms avg, %.02lf ms last [last updated: %lu ms ago]\n",
(double)_peer->get_rtt().avg_rtt() / 1e3,
(double)_peer->get_rtt().cur_rtt() / 1e3,
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now()
- _peer->get_rtt().get_last_update()).count()
);
printf("==== End state dump ====\n");
}

View file

@ -44,6 +44,10 @@ class UdpVpn {
// A state dump has been requested
void dump_requested() { _dump_requested = true; }
size_t get_mtu() const { return _vpn_mtu; }
size_t transmit_to_peer(VpnPacket& packet);
protected:
virtual void acquire_peer(
VpnDataPacket& packet,
@ -54,8 +58,6 @@ class UdpVpn {
size_t read_from_udp(char* buffer, size_t len, sockaddr_in6& peer_addr);
size_t read_from_udp(VpnPacket& packet, sockaddr_in6& peer_addr);
size_t transmit_to_peer(VpnPacket& packet);
void receive_from_tun();
void receive_from_udp();
@ -70,4 +72,6 @@ class UdpVpn {
TunDevice _tun_dev;
std::unique_ptr<VpnPeer> _peer;
std::chrono::steady_clock::time_point _last_control_sent;
};

View file

@ -68,18 +68,21 @@ void VpnPacket::set_control(bool is_control) {
void VpnPacket::prepare_for_sending() {
uint32_t* ts_field = (uint32_t*) (_data.get() + DATA_TIMESTAMP_POS);
*ts_field &= 0x80000000UL;
*ts_field &= htonl(0x80000000UL);
*(uint32_t*)(_data.get() + DATA_SEQNO_POS) = htonl(next_seqno());
*ts_field |=
htonl((std::chrono::time_point_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now()).time_since_epoch().count())
& 0x7fffffffUL);
htonl(to_us_timestamp(
std::chrono::time_point_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now()
).time_since_epoch().count())
);
}
void VpnPacket::upon_reception() {
_reception_timestamp =
std::chrono::time_point_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now()).time_since_epoch().count();
_reception_timestamp = to_us_timestamp(
std::chrono::time_point_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now()
).time_since_epoch().count());
}
uint32_t VpnPacket::next_seqno() {
@ -112,8 +115,13 @@ VpnDataPacket::VpnDataPacket(VpnPacket&& move_from)
: VpnPacket(std::move(move_from)), _ipv6_parsed(false)
{}
bool VpnDataPacket::parse_as_ipv6() {
return parse_ipv6_header(get_payload(), get_payload_size(), _ipv6_header);
bool VpnDataPacket::parse_as_ipv6(bool reparse) {
if(_ipv6_parsed && !reparse)
return true;
_ipv6_parsed =
parse_ipv6_header(get_payload(), get_payload_size(), _ipv6_header);
return _ipv6_parsed;
}
VpnPacketTLV::VpnPacketTLV(VpnControlPacket& packet, size_t payload_offset)
@ -169,3 +177,45 @@ VpnPacketTLV::PayloadType VpnPacketTLV::get_type() const {
void VpnPacketTLV::set_type(VpnPacketTLV::PayloadType type) {
*(uint8_t*)(get_data()) = (uint8_t) type;
}
/* ========== VpnTlvRTTQ ========== */
VpnTlvRTTQ::VpnTlvRTTQ(VpnControlPacket& packet, size_t payload_offset)
: VpnPacketTLV(packet, payload_offset) {}
VpnTlvRTTQ::VpnTlvRTTQ(const VpnPacketTLV& other)
: VpnPacketTLV(other) {}
VpnTlvRTTQ VpnTlvRTTQ::create(VpnControlPacket& packet) {
return VpnPacketTLV::create(packet, VpnPacketTLV::PAYLOAD_TYPE_RTTQ);
}
/* ========== VpnTlvRTTA ========== */
const uint32_t VpnTlvRTTA::EXP_TS_POS = 0, VpnTlvRTTA::RECV_TS_POS = 4;
VpnTlvRTTA::VpnTlvRTTA(VpnControlPacket& packet, size_t payload_offset)
: VpnPacketTLV(packet, payload_offset) {}
VpnTlvRTTA::VpnTlvRTTA(const VpnPacketTLV& other)
: VpnPacketTLV(other) {}
VpnTlvRTTA VpnTlvRTTA::create(VpnControlPacket& packet) {
VpnTlvRTTA tlv =
VpnPacketTLV::create(packet, VpnPacketTLV::PAYLOAD_TYPE_RTTA);
tlv.set_payload_size(8);
memset(tlv.get_payload(), 0, 8);
return tlv;
}
uint32_t VpnTlvRTTA::get_exp_ts() const {
return ntohl(*(uint32_t*)(get_payload() + EXP_TS_POS));
}
void VpnTlvRTTA::set_exp_ts(uint32_t ts) {
*(uint32_t*)(get_payload() + EXP_TS_POS) = htonl(ts);
}
uint32_t VpnTlvRTTA::get_recv_ts() const {
return ntohl(*(uint32_t*)(get_payload() + RECV_TS_POS));
}
void VpnTlvRTTA::set_recv_ts(uint32_t ts) {
*(uint32_t*)(get_payload() + RECV_TS_POS) = htonl(ts);
}

View file

@ -69,6 +69,9 @@ class VpnPacket {
/// Set packet peer -- used for sequence numbers and loss rate
void set_peer(VpnPeer* peer);
/// Checks whether the packet currently bears a payload
bool is_empty() const { return get_payload_size() == 0; }
/// Get a pointer to the packet payload (const version)
const char* get_payload() const {
return _data.get() + VPN_HEADER_BYTES; }
@ -155,8 +158,10 @@ class VpnDataPacket: public VpnPacket {
static VpnDataPacket create(VpnPacket& packet);
/// Try to parse the packet as IPv6, return `false` upon failure.
bool parse_as_ipv6();
/** Try to parse the packet as IPv6, return `false` upon failure.
* Does not reparse if _ipv6_parsed, unless reparse is true
*/
bool parse_as_ipv6(bool reparse=false);
bool ipv6_parsed() const { return _ipv6_parsed; }
const IPv6Header& get_ipv6_header() const { return _ipv6_header; }
@ -175,6 +180,8 @@ class VpnPacketTLV {
PAYLOAD_TYPE_UNDEF, ///< Undefined packet type
PAYLOAD_TYPE_RR, ///< Receiver report
PAYLOAD_TYPE_REMB, ///< Receiver Estimated Maximum Bitrate
PAYLOAD_TYPE_RTTQ, ///< RTT update query
PAYLOAD_TYPE_RTTA, ///< RTT update answer
};
@ -238,3 +245,28 @@ class VpnPacketTLV {
VpnControlPacket& _packet;
size_t _tlv_pos;
};
class VpnTlvRTTQ: public VpnPacketTLV {
public:
VpnTlvRTTQ(VpnControlPacket& packet, size_t payload_offset);
VpnTlvRTTQ(const VpnPacketTLV& other);
static VpnTlvRTTQ create(VpnControlPacket& packet);
};
class VpnTlvRTTA: public VpnPacketTLV {
public:
VpnTlvRTTA(VpnControlPacket& packet, size_t payload_offset);
VpnTlvRTTA(const VpnPacketTLV& other);
static VpnTlvRTTA create(VpnControlPacket& packet);
uint32_t get_exp_ts() const;
void set_exp_ts(uint32_t ts);
uint32_t get_recv_ts() const;
void set_recv_ts(uint32_t ts);
private:
static const uint32_t EXP_TS_POS, RECV_TS_POS;
};

View file

@ -5,10 +5,21 @@
#include <cstring>
#include <functional>
const double RTTLogger::EXP_AVG_FACTOR = 0.75;
const unsigned int RTTLogger::BASE_UPDATE_DELAY = 1000; // ms
VpnPeer::VpnPeer(UdpVpn* vpn, const sockaddr_in6& ext_addr,
const in6_addr& int_addr)
: _vpn(vpn), _ext_addr(ext_addr), _int_addr(int_addr), _next_send_seqno(0)
{}
{
cycle_next_control();
}
void VpnPeer::cycle_next_control() {
_next_control_packet =
std::make_unique<VpnControlPacket>(_vpn->get_mtu(), false);
_next_control_packet->set_peer(this);
}
void VpnPeer::set_int_addr(const in6_addr& int_addr) {
memcpy(&_int_addr, &int_addr, sizeof(_int_addr));
@ -33,10 +44,28 @@ void VpnPeer::got_inbound_packet(const VpnPacket& packet) {
_packet_loss.log_packet(packet.get_seqno());
}
bool VpnPeer::send_control_packet() {
if(_rtt.update_due(!_next_control_packet->is_empty()))
VpnTlvRTTQ::create(*_next_control_packet);
if(!_next_control_packet->is_empty()) {
_next_control_packet->prepare_for_sending();
_vpn->transmit_to_peer(*_next_control_packet);
cycle_next_control();
return true;
}
return false;
}
void VpnPeer::make_rtta_for(const VpnTlvRTTQ& rttq) {
VpnTlvRTTA rtta = VpnTlvRTTA::create(*_next_control_packet);
rtta.set_exp_ts(rttq.get_packet().get_sending_timestamp());
rtta.set_recv_ts(rttq.get_packet().get_reception_timestamp());
}
PacketLossLogger::PacketLossLogger() : _cur_seqno(0) {}
void PacketLossLogger::log_packet(uint32_t seqno) {
kdebugf(">> Logging %lu (loss %lf)\n", seqno, get_loss_rate());
uint32_t m_seqno = seqno % PACKET_LOSS_HISTSIZE;
int64_t diff = (int64_t)seqno - _cur_seqno;
@ -73,3 +102,37 @@ void PacketLossLogger::reboot() {
_packet_loss_hist.reset();
_received_ahead.reset();
}
RTTLogger::RTTLogger() :
_avg_rtt(0), _cur_rtt(0),
_last_update(std::chrono::steady_clock::time_point::min())
{
_last_update =
std::chrono::steady_clock::now() - std::chrono::seconds(1);
// This avoids situations where both peers try to renew their RTT at the
// same time.
_update_delay = BASE_UPDATE_DELAY + rand() % 100 - 50;
}
void RTTLogger::log(const VpnTlvRTTA& rtt_answer) {
uint32_t local_t0 = rtt_answer.get_exp_ts(),
remote_t0 = rtt_answer.get_recv_ts(),
remote_t1 = rtt_answer.get_packet().get_sending_timestamp(),
local_t1 = rtt_answer.get_packet().get_reception_timestamp();
_cur_rtt = (local_t1 - local_t0) - (remote_t1 - remote_t0);
if(UNLIKELY(_avg_rtt == 0))
_avg_rtt = _cur_rtt;
else
_avg_rtt = (1 - EXP_AVG_FACTOR) * _avg_rtt
+ EXP_AVG_FACTOR * _cur_rtt;
_last_update = std::chrono::steady_clock::now();
}
bool RTTLogger::update_due(bool soon) const {
unsigned int ms_since_last_update =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - _last_update).count();
return ms_since_last_update >= (soon ? _update_delay/2 : _update_delay);
}

View file

@ -4,6 +4,7 @@
#include <netinet/in.h>
#include <bitset>
#include <chrono>
#include "util.hpp"
#include "VpnPacket.hpp"
@ -37,6 +38,32 @@ class PacketLossLogger {
uint32_t _cur_seqno;
};
/** Round-trip time logger. All timestamps/delays are in microseconds. */
class RTTLogger {
public:
RTTLogger();
uint32_t avg_rtt() const { return _avg_rtt; }
uint32_t cur_rtt() const { return _cur_rtt; }
std::chrono::steady_clock::time_point get_last_update() const {
return _last_update;
}
/** Checks whether an update is due.
* If soon is true, divides the inter-update delay by 2.
*/
bool update_due(bool soon=false) const;
void log(const VpnTlvRTTA& rtt_answer);
private:
uint32_t _avg_rtt, _cur_rtt;
static const double EXP_AVG_FACTOR;
unsigned int _update_delay; // in ms
static const unsigned int BASE_UPDATE_DELAY; // in ms
std::chrono::steady_clock::time_point _last_update;
};
class VpnPeer {
public:
class NetError : public MsgException {
@ -57,6 +84,9 @@ class VpnPeer {
void set_int_addr(const in6_addr& int_addr);
const PacketLossLogger& get_loss_logger() { return _packet_loss; }
const RTTLogger& get_rtt() { return _rtt; }
void log_rtta(const VpnTlvRTTA& rtta) { _rtt.log(rtta); }
size_t write(const char* data, size_t len);
size_t write(const VpnPacket& packet);
@ -66,6 +96,16 @@ class VpnPeer {
void got_inbound_packet(const VpnPacket& packet);
/* === Control protocol === */
/// Send a control packet if there is data to be sent
bool send_control_packet();
/// Append a RTTA for the given RTTQ to the next control packet
void make_rtta_for(const VpnTlvRTTQ& rttq);
private: // meth
void cycle_next_control(); /// Generate a fresh next control packet
private:
UdpVpn* _vpn;
sockaddr_in6 _ext_addr;
@ -73,4 +113,7 @@ class VpnPeer {
uint32_t _next_send_seqno;
PacketLossLogger _packet_loss;
RTTLogger _rtt;
std::unique_ptr<VpnControlPacket> _next_control_packet;
};

View file

@ -1,4 +1,5 @@
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
@ -94,6 +95,8 @@ int main(int argc, char** argv) {
}
printf("=== END OPTIONS ==\n\n");
srand(time(NULL)); // FIXME something more secure if we ever need crypto
signal(SIGINT, stop_sig_handler);
signal(SIGUSR1, dump_sig_handler);

View file

@ -48,6 +48,13 @@ void do_debugf(int level, const char *format, ...);
const char* format_address(const unsigned char* address);
/** remove the upper bit from a microsecond timestamp, to conform with the
* packet header timestamp format. */
inline uint32_t to_us_timestamp(uint32_t clock_output) {
return clock_output & 0x7fffffff;
}
/** in6_addr hash & equality */
namespace std {
template<>