Measure RTT
This commit is contained in:
parent
c5541d1e79
commit
a08808344e
8 changed files with 253 additions and 17 deletions
38
UdpVpn.cpp
38
UdpVpn.cpp
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
67
VpnPeer.cpp
67
VpnPeer.cpp
|
@ -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);
|
||||
}
|
||||
|
|
43
VpnPeer.hpp
43
VpnPeer.hpp
|
@ -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;
|
||||
};
|
||||
|
|
3
main.cpp
3
main.cpp
|
@ -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);
|
||||
|
||||
|
|
7
util.hpp
7
util.hpp
|
@ -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<>
|
||||
|
|
Loading…
Reference in a new issue