Compare commits
2 commits
f07f2a853d
...
a08808344e
Author | SHA1 | Date | |
---|---|---|---|
Théophile Bastian | a08808344e | ||
Théophile Bastian | c5541d1e79 |
111
UdpVpn.cpp
111
UdpVpn.cpp
|
@ -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");
|
||||
}
|
||||
|
|
21
UdpVpn.hpp
21
UdpVpn.hpp
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -8,6 +8,6 @@ class UdpVpnClient: public UdpVpn {
|
|||
|
||||
protected:
|
||||
virtual void acquire_peer(
|
||||
TunnelledPacket& packet,
|
||||
VpnDataPacket& packet,
|
||||
const sockaddr_in6& peer_ext_addr);
|
||||
};
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
|
163
VpnPacket.cpp
163
VpnPacket.cpp
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
158
VpnPacket.hpp
158
VpnPacket.hpp
|
@ -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;
|
||||
};
|
||||
|
|
70
VpnPeer.cpp
70
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));
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
49
VpnPeer.hpp
49
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 {
|
||||
|
@ -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;
|
||||
};
|
||||
|
|
8
main.cpp
8
main.cpp
|
@ -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) {
|
||||
|
|
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