diff --git a/UdpVpn.cpp b/UdpVpn.cpp index 7d35f20..585cb79 100644 --- a/UdpVpn.cpp +++ b/UdpVpn.cpp @@ -1,5 +1,6 @@ #include "UdpVpn.hpp" +#include #include #include #include @@ -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::steady_clock::now() + - _peer->get_rtt().get_last_update()).count() + ); printf("==== End state dump ====\n"); } diff --git a/UdpVpn.hpp b/UdpVpn.hpp index 55b835a..d8a1585 100644 --- a/UdpVpn.hpp +++ b/UdpVpn.hpp @@ -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 _peer; + + std::chrono::steady_clock::time_point _last_control_sent; }; diff --git a/VpnPacket.cpp b/VpnPacket.cpp index a007eca..c9e3f26 100644 --- a/VpnPacket.cpp +++ b/VpnPacket.cpp @@ -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::steady_clock::now()).time_since_epoch().count()) - & 0x7fffffffUL); + htonl(to_us_timestamp( + std::chrono::time_point_cast( + std::chrono::steady_clock::now() + ).time_since_epoch().count()) + ); } void VpnPacket::upon_reception() { - _reception_timestamp = - std::chrono::time_point_cast( - std::chrono::steady_clock::now()).time_since_epoch().count(); + _reception_timestamp = to_us_timestamp( + std::chrono::time_point_cast( + 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); +} + diff --git a/VpnPacket.hpp b/VpnPacket.hpp index f341afd..7339bb0 100644 --- a/VpnPacket.hpp +++ b/VpnPacket.hpp @@ -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; +}; diff --git a/VpnPeer.cpp b/VpnPeer.cpp index c5068f1..e071d0c 100644 --- a/VpnPeer.cpp +++ b/VpnPeer.cpp @@ -5,10 +5,21 @@ #include #include +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(_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::steady_clock::now() - _last_update).count(); + return ms_since_last_update >= (soon ? _update_delay/2 : _update_delay); +} diff --git a/VpnPeer.hpp b/VpnPeer.hpp index ac1355c..60fa5e8 100644 --- a/VpnPeer.hpp +++ b/VpnPeer.hpp @@ -4,6 +4,7 @@ #include #include +#include #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 _next_control_packet; }; diff --git a/main.cpp b/main.cpp index b66097f..f17a034 100644 --- a/main.cpp +++ b/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -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); diff --git a/util.hpp b/util.hpp index 8bc5a9e..b9ef545 100644 --- a/util.hpp +++ b/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<>