diff --git a/nat-helper/CMakeLists.txt b/nat-helper/CMakeLists.txt new file mode 100644 index 000000000..aff358977 --- /dev/null +++ b/nat-helper/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.14) + +project(nat-helper + LANGUAGES CXX + VERSION 0.0.1 + ) + +set(SOURCES + main.cpp + nat-helper.cpp + client.cpp + message.cpp + room.cpp + ) + +add_executable(nat-helper main.cpp ${SOURCES}) + +set_property(TARGET nat-helper PROPERTY CXX_STANDARD 17) + +if (NOT CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "") + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE) +endif() + +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + include(CheckPIESupported) + check_pie_supported() + set_target_properties(nat-helper PROPERTIES POSITION_INDEPENDENT_CODE TRUE) + target_compile_definitions(nat-helper PRIVATE FORTIFY_SOURCE=2) + target_compile_options(nat-helper PRIVATE -fstack-protector-strong) + target_compile_options(nat-helper PRIVATE -Wall -Wextra -pedantic) + target_link_options(nat-helper PRIVATE "SHELL:-z relro") + target_link_options(nat-helper PRIVATE "SHELL:-z now") +endif() + +target_link_libraries(nat-helper PRIVATE + pthread + ) + diff --git a/nat-helper/README.md b/nat-helper/README.md new file mode 100644 index 000000000..7824e7f9f --- /dev/null +++ b/nat-helper/README.md @@ -0,0 +1,42 @@ +# Hole punching coordinator for UltraGrid + +This utility serves as a meeting point for UltraGrid clients, that need to +connect to each other, but don't have publicly routable IP addresses. + +Building +--------- + mkdir build && cd build + cmake .. + make + +Usage +--------- + ./nat-helper [-h/--help] [-p/--port ] + +If no port is specified, 12558 is used. + +Protocol description +--------- + +Clients connect to the nat-helper server, identify themselves with a name, and +join a room. Once two clients enter the same room, nat-helper forwards name, +sdp description string, and all candidate address pairs between the two +clients. + +All communication is done via messages that have the following structure: + +
+ +`HEADER`: 5B string containing length of MSG_BODY, null-termination optional +`MSG_BODY`: content of message, length determined by header, max 2048B + +After establishing connection to the nat-helper server, following messages are +sent and received in that order: +1. Client sends its name +2. Client sends room name to join +3. Client sends its sdp description +4. Client receives the name of the other client in the room +5. Client receives the sdp description of the other client + +After that the client sends and receives sdp candidate pairs as they are +discovered. diff --git a/nat-helper/client.cpp b/nat-helper/client.cpp new file mode 100644 index 000000000..797b08f28 --- /dev/null +++ b/nat-helper/client.cpp @@ -0,0 +1,140 @@ +/** + * @file client.cpp + * @author Martin Piatka + */ +/* + * Copyright (c) 2021 CESNET, z. s. p. o. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "client.hpp" + +Client::Client(asio::ip::tcp::socket&& socket) : socket(std::move(socket)) { } + +void Client::readDescription(std::function onComplete){ + using namespace std::placeholders; + inMsg.async_readMsg(socket, + std::bind(&Client::readNameComplete, + shared_from_this(), std::ref(socket), onComplete, _1)); +} + +void Client::readNameComplete(asio::ip::tcp::socket& socket, + std::function onComplete, + bool success) +{ + if(!success){ + onComplete(*this, false); + return; + } + + clientName = std::string(inMsg.getStr()); + using namespace std::placeholders; + inMsg.async_readMsg(socket, + std::bind(&Client::readRoomComplete, + shared_from_this(), std::ref(socket), onComplete, _1)); +} + +void Client::readRoomComplete(asio::ip::tcp::socket& socket, + std::function onComplete, + bool success) +{ + if(!success){ + onComplete(*this, false); + return; + } + + roomName = std::string(inMsg.getStr()); + using namespace std::placeholders; + inMsg.async_readMsg(socket, + std::bind(&Client::readDescComplete, shared_from_this(), onComplete, _1)); +} + +void Client::readDescComplete( + std::function onComplete, + bool success) +{ + if(!success){ + onComplete(*this, false); + return; + } + + sdpDesc = std::string(inMsg.getStr()); + onComplete(*this, true); +} + +void Client::readCandidate(std::function onComplete){ + using namespace std::placeholders; + inMsg.async_readMsg(socket, + std::bind(&Client::readCandidateComplete, + shared_from_this(), onComplete, _1)); +} + +void Client::readCandidateComplete( + std::function onComplete, + bool success) +{ + if(!success){ + onComplete(*this, false); + return; + } + + candidates.emplace_back(inMsg.getStr()); + onComplete(*this, true); +} + +bool Client::isSendCallbackPending() const{ + return !sendQueue.empty() || outMsg.isSendingNow(); +} + + +void Client::sendMsg(std::string_view msg){ + if(isSendCallbackPending()){ + sendQueue.emplace(msg); + return; + } + + outMsg.setStr(msg); + using namespace std::placeholders; + outMsg.async_sendMsg(socket, std::bind(&Client::onMsgSent, shared_from_this(), _1)); +} + +void Client::onMsgSent(bool success){ + if(!success){ + //TODO + } + + if(sendQueue.empty()) + return; + + using namespace std::placeholders; + outMsg.setStr(sendQueue.front()); + sendQueue.pop(); + outMsg.async_sendMsg(socket, std::bind(&Client::onMsgSent, shared_from_this(), _1)); +} diff --git a/nat-helper/client.hpp b/nat-helper/client.hpp new file mode 100644 index 000000000..1e0e939a7 --- /dev/null +++ b/nat-helper/client.hpp @@ -0,0 +1,97 @@ +/** + * @file client.hpp + * @author Martin Piatka + */ +/* + * Copyright (c) 2021 CESNET, z. s. p. o. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef UG_HOLE_PUNCH_CLIENT_HPP +#define UG_HOLE_PUNCH_CLIENT_HPP + +#include +#include +#include +#include +#include +#include +#include "message.hpp" + +class Client : public std::enable_shared_from_this{ +public: + Client(asio::ip::tcp::socket&& socket); + Client(const Client&) = delete; + Client(Client&&) = delete; + + Client& operator=(const Client&) = delete; + Client& operator=(Client&&) = delete; + + void readDescription(std::function onComplete); + std::string getClientName() { return clientName; } + std::string getRoomName() { return roomName; } + std::string getSdpDesc() { return sdpDesc; } + + void sendMsg(std::string_view msg); + + void readCandidate(std::function onComplete); + const std::vector& getCandidates() { return candidates; } + + bool isSendCallbackPending() const; + +private: + void readNameComplete(asio::ip::tcp::socket& socket, + std::function onComplete, bool success); + + void readRoomComplete(asio::ip::tcp::socket& socket, + std::function onComplete, bool success); + + void readDescComplete( + std::function onComplete, bool success); + + void readCandidateComplete( + std::function onComplete, bool success); + + void onMsgSent(bool success); + + std::string clientName; + std::string roomName; + std::string sdpDesc; + std::vector candidates; + + std::queue sendQueue; + + Message inMsg; + Message outMsg; + asio::ip::tcp::socket socket; +}; + + +#endif diff --git a/nat-helper/main.cpp b/nat-helper/main.cpp new file mode 100644 index 000000000..206e8a45c --- /dev/null +++ b/nat-helper/main.cpp @@ -0,0 +1,90 @@ +/** + * @file main.cpp + * @author Martin Piatka + */ +/* + * Copyright (c) 2021 CESNET, z. s. p. o. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include "nat-helper.hpp" + +namespace { + void printUsage(std::string_view name){ + std::cout << "Usage: " + << name << " [-h/--help] [-p/--port ]\n"; + } +} + +int main(int argc, char **argv){ + int port = 12558; + + for(int i = 1; i < argc; i++){ + std::string_view arg(argv[i]); + if(arg == "-h" || arg == "--help"){ + printUsage(argv[0]); + return 0; + } else if(arg == "-p" || arg == "--port"){ + if(i + 1 >= argc){ + std::cerr << "Expected port number\n"; + printUsage(argv[0]); + return 1; + } + std::string_view portStr(argv[++i]); + auto res = std::from_chars(portStr.data(), portStr.data() + portStr.size(), port); + if(res.ec != std::errc() || res.ptr == portStr.data()){ + std::cerr << "Failed to parse port number\n"; + printUsage(argv[0]); + return 1; + } + } else { + std::cerr << "Unknown argument " << arg << std::endl; + printUsage(argv[0]); + return 1; + } + } + + + NatHelper server(port); + + server.run(); + + for(std::string in; std::getline(std::cin, in);){ + if(in == "exit"){ + break; + } + } + + server.stop(); + + return 0; +} diff --git a/nat-helper/message.cpp b/nat-helper/message.cpp new file mode 100644 index 000000000..976808bd2 --- /dev/null +++ b/nat-helper/message.cpp @@ -0,0 +1,120 @@ +/** + * @file message.cpp + * @author Martin Piatka + */ +/* + * Copyright (c) 2021 CESNET, z. s. p. o. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include "message.hpp" + +std::string_view Message::getStr() const{ + return std::string_view(data + headerSize, size); +} + +void Message::async_readMsg(asio::ip::tcp::socket& socket, + std::function onComplete) +{ + using namespace std::placeholders; + asio::async_read(socket, + asio::buffer(data, headerSize), + std::bind(&Message::async_readBody, + this, std::ref(socket), onComplete, _1, _2)); +} + +void Message::setStr(std::string_view msg){ + assert(!sendingNow); + size = std::min(bodySize, msg.size()); + + memset(data, ' ', headerSize); + std::to_chars(data, data + headerSize, size); + + memcpy(data + headerSize, msg.data(), size); +} + +void Message::async_sendMsg(asio::ip::tcp::socket& socket, + std::function onComplete) +{ + using namespace std::placeholders; + sendingNow = true; + asio::async_write(socket, + asio::buffer(data, headerSize + size), + std::bind(&Message::async_sendComplete, + this, onComplete, _1, _2)); +} + +void Message::async_readBody(asio::ip::tcp::socket& socket, + std::function onComplete, + const std::error_code& ec, [[maybe_unused]] size_t readLen) +{ + if(ec){ + onComplete(false); + return; + } + + assert(readLen == headerSize); + size_t expectedSize = 0; + + auto res = std::from_chars(data, data + headerSize, expectedSize); + if(res.ec != std::errc()){ + onComplete(false); + } + + using namespace std::placeholders; + asio::async_read(socket, + asio::buffer(data + headerSize, expectedSize), + std::bind(&Message::async_readBodyComplete, + this, onComplete, _1, _2)); + +} + +void Message::async_readBodyComplete(std::function onComplete, + const std::error_code& ec, size_t readLen) +{ + if(ec){ + onComplete(false); + return; + } + + size = readLen; + + onComplete(true); +} + +void Message::async_sendComplete(std::function onComplete, + const std::error_code& ec, [[maybe_unused]] size_t sentLen) +{ + sendingNow = false; + onComplete(!ec); +} diff --git a/nat-helper/message.hpp b/nat-helper/message.hpp new file mode 100644 index 000000000..f108fb90a --- /dev/null +++ b/nat-helper/message.hpp @@ -0,0 +1,82 @@ +/** + * @file message.hpp + * @author Martin Piatka + */ +/* + * Copyright (c) 2021 CESNET, z. s. p. o. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef UG_HOLE_PUNCH_MESSAGE_HPP +#define UG_HOLE_PUNCH_MESSAGE_HPP + +#include +#include +#include + +class Message{ +public: + Message() = default; + + void async_readMsg(asio::ip::tcp::socket& socket, + std::function onComplete); + + std::string_view getStr() const; + + void setStr(std::string_view msg); + void async_sendMsg(asio::ip::tcp::socket& socket, + std::function onComplete); + + size_t getSize() const { return size; } + bool empty() const { return size == 0; } + bool isSendingNow() const { return sendingNow; } + void clear(); + +private: + void async_readBody(asio::ip::tcp::socket& socket, + std::function onComplete, + const std::error_code& ec, size_t readLen); + + void async_readBodyComplete(std::function onComplete, + const std::error_code& ec, size_t readLen); + + void async_sendComplete(std::function onComplete, + const std::error_code& ec, size_t sentLen); + + size_t size = 0; + + bool sendingNow = false; + + static constexpr size_t headerSize = 5; + static constexpr size_t bodySize = 2048; + char data[headerSize + bodySize]; +}; + +#endif diff --git a/nat-helper/nat-helper.cpp b/nat-helper/nat-helper.cpp new file mode 100644 index 000000000..cb7530e1f --- /dev/null +++ b/nat-helper/nat-helper.cpp @@ -0,0 +1,138 @@ +/** + * @file nat-helper.cpp + * @author Martin Piatka + */ +/* + * Copyright (c) 2021 CESNET, z. s. p. o. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include "nat-helper.hpp" + +NatHelper::NatHelper(int port) : port(port), + io_service(), + //work_guard(io_service.get_executor()), + acceptor(io_service), + pendingSocket(io_service) +{ + +} + +NatHelper::NatHelper() : NatHelper(12558) +{ + +} + + +void NatHelper::worker(){ + std::cout << "Running" << std::endl; + io_service.run(); + std::cout << "End" << std::endl; +} + +void NatHelper::run(){ + using namespace std::placeholders; + + asio::ip::tcp::endpoint endpoint(asio::ip::tcp::v4(), port); + acceptor.open(endpoint.protocol()); + acceptor.bind(endpoint); + acceptor.listen(asio::socket_base::max_connections); + acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(false)); + acceptor.async_accept(pendingSocket, + std::bind(&NatHelper::onConnectionAccepted, this, _1)); + + std::cout << "Starting thread" << std::endl; + worker_thread = std::thread(&NatHelper::worker, this); +} + +void NatHelper::stop(){ + std::cout << "Stopping..." << std::endl; + io_service.stop(); + worker_thread.join(); +} + +void NatHelper::onConnectionAccepted(const std::error_code& ec){ + if(ec){ + std::cerr << "Error accepting client connection: " << ec.message() << std::endl; + return; + } + + using namespace std::placeholders; + + auto client = std::make_shared(std::move(pendingSocket)); + client->readDescription( + std::bind(&NatHelper::onClientDesc, this, _1, _2)); + incomingClients.insert({client.get(), std::move(client)}); + + acceptor.async_accept(pendingSocket, + std::bind(&NatHelper::onConnectionAccepted, this, _1)); +} + +void NatHelper::cleanEmptyRooms(){ + for(auto it = rooms.begin(); it != rooms.end(); ){ + if(it->second->isEmpty()){ + std::cout << "Removing empty room " << it->first << "\n"; + it = rooms.erase(it); + } else { + it++; + } + } +} + +void NatHelper::onClientDesc(Client& client, bool success){ + if(!success){ + std::cerr << "Error reading client description" << std::endl; + incomingClients.erase(&client); + return; + } + + const auto& roomName = client.getRoomName(); + std::cout << "Moving client " << client.getClientName() + << " to room " << roomName + << std::endl; + + auto roomIt = rooms.find(roomName); + if(roomIt == rooms.end()){ + cleanEmptyRooms(); + std::cout << "Creating room " << roomName << std::endl; + roomIt = rooms.insert({roomName, std::make_shared(roomName)}).first; + } + + auto clientIt = incomingClients.find(&client); + auto& room = roomIt->second; + if(room->isFull()){ + std::cerr << "Room " << roomName << " is full, dropping client" << std::endl; + } else { + roomIt->second->addClient(std::move(clientIt->second)); + } + incomingClients.erase(clientIt); +} diff --git a/nat-helper/nat-helper.hpp b/nat-helper/nat-helper.hpp new file mode 100644 index 000000000..c9d494a02 --- /dev/null +++ b/nat-helper/nat-helper.hpp @@ -0,0 +1,77 @@ +/** + * @file nat-helper.hpp + * @author Martin Piatka + */ +/* + * Copyright (c) 2021 CESNET, z. s. p. o. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef UG_NAT_HELPER +#define UG_NAT_HELPER + +#include +#include +#include +#include +#include "client.hpp" +#include "room.hpp" + +class NatHelper { +public: + NatHelper(); + NatHelper(int port); + ~NatHelper() = default; + + void run(); + void stop(); + +private: + void worker(); + + void onConnectionAccepted(const std::error_code& ec); + void onClientDesc(Client& client, bool success); + + void cleanEmptyRooms(); + + int port; + + asio::io_service io_service; + //asio::executor_work_guard work_guard; + std::thread worker_thread; + + asio::ip::tcp::acceptor acceptor; + asio::ip::tcp::socket pendingSocket; + + std::map> incomingClients; + std::map> rooms; +}; + +#endif //UG_NAT_HELPER diff --git a/nat-helper/room.cpp b/nat-helper/room.cpp new file mode 100644 index 000000000..faa238088 --- /dev/null +++ b/nat-helper/room.cpp @@ -0,0 +1,88 @@ +/** + * @file room.cpp + * @author Martin Piatka + */ +/* + * Copyright (c) 2021 CESNET, z. s. p. o. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include "room.hpp" + +Room::Room(std::string name) : name(std::move(name)) { } + +void Room::addClient(std::shared_ptr&& client){ + assert(!isFull()); + + Client *clientPtr = client.get(); + + for(auto& [c, _] : clients){ + c->sendMsg(clientPtr->getClientName()); + c->sendMsg(clientPtr->getSdpDesc()); + + clientPtr->sendMsg(c->getClientName()); + clientPtr->sendMsg(c->getSdpDesc()); + for(const auto& candidate : c->getCandidates()){ + clientPtr->sendMsg(candidate); + } + } + + clients.insert({clientPtr, std::move(client)}); + + using namespace std::placeholders; + clientPtr->readCandidate( + std::bind(&Room::onClientCandidate, shared_from_this(), _1, _2)); + +} + +void Room::onClientCandidate(Client& client, bool success){ + if(!success){ + std::cerr << "Error reading candidate, removing client " + << client.getClientName() << std::endl; + + clients.erase(&client); + return; + } + + std::cout << "Client candidate recieved" << std::endl; + + for(auto& [c, cu] : clients){ + if(&client == c) + continue; + + c->sendMsg(client.getCandidates().back()); + } + + using namespace std::placeholders; + client.readCandidate( + std::bind(&Room::onClientCandidate, shared_from_this(), _1, _2)); +} diff --git a/nat-helper/room.hpp b/nat-helper/room.hpp new file mode 100644 index 000000000..263e26c13 --- /dev/null +++ b/nat-helper/room.hpp @@ -0,0 +1,60 @@ +/** + * @file room.hpp + * @author Martin Piatka + */ +/* + * Copyright (c) 2021 CESNET, z. s. p. o. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef UG_HOLE_PUNCH_ROOM_HPP +#define UG_HOLE_PUNCH_ROOM_HPP + +#include +#include +#include "client.hpp" + +class Room : public std::enable_shared_from_this{ +public: + Room(std::string name); + + void addClient(std::shared_ptr&& client); + + bool isFull() const { return clients.size() >= 2; } + bool isEmpty() const { return clients.empty(); } + +private: + void onClientCandidate(Client& client, bool success); + + std::string name; + std::map> clients; +}; + +#endif