Compare commits

...

6 commits

15 changed files with 430 additions and 106 deletions

View file

@ -2,7 +2,11 @@ CXX=g++
CXXFLAGS=-O2 -g -Wall -Wextra -std=c++17
CXXLIBS=
OBJS=UdpVpn.o TunDevice.o util.o main.o
OBJS= \
UdpVpn.o UdpVpnClient.o UdpVpnServer.o \
VpnPeer.o \
TunDevice.o \
ip_header.o util.o main.o
TARGET=congestvpn
all: $(TARGET)

View file

@ -29,7 +29,7 @@ TunDevice::TunDevice(const std::string& dev)
*
* IFF_NO_PI - Do not provide packet information
*/
ifr.ifr_flags = IFF_TUN;
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
if(!dev.empty()) {
if(dev.size() >= IFNAMSIZ - 2)
throw TunDevice::InitializationError("Device name is too long.");
@ -44,6 +44,25 @@ TunDevice::TunDevice(const std::string& dev)
_dev_name = ifr.ifr_name;
_fd = fd;
// Bring interface up
kdebugf("Bringing interface up...\n");
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // Any socket will do
if(ioctl(sockfd, SIOCGIFFLAGS, (void*) &ifr) < 0) {
close(fd);
close(sockfd);
throw TunDevice::InitializationError(
"Could not get tunnel interface flags", errno, true);
}
ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
if(ioctl(sockfd, SIOCSIFFLAGS, (void*) &ifr) < 0) {
close(fd);
close(sockfd);
throw TunDevice::InitializationError(
"Could not bring tunnel interface up", errno, true);
}
close(sockfd);
// The device is now fully set up
_poll_fd.fd = _fd;
_poll_fd.events = POLLIN;

View file

@ -9,12 +9,11 @@
#include <poll.h>
#include <errno.h>
UdpVpn::UdpVpn()
: _bound(false), _stopped(false), _has_peer(false), _tun_dev("cvpn%d")
{
memset(&_serv_addr, 0, sizeof(_serv_addr));
memset(&_peer_addr, 0, sizeof(_peer_addr));
#include "ip_header.hpp"
UdpVpn::UdpVpn()
: _stopped(false), _tun_dev("cvpn%d")
{
_socket = socket(AF_INET6, SOCK_DGRAM, 0);
if(_socket < 0)
throw UdpVpn::InitializationError("Cannot create socket", errno, true);
@ -24,36 +23,6 @@ UdpVpn::~UdpVpn() {
close(_socket);
}
void UdpVpn::bind(in_port_t port) {
bind(in6addr_any, port);
}
void UdpVpn::bind(const struct in6_addr& bind_addr6, in_port_t port) {
int rc;
_serv_addr.sin6_family = AF_INET6;
_serv_addr.sin6_port = htons(port);
_serv_addr.sin6_addr = bind_addr6;
rc = ::bind(
_socket, (const struct sockaddr*)&_serv_addr, sizeof(_serv_addr));
if(rc < 0) {
throw UdpVpn::InitializationError("Cannot bind socket", errno, true);
}
debugf("> Listening on port %d\n", port);
_bound = true;
}
void UdpVpn::set_peer(const sockaddr_in6& peer_addr) {
memcpy(&_peer_addr, &peer_addr, sizeof(_peer_addr));
_has_peer = true;
char peer_addr_str[INET6_ADDRSTRLEN];
inet_ntop(
AF_INET6, &(peer_addr.sin6_addr), peer_addr_str, INET6_ADDRSTRLEN);
debugf("Set peer to %s:%d\n", peer_addr_str, ntohs(peer_addr.sin6_port));
}
void UdpVpn::run() {
int rc;
int start_at_fd = 0; // read from polled fds in round-robin fashion
@ -98,63 +67,40 @@ void UdpVpn::run() {
}
}
void UdpVpn::receive_from_tun() {
size_t UdpVpn::read_from_tun(char* buffer, size_t len) {
// We know that there is data available -- use `read()`
char buffer[1500];
size_t nread = _tun_dev.read(buffer, 1500);
size_t nread = _tun_dev.read(buffer, len);
if(nread == 0)
return;
return 0;
kdebugf("Transmitting packet of size %d to peer\n", nread);
send_over_udp(buffer, nread);
return nread;
}
void UdpVpn::receive_from_udp() {
size_t UdpVpn::read_from_udp(char* buffer, size_t len,
sockaddr_in6& peer_addr)
{
ssize_t nread;
char buffer[1500];
struct sockaddr_in6 peer_addr;
socklen_t peer_addr_len = sizeof(peer_addr);
nread = recvfrom(_socket, buffer, 1500, MSG_WAITALL,
nread = recvfrom(_socket, buffer, len, MSG_WAITALL,
(struct sockaddr*) &peer_addr, &peer_addr_len);
if(nread < 0)
throw UdpVpn::NetError("Cannot receive datagram", errno, true);
if(nread == 0)
return;
return 0;
if(peer_addr.sin6_family != AF_INET6) {
debugf("WARNING: Received non-ipv6 family datagram %d. Ignoring.\n",
peer_addr.sin6_family);
return;
return 0;
}
if(peer_addr_len != sizeof(peer_addr)) {
debugf("WARNING: received unexpected source address length %u."
"Ignoring.\n",
peer_addr_len);
return;
}
set_peer(peer_addr);
// Reinject into tun
kdebugf("Receiving packet of size %d from peer\n", nread);
_tun_dev.write(buffer, nread);
}
size_t UdpVpn::send_over_udp(const char* data, size_t len) {
ssize_t nsent;
if(!_has_peer) {
debugf("Dropping packet to be transmitted: no peer.\n");
return 0;
}
nsent = sendto(_socket, data, len, MSG_CONFIRM,
(const struct sockaddr*) &_peer_addr, sizeof(_peer_addr));
if(nsent < 0)
throw NetError("Could not send UDP packet", errno, true);
return (size_t) nsent;
return nread;
}

View file

@ -5,6 +5,7 @@
#include "util.hpp"
#include "TunDevice.hpp"
#include "VpnPeer.hpp"
/** Handles UDP communication */
@ -28,18 +29,10 @@ class UdpVpn {
};
UdpVpn();
~UdpVpn();
void bind(in_port_t port);
void bind(const struct in6_addr& bind_addr6, in_port_t port);
virtual ~UdpVpn();
int get_socket_fd() const { return _socket; }
const TunDevice& get_tun_dev() const { return _tun_dev; }
bool is_bound() const { return _bound; }
// Sets the peer address
void set_peer(const sockaddr_in6& peer_addr);
void unset_peer() { _has_peer = false; }
// Run the server.
void run();
@ -48,17 +41,14 @@ class UdpVpn {
void stop() { _stopped = true; }
protected:
void receive_from_tun();
void receive_from_udp();
virtual void receive_from_tun() = 0;
virtual void receive_from_udp() = 0;
size_t send_over_udp(const char* data, size_t len);
size_t read_from_tun(char* buffer, size_t len);
size_t read_from_udp(char* buffer, size_t len, sockaddr_in6& peer_addr);
private:
int _socket;
bool _bound;
bool _stopped;
bool _has_peer;
struct sockaddr_in6 _serv_addr, _peer_addr;
TunDevice _tun_dev;
};

67
UdpVpnClient.cpp Normal file
View file

@ -0,0 +1,67 @@
#include <cstring>
#include "UdpVpnClient.hpp"
#include "ip_header.hpp"
UdpVpnClient::UdpVpnClient(const struct sockaddr_in6& server) : UdpVpn() {
memset(&_server_addr, 0, sizeof(_server_addr));
set_server(server);
}
void UdpVpnClient::set_server(const struct sockaddr_in6& server_addr) {
if(server_addr.sin6_family != AF_INET6)
throw UdpVpn::InitializationError("Server address must be IPv6");
memcpy(&_server_addr, &server_addr, sizeof(_server_addr));
}
void UdpVpnClient::receive_from_tun() {
char buffer[1500];
size_t nread = read_from_tun(buffer, 1500);
if(nread == 0)
return;
// Parse inner packet header -- assume IPv6 for now [FIXME]
IPv6Header inner_header;
if(!parse_ipv6_header(buffer, nread, inner_header)) {
// Not a valid header -- ignore the packet
debugf("Ignoring outgoing packet with invalid header\n");
return;
}
kdebugf("Transmitting %s -> %s, size %d\n",
format_address(inner_header.source.s6_addr),
format_address(inner_header.dest.s6_addr),
nread);
write_to_server(buffer, nread);
}
void UdpVpnClient::receive_from_udp() {
char buffer[1500];
sockaddr_in6 peer_addr;
size_t nread = read_from_udp(buffer, 1500, peer_addr);
// Parse inner packet header -- assume IPv6 for now [FIXME]
IPv6Header inner_header;
if(!parse_ipv6_header(buffer, nread, inner_header)) {
// Not a valid header -- ignore the packet
debugf("Ignoring packet with invalid header\n");
return;
}
// Reinject into tun
kdebugf("Receiving packet of size %d from peer\n", nread);
_tun_dev.write(buffer, nread);
}
size_t UdpVpnClient::write_to_server(const char* data, size_t len) {
ssize_t nsent;
nsent = sendto(_socket, data, len, MSG_CONFIRM,
(const struct sockaddr*) &_server_addr, sizeof(_server_addr));
if(nsent < 0)
throw NetError("Could not send UDP packet", errno, true);
return (size_t) nsent;
}

18
UdpVpnClient.hpp Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include "UdpVpn.hpp"
class UdpVpnClient: public UdpVpn {
public:
UdpVpnClient(const struct sockaddr_in6& server);
protected:
void set_server(const struct sockaddr_in6& server_addr);
virtual void receive_from_tun();
virtual void receive_from_udp();
size_t write_to_server(const char* data, size_t len);
struct sockaddr_in6 _server_addr;
};

104
UdpVpnServer.cpp Normal file
View file

@ -0,0 +1,104 @@
#include <cstring>
#include "UdpVpnServer.hpp"
#include "ip_header.hpp"
UdpVpnServer::UdpVpnServer(in_port_t port) : UdpVpn() {
memset(&_bind_addr, 0, sizeof(_bind_addr));
bind(in6addr_any, port);
}
UdpVpnServer::UdpVpnServer(const struct in6_addr& bind_addr6, in_port_t port)
: UdpVpn()
{
memset(&_bind_addr, 0, sizeof(_bind_addr));
bind(bind_addr6, port);
}
void UdpVpnServer::bind(const struct in6_addr& bind_addr6, in_port_t port) {
int rc;
_bind_addr.sin6_family = AF_INET6;
_bind_addr.sin6_port = htons(port);
_bind_addr.sin6_addr = bind_addr6;
rc = ::bind(
_socket, (const struct sockaddr*)&_bind_addr, sizeof(_bind_addr));
if(rc < 0) {
throw UdpVpn::InitializationError("Cannot bind socket", errno, true);
}
debugf("> Listening on port %d\n", port);
}
std::shared_ptr<VpnPeer> UdpVpnServer::get_peer_for_ip(
const in6_addr& peer_addr)
{
auto peer_iter = _peers.find(peer_addr);
if(peer_iter == _peers.end()) // Unknown peer
return nullptr;
return peer_iter->second;
}
void UdpVpnServer::receive_from_tun() {
char buffer[1500];
size_t nread = read_from_tun(buffer, 1500);
if(nread == 0)
return;
// Parse inner packet header -- assume IPv6 for now [FIXME]
IPv6Header inner_header;
if(!parse_ipv6_header(buffer, nread, inner_header)) {
// Not a valid header -- ignore the packet
debugf("Ignoring outgoing packet with invalid header\n");
return;
}
// Recover VpnPeer -- or drop if new
std::shared_ptr<VpnPeer> peer = get_peer_for_ip(inner_header.dest);
if(!peer) {
debugf("Dropping packet for destination %s -- unknown peer.\n",
format_address(inner_header.dest.s6_addr));
return;
}
kdebugf("Transmitting %s -> %s, size %d\n",
format_address(inner_header.source.s6_addr),
format_address(inner_header.dest.s6_addr),
nread);
peer->write(buffer, nread);
}
void UdpVpnServer::receive_from_udp() {
char buffer[1500];
sockaddr_in6 peer_addr;
size_t nread = read_from_udp(buffer, 1500, peer_addr);
// Parse inner packet header -- assume IPv6 for now [FIXME]
IPv6Header inner_header;
if(!parse_ipv6_header(buffer, nread, inner_header)) {
// Not a valid header -- ignore the packet
debugf("Ignoring packet with invalid header\n");
return;
}
// Recover VpnPeer -- or create if new
std::shared_ptr<VpnPeer> peer = get_peer_for_ip(inner_header.source);
if(!peer) {
peer = std::make_shared<VpnPeer>(this, peer_addr, inner_header.source);
auto insert_result = _peers.insert({inner_header.source, peer});
debugf("Got new peer %s:%d -- %s [really inserted=%d]\n",
format_address(peer_addr.sin6_addr.s6_addr),
htons(peer_addr.sin6_port),
format_address(inner_header.source.s6_addr),
insert_result.second);
}
// VpnPeer* peer = (peer_iter->second);
// TODO -- pass the packet to `peer` for a cleaner flow
// Reinject into tun
kdebugf("Receiving packet of size %d from peer\n", nread);
_tun_dev.write(buffer, nread);
}

23
UdpVpnServer.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include <unordered_map>
#include <memory>
#include "UdpVpn.hpp"
class UdpVpnServer: public UdpVpn {
public:
UdpVpnServer(in_port_t port);
UdpVpnServer(const struct in6_addr& bind_addr6, in_port_t port);
protected:
void bind(const struct in6_addr& bind_addr6, in_port_t port);
/** Get the peer associated to this (internal) IP. */
std::shared_ptr<VpnPeer> get_peer_for_ip(const in6_addr& peer_addr);
virtual void receive_from_tun();
virtual void receive_from_udp();
struct sockaddr_in6 _bind_addr;
std::unordered_map<in6_addr, std::shared_ptr<VpnPeer>> _peers;
};

23
VpnPeer.cpp Normal file
View file

@ -0,0 +1,23 @@
#include "VpnPeer.hpp"
#include "UdpVpn.hpp"
#include <cstdint>
#include <cstring>
#include <functional>
VpnPeer::VpnPeer(UdpVpn* vpn, const sockaddr_in6& ext_addr,
const in6_addr int_addr)
: _vpn(vpn), _ext_addr(ext_addr), _int_addr(int_addr)
{}
size_t VpnPeer::write(const char* data, size_t len) {
ssize_t nsent;
nsent = sendto(_vpn->get_socket_fd(), data, len, MSG_CONFIRM,
(const struct sockaddr*) &_ext_addr, sizeof(_ext_addr));
if(nsent < 0)
throw NetError("Could not send UDP packet", errno, true);
return (size_t) nsent;
}

33
VpnPeer.hpp Normal file
View file

@ -0,0 +1,33 @@
#pragma once
/** A peer of a bound (server) instance of UdpVpn */
#include <netinet/in.h>
#include "util.hpp"
class UdpVpn;
class VpnPeer {
public:
class NetError : public MsgException {
public:
NetError(
const std::string& msg,
int code=0,
bool is_perror=false)
: MsgException(msg, code, is_perror) {}
};
VpnPeer(UdpVpn* vpn, const sockaddr_in6& ext_addr,
const in6_addr int_addr);
const sockaddr_in6& get_ext_addr() const { return _ext_addr; }
const in6_addr& get_int_addr() const { return _int_addr; }
size_t write(const char* data, size_t len);
private:
UdpVpn* _vpn;
sockaddr_in6 _ext_addr;
in6_addr _int_addr;
};

19
ip_header.cpp Normal file
View file

@ -0,0 +1,19 @@
#include <cstring>
#include "ip_header.hpp"
#include "util.hpp"
bool parse_ipv6_header(const char* data, size_t len, IPv6Header& header) {
if(len < 40) { // header too short
kdebugf("Bad v6 header -- too short.\n");
return false;
}
if((data[0] & 0xf0) != 0x60) { // bad version number
kdebugf("Bad v6 header -- bad version number (byte = %x).\n", data[0]);
return false;
}
memcpy(&(header.source), data + 8, 16);
memcpy(&(header.dest), data + 24, 16);
return true;
}

11
ip_header.hpp Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include <netinet/in.h>
struct IPv6Header {
in6_addr source;
in6_addr dest;
};
/** Parse an IPv6 header, filling `header`. Returns `true` on success. */
bool parse_ipv6_header(const char* data, size_t len, IPv6Header& header);

View file

@ -5,6 +5,8 @@
#include <string.h>
#include "UdpVpn.hpp"
#include "UdpVpnServer.hpp"
#include "UdpVpnClient.hpp"
#include "util.hpp"
struct ProgOptions {
@ -91,32 +93,40 @@ int main(int argc, char** argv) {
signal(SIGINT, stop_sig_handler);
try {
UdpVpn vpn;
vpn_instance = &vpn;
if(program_options.listen) {
vpn.bind(program_options.bind_addr, program_options.bind_port);
if(program_options.listen && program_options.has_peer) {
fprintf(stderr,
"ERROR: Cannot be a server and a client at the same time "
"-- provide either -l or -s.\n");
return 1;
}
if(program_options.has_peer) {
if(program_options.listen) {
vpn_instance = new UdpVpnServer(
program_options.bind_addr, program_options.bind_port);
} else if(program_options.has_peer) {
if(program_options.server_port == 0) {
fprintf(stderr,
"Initial peer set without a port -- ignoring.\n");
}
else {
struct sockaddr_in6 server_addr;
server_addr.sin6_family = AF_INET6;
memcpy(&server_addr.sin6_addr,
&program_options.server_addr,
sizeof(server_addr.sin6_addr));
server_addr.sin6_port = htons(program_options.server_port);
vpn.set_peer(server_addr);
"ERROR: A client instance must be given a server port "
"-- please provide -p.\n");
return 1;
}
struct sockaddr_in6 server_addr;
server_addr.sin6_family = AF_INET6; memcpy(&server_addr.sin6_addr, &program_options.server_addr,
sizeof(server_addr.sin6_addr));
server_addr.sin6_port = htons(program_options.server_port);
vpn_instance = new UdpVpnClient(server_addr);
} else {
fprintf(stderr,
"ERROR: Must be either a server or a client "
"-- provide either -l or -s.\n");
return 1;
}
printf("Starting to listen...\n");
vpn.run();
vpn_instance = nullptr;
vpn_instance->run();
delete vpn_instance;
printf("Shutting down.\n");
} catch(const TunDevice::InitializationError& exn) {

View file

@ -1,6 +1,7 @@
#include <cstdio>
#include <string.h>
#include <stdarg.h>
#include <arpa/inet.h>
#include "util.hpp"
@ -17,6 +18,41 @@ void do_debugf(int level, const char *format, ...)
va_end(args);
}
const char *
format_address(const unsigned char *address)
{
static char buf[4][INET6_ADDRSTRLEN];
static int i = 0;
i = (i + 1) % 4;
/*
if(v4mapped(address))
inet_ntop(AF_INET, address + 12, buf[i], INET6_ADDRSTRLEN);
else
*/
inet_ntop(AF_INET6, address, buf[i], INET6_ADDRSTRLEN);
return buf[i];
}
namespace std {
size_t hash<in6_addr>::operator() (const in6_addr& addr) const {
size_t out_hash = 0;
for(int i=0; i < 4; ++i) {
uint32_t value;
memcpy((unsigned char*)(&value),
addr.s6_addr + 4*i,
4);
out_hash ^= (std::hash<uint32_t>{}(value) << 1);
}
return out_hash;
}
bool equal_to<in6_addr>::operator()(
const in6_addr& lhs, const in6_addr& rhs) const
{
return memcmp(lhs.s6_addr, rhs.s6_addr, sizeof(lhs.s6_addr)) == 0;
}
}
MsgException::MsgException(const std::string& msg, int code, bool is_perror)
: _msg(msg), _code(code)
{

View file

@ -2,6 +2,7 @@
#include <exception>
#include <string>
#include <netinet/in.h>
/* Debugging -- taken from babeld */
@ -43,6 +44,26 @@ static inline void kdebugf(const char *format, ...) { return; }
void do_debugf(int level, const char *format, ...);
/** format_address -- taken from babeld */
const char* format_address(const unsigned char* address);
/** in6_addr hash & equality */
namespace std {
template<>
class hash<in6_addr> {
public:
size_t operator()(const in6_addr& addr) const;
};
template<>
class equal_to<in6_addr> {
public:
bool operator()(const in6_addr& lhs, const in6_addr& rhs) const;
};
}
/** MsgException -- an exception bearing a passed explanation message
*