diff --git a/README.md b/README.md index 043b6af..8f1dd6e 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,12 @@ Javascript web server fnctionality to allow easy status checks #### Packaging Create official packages for release +#### Storage Efficiency + +Currently the persistant storage is in the form of JSON + +-- Look into if SQLite would work better + ## Installation 1. Clone the repository: @@ -66,12 +72,12 @@ cd netmap Mac-OS ``` bash - brew install boost nlohmann-json + brew install boost nlohmann-json tinyxml2 ``` Debian ``` bash - sudo apt install nlohmann-json3-dev boost + sudo apt install nlohmann-json3-dev boost libtinyxml2.6.2-dev ``` -- the boost library is required for the ASIO library to work with CMake, however ASIO is also included with Clang if you would rather use that @@ -93,7 +99,8 @@ cd netmap To launch the graphical user interface (GUI) and start visualizing networks: ``` bash -python3 run.py +./build.sh -cs +python3 -m core.run ``` ## Contributing diff --git a/backend/CMakeLists.txt b/backend/CMakeLists.txt index 8a1a92e..b6ff115 100644 --- a/backend/CMakeLists.txt +++ b/backend/CMakeLists.txt @@ -11,40 +11,61 @@ project(netmap_backend) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) -# Add the include directories for libraries -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_SOURCE_DIR}/externals) +# Define include directories +include_directories( + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/utilities/include + ${CMAKE_SOURCE_DIR}/externals +) -# Importing Boost +# Import Boost set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_MULTITHREADED ON) set(Boost_USE_STATIC_RUNTIME OFF) find_package(Boost REQUIRED COMPONENTS system) - include_directories(${Boost_INCLUDE_DIRS}) # Import nlohmann-json find_package(nlohmann_json REQUIRED) include_directories(${nlohmann_json_INCLUDE_DIRS}) -# Add the libraries -add_library(traceroute src/utilities/traceroute.cpp) -add_library(ping src/utilities/ping.cpp) -add_library(dns_lookup src/utilities/dns_lookup.cpp) -add_library(network_scan src/utilities/network_scan.cpp) +# Import tinyxml2 +find_package(tinyxml2 REQUIRED) + +# Find all .cpp files in utilities/src +file(GLOB UTILITIES_SRC_FILES "utilities/src/*.cpp") + +# Define libraries based on the .cpp files +foreach(SOURCE_FILE ${UTILITIES_SRC_FILES}) + get_filename_component(LIBRARY_NAME ${SOURCE_FILE} NAME_WE) + + # Create a library for each source file + add_library(${LIBRARY_NAME} ${SOURCE_FILE}) + + # Ensure it has access to the include directories + target_include_directories(${LIBRARY_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/utilities/include) +endforeach() + +# Add the server library add_library(server src/network_server.cpp) target_link_libraries(server ${Boost_LIBRARIES}) -# Create an executable for the main program that links with the server library -add_executable(netmap_backend src/main.cpp src/network_state.cpp src/node.cpp) +# Create the executable for the main program +add_executable(netmap_backend + src/main.cpp + src/network_state.cpp + src/node.cpp +) -# Link the server library to the executable target_link_libraries(netmap_backend server - traceroute - ping - dns_lookup - network_scan nlohmann_json::nlohmann_json + tinyxml2::tinyxml2 ) + +# Link all dynamically created libraries +foreach(SOURCE_FILE ${UTILITIES_SRC_FILES}) + get_filename_component(LIBRARY_NAME ${SOURCE_FILE} NAME_WE) + target_link_libraries(netmap_backend ${LIBRARY_NAME}) +endforeach() \ No newline at end of file diff --git a/backend/include/commands.hpp b/backend/include/commands.hpp new file mode 100644 index 0000000..188a6b2 --- /dev/null +++ b/backend/include/commands.hpp @@ -0,0 +1,12 @@ +#ifndef COMMANDS_HPP +#define COMMANDS_HPP + +#include // For std::string +#include // For std::vector +#include "dig_x.hpp" +#include "nmap_x.hpp" +#include "ping_x.hpp" +#include "traceroute_x.hpp" + + +#endif // COMMANDS_HPP \ No newline at end of file diff --git a/backend/include/dns_lookup.hpp b/backend/include/dns_lookup.hpp deleted file mode 100644 index f9c5219..0000000 --- a/backend/include/dns_lookup.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef DNS_LOOKUP_HPP -#define DNS_LOOKUP_HPP -#include -std::string dns_lookup(const std::string& hostname); -#endif \ No newline at end of file diff --git a/backend/include/network_scan.hpp b/backend/include/network_scan.hpp deleted file mode 100644 index 7c1e416..0000000 --- a/backend/include/network_scan.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef NETWORK_SCAN_HPP -#define NETWORK_SCAN_HPP -#include -std::vector scan_network(); -#endif \ No newline at end of file diff --git a/backend/include/ping.hpp b/backend/include/ping.hpp deleted file mode 100644 index a863bf4..0000000 --- a/backend/include/ping.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef PING_HPP -#define PING_HPP -#include -std::string ping(const std::string& ip); -#endif \ No newline at end of file diff --git a/backend/include/plugin.hpp b/backend/include/plugin.hpp new file mode 100644 index 0000000..f8aca89 --- /dev/null +++ b/backend/include/plugin.hpp @@ -0,0 +1,28 @@ +#ifndef PLUGIN_HPP +#define PLUGIN_HPP + +#include + +class Plugin { +public: + virtual ~Plugin() {} + + // Initialize the plugin + virtual void initialize() = 0; + + // Execute the plugin (e.g., update stats or provide functionality) + virtual void execute() = 0; + + // Return a name or description for the plugin + virtual std::string name() const = 0; + + // Cleanup any resources used by the plugin + virtual void cleanup() = 0; +}; + +extern "C" { + Plugin* create_plugin(); + void destroy_plugin(Plugin* plugin); +} + +#endif // PLUGIN_HPP \ No newline at end of file diff --git a/backend/include/traceroute.hpp b/backend/include/traceroute.hpp deleted file mode 100644 index ccb3879..0000000 --- a/backend/include/traceroute.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef TRACEROUTE_HPP -#define TRACEROUTE_HPP -#include -std::string traceroute(const std::string& ip); -#endif \ No newline at end of file diff --git a/backend/src/main.cpp b/backend/src/main.cpp index b8b26cd..23b5a88 100644 --- a/backend/src/main.cpp +++ b/backend/src/main.cpp @@ -1,6 +1,63 @@ #include "network_server.hpp" +#include "plugin.hpp" #include -#include // For std::stoi +#include +#include // For dynamic loading on Linux/macOS +#include // For reading directories +#include +#include + +void loadPlugins(const std::string& pluginFolder) { + DIR* dir = opendir(pluginFolder.c_str()); + if (dir == nullptr) { + std::cerr << "Error opening plugin directory: " << pluginFolder << std::endl; + return; + } + + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + // Only process files that have .so extension + std::string filename(entry->d_name); + if (filename.substr(filename.find_last_of(".") + 1) == "so") { + std::string pluginPath = pluginFolder + "/" + filename; + + // Load the plugin dynamically + void* handle = dlopen(pluginPath.c_str(), RTLD_LAZY); + if (!handle) { + std::cerr << "Error loading plugin " << pluginPath << ": " << dlerror() << std::endl; + continue; // Skip this plugin and continue with the others + } + + // Load the 'create_plugin' and 'destroy_plugin' functions + typedef Plugin* (*CreatePlugin)(); + typedef void (*DestroyPlugin)(Plugin*); + CreatePlugin create_plugin = (CreatePlugin) dlsym(handle, "create_plugin"); + DestroyPlugin destroy_plugin = (DestroyPlugin) dlsym(handle, "destroy_plugin"); + + if (!create_plugin || !destroy_plugin) { + std::cerr << "Error loading functions from plugin " << pluginPath << ": " << dlerror() << std::endl; + dlclose(handle); + continue; + } + + // Create the plugin and initialize it + Plugin* plugin = create_plugin(); + plugin->initialize(); + + // Execute the plugin + plugin->execute(); + + std::cout << "Loaded plugin: " << plugin->name() << std::endl; + + // Cleanup and unload the plugin + plugin->cleanup(); + destroy_plugin(plugin); + dlclose(handle); + } + } + + closedir(dir); +} int main(int argc, char* argv[]) { if (argc != 2) { @@ -15,6 +72,10 @@ int main(int argc, char* argv[]) { // Create the server object and start it NetworkServer server(port); server.start(); + + // Load and execute plugins from the ../plugins directory + loadPlugins("../plugins"); + } catch (const std::invalid_argument& e) { std::cerr << "Error: Invalid port number. Please provide a valid integer." << std::endl; return 1; diff --git a/backend/src/network_server.cpp b/backend/src/network_server.cpp index 27e3265..0f280a1 100644 --- a/backend/src/network_server.cpp +++ b/backend/src/network_server.cpp @@ -1,7 +1,21 @@ #include "network_server.hpp" +#include "commands.hpp" // Universal import for all command header files #include +#include +#include #include +// Function to parse command input into a vector of arguments +std::vector parse_command(const std::string& input) { + std::vector tokens; + std::istringstream iss(input); + std::string token; + while (iss >> token) { + tokens.push_back(token); + } + return tokens; +} + NetworkServer::NetworkServer(short port) : io_context(), acceptor(io_context, tcp::endpoint(tcp::v4(), port)) {} @@ -16,13 +30,9 @@ void NetworkServer::start() { void NetworkServer::handle_client(tcp::socket socket) { try { - std::cout << "Client connected" << std::endl; - - // Read initial message to determine client type asio::streambuf buffer; asio::read_until(socket, buffer, "\n"); std::istream input(&buffer); - std::string client_type; std::getline(input, client_type); @@ -53,18 +63,36 @@ void NetworkServer::handle_cli(tcp::socket socket) { std::string command; std::getline(input, command); - if (command == "ping") { - send_response(socket, "Pinging the network..."); - } - else if (command == "traceroute") { - send_response(socket, "Running traceroute..."); - } - else if (command == "exit") { + if (command == "exit") { break; + } + + std::vector args = parse_command(command); + if (args.empty()) { + send_response(socket, "Invalid command"); + continue; + } + + std::string response; + std::string cmd = args[0]; + + if (cmd == "ping") { + response = Ping::run(args); + } + else if (cmd == "traceroute") { + response = Traceroute::run(args); } + else if (cmd == "nmap") { + response = Nmap::run(args); + } + else if (cmd == "dig") { + response = Dig::run(args); + } else { - send_response(socket, "Unknown CLI command"); + response = "Unknown CLI command"; } + + send_response(socket, response); } } catch (const std::exception& e) { std::cerr << "CLI client error: " << e.what() << std::endl; @@ -78,22 +106,14 @@ void NetworkServer::handle_gui(tcp::socket socket) { asio::read_until(socket, buffer, "\n"); std::istream input(&buffer); - std::string request; - std::getline(input, request); + std::string gui_data; + std::getline(input, gui_data); - // GUI messages might be JSON or structured differently - if (request == "get_network_state") { - send_response(socket, "{ \"status\": \"ok\", \"message\": \"Network state fetched\" }"); - } - else if (request == "highlight_node") { - send_response(socket, "{ \"status\": \"ok\", \"message\": \"Node highlighted\" }"); - } - else if (request == "exit") { + if (gui_data == "exit") { break; - } - else { - send_response(socket, "{ \"status\": \"error\", \"message\": \"Unknown GUI request\" }"); } + + std::cout << "Received GUI data: " << gui_data << std::endl; } } catch (const std::exception& e) { std::cerr << "GUI client error: " << e.what() << std::endl; diff --git a/backend/src/utilities/dns_lookup.cpp b/backend/src/utilities/dns_lookup.cpp deleted file mode 100644 index af8bef0..0000000 --- a/backend/src/utilities/dns_lookup.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "dns_lookup.hpp" -#include -#include -#include -#include - -std::string dns_lookup(const std::string& hostname) { - struct hostent *he = gethostbyname(hostname.c_str()); - if (he == nullptr) return "DNS lookup failed"; - return std::string(inet_ntoa(*(struct in_addr*)he->h_addr)); -} \ No newline at end of file diff --git a/backend/src/utilities/network_scan.cpp b/backend/src/utilities/network_scan.cpp deleted file mode 100644 index f321c72..0000000 --- a/backend/src/utilities/network_scan.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "network_scan.hpp" -#include - -std::vector scan_network() { - return {"192.168.1.1", "192.168.1.2"}; -} \ No newline at end of file diff --git a/backend/src/utilities/ping.cpp b/backend/src/utilities/ping.cpp deleted file mode 100644 index 051367e..0000000 --- a/backend/src/utilities/ping.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "ping.hpp" -#include - -std::string ping(const std::string& ip) { - return "Ping response from " + ip; -} \ No newline at end of file diff --git a/backend/src/utilities/traceroute.cpp b/backend/src/utilities/traceroute.cpp deleted file mode 100644 index 4f3ab5d..0000000 --- a/backend/src/utilities/traceroute.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "traceroute.hpp" -#include - -std::string traceroute(const std::string& ip) { - return "Traceroute result for " + ip; -} \ No newline at end of file diff --git a/backend/utilities/include/dig_x.hpp b/backend/utilities/include/dig_x.hpp new file mode 100644 index 0000000..6b7fb54 --- /dev/null +++ b/backend/utilities/include/dig_x.hpp @@ -0,0 +1,20 @@ +#ifndef DIG_X_HPP +#define DIG_X_HPP + +#include "commands.hpp" +#include +#include + +// Define bitwise flags for Dig options +#define DIG_A_RECORD (1 << 0) // Extract A record +#define DIG_MX_RECORD (1 << 1) // Extract MX record +#define DIG_CNAME_RECORD (1 << 2) // Extract CNAME record +#define DIG_NS_RECORD (1 << 3) // Extract NS record +#define DIG_SOA_RECORD (1 << 4) // Extract SOA record + +class Dig{ +public: + static std::string run(const std::vector& args); +}; + +#endif // DIG_X_HPP \ No newline at end of file diff --git a/backend/utilities/include/nmap_x.hpp b/backend/utilities/include/nmap_x.hpp new file mode 100644 index 0000000..aaa5211 --- /dev/null +++ b/backend/utilities/include/nmap_x.hpp @@ -0,0 +1,20 @@ +#ifndef NMAP_X_HPP +#define NMAP_X_HPP + +#include "commands.hpp" +#include +#include + +// Define bitwise flags for Nmap options +#define NMAP_OPEN_PORT (1 << 0) // Extract open ports +#define NMAP_PUBLIC_IP (1 << 1) // Extract public IP +#define NMAP_MAC_ADDRESS (1 << 2) // Extract MAC address +#define NMAP_DOMAIN (1 << 3) // Extract domain name +#define NMAP_OS (1 << 4) // Extract OS information + +class Nmap{ +public: + static std::string run(const std::vector& args); +}; + +#endif // NMAP_X_HPP \ No newline at end of file diff --git a/backend/utilities/include/ping_x.hpp b/backend/utilities/include/ping_x.hpp new file mode 100644 index 0000000..0407b06 --- /dev/null +++ b/backend/utilities/include/ping_x.hpp @@ -0,0 +1,20 @@ +#ifndef PING_X_HPP +#define PING_X_HPP + +#include "commands.hpp" +#include +#include + +// Define bitwise flags for Ping options +#define PING_LATENCY (1 << 0) // Extract latency (RTT) +#define PING_PACKET_LOSS (1 << 1) // Extract packet loss percentage +#define PING_TTL (1 << 2) // Extract TTL (Time to Live) +#define PING_IP (1 << 3) // Extract target IP address + +class Ping{ +public: + static std::string run(const std::vector& args); +}; + + +#endif // PING_X_HPP \ No newline at end of file diff --git a/backend/utilities/include/traceroute_x.hpp b/backend/utilities/include/traceroute_x.hpp new file mode 100644 index 0000000..7e20c2e --- /dev/null +++ b/backend/utilities/include/traceroute_x.hpp @@ -0,0 +1,17 @@ +#ifndef TRACEROUTE_X_HPP +#define TRACEROUTE_X_HPP + +#include +#include + +// Define bitwise flags for Traceroute options +#define TRACE_HOPS (1 << 0) // Extract number of hops +#define TRACE_LATENCY (1 << 1) // Extract per-hop latency +#define TRACE_IPS (1 << 2) // Extract IP addresses in the route + +class Traceroute{ +public: + static std::string run(const std::vector& args); +}; + +#endif // TRACEROUTE_X_HPP \ No newline at end of file diff --git a/backend/utilities/src/dig_x.cpp b/backend/utilities/src/dig_x.cpp new file mode 100644 index 0000000..3df6a24 --- /dev/null +++ b/backend/utilities/src/dig_x.cpp @@ -0,0 +1,114 @@ +#include "dig_x.hpp" +#include +#include +#include +#include +#include + + +// Function to execute the dig command and return the output +std::string execute_dig(const std::string& domain) { + std::string command = "dig " + domain + " ANY +short 2>&1"; + FILE* pipe = popen(command.c_str(), "r"); + if (!pipe) throw std::runtime_error("Failed to execute dig command"); + + char buffer[256]; + std::string result; + while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { + result += buffer; + } + pclose(pipe); + return result; +} + +// Helper function to extract A record (IPv4) +std::string parse_a_record(const std::string& raw_response) { + std::regex re("\\b([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})\\b"); + std::string result; + std::sregex_iterator begin(raw_response.begin(), raw_response.end(), re); + std::sregex_iterator end; + + for (auto it = begin; it != end; ++it) { + result += "A Record: " + it->str() + "\n"; + } + return result; +} + +// Helper function to extract MX record +std::string parse_mx_record(const std::string& raw_response) { + std::regex re("([0-9]+)\\s+IN\\s+MX\\s+([a-zA-Z0-9.-]+)"); + std::string result; + std::sregex_iterator begin(raw_response.begin(), raw_response.end(), re); + std::sregex_iterator end; + + for (auto it = begin; it != end; ++it) { + result += "MX Record: " + (*it)[2].str() + " (Priority: " + (*it)[1].str() + ")\n"; + } + return result; +} + +// Helper function to extract CNAME record +std::string parse_cname_record(const std::string& raw_response) { + std::regex re("CNAME\\s+([a-zA-Z0-9.-]+)"); + std::smatch match; + if (std::regex_search(raw_response, match, re)) { + return "CNAME Record: " + match[1].str() + "\n"; + } + return ""; +} + +// Helper function to extract NS record +std::string parse_ns_record(const std::string& raw_response) { + std::regex re("IN\\s+NS\\s+([a-zA-Z0-9.-]+)"); + std::string result; + std::sregex_iterator begin(raw_response.begin(), raw_response.end(), re); + std::sregex_iterator end; + + for (auto it = begin; it != end; ++it) { + result += "NS Record: " + (*it)[1].str() + "\n"; + } + return result; +} + +// Helper function to extract SOA record +std::string parse_soa_record(const std::string& raw_response) { + std::regex re("SOA\\s+([a-zA-Z0-9.-]+)\\s+([a-zA-Z0-9.-]+)"); + std::smatch match; + if (std::regex_search(raw_response, match, re)) { + return "SOA Record: " + match[1].str() + " " + match[2].str() + "\n"; + } + return ""; +} + +std::string dig_x(const std::string& domain, int options) { + std::string raw_response = execute_dig(domain); + + std::string result; + if (options & DIG_A_RECORD) result += parse_a_record(raw_response); + if (options & DIG_MX_RECORD) result += parse_mx_record(raw_response); + if (options & DIG_CNAME_RECORD) result += parse_cname_record(raw_response); + if (options & DIG_NS_RECORD) result += parse_ns_record(raw_response); + if (options & DIG_SOA_RECORD) result += parse_soa_record(raw_response); + + return result.empty() ? "No matching records found\n" : result; +} + + +std::string Dig::run(const std::vector& args) { + if (args.size() < 2) { + return "Usage: dig_x [options]\nOptions: --a-record --mx-record --cname --ns-record --soa-record\n"; + } + + std::string target = args[1]; + int options = 0; + + for (size_t i = 2; i < args.size(); ++i) { + if (args[i] == "--a-record") options |= DIG_A_RECORD; + else if (args[i] == "--mx-record") options |= DIG_MX_RECORD; + else if (args[i] == "--cname") options |= DIG_CNAME_RECORD; + else if (args[i] == "--ns-record") options |= DIG_NS_RECORD; + else if (args[i] == "--soa-record") options |= DIG_SOA_RECORD; + } + + return dig_x(target, options); +} \ No newline at end of file diff --git a/backend/utilities/src/nmap_x.cpp b/backend/utilities/src/nmap_x.cpp new file mode 100644 index 0000000..edeff01 --- /dev/null +++ b/backend/utilities/src/nmap_x.cpp @@ -0,0 +1,134 @@ +#include "nmap_x.hpp" +#include +#include +#include +#include +#include +#include + +using namespace tinyxml2; + +std::string run_nmap(const std::string& target, uint8_t options) { + std::ostringstream cmd_stream; + cmd_stream << "nmap -oX - "; + + if (options & NMAP_OPEN_PORT) cmd_stream << "-p- "; + if (options & NMAP_PUBLIC_IP) cmd_stream << "--unprivileged "; + if (options & NMAP_MAC_ADDRESS) cmd_stream << "--osscan-guess "; + if (options & NMAP_DOMAIN) cmd_stream << "--dns-servers "; + if (options & NMAP_OS) cmd_stream << "-O "; + + cmd_stream << target; + std::string cmd = cmd_stream.str(); + + FILE* pipe = popen(cmd.c_str(), "r"); + if (!pipe) throw std::runtime_error("Failed to run nmap command"); + + char buffer[128]; + std::string result; + while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { + result += buffer; + } + pclose(pipe); + return result; +} + +std::vector parse_nmap_xml(const std::string& xml_data, uint8_t options) { + XMLDocument doc; + std::vector results; + if (doc.Parse(xml_data.c_str()) != XML_SUCCESS) { + throw std::runtime_error("Failed to parse XML data from nmap"); + } + + XMLElement* root = doc.RootElement(); + if (!root) { + throw std::runtime_error("Failed to find root element in nmap XML"); + } + + XMLElement* host = root->FirstChildElement("host"); + while (host) { + if (options & NMAP_OPEN_PORT) { + XMLElement* ports = host->FirstChildElement("ports"); + if (ports) { + for (XMLElement* port = ports->FirstChildElement("port"); port; port = port->NextSiblingElement("port")) { + const char* port_str = port->Attribute("portid"); + if (port_str) results.push_back("Open Port: " + std::string(port_str)); + } + } + } + if (options & NMAP_PUBLIC_IP) { + XMLElement* address = host->FirstChildElement("address"); + if (address) { + const char* ip = address->Attribute("addr"); + if (ip) results.push_back("Public IP: " + std::string(ip)); + } + } + if (options & NMAP_MAC_ADDRESS) { + XMLElement* mac = host->FirstChildElement("address"); + if (mac && mac->Attribute("addrtype", "mac")) { + results.push_back("MAC Address: " + std::string(mac->Attribute("addr"))); + } + } + if (options & NMAP_DOMAIN) { + XMLElement* hostnames = host->FirstChildElement("hostnames"); + if (hostnames) { + for (XMLElement* hostname = hostnames->FirstChildElement("hostname"); hostname; hostname = hostname->NextSiblingElement("hostname")) { + const char* domain = hostname->Attribute("name"); + if (domain) results.push_back("Domain: " + std::string(domain)); + } + } + } + if (options & NMAP_OS) { + XMLElement* os = host->FirstChildElement("os"); + if (os) { + XMLElement* osmatch = os->FirstChildElement("osmatch"); + if (osmatch) { + const char* os_name = osmatch->Attribute("name"); + if (os_name) results.push_back("OS: " + std::string(os_name)); + } + } + } + host = host->NextSiblingElement("host"); + } + return results; +} + +std::vector nmap_x(const std::string& target, uint8_t options) { + try { + std::string scan_result = run_nmap(target, options); + return parse_nmap_xml(scan_result, options); + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return {}; + } +} + +std::string Nmap::run(const std::vector& args) { + if (args.size() < 2) { + return "Usage: nmap_x [options]"; + } + + std::string target = args[1]; + uint8_t options = 0; + + std::unordered_map option_map = { + {"--open-ports", NMAP_OPEN_PORT}, + {"--public-ip", NMAP_PUBLIC_IP}, + {"--mac-address", NMAP_MAC_ADDRESS}, + {"--domain", NMAP_DOMAIN}, + {"--os", NMAP_OS} + }; + + for (size_t i = 2; i < args.size(); ++i) { + auto it = option_map.find(args[i]); + if (it != option_map.end()) { + options |= it->second; + } + } + + std::vector results = nmap_x(target, options); + for (const std::string& res : results) { + std::cout << res << std::endl; + } + return 0; +} diff --git a/backend/utilities/src/ping_x.cpp b/backend/utilities/src/ping_x.cpp new file mode 100644 index 0000000..99ff3f6 --- /dev/null +++ b/backend/utilities/src/ping_x.cpp @@ -0,0 +1,76 @@ +#include "ping_x.hpp" +#include +#include +#include +#include + +std::string execute_ping(const std::string& ip) { + std::string command = "ping -c 4 " + ip + " 2>&1"; + FILE* pipe = popen(command.c_str(), "r"); + if (!pipe) throw std::runtime_error("Failed to execute ping command"); + + char buffer[128]; + std::string result; + while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { + result += buffer; + } + pclose(pipe); + return result; +} + +std::string parse_ping_latency(const std::string& response) { + std::regex re("min/avg/max/mdev = ([0-9.]+)/([0-9.]+)/([0-9.]+)/([0-9.]+)"); + std::smatch match; + if (std::regex_search(response, match, re)) { + return "Latency (RTT): " + match[2].str() + " ms\n"; + } + return ""; +} + +std::string parse_ping_packet_loss(const std::string& response) { + std::regex re("([0-9]+)% packet loss"); + std::smatch match; + if (std::regex_search(response, match, re)) { + return "Packet Loss: " + match[1].str() + "%\n"; + } + return ""; +} + +std::string parse_ping_ttl(const std::string& response) { + std::regex re("ttl=([0-9]+)"); + std::smatch match; + if (std::regex_search(response, match, re)) { + return "TTL: " + match[1].str() + "\n"; + } + return ""; +} + +std::string ping_x(const std::string& ip, int options) { + std::string response = execute_ping(ip); + std::string result; + + if (options & PING_LATENCY) result += parse_ping_latency(response); + if (options & PING_PACKET_LOSS) result += parse_ping_packet_loss(response); + if (options & PING_TTL) result += parse_ping_ttl(response); + if (options & PING_IP) result += "Target IP: " + ip + "\n"; + + return result.empty() ? "No matching ping results\n" : result; +} + +std::string Ping::run(const std::vector& args) { + if (args.size() < 2) { + return "Usage: ping_x [options]\nOptions: --latency --loss --ttl --ip\n"; + } + + std::string ip = args[1]; + int options = 0; + + for (size_t i = 2; i < args.size(); ++i) { + if (args[i] == "--latency") options |= PING_LATENCY; + else if (args[i] == "--loss") options |= PING_PACKET_LOSS; + else if (args[i] == "--ttl") options |= PING_TTL; + else if (args[i] == "--ip") options |= PING_IP; + } + + return ping_x(ip, options); +} diff --git a/backend/utilities/src/traceroute_x.cpp b/backend/utilities/src/traceroute_x.cpp new file mode 100644 index 0000000..bf56e4c --- /dev/null +++ b/backend/utilities/src/traceroute_x.cpp @@ -0,0 +1,68 @@ +#include "traceroute_x.hpp" +#include +#include +#include +#include + +std::string execute_traceroute(const std::string& target) { + std::string command = "traceroute -n " + target + " 2>&1"; + FILE* pipe = popen(command.c_str(), "r"); + if (!pipe) throw std::runtime_error("Failed to execute traceroute command"); + + char buffer[256]; + std::string result; + while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { + result += buffer; + } + pclose(pipe); + return result; +} + +std::string parse_hop_count(const std::string& response) { + std::istringstream stream(response); + std::string line; + int hop_count = 0; + while (std::getline(stream, line)) hop_count++; + return "Total Hops: " + std::to_string(hop_count) + "\n"; +} + +std::string parse_ips(std::string response) { + std::regex re("\\b(\\d+\\.\\d+\\.\\d+\\.\\d+)\\b"); + std::smatch match; + std::string result = "Traceroute IPs: "; + bool found = false; + while (std::regex_search(response, match, re)) { + result += match[1].str() + " -> "; + response = match.suffix().str(); + found = true; + } + return found ? result + "End\n" : ""; +} + +std::string traceroute_x(const std::string& target, int options) { + std::string response = execute_traceroute(target); + std::string result; + + if (options & TRACE_HOPS) result += parse_hop_count(response); + if (options & TRACE_LATENCY) result += response; // Return raw latency info + if (options & TRACE_IPS) result += parse_ips(response); + + return result.empty() ? "No matching traceroute results\n" : result; +} + +std::string Traceroute::run(const std::vector& args) { + if (args.size() < 2) { + return "Usage: traceroute_x [options]\nOptions: --hops --latency --ips\n"; + } + + std::string target = args[1]; + int options = 0; + + for (size_t i = 2; i < args.size(); ++i) { + if (args[i] == "--hops") options |= TRACE_HOPS; + else if (args[i] == "--latency") options |= TRACE_LATENCY; + else if (args[i] == "--ips") options |= TRACE_IPS; + } + + return traceroute_x(target, options); +} \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..0cd93c5 --- /dev/null +++ b/build.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +# Function to display usage instructions +usage() { + echo "Usage: $0 [-c] [-s]" + echo "Options:" + echo " -c Build the CLI frontend (located at ./frontend/netmap_cli/build)" + echo " -s Build the backend program (located at ./backend/build)" + echo " --help Display this help message" + exit 1 +} + +# Parse command-line arguments +while getopts "cs" opt; do + case ${opt} in + c) + BUILD_CLI=true + ;; + s) + BUILD_BACKEND=true + ;; + *) + usage + ;; + esac +done + +# Build the CLI +if [ "$BUILD_CLI" = true ]; then + echo "Building the CLI frontend..." + cd ./frontend/netmap_cli + mkdir -p build + cd build + make + if [ $? -eq 0 ]; then + echo "CLI frontend built successfully!" + else + echo "Error building the CLI frontend!" + exit 1 + fi + cd ../../.. +fi + +# Build the backend +if [ "$BUILD_BACKEND" = true ]; then + echo "Building the backend program..." + cd ./backend + mkdir -p build + cd build + make + if [ $? -eq 0 ]; then + echo "Backend program built successfully!" + else + echo "Error building the backend program!" + exit 1 + fi + cd ../../.. +fi + +# If neither -c nor -s is provided, show usage +if [ "$BUILD_CLI" != true ] && [ "$BUILD_BACKEND" != true ]; then + usage +fi \ No newline at end of file diff --git a/core/run.py b/core/run.py index 7af5c94..df71d25 100644 --- a/core/run.py +++ b/core/run.py @@ -36,7 +36,7 @@ def main(): time.sleep(0.5) if not args.server_only: if not args.no_cli: - cli_cmd = f"python3 ./frontend/cli.py --port {port}" + cli_cmd = f"./frontend/netmap_cli/build/netmap_cli -H localhost {port}" open_terminal(cli_cmd) if not args.no_gui: diff --git a/frontend/cli.py b/frontend/cli.py deleted file mode 100644 index 140107b..0000000 --- a/frontend/cli.py +++ /dev/null @@ -1,30 +0,0 @@ -import socket -import argparse - -class CLI: - def __init__(self, port): - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.connect(("localhost", port)) - # Send 'False' to indicate that it's a CLI client - self.socket.sendall(b"0\n") - - def run(self): - while True: - command = input("netmap> ") - if command == "exit": - self.socket.close() - break - self.send_command(command) - - def send_command(self, command): - self.socket.sendall(command.encode() + b"\n") - response = self.socket.recv(1024).decode() - print(f"Server Response: {response}") - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="CLI client for NetMap") - parser.add_argument("--port", type=int, default=12345, help="Specify the server port (default: 12345)") - args = parser.parse_args() - - cli = CLI(args.port) - cli.run() \ No newline at end of file diff --git a/frontend/netmap_cli/CMakeLists.txt b/frontend/netmap_cli/CMakeLists.txt new file mode 100644 index 0000000..f2102ea --- /dev/null +++ b/frontend/netmap_cli/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.10) +project(NetMapCLI) + +# Set C++ standard +set(CMAKE_CXX_STANDARD 17) + +# Add the source files +set(SOURCES + main.cpp + cli.cpp +) + +# Add the header files directory +include_directories(${CMAKE_SOURCE_DIR}) + +# Create the executable +add_executable(netmap_cli ${SOURCES}) + +# Link with necessary libraries +if(UNIX) + target_link_libraries(netmap_cli pthread) # Link pthread for Unix systems +endif() \ No newline at end of file diff --git a/frontend/netmap_cli/cli.cpp b/frontend/netmap_cli/cli.cpp new file mode 100644 index 0000000..c25b198 --- /dev/null +++ b/frontend/netmap_cli/cli.cpp @@ -0,0 +1,69 @@ +#include "cli.hpp" +#include +#include +#include +#include +#include + +CLI::CLI(const std::string& host, int port) { + // Create socket + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + perror("Error opening socket"); + exit(EXIT_FAILURE); + } + + // Set up the server address structure + sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + + // Convert IP address to binary form + if (inet_pton(AF_INET, host.c_str(), &server_addr.sin_addr) <= 0) { + perror("Invalid address"); + exit(EXIT_FAILURE); + } + + // Connect to the server + if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { + perror("Connection failed"); + exit(EXIT_FAILURE); + } + + // Send 'False' to indicate this is a CLI client + std::string message = "0\n"; + send(sockfd, message.c_str(), message.length(), 0); +} + +CLI::~CLI() { + close(sockfd); +} + +void CLI::run() { + std::string command; + while (true) { + std::cout << "netmap> "; + std::getline(std::cin, command); + + if (command == "exit") { + break; + } + + send_command(command); + } +} + +void CLI::send_command(const std::string& command) { + // Send the command + send(sockfd, command.c_str(), command.length(), 0); + send(sockfd, "\n", 1, 0); + + // Receive the response + char buffer[1024] = {0}; + int n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); + if (n > 0) { + buffer[n] = '\0'; // Null-terminate the string + std::cout << "Server Response: " << buffer << std::endl; + } +} \ No newline at end of file diff --git a/frontend/netmap_cli/cli.hpp b/frontend/netmap_cli/cli.hpp new file mode 100644 index 0000000..010b3fc --- /dev/null +++ b/frontend/netmap_cli/cli.hpp @@ -0,0 +1,19 @@ +#ifndef CLI_HPP +#define CLI_HPP + +#include + +class CLI { +public: + CLI(const std::string& host, int port); + ~CLI(); + + void run(); + +private: + int sockfd; + + void send_command(const std::string& command); +}; + +#endif // CLI_HPP \ No newline at end of file diff --git a/frontend/netmap_cli/main.cpp b/frontend/netmap_cli/main.cpp new file mode 100644 index 0000000..b913dbd --- /dev/null +++ b/frontend/netmap_cli/main.cpp @@ -0,0 +1,85 @@ +#include "cli.hpp" +#include +#include +#include +#include +#include +#include + +void display_help() { + std::cout << "Usage: netmap_cli [options]\n"; + std::cout << "Options:\n"; + std::cout << " -h Specify the target IP address (default: localhost)\n"; + std::cout << " -H Specify the target domain to resolve to an IP\n"; + std::cout << " -p Specify the port (default: 12345)\n"; + std::cout << " --help Display this help message\n"; +} + +std::string resolve_hostname(const std::string& domain) { + struct addrinfo hints, *res; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; // IPv4 + hints.ai_socktype = SOCK_STREAM; + + int err = getaddrinfo(domain.c_str(), nullptr, &hints, &res); + if (err != 0) { + std::cerr << "Error resolving domain: " << gai_strerror(err) << std::endl; + exit(EXIT_FAILURE); + } + + // Convert the first resolved address to a string + char ip_str[INET_ADDRSTRLEN]; + struct sockaddr_in* sockaddr_in = (struct sockaddr_in*)res->ai_addr; + inet_ntop(AF_INET, &sockaddr_in->sin_addr, ip_str, sizeof(ip_str)); + + freeaddrinfo(res); // Free the resolved info + return std::string(ip_str); +} + +int main(int argc, char* argv[]) { + int port = 12345; + std::string host = "localhost"; // Default IP address + bool resolve_dns = false; + std::string domain; + + // Handle command-line arguments + int opt; + while ((opt = getopt(argc, argv, "h:H:p:")) != -1) { + switch (opt) { + case 'h': + host = optarg; + break; + case 'H': + resolve_dns = true; + domain = optarg; + break; + case 'p': + port = std::stoi(optarg); + break; + case '?': + display_help(); + return EXIT_SUCCESS; + default: + display_help(); + return EXIT_FAILURE; + } + } + + // Check for --help flag + if (argc == 2 && std::string(argv[1]) == "--help") { + display_help(); + return EXIT_SUCCESS; + } + + // If DNS resolution is requested, resolve the domain + if (resolve_dns) { + host = resolve_hostname(domain); + std::cout << "Resolved DNS: " << domain << " to IP: " << host << std::endl; + } + + // Create and run the CLI + CLI cli(host, port); + cli.run(); + + return 0; +} \ No newline at end of file