diff --git a/CMakeLists.txt b/CMakeLists.txt index 5679fd7..dfa2cb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,4 +10,6 @@ add_executable(mcpinger main.cpp protocol_utils.cpp protocol_utils.h network_utils.cpp - network_utils.h) + network_utils.h + utils.cpp + utils.h) diff --git a/cli_parser.cpp b/cli_parser.cpp index 0b52f9a..d9ed1d0 100644 --- a/cli_parser.cpp +++ b/cli_parser.cpp @@ -1,17 +1,18 @@ +//cli_parser.cpp #include "cli_parser.h" #include #include -void config::parseCommandLine(int argc, char* argv[]) { +void config::parseCommandLine(int argc, char *argv[]) { const struct option longOptions[] = { - {"host", required_argument, nullptr, 'h'}, - {"port", required_argument, nullptr, 'p'}, - {"vhost", required_argument, nullptr, 'v'}, - {"vport", required_argument, nullptr, 'b'}, + {"host", required_argument, nullptr, 'h'}, + {"port", required_argument, nullptr, 'p'}, + {"vhost", required_argument, nullptr, 'v'}, + {"vport", required_argument, nullptr, 'b'}, {"version", required_argument, nullptr, 'r'}, - {"state", required_argument, nullptr, 's'}, - {"help", no_argument, nullptr, 0}, - {nullptr, 0, nullptr, 0} + {"state", required_argument, nullptr, 's'}, + {"help", no_argument, nullptr, 0}, + {nullptr, 0, nullptr, 0} }; int opt; diff --git a/cli_parser.h b/cli_parser.h index 28e12ff..5b48581 100644 --- a/cli_parser.h +++ b/cli_parser.h @@ -1,3 +1,4 @@ +//cli_parser.h #ifndef MCPINGER_CLI_PARSER_H #define MCPINGER_CLI_PARSER_H @@ -11,8 +12,9 @@ struct config { int protocolVersion = -1; int nextState = 1; - void parseCommandLine(int argc, char* argv[]); - static void printHelp() ; + void parseCommandLine(int argc, char *argv[]); + + static void printHelp(); }; #endif // MCPINGER_CLI_PARSER_H diff --git a/main.cpp b/main.cpp index b14c343..81e3b0f 100644 --- a/main.cpp +++ b/main.cpp @@ -1,8 +1,59 @@ #include "cli_parser.h" #include "network_utils.h" +#include "utils.h" +#include +#include +#include +#include -int main(int argc, char* argv[]) { - config config; - config.parseCommandLine(argc, argv); - return pingMinecraftServer(config.hostname, config.port, config.virtualHostname, config.virtualPort, config.protocolVersion, config.nextState); +std::mutex fileMutex; + +void scanIp(const std::string &ip, int port, const config &cfg, const std::string &filename) { + std::string result = pingMinecraftServer(ip, port, cfg.virtualHostname, cfg.virtualPort, cfg.protocolVersion, + cfg.nextState); + if (!result.starts_with("ERROR")) { + std::lock_guard lock(fileMutex); // Locking here to manage concurrent file access + std::ofstream file(filename, std::ios::app | std::ios::out); + if (!file.is_open()) { + std::cerr << "Failed to open file for writing: " << filename << std::endl; + return; + } + // Write the scan result as a tab-separated line + file << ip << '\t' + << port << '\t' + << cfg.virtualHostname << '\t' + << cfg.virtualPort << '\t' + << cfg.protocolVersion << '\t' + << cfg.nextState << '\t' + << result << '\n'; + file.close(); + } } + +int main(int argc, char *argv[]) { + config cfg; + cfg.parseCommandLine(argc, argv); + std::vector ips = parseIps(cfg.hostname); // handles both CIDR and single IPs + std::string outputFilename = "responsive_" + makePathSafe(cfg.hostname) + ".tsv"; + + // Create and write headers to the output file + std::ofstream outfile(outputFilename, std::ios::out); + if (!outfile.is_open()) { + std::cerr << "Failed to open file for writing: " << outputFilename << std::endl; + return 1; + } + outfile << "IP Address\tPort\tVirtual Hostname\tVirtual Port\tProtocol Version\tNext State\tStatus\n"; + outfile.close(); + + // Multithreaded scanning + std::vector threads; + for (const auto &ip: ips) { + (void) threads.emplace_back(scanIp, ip, cfg.port, std::ref(cfg), outputFilename); + usleep(10000); + } + for (auto &thread: threads) { + thread.join(); + } + + return 0; +} \ No newline at end of file diff --git a/network_utils.cpp b/network_utils.cpp index f8d7672..fb9c333 100644 --- a/network_utils.cpp +++ b/network_utils.cpp @@ -1,3 +1,5 @@ +//network_utils.cpp + #include "network_utils.h" #include #include @@ -7,7 +9,7 @@ #include #include "protocol_utils.h" -int sendData(int sockfd, const std::vector& data) { +int sendData(int sockfd, const std::vector &data) { ssize_t sent = send(sockfd, data.data(), data.size(), 0); if (sent < 0) { perror("Error sending data"); @@ -16,7 +18,7 @@ int sendData(int sockfd, const std::vector& data) { return SUCCESS; } -int receiveData(int sockfd, std::vector& buffer) { +int receiveData(int sockfd, std::vector &buffer) { buffer.resize(1024); ssize_t bytesReceived = recv(sockfd, buffer.data(), buffer.size(), 0); if (bytesReceived < 0) { @@ -27,33 +29,41 @@ int receiveData(int sockfd, std::vector& buffer) { return SUCCESS; } -int pingMinecraftServer(const std::string& hostname, int port, const std::string& virtualHostname, int virtualPort, int protocolVersionIn, int nextStateIn) { +std::string +pingMinecraftServer(const std::string &hostname, int port, const std::string &virtualHostname, int virtualPort, + int protocolVersionIn, int nextStateIn) { struct addrinfo hints{}, *res, *result; int sockfd, err; - (void)memset(&hints, 0, sizeof(hints)); + (void) memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; err = getaddrinfo(hostname.c_str(), std::to_string(port).c_str(), &hints, &result); if (err != 0) { std::cerr << "Error resolving hostname: " << gai_strerror(err) << std::endl; - return ERROR_RESOLVING_HOSTNAME; + return "ERROR_RESOLVING_HOSTNAME"; } for (res = result; res != nullptr; res = res->ai_next) { sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sockfd == -1) continue; + // Set timeouts + struct timeval timeout; + timeout.tv_sec = 5; // 5 seconds timeout + timeout.tv_usec = 0; + setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *) &timeout, sizeof(timeout)); + setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *) &timeout, sizeof(timeout)); + if (connect(sockfd, res->ai_addr, res->ai_addrlen) != -1) break; - (void)close(sockfd); + (void) close(sockfd); } if (res == nullptr) { - std::cerr << "Could not connect to any address" << std::endl; freeaddrinfo(result); - return ERROR_CONNECTION_FAILED; + return "ERROR_CONNECTION_FAILED"; } freeaddrinfo(result); @@ -63,34 +73,35 @@ int pingMinecraftServer(const std::string& hostname, int port, const std::string packet.push_back(0x00U); // Packet ID for handshake std::vector protocolVersion = encodeVarint(protocolVersionIn); std::vector serverAddress = encodeString(virtualHostname); - std::vector serverPort = {static_cast((virtualPort >> 8) & 0xFFU), static_cast(virtualPort & 0xFFU) }; + std::vector serverPort = {static_cast((virtualPort >> 8) & 0xFF), + static_cast(virtualPort & 0xFFU)}; std::vector nextState = encodeVarint(nextStateIn); - (void)packet.insert(packet.end(), protocolVersion.begin(), protocolVersion.end()); - (void)packet.insert(packet.end(), serverAddress.begin(), serverAddress.end()); - (void)packet.insert(packet.end(), serverPort.begin(), serverPort.end()); - (void)packet.insert(packet.end(), nextState.begin(), nextState.end()); + (void) packet.insert(packet.end(), protocolVersion.begin(), protocolVersion.end()); + (void) packet.insert(packet.end(), serverAddress.begin(), serverAddress.end()); + (void) packet.insert(packet.end(), serverPort.begin(), serverPort.end()); + (void) packet.insert(packet.end(), nextState.begin(), nextState.end()); std::vector handshakeLength = encodeVarint(packet.size()); - (void)packet.insert(packet.begin(), handshakeLength.begin(), handshakeLength.end()); + (void) packet.insert(packet.begin(), handshakeLength.begin(), handshakeLength.end()); if (sendData(sockfd, packet) != SUCCESS) { - (void)close(sockfd); - return ERROR_SENDING_DATA; + (void) close(sockfd); + return "ERROR_SENDING_DATA"; } // Status request std::vector statusRequest = {0x01U, 0x00U}; // Packet length and Packet ID for status request if (sendData(sockfd, statusRequest) != SUCCESS) { - (void)close(sockfd); - return ERROR_SENDING_DATA; + (void) close(sockfd); + return "ERROR_SENDING_DATA"; } // Read status response std::vector response; if (receiveData(sockfd, response) != SUCCESS) { - (void)close(sockfd); - return ERROR_RECEIVING_DATA; + (void) close(sockfd); + return "ERROR_RECEIVING_DATA"; } // Extract JSON string (assuming it starts from the position of the first '{' found in the response) @@ -98,10 +109,10 @@ int pingMinecraftServer(const std::string& hostname, int port, const std::string if (pos != response.end()) { std::string json(pos, response.end()); std::cout << json << std::endl; + (void) close(sockfd); + return json; } else { - std::cerr << "Failed to find JSON response in server data." << std::endl; + (void) close(sockfd); + return "ERROR_JSON"; } - - (void)close(sockfd); - return SUCCESS; } diff --git a/network_utils.h b/network_utils.h index efdde9a..220290e 100644 --- a/network_utils.h +++ b/network_utils.h @@ -1,3 +1,4 @@ +//network_utils.h #ifndef MCPINGER_NETWORK_UTILS_H #define MCPINGER_NETWORK_UTILS_H @@ -10,8 +11,12 @@ #define ERROR_SENDING_DATA (-4) #define ERROR_RECEIVING_DATA (-5) -int sendData(int sockfd, const std::vector& data); -int receiveData(int sockfd, std::vector& buffer); -int pingMinecraftServer(const std::string& hostname, int port, const std::string& virtualHostname, int virtualPort, int protocolVersion, int nextState); +int sendData(int sockfd, const std::vector &data); + +int receiveData(int sockfd, std::vector &buffer); + +std::string +pingMinecraftServer(const std::string &hostname, int port, const std::string &virtualHostname, int virtualPort, + int protocolVersionIn, int nextStateIn); #endif // MCPINGER_NETWORK_UTILS_H diff --git a/protocol_utils.cpp b/protocol_utils.cpp index 68683bd..2960403 100644 --- a/protocol_utils.cpp +++ b/protocol_utils.cpp @@ -1,3 +1,4 @@ +//protocol_utils.cpp #include "protocol_utils.h" std::vector encodeVarint(int value) { @@ -14,8 +15,8 @@ std::vector encodeVarint(int value) { return varint; } -std::vector encodeString(const std::string& str) { +std::vector encodeString(const std::string &str) { std::vector data = encodeVarint(str.length()); - (void)data.insert(data.end(), str.begin(), str.end()); + (void) data.insert(data.end(), str.begin(), str.end()); return data; } diff --git a/protocol_utils.h b/protocol_utils.h index 21e390d..523ce85 100644 --- a/protocol_utils.h +++ b/protocol_utils.h @@ -1,3 +1,4 @@ +//protocol_utils.h #ifndef MCPINGER_PROTOCOL_UTILS_H #define MCPINGER_PROTOCOL_UTILS_H @@ -5,6 +6,7 @@ #include std::vector encodeVarint(int value); -std::vector encodeString(const std::string& str); + +std::vector encodeString(const std::string &str); #endif // MCPINGER_PROTOCOL_UTILS_H diff --git a/utils.cpp b/utils.cpp new file mode 100644 index 0000000..3066c24 --- /dev/null +++ b/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" +#include +#include +#include + +std::vector cidrToIps(const std::string &cidr) { + std::vector result; + // Buffer to hold the IP address in text form + int ip[4], bits; // Array to hold each byte of the IP, and the bits for subnet mask + + // Parse CIDR notation + (void) sscanf(cidr.c_str(), "%d.%d.%d.%d/%d", &ip[0], &ip[1], &ip[2], &ip[3], &bits); + + // Convert parsed IP to struct in_addr + struct in_addr ipAddr{}; + ipAddr.s_addr = htonl((ip[0] << 24) | (ip[1] << 16) | (ip[2] << 8) | ip[3]); + + // Calculate network address and number of hosts + uint32_t ipNum = ntohl(ipAddr.s_addr); + uint32_t numHosts = (1 << (32 - bits)) - 2; // Host count excludes network and broadcast addresses + uint32_t network = ipNum & (~((1 << (32 - bits)) - 1)); // Network address + + for (uint32_t i = 1; i <= numHosts; i++) { + struct in_addr addr{}; + addr.s_addr = htonl(network + i); + char ipStr[INET_ADDRSTRLEN]; + (void) inet_ntop(AF_INET, &addr, ipStr, INET_ADDRSTRLEN); + (void) result.emplace_back(ipStr); + } + return result; +} + +// Utility function to check and expand CIDR or single IP +std::vector parseIps(const std::string &input) { + std::vector ips; + std::regex cidrPattern(R"(\b\d{1,3}(?:\.\d{1,3}){3}\/\d{1,2}\b)"); // Simple regex for CIDR validation + if (std::regex_match(input, cidrPattern)) { + // It's a CIDR + ips = cidrToIps(input); + } else { + // It's a single IP + ips.push_back(input); + } + return ips; +} + +std::string makePathSafe(const std::string& input) { + // Define a set of characters that are not allowed in file names + const std::unordered_set unsafeChars = { + '/', '\\', ':', '*', '?', '"', '<', '>', '|', '.' + }; + + // Start with the original string + std::string result = input; + + // Replace each unsafe character with an underscore + for (char& c : result) { + if (unsafeChars.count(c)) { + c = '_'; + } + } + + return result; +} \ No newline at end of file diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..1ae528b --- /dev/null +++ b/utils.h @@ -0,0 +1,13 @@ +#ifndef MCPINGER_UTILS_H +#define MCPINGER_UTILS_H + +#include +#include + +std::vector cidrToIps(const std::string &cidr); + +std::vector parseIps(const std::string &input); + +std::string makePathSafe(const std::string& input); + +#endif // MCPINGER_UTILS_H