//Copyright 2022, FOSS-VG Developers and Contributers // // Author(s): // BodgeMaster, Shwoomple // //This program is free software: you can redistribute it and/or modify it //under the terms of the GNU Affero General Public License as published //by the Free Software Foundation, version 3. // //This program is distributed in the hope that it will be useful, //but WITHOUT ANY WARRANTY; without even the implied //warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. //See the GNU Affero General Public License for more details. // //You should have received a copy of the GNU Affero General Public License //version 3 along with this program. //If not, see https://www.gnu.org/licenses/agpl-3.0.en.html #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../lib/cli.hpp" #include "../lib/error.hpp" #define EXIT_SUCCESS 0 #define EXIT_RUNTIME 1 #define EXIT_USAGE 2 #define EXIT_UNIMPLEMENTED 3 #define EXIT_SIGNAL 4 // TCP v4 server sockpp::tcp_socket tcpSocket; sockpp::tcp_acceptor tcpAcceptor; // TCP v4 client sockpp::tcp_connector tcpConnector; // TCP v6 server sockpp::tcp6_socket tcp6Socket; sockpp::tcp6_acceptor tcp6Acceptor; // TCP v6 client sockpp::tcp6_connector tcp6Connector; // UDP v4 server and client sockpp::udp_socket udpSocket; // UDP v6 server and client sockpp::udp6_socket udp6Socket; bool exitProgram = false; bool ipv4 = false; bool ipv6 = false; bool tcp = false; bool udp = false; bool outgoing = false; std::string host; in_port_t port; // This is probably bigger than the MTU on any given network. // This should allow us to read entire packets at once when they arrive // slowly enough to be read individually. const uint32_t bufferSize = 2048; uint8_t networkBuffer[bufferSize]; std::string readByteFromStdin() { std::string input = ""; // read 2 characters from stdin char characterInput; bool readByte = false; uint16_t iterationsSinceLastInput = 0; while (input.length() < 2 && !exitProgram) { if (std::cin.good()) { std::cin.get(characterInput); readByte = true; } else { readByte = false; } // ignore space, tabs, and newlines if (readByte && characterInput!=' ' && characterInput!='\n' && characterInput!='\r' && characterInput!='\t') { input.push_back(characterInput); } iterationsSinceLastInput++; if (readByte) { iterationsSinceLastInput = 0; } if (iterationsSinceLastInput>1024) { // prevent integer overflow iterationsSinceLastInput = 1024; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } return exitProgram? "" : std::string(1, (char) std::stoi(input, nullptr, 16)); } void signalHandler(int signal) { // shut down gracefully exitProgram = true; // tell sockpp to close TCP socket if open because it blocks when trying // to read and there is no data tcpAcceptor.shutdown(); if (tcpSocket) { tcpSocket.shutdown(SHUT_RD); } if (tcpConnector) { tcpConnector.shutdown(SHUT_RD); } if (tcp6Socket) { tcp6Socket.shutdown(SHUT_RD); } if (tcp6Connector) { tcp6Connector.shutdown(SHUT_RD); } if (udpSocket) { udpSocket.shutdown(SHUT_RD); } if (udp6Socket) { udp6Socket.shutdown(SHUT_RD); } std::cerr << "Received signal " << signal << ", bye!" << std::endl; std::exit(EXIT_SIGNAL); } void readFromTCP() { ssize_t byteCount; while (!exitProgram && (outgoing? (byteCount = tcpConnector.read(networkBuffer, bufferSize)) > 0 : (byteCount = tcpSocket.read(networkBuffer, bufferSize)) > 0)) { for (ssize_t i=0; i 0 : (byteCount = tcp6Socket.read(networkBuffer, bufferSize)) > 0)) { for (ssize_t i=0; i 0) { std::cout << peer << ": "; for (ssize_t i=0; i 0) { std::cout << peer << ": "; for (ssize_t i=0; i flags; flags.push_back(CLI::Flag('4', "ipv4", "use IPv4, either this or IPv6 has to be specified")); flags.push_back(CLI::Flag('6', "ipv6", "use IPv6, either this or IPv4 has to be specified")); flags.push_back(CLI::Flag('t', "tcp", "use TCP, either this or UDP has to be specified")); flags.push_back(CLI::Flag('u', "udp", "use UDP, either this or TCP has to be specified")); flags.push_back(CLI::Flag('h', "help", "print this information and exit")); flags.push_back(CLI::Flag('l', "license", "print license information and exit")); std::vector options; options.push_back(CLI::Option('c', "connect", "HOST", "make an outgoing connection to HOST instead of listening for an incoming connection")); options.push_back(CLI::Option('b', "bind", "ADDRESS", "(UDP only) bind to ADDRESS instead of localhost")); std::vector arguments; arguments.push_back(CLI::Argument("PORT", "the port to lsiten on (or connect to)")); CLI::ArgumentsParser cliParser = CLI::ArgumentsParser(argc, argv, flags, options, arguments, "Arbitrary TCP/UDP connections in hex format", "Spaces, tabs, newlines, and carriage returns in the input are ignored.\n\tYou may want to disable input echoing using `stty -echo` (reenable using `stty echo`).\n\tNote that many terminals do line buffering on the input by default."); if (cliParser.getFlag("help").value) { std::cout << cliParser.getUsage() << std::endl; return EXIT_SUCCESS; } if (cliParser.getFlag("license").value) { std::cout << "Copyright 2022, FOSS-VG Developers and Contributers\n" << "\n" << "Hexnet is part of the FOSS-VG development tool suite.\n" << "\n" << "This program is free software: you can redistribute it and/or modify it\n" << "under the terms of the GNU Affero General Public License as published\n" << "by the Free Software Foundation, version 3.\n" << "\n" << "This program is distributed in the hope that it will be useful,\n" << "but WITHOUT ANY WARRANTY; without even the implied\n" << "warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" << "See the GNU Affero General Public License for more details.\n" << "\n" << "You should have received a copy of the GNU Affero General Public License\n" << "version 3 along with this program.\n" << "If not, see https://www.gnu.org/licenses/agpl-3.0.en.html" << std::endl; return EXIT_SUCCESS; } if (cliParser.wrongUsage) { std::cout << cliParser.getUsage() << std::endl; return EXIT_USAGE; } ipv4 = cliParser.getFlag("ipv4").value; ipv6 = cliParser.getFlag("ipv6").value; tcp = cliParser.getFlag("tcp").value; udp = cliParser.getFlag("udp").value; if (cliParser.getOption("connect").errorCode != ErrorCodes::NOT_PRESENT) { outgoing = true; host = cliParser.getOption("connect").value; } if (!(ipv4 || ipv6) || (ipv4 && ipv6) || !(tcp || udp) || (tcp && udp)) { std::cout << "Please specify which protocols to use (one of IPv4/IPv6, one of TCP/UDP)." << std::endl; return EXIT_USAGE; } port = (in_port_t) std::stoi(cliParser.getArgument(0).value); if (outgoing) { if (tcp) { std::cerr << "Connecting to " << host << " on port " << (int) port << " (TCP)..." << std::endl; if (ipv4) { // TCP v4 out tcpConnector = sockpp::tcp_connector({host, port}); if (!tcpConnector) { std::cerr << "Error connecting to " << host << " on port " << port << std::endl; std::cerr << tcpConnector.last_error_str() << std::endl; return EXIT_RUNTIME; } std::thread threadReadFromTCP = std::thread(readFromTCP); std::thread threadWriteToTCP = std::thread(writeToTCP); threadReadFromTCP.join(); threadWriteToTCP.join(); std::cout << std::endl; return EXIT_SUCCESS; } else { // TCP v6 out tcp6Connector = sockpp::tcp6_connector({host, port}); if (!tcp6Connector) { std::cerr << "Error connecting to " << host << " on port " << port << std::endl; std::cerr << tcp6Connector.last_error_str() << std::endl; return EXIT_RUNTIME; } std::thread threadReadFromTCP6 = std::thread(readFromTCP6); std::thread threadWriteToTCP6 = std::thread(writeToTCP6); threadReadFromTCP6.join(); threadWriteToTCP6.join(); std::cout << std::endl; return EXIT_SUCCESS; } } else { std::cerr << "Talking to " << host << " on port " << (int) port << " (UDP)..." << std::endl; if (ipv4) { // UDP v4 out if (!udpSocket) { std::cerr << "Error creating UDP socket: " << udpSocket.last_error_str() << std::endl; } // Btw: Did you know that UDP has no concept of a connection? sockpp::udp_socket::addr_t peer = sockpp::inet_address(host, port); if (!udpSocket.connect(peer)) { std::cerr << "Error associating socket with " << host << " port " << port << std::endl; std::cerr << udpSocket.last_error_str() << std::endl; } std::thread threadReadFromUDP = std::thread(readFromUDP); std::thread threadWriteToUDP = std::thread(writeToUDP, peer); threadReadFromUDP.join(); threadWriteToUDP.join(); std::cout << std::endl; return EXIT_SUCCESS; } else { // UDP v6 out if (!udp6Socket) { std::cerr << "Error creating UDP socket: " << udp6Socket.last_error_str() << std::endl; } // Btw: Did you know that UDP has no concept of a connection? sockpp::udp6_socket::addr_t peer = sockpp::inet6_address(host, port); if (!udp6Socket.connect(peer)) { std::cerr << "Error associating socket with " << host << " port " << port << std::endl; std::cerr << udp6Socket.last_error_str() << std::endl; } std::thread threadReadFromUDP6 = std::thread(readFromUDP6); std::thread threadWriteToUDP6 = std::thread(writeToUDP6, peer); threadReadFromUDP6.join(); threadWriteToUDP6.join(); std::cout << std::endl; return EXIT_SUCCESS; } } } else { if (tcp) { std::cerr << "Listening on port " << (int) port << " (TCP)..." << std::endl; if (ipv4) { // TCP v4 in tcpAcceptor = sockpp::tcp_acceptor(port); if (!tcpAcceptor) { std::cerr << "Error while creating TCP acceptor: " << tcpAcceptor.last_error_str() << std::endl; return EXIT_RUNTIME; } sockpp::inet_address peer; tcpSocket = tcpAcceptor.accept(&peer); std::cerr << "Incoming connection from " << peer << std::endl; if (!tcpSocket) { std::cerr << "Error on incoming connection: " << tcpAcceptor.last_error_str() << std::endl; return EXIT_RUNTIME; } std::thread threadReadFromTCP = std::thread(readFromTCP); std::thread threadWriteToTCP = std::thread(writeToTCP); threadReadFromTCP.join(); threadWriteToTCP.join(); std::cout << std::endl; return EXIT_SUCCESS; } else { // TCP v6 in tcp6Acceptor = sockpp::tcp6_acceptor(port); if (!tcp6Acceptor) { std::cerr << "Error while creating TCP acceptor: " << tcp6Acceptor.last_error_str() << std::endl; return EXIT_RUNTIME; } sockpp::inet6_address peer; tcp6Socket = tcp6Acceptor.accept(&peer); std::cerr << "Incoming connection from " << peer << std::endl; if (!tcp6Socket) { std::cerr << "Error on incoming connection: " << tcp6Acceptor.last_error_str() << std::endl; return EXIT_RUNTIME; } std::thread threadReadFromTCP6 = std::thread(readFromTCP6); std::thread threadWriteToTCP6 = std::thread(writeToTCP6); threadReadFromTCP6.join(); threadWriteToTCP6.join(); std::cout << std::endl; return EXIT_SUCCESS; } } else { std::string address = "localhost"; if (cliParser.getOption("bind").errorCode != ErrorCodes::NOT_PRESENT) { address = cliParser.getOption("bind").value; } std::cerr << "Listening on " << address << " port " << (int) port << " (UDP)..." << std::endl; if (ipv4) { // UDP v4 in if (!udpSocket) { std::cerr << "Error creating UDP socket: " << udpSocket.last_error_str() << std::endl; } if (!udpSocket.bind(sockpp::inet_address(address, port))) { std::cerr << "Error binding UDP socket to " << address << " port " << port << ": " << udpSocket.last_error_str() << std::endl; } std::thread threadReadFromUDP = std::thread(readFromUDP); // Can't send bc we have no idea where to send to. //std::thread threadWriteToUDP = std::thread(writeToUDP); threadReadFromUDP.join(); //threadWriteToUDP.join(); std::cout << std::endl; return EXIT_SUCCESS; } else { // UDP v6 in if (!udp6Socket) { std::cerr << "Error creating UDP socket: " << udp6Socket.last_error_str() << std::endl; } if (!udp6Socket.bind(sockpp::inet6_address(address, port))) { std::cerr << "Error binding UDP socket to " << address << " port " << port << ": " << udp6Socket.last_error_str() << std::endl; } std::thread threadReadFromUDP6 = std::thread(readFromUDP6); // Can't send bc we have no idea where to send to. //std::thread threadWriteToUDP6 = std::thread(writeToUDP6); threadReadFromUDP6.join(); //threadWriteToUDP6.join(); std::cout << std::endl; return EXIT_SUCCESS; } } } }