From b8bb6d2c5f37595418e0615c225eb62ac64d70a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Bastian?= Date: Fri, 5 Jun 2020 16:14:49 +0200 Subject: [PATCH] Split UdpVpn in -Client and -Server flavour --- Makefile | 6 ++- UdpVpn.cpp | 84 +++++++------------------------------- UdpVpn.hpp | 22 +++------- UdpVpnClient.cpp | 67 ++++++++++++++++++++++++++++++ UdpVpnClient.hpp | 18 ++++++++ UdpVpnServer.cpp | 104 +++++++++++++++++++++++++++++++++++++++++++++++ UdpVpnServer.hpp | 23 +++++++++++ main.cpp | 48 +++++++++++++--------- 8 files changed, 267 insertions(+), 105 deletions(-) create mode 100644 UdpVpnClient.cpp create mode 100644 UdpVpnClient.hpp create mode 100644 UdpVpnServer.cpp create mode 100644 UdpVpnServer.hpp diff --git a/Makefile b/Makefile index efdbb2a..1a831a5 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,11 @@ CXX=g++ CXXFLAGS=-O2 -g -Wall -Wextra -std=c++17 CXXLIBS= -OBJS=UdpVpn.o VpnPeer.o TunDevice.o ip_header.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) diff --git a/UdpVpn.cpp b/UdpVpn.cpp index d6609b0..a1e81a0 100644 --- a/UdpVpn.cpp +++ b/UdpVpn.cpp @@ -9,12 +9,11 @@ #include #include -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; } diff --git a/UdpVpn.hpp b/UdpVpn.hpp index 3b190be..6e59168 100644 --- a/UdpVpn.hpp +++ b/UdpVpn.hpp @@ -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; }; diff --git a/UdpVpnClient.cpp b/UdpVpnClient.cpp new file mode 100644 index 0000000..7c44e2d --- /dev/null +++ b/UdpVpnClient.cpp @@ -0,0 +1,67 @@ +#include + +#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; +} diff --git a/UdpVpnClient.hpp b/UdpVpnClient.hpp new file mode 100644 index 0000000..6227fec --- /dev/null +++ b/UdpVpnClient.hpp @@ -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; +}; diff --git a/UdpVpnServer.cpp b/UdpVpnServer.cpp new file mode 100644 index 0000000..d3946fe --- /dev/null +++ b/UdpVpnServer.cpp @@ -0,0 +1,104 @@ +#include + +#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 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 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 peer = get_peer_for_ip(inner_header.source); + if(!peer) { + peer = std::make_shared(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); +} diff --git a/UdpVpnServer.hpp b/UdpVpnServer.hpp new file mode 100644 index 0000000..2bcb73f --- /dev/null +++ b/UdpVpnServer.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#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 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> _peers; +}; diff --git a/main.cpp b/main.cpp index ac13077..a6b759c 100644 --- a/main.cpp +++ b/main.cpp @@ -5,6 +5,8 @@ #include #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) {