Compare commits

...

2 commits

Author SHA1 Message Date
Théophile Bastian a08808344e Measure RTT 2020-06-28 23:29:39 +02:00
Théophile Bastian c5541d1e79 Split data and control packets 2020-06-26 19:41:55 +02:00
12 changed files with 469 additions and 128 deletions

View file

@ -1,5 +1,6 @@
#include "UdpVpn.hpp"
#include <chrono>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
@ -8,14 +9,18 @@
#include <string.h>
#include <poll.h>
#include <errno.h>
#include <math.h>
#include "ip_header.hpp"
static const size_t VPN_MTU = 1460; // TODO determine this -- issue #3
UdpVpn::UdpVpn()
: _stopped(false), _vpn_mtu(VPN_MTU), _tun_dev("cvpn%d"), _peer(nullptr)
: _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)
@ -42,7 +47,12 @@ void UdpVpn::run() {
poll_fds[1].events = POLLIN;
while(!_stopped) {
rc = poll(poll_fds, nfds, -1);
if(_dump_requested) {
dump_state();
_dump_requested = false;
}
rc = poll(poll_fds, nfds, 100); // timeout every 100ms
if(rc < 0) {
if(errno == EINTR) // Interrupt.
@ -50,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;
@ -75,7 +96,7 @@ size_t UdpVpn::read_from_tun(char* buffer, size_t len) {
return _tun_dev.read(buffer, len);
}
size_t UdpVpn::read_from_tun(TunnelledPacket& packet) {
size_t UdpVpn::read_from_tun(VpnDataPacket& packet) {
size_t payload_space = packet.get_payload_space();
size_t nread = read_from_tun(packet.get_payload(), payload_space);
packet.set_payload_size(nread);
@ -136,9 +157,8 @@ size_t UdpVpn::transmit_to_peer(VpnPacket& packet) {
}
void UdpVpn::receive_from_tun() {
VpnPacket packet(_vpn_mtu);
TunnelledPacket tunnelled = TunnelledPacket::create(packet);
size_t nread = read_from_tun(tunnelled);
VpnDataPacket packet(_vpn_mtu, false);
size_t nread = read_from_tun(packet);
if(nread == 0)
return;
@ -149,8 +169,8 @@ void UdpVpn::receive_from_tun() {
packet.set_peer(_peer.get());
kdebugf("Transmitting %s -> %s, size %d\n",
format_address(tunnelled.get_ipv6_header().source.s6_addr),
format_address(tunnelled.get_ipv6_header().dest.s6_addr),
format_address(packet.get_ipv6_header().source.s6_addr),
format_address(packet.get_ipv6_header().dest.s6_addr),
nread);
packet.prepare_for_sending();
@ -158,7 +178,7 @@ void UdpVpn::receive_from_tun() {
}
void UdpVpn::receive_from_udp() {
VpnPacket packet(_vpn_mtu);
VpnPacket packet(_vpn_mtu, true);
sockaddr_in6 peer_ext_addr;
size_t nread = read_from_udp(packet, peer_ext_addr);
if(nread == 0)
@ -167,32 +187,61 @@ void UdpVpn::receive_from_udp() {
// If we don't have a peer yet -- we're just setting the peer to nullptr.
packet.set_peer(_peer.get());
for(VpnPacketTLV tlv=packet.first_tlv();
!tlv.past_the_end();
tlv.seek_next_tlv())
{
switch(tlv.get_type()) {
case VpnPacket::PAYLOAD_TYPE_TUNNELLED:
{
TunnelledPacket tunnelled(tlv);
acquire_peer(tunnelled, peer_ext_addr);
receive_tunnelled_tlv(tunnelled);
}
break;
if(packet.is_control()) {
VpnControlPacket ctrl_packet(std::move(packet));
case VpnPacket::PAYLOAD_TYPE_UNDEF:
default:
debugf("#%d+%lu: ignoring TLV with bad type %d.\n",
packet.get_seqno(), tlv.get_offset(),
tlv.get_type());
break;
for(VpnPacketTLV tlv=ctrl_packet.first_tlv();
!tlv.past_the_end();
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",
ctrl_packet.get_seqno(), tlv.get_offset(),
tlv.get_type());
break;
}
}
} else {
VpnDataPacket data_packet(std::move(packet));
acquire_peer(data_packet, peer_ext_addr);
receive_tunnelled_tlv(data_packet);
}
}
void UdpVpn::receive_tunnelled_tlv(TunnelledPacket& packet) {
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\n",
packet.get_payload_size());
kdebugf("Reinjecting tunnelled packet of size %d [#%ld, TS=%ld μs]\n",
packet.get_payload_size(),
packet.get_seqno(),
packet.get_sending_timestamp());
_tun_dev.write(packet.get_payload(), packet.get_payload_size());
}
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

@ -41,28 +41,37 @@ class UdpVpn {
// Stop the server. Can be called from an interrupt.
void stop() { _stopped = true; }
// 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(
TunnelledPacket& packet,
VpnDataPacket& packet,
const sockaddr_in6& peer_ext_addr) = 0;
size_t read_from_tun(char* buffer, size_t len);
size_t read_from_tun(TunnelledPacket& packet);
size_t read_from_tun(VpnDataPacket& packet);
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();
void receive_tunnelled_tlv(TunnelledPacket& packet);
void receive_tunnelled_tlv(VpnDataPacket& packet);
void dump_state() const;
int _socket;
bool _stopped;
bool _stopped, _dump_requested;
size_t _vpn_mtu;
TunDevice _tun_dev;
std::unique_ptr<VpnPeer> _peer;
std::chrono::steady_clock::time_point _last_control_sent;
};

View file

@ -8,7 +8,7 @@ UdpVpnClient::UdpVpnClient(const struct sockaddr_in6& server) : UdpVpn() {
}
void UdpVpnClient::acquire_peer(
TunnelledPacket& packet,
VpnDataPacket& packet,
const sockaddr_in6&)
{
if(!packet.parse_as_ipv6())

View file

@ -8,6 +8,6 @@ class UdpVpnClient: public UdpVpn {
protected:
virtual void acquire_peer(
TunnelledPacket& packet,
VpnDataPacket& packet,
const sockaddr_in6& peer_ext_addr);
};

View file

@ -16,7 +16,7 @@ UdpVpnServer::UdpVpnServer(const struct in6_addr& bind_addr6, in_port_t port)
}
void UdpVpnServer::acquire_peer(
TunnelledPacket& packet,
VpnDataPacket& packet,
const sockaddr_in6& peer_ext_addr)
{
if(_peer)
@ -28,7 +28,7 @@ void UdpVpnServer::acquire_peer(
const in6_addr& peer_inner_addr = packet.get_ipv6_header().source;
_peer = std::make_unique<VpnPeer>(this, peer_ext_addr, peer_inner_addr);
packet.get_packet().set_peer(_peer.get());
packet.set_peer(_peer.get());
debugf("Got new peer %s:%d -- %s\n",
format_address(peer_ext_addr.sin6_addr.s6_addr),

View file

@ -11,7 +11,7 @@ class UdpVpnServer: public UdpVpn {
UdpVpnServer(const struct in6_addr& bind_addr6, in_port_t port);
protected:
virtual void acquire_peer(
TunnelledPacket& packet,
VpnDataPacket& packet,
const sockaddr_in6& peer_ext_addr);
void bind(const struct in6_addr& bind_addr6, in_port_t port);

View file

@ -5,7 +5,7 @@
#include <cstring>
const size_t VpnPacket::VPN_HEADER_BYTES = 8;
const size_t VpnPacket::TLV_HEADER_BYTES = 3;
const size_t VpnControlPacket::TLV_HEADER_BYTES = 3;
static const size_t OUTER_HEADERS_BYTES =
40 /* IPv6 header */ + 8 /* UDP header */;
@ -13,46 +13,76 @@ static const size_t OUTER_HEADERS_BYTES =
static const int
DATA_SEQNO_POS = 0,
DATA_CTRLBIT_POS = 4,
DATA_TIMESTAMP_POS = 4;
VpnPacket::VpnPacket(size_t mtu)
: _peer(nullptr), _data_space(mtu-OUTER_HEADERS_BYTES),
VpnPacket::VpnPacket(size_t mtu, bool inbound)
: _peer(nullptr), _inbound(inbound), _data_space(mtu-OUTER_HEADERS_BYTES),
_data_size(VPN_HEADER_BYTES), _reception_timestamp(0)
{
_data = new char[mtu - OUTER_HEADERS_BYTES];
_data = std::unique_ptr<char[]>(new char[mtu - OUTER_HEADERS_BYTES]);
}
VpnPacket::~VpnPacket() {
delete[] _data;
}
VpnPacket::~VpnPacket() {}
VpnPacketTLV VpnPacket::first_tlv() {
return VpnPacketTLV(*this, 0);
}
VpnPacket::VpnPacket(VpnPacket&& move_from) :
_peer(move_from._peer),
_inbound(move_from._inbound),
_data(std::move(move_from._data)),
_data_space(move_from._data_space),
_data_size(move_from._data_size),
_reception_timestamp(move_from._reception_timestamp)
{}
size_t VpnPacket::get_tunnelled_mtu(size_t udp_mtu) {
return udp_mtu - OUTER_HEADERS_BYTES - VPN_HEADER_BYTES - TLV_HEADER_BYTES;
return udp_mtu - OUTER_HEADERS_BYTES - VPN_HEADER_BYTES;
}
void VpnPacket::set_peer(VpnPeer* peer) {
_peer = peer;
if(_peer && _inbound)
_peer->got_inbound_packet(*this);
}
uint32_t VpnPacket::get_seqno() const {
return *(uint32_t*)(_data + DATA_SEQNO_POS);
return ntohl(*(uint32_t*)(_data.get() + DATA_SEQNO_POS));
}
uint32_t VpnPacket::get_sending_timestamp() const {
return *(uint32_t*)(_data + DATA_TIMESTAMP_POS);
return ntohl(
*(uint32_t*)(_data.get() + DATA_TIMESTAMP_POS) & 0x7fffffffUL
);
}
bool VpnPacket::is_control() const {
return *(unsigned char*)(_data.get() + DATA_CTRLBIT_POS) & 0x80;
}
void VpnPacket::set_control(bool is_control) {
unsigned char* ctrl_field =
(unsigned char*) (_data.get() + DATA_CTRLBIT_POS);
*ctrl_field &= 0x7f;
if(is_control)
*ctrl_field |= 0x80;
}
void VpnPacket::prepare_for_sending() {
*(uint32_t*)(_data + DATA_SEQNO_POS) = next_seqno();
*(uint32_t*)(_data + DATA_TIMESTAMP_POS) =
std::chrono::time_point_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now()).time_since_epoch().count();
uint32_t* ts_field = (uint32_t*) (_data.get() + DATA_TIMESTAMP_POS);
*ts_field &= htonl(0x80000000UL);
*(uint32_t*)(_data.get() + DATA_SEQNO_POS) = htonl(next_seqno());
*ts_field |=
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() {
@ -61,8 +91,40 @@ uint32_t VpnPacket::next_seqno() {
return _peer->next_seqno();
}
VpnControlPacket::VpnControlPacket(size_t mtu, bool inbound)
: VpnPacket(mtu, inbound)
{
set_control(true);
}
VpnPacketTLV::VpnPacketTLV(VpnPacket& packet, size_t payload_offset)
VpnControlPacket::VpnControlPacket(VpnPacket&& move_from)
: VpnPacket(std::move(move_from))
{}
VpnPacketTLV VpnControlPacket::first_tlv() {
return VpnPacketTLV(*this, 0);
}
VpnDataPacket::VpnDataPacket(size_t mtu, bool inbound)
: VpnPacket(mtu, inbound), _ipv6_parsed(false)
{
set_control(false);
}
VpnDataPacket::VpnDataPacket(VpnPacket&& move_from)
: VpnPacket(std::move(move_from)), _ipv6_parsed(false)
{}
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)
: _packet(packet), _tlv_pos(payload_offset)
{}
@ -71,10 +133,10 @@ VpnPacketTLV::VpnPacketTLV(const VpnPacketTLV& other) :
{}
VpnPacketTLV VpnPacketTLV::create(
VpnPacket& packet, VpnPacket::PayloadType type)
VpnControlPacket& packet, VpnPacketTLV::PayloadType type)
{
VpnPacketTLV tlv = VpnPacketTLV(packet, packet.get_payload_size());
packet.increase_payload_size(VpnPacket::TLV_HEADER_BYTES);
packet.increase_payload_size(VpnControlPacket::TLV_HEADER_BYTES);
char* data = tlv.get_data();
data[0] = type;
@ -89,7 +151,8 @@ VpnPacketTLV VpnPacketTLV::next_tlv() {
}
void VpnPacketTLV::seek_next_tlv() {
_tlv_pos = _tlv_pos + VpnPacket::TLV_HEADER_BYTES + get_payload_size();
_tlv_pos =
_tlv_pos + VpnControlPacket::TLV_HEADER_BYTES + get_payload_size();
}
uint16_t VpnPacketTLV::get_payload_size() const {
@ -108,25 +171,51 @@ uint16_t VpnPacketTLV::get_payload_space() const {
+ get_payload_size();
}
VpnPacket::PayloadType VpnPacketTLV::get_type() const {
return (VpnPacket::PayloadType)(*(uint8_t*)(get_data()));
VpnPacketTLV::PayloadType VpnPacketTLV::get_type() const {
return (PayloadType)(*(uint8_t*)(get_data()));
}
void VpnPacketTLV::set_type(VpnPacket::PayloadType type) {
void VpnPacketTLV::set_type(VpnPacketTLV::PayloadType type) {
*(uint8_t*)(get_data()) = (uint8_t) type;
}
TunnelledPacket::TunnelledPacket(VpnPacket& packet, size_t payload_offset)
: VpnPacketTLV(packet, payload_offset)
{}
TunnelledPacket::TunnelledPacket(const VpnPacketTLV& copy)
: VpnPacketTLV(copy._packet, copy._tlv_pos)
{}
/* ========== VpnTlvRTTQ ========== */
VpnTlvRTTQ::VpnTlvRTTQ(VpnControlPacket& packet, size_t payload_offset)
: VpnPacketTLV(packet, payload_offset) {}
VpnTlvRTTQ::VpnTlvRTTQ(const VpnPacketTLV& other)
: VpnPacketTLV(other) {}
TunnelledPacket TunnelledPacket::create(VpnPacket& packet) {
return VpnPacketTLV::create(packet, VpnPacket::PAYLOAD_TYPE_TUNNELLED);
VpnTlvRTTQ VpnTlvRTTQ::create(VpnControlPacket& packet) {
return VpnPacketTLV::create(packet, VpnPacketTLV::PAYLOAD_TYPE_RTTQ);
}
bool TunnelledPacket::parse_as_ipv6() {
return parse_ipv6_header(get_payload(), get_payload_size(), _ipv6_header);
/* ========== 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

@ -14,7 +14,17 @@
* +---------------+---------------+---------------+---------------+
* | Sequence number [4B] |
* +---------------------------------------------------------------+
* | Sending timestamp (μs) [4B] |
* |C| Sending timestamp (μs) [4B-1b] |
* +---------------+-----------------------------------------------+
* | ... |
* +---------------------------------------------------------------+
*
* Where C is a single bit. If set, the packet is a control packet, containing
* TLVs. If unset, the packet is a tunnelled packet bearing data to be
* reinjected.
*
* If C is set, the remaining of the packet has the following structure:
*
* +---------------+-----------------------------------------------+
* | Type [1B] | First payload size (B) [2B] | Payload |
* +---------------+-----------------------------------------------+
@ -27,7 +37,7 @@
* | ... |
* +---------------------------------------------------------------+
*
* Where
* Where:
* - Type is one of the values from PayloadType below;
* - Sender ID is an arbitrary value, recommended to be randomly chosen
* - Payload size is the size of the payload (excluding headers), in bytes;
@ -40,32 +50,33 @@ class VpnPacketTLV;
class VpnPacket {
public:
static const size_t VPN_HEADER_BYTES;
static const size_t TLV_HEADER_BYTES;
enum PayloadType {
PAYLOAD_TYPE_UNDEF, ///< Undefined packet type
PAYLOAD_TYPE_TUNNELLED, ///< A tunnelled packet
PAYLOAD_TYPE_RR, ///< Receiver report
PAYLOAD_TYPE_REMB, ///< Receiver Estimated Maximum Bitrate
};
class PeerNotSet: public std::exception {};
VpnPacket(size_t mtu);
VpnPacket(size_t mtu, bool inbound);
~VpnPacket();
VpnPacketTLV first_tlv();
// Remove copy constructor and operator=
VpnPacket(const VpnPacket& copy) = delete;
VpnPacket& operator=(const VpnPacket& copy) = delete;
/// Move constructor
VpnPacket(VpnPacket&& move_from);
VpnPacket& operator=(VpnPacket&& move_from) = delete;
/// Get the maximal payload space for a given tunnel MTU
static size_t get_tunnelled_mtu(size_t udp_mtu);
/// Set packet peer -- used for sequence numbers
void set_peer(VpnPeer* peer) { _peer = peer; }
/// 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 + VPN_HEADER_BYTES; }
const char* get_payload() const {
return _data.get() + VPN_HEADER_BYTES; }
/// Get a pointer to the packet payload
char* get_payload() { return _data + VPN_HEADER_BYTES; }
char* get_payload() { return _data.get() + VPN_HEADER_BYTES; }
/// Get a pointer to the first free byte of the packet payload
char* get_next_payload() { return get_payload() + get_payload_size(); }
/// Get the space allocated for the packet payload
@ -83,9 +94,9 @@ class VpnPacket {
_data_size += payload_size_increment; }
/// Get a pointer to the full packet data (const version)
const char* get_data() const { return _data; }
const char* get_data() const { return _data.get(); }
/// Get a pointer to the full packet data
char* get_data() { return _data; }
char* get_data() { return _data.get(); }
/// Get the space allocated for the packet
size_t get_data_space() const { return _data_space; }
/// Get the total current size of the packet
@ -99,6 +110,8 @@ class VpnPacket {
uint32_t get_sending_timestamp() const;
/// Get this packet's reception timestamp
uint32_t get_reception_timestamp() const { return _reception_timestamp; }
/// Check whether this packet is control or data
bool is_control() const;
/** Fill the headers of the packet. This method must be called as close
* to the time the packet is actually sent as possible, as it handles
@ -111,30 +124,73 @@ class VpnPacket {
*/
void upon_reception();
protected:
void set_control(bool is_control);
private: // methods
inline uint32_t next_seqno();
private:
VpnPeer* _peer; // raw pointer: we do not own the peer in any way
bool _inbound; ///< is the packet received or sent?
char* _data;
std::unique_ptr<char[]> _data;
size_t _data_space, _data_size;
uint32_t _reception_timestamp;
};
class VpnControlPacket: public VpnPacket {
public:
static const size_t TLV_HEADER_BYTES;
VpnControlPacket(size_t mtu, bool inbound);
VpnControlPacket(VpnPacket&& move_from);
VpnPacketTLV first_tlv();
};
/** A packet sent through the VPN tunnel. */
class VpnDataPacket: public VpnPacket {
public:
VpnDataPacket(size_t mtu, bool inbound);
VpnDataPacket(VpnPacket&& move_from);
static VpnDataPacket create(VpnPacket& packet);
/** 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; }
private:
bool _ipv6_parsed;
IPv6Header _ipv6_header;
};
/** Base class for a TLV contained in a VpnPacket */
class VpnPacketTLV {
public:
VpnPacketTLV(VpnPacket& packet, size_t payload_offset);
VpnPacketTLV(VpnControlPacket& packet, size_t payload_offset);
VpnPacketTLV(const VpnPacketTLV& other);
static VpnPacketTLV create(
VpnPacket& packet,
VpnPacket::PayloadType type=VpnPacket::PAYLOAD_TYPE_UNDEF);
enum PayloadType {
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
};
const VpnPacket& get_packet() const { return _packet; }
VpnPacket& get_packet() { return _packet; }
static VpnPacketTLV create(
VpnControlPacket& packet,
PayloadType type=PAYLOAD_TYPE_UNDEF);
const VpnControlPacket& get_packet() const { return _packet; }
VpnControlPacket& get_packet() { return _packet; }
/// Get the next TLV in this packet.
VpnPacketTLV next_tlv();
@ -150,10 +206,12 @@ class VpnPacketTLV {
/// Get a pointer to the payload (const version)
const char* get_payload() const {
return _packet.get_payload() + _tlv_pos + VpnPacket::TLV_HEADER_BYTES; }
return _packet.get_payload() + _tlv_pos
+ VpnControlPacket::TLV_HEADER_BYTES; }
/// Get a pointer to the payload
char* get_payload() {
return _packet.get_payload() + _tlv_pos + VpnPacket::TLV_HEADER_BYTES; }
return _packet.get_payload() + _tlv_pos
+ VpnControlPacket::TLV_HEADER_BYTES; }
/// Get the current payload size
uint16_t get_payload_size() const;
/// Set the current payload size
@ -168,43 +226,47 @@ class VpnPacketTLV {
char* get_data() { return _packet.get_payload() + _tlv_pos; }
/// Get the current raw data size
uint16_t get_data_size() const {
return get_payload_size() + VpnPacket::TLV_HEADER_BYTES; }
return get_payload_size() + VpnControlPacket::TLV_HEADER_BYTES; }
/// Set the current raw data size
void set_data_size(uint16_t size) {
set_payload_size(size + VpnPacket::TLV_HEADER_BYTES); }
set_payload_size(size + VpnControlPacket::TLV_HEADER_BYTES); }
/// Get this TLV's type
VpnPacket::PayloadType get_type() const;
PayloadType get_type() const;
bool operator==(const VpnPacketTLV& other) const {
return &_packet == &other._packet && _tlv_pos == other._tlv_pos; }
protected: // meth
/// Set this TLV's type
void set_type(VpnPacket::PayloadType type);
void set_type(PayloadType type);
protected:
VpnPacket& _packet;
VpnControlPacket& _packet;
size_t _tlv_pos;
friend class TunnelledPacket;
};
/** A packet sent through the VPN tunnel.
*
* This must instantiated just before filling it with data. */
class TunnelledPacket: public VpnPacketTLV {
class VpnTlvRTTQ: public VpnPacketTLV {
public:
TunnelledPacket(VpnPacket& packet, size_t payload_offset);
TunnelledPacket(const VpnPacketTLV& copy);
static TunnelledPacket create(VpnPacket& packet);
VpnTlvRTTQ(VpnControlPacket& packet, size_t payload_offset);
VpnTlvRTTQ(const VpnPacketTLV& other);
/// Try to parse the packet as IPv6, return `false` upon failure.
bool parse_as_ipv6();
bool ipv6_parsed() const { return _ipv6_parsed; }
const IPv6Header& get_ipv6_header() const { return _ipv6_header; }
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:
bool _ipv6_parsed;
IPv6Header _ipv6_header;
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));
@ -29,6 +40,29 @@ size_t VpnPeer::write(const VpnPacket& packet) {
return write(packet.get_data(), packet.get_data_size());
}
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) {
@ -68,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 {
@ -56,15 +83,37 @@ 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);
uint32_t peek_next_seqno() const { return _next_send_seqno; }
uint32_t next_seqno() { return _next_send_seqno++; }
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;
in6_addr _int_addr;
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>
@ -27,6 +28,10 @@ void stop_sig_handler(int signal) {
vpn_instance->stop();
}
void dump_sig_handler(int /*signal*/) {
vpn_instance->dump_requested();
}
bool parse_options(int argc, char** argv, ProgOptions& opts) {
int option;
memset(&opts, 0, sizeof(opts));
@ -90,7 +95,10 @@ 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);
try {
if(program_options.listen && program_options.has_peer) {

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<>