From 2ab28e681dcd9fabe748536d390be0f2eb781830 Mon Sep 17 00:00:00 2001 From: deniskhud Date: Sun, 4 Jan 2026 16:21:24 +0100 Subject: [PATCH] add: simple port scanner --- PROJECTS/simple-port-scanner/CMakeLists.txt | 16 ++ PROJECTS/simple-port-scanner/README.md | 34 +++ PROJECTS/simple-port-scanner/main.cpp | 44 ++++ .../simple-port-scanner/src/PortScanner.cpp | 193 ++++++++++++++++++ .../simple-port-scanner/src/PortScanner.hpp | 50 +++++ 5 files changed, 337 insertions(+) create mode 100644 PROJECTS/simple-port-scanner/CMakeLists.txt create mode 100644 PROJECTS/simple-port-scanner/README.md create mode 100644 PROJECTS/simple-port-scanner/main.cpp create mode 100644 PROJECTS/simple-port-scanner/src/PortScanner.cpp create mode 100644 PROJECTS/simple-port-scanner/src/PortScanner.hpp diff --git a/PROJECTS/simple-port-scanner/CMakeLists.txt b/PROJECTS/simple-port-scanner/CMakeLists.txt new file mode 100644 index 00000000..8ded6f2f --- /dev/null +++ b/PROJECTS/simple-port-scanner/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.31) +project(simplePortScanner) + +set(CMAKE_CXX_STANDARD 20) +find_package(Boost REQUIRED COMPONENTS program_options) + + + +add_executable(simplePortScanner main.cpp + src/PortScanner.hpp + src/PortScanner.cpp + ) + +target_link_libraries(simplePortScanner + Boost::program_options +) diff --git a/PROJECTS/simple-port-scanner/README.md b/PROJECTS/simple-port-scanner/README.md new file mode 100644 index 00000000..bbfdbe84 --- /dev/null +++ b/PROJECTS/simple-port-scanner/README.md @@ -0,0 +1,34 @@ +# Simple Port Scanner (C++) + +An asynchronous TCP port scanner implemented in C++ using boost::asio + +## Features +- Configurable port ranges (e.g. 1-1024, 1-65535) +- Adjustable concurrency level +- Connection timeouts +- Clean and readable CLI output + +## Educational Value +- Asynchronous IO using boost::asio +- TCP socket programming +- Concurrency control +- Basic network reconnaissance techniques + +## Build + +### Requirements +- C++20 +- Boost library +- CMake >= 3.16 + +### Build Instructions +```bash +mkdir build && cd build +cmake .. +make +``` +## Usage + +```bash +./port_scanner -i 127.0.0.1 -p 1-1024 -t 100 -e 2 + diff --git a/PROJECTS/simple-port-scanner/main.cpp b/PROJECTS/simple-port-scanner/main.cpp new file mode 100644 index 00000000..47f146ff --- /dev/null +++ b/PROJECTS/simple-port-scanner/main.cpp @@ -0,0 +1,44 @@ +#include "src/PortScanner.hpp" +#include +namespace po = boost::program_options; + +int main(int argc, char* argv[]) { + + po::options_description desc("Allowed options"); + desc.add_options() + ("help,h", "produce help message") + ("dname,i", po::value()->default_value("127.0.0.1"), "set domain name or IP address") + ("ports,p", po::value()->default_value("1-1024"), "set a port range from 1 to n") + ("threads,t", po::value()->default_value(100), "max concurrent threads") + ("expiry_time,e", po::value()->default_value(2)->value_name("sec"), "timeout in seconds") + ("verbose,v", "verbose output"); + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if (vm.count("help")) { + std::cout << desc << "\n"; + std::cout << "Examples:\n" + << " Scan common ports on localhost:\n" + << " ./port_scanner -i 127.0.0.1 -p 1-1024\n\n" + << " Full TCP port scan:\n" + << " ./port_scanner -i 192.168.1.1 -p 65535 -t 200\n\n" + << " Scan with custom timeout:\n" + << " ./port_scanner -i example.com -p 80-443 -e 5\n\n" + << " Postscriptum:\n" + << " Scan only systems you own or have explicit permission to test.\n"; + return 0; + } + std::string ip = vm["dname"].as(); + std::string port = vm["ports"].as(); + int threads = vm["threads"].as(); + uint8_t expiry_time = vm["expiry_time"].as(); + + PortScanner scanner; + scanner.set_options(ip, port, threads, expiry_time); + scanner.start(); + scanner.run(); + + return 0; +} diff --git a/PROJECTS/simple-port-scanner/src/PortScanner.cpp b/PROJECTS/simple-port-scanner/src/PortScanner.cpp new file mode 100644 index 00000000..cc34e927 --- /dev/null +++ b/PROJECTS/simple-port-scanner/src/PortScanner.cpp @@ -0,0 +1,193 @@ +#include "PortScanner.hpp" + +const std::unordered_map PortScanner::basicPorts{ + {21, "FTP"}, + {22, "SSH"}, + {23, "TelNet"}, + {25, "SMTP"}, + {53, "DNS"}, + {67, "DHCP server"}, + {68, "DHCP client"}, + {80, "HTTP"}, + {110, "POP3"}, + {143, "IMAP"}, + {161, "SNMP"}, + {443, "HTTPS"}, + {445, "SMB"}, + {465, "SMTPS"}, + {993, "IMAPS"}, + {1080, "SOCKS"}, + {1521, "ORACLE DB"}, + {3306, "MySQL"}, + {3389, "RDP"}, + {5432, "PostgreSQL"}, + {6379, "Redis"}, +}; + +void PortScanner::parse_port(std::string& port) { + auto t = std::find(port.begin(), port.end(), '-'); + if (t == port.end()) { + startPort = 1; + endPort = std::stoi(port); + return; + } + auto it = port.begin(); + std::string s = "", e = ""; + while (it != port.end()) { + if (*it == '-') { + break; + } + s += *it; + ++it; + } + ++it; + while (it != port.end()) { + e += *it; + ++it; + } + + int start = std::stoi(s); + int end = std::stoi(e); + //check a valid bounds + if (start == 0 || end > MAX_PORT || start > end) { + startPort = 1; + endPort = MAX_PORT; + } + else { + startPort = static_cast(start); + endPort = static_cast(end); + } +} +PortScanner::PortScanner(std::string& domainName, std::string& port, int max_threads, std::uint8_t expiry_time) { + this->domainName = std::move(domainName); + this->MAX_THREADS = max_threads; + this->expiry_time = expiry_time; + + parse_port(port); + auto result = resolver.resolve(this->domainName, ""); + endpoint = *result.begin(); + + setup_queue(); + +} + +void PortScanner::setup_queue() { + q = std::queue(); + for (int i = startPort; i <= endPort; i++) { + q.push(i); + } +} + + +void PortScanner::set_options(std::string& domainName, std::string& port, int max_threads, std::uint8_t expiry_time) { + this->domainName = std::move(domainName); + this->MAX_THREADS = max_threads; + this->expiry_time = expiry_time; + parse_port(port); + + + auto result = resolver.resolve(this->domainName, ""); + endpoint = *result.begin(); + + +} + +void PortScanner::set_max_port(std::uint16_t port) { + endPort = port; +} +void PortScanner::set_max_threads(int value) { + MAX_THREADS = value; +} + +void PortScanner::set_ip_address(std::string ip) { + domainName = std::move(ip); + +} + +void PortScanner::set_expiry_time(std::uint8_t value) { + expiry_time = value; +} + +void PortScanner::start() { + setup_queue(); + for (int i = 0; i < MAX_THREADS; i++) { + boost::asio::post(strand, [this]() { + scan(); + }); + + } +} + +void PortScanner::run() { + printf("PORT\tSTATE\tSERVICE\tBANNER\n"); + io.run(); + printf("\nResult:\n"); + printf(" Open ports: %d\n", open_ports); + printf(" Closed ports: %d\n", closed_ports); + printf(" Filtered ports: %d\n", filtered_ports); +} + +void PortScanner::scan() { + if (q.empty() || cnt >= MAX_THREADS) return; + + uint16_t port = q.front(); + q.pop(); + ++cnt; + + auto socket = std::make_shared(io); + auto timer = std::make_shared(io); + auto complete = std::make_shared(false); + + tcp::endpoint endpoint(this->endpoint.address(), port); + + timer->expires_after(std::chrono::seconds(expiry_time)); + + timer->async_wait(boost::asio::bind_executor(strand, [this, complete, socket, port](boost::system::error_code ec) { + if (!ec && !*complete) { + *complete = true; + socket->close(); + printf("%i\t%s\t%s\t%s\n", port, "FILTERED", "NULL", "NULL"); + ++filtered_ports; + --cnt; + scan(); + } + + })); + + socket->async_connect(endpoint, boost::asio::bind_executor(strand, [this,socket, timer, port, complete](boost::system::error_code ec) { + if (*complete) return; + *complete = true; + timer->cancel(); + + std::string service = "---"; + auto banner = std::make_shared("---"); + auto it = basicPorts.find(port); + if (it != basicPorts.end()) { + service = it->second; + } + + if (!ec) { + auto buf = std::make_shared>(); + + socket->async_read_some(boost::asio::buffer(*buf),boost::asio::bind_executor(strand, + [this, port, buf, banner, service](boost::system::error_code ec, std::size_t n) { + if (!ec && n > 0) { + banner->assign(buf->data(), n); + } + printf("%i\t%sOPEN%s\t%s\t%s\n", port, GREEN,RESET, service.c_str(), banner->c_str()); + ++open_ports; + --cnt; + scan(); + })); + + } + else{ + printf("%i\t%sCLOSED%s\t%s\t%s\n", port, RED, RESET, service.c_str(), banner->c_str()); + ++closed_ports; + --cnt; + scan(); + } + + })); + +} \ No newline at end of file diff --git a/PROJECTS/simple-port-scanner/src/PortScanner.hpp b/PROJECTS/simple-port-scanner/src/PortScanner.hpp new file mode 100644 index 00000000..b41a7a68 --- /dev/null +++ b/PROJECTS/simple-port-scanner/src/PortScanner.hpp @@ -0,0 +1,50 @@ +#ifndef PORTSCANNER_HPP +#define PORTSCANNER_HPP +#include +#include +#define RED "\033[31m" +#define GREEN "\033[32m" +#define RESET "\033[0m" +using boost::asio::ip::tcp; +class PortScanner { +private: + static const std::unordered_map basicPorts; + static const uint16_t MAX_PORT = 65535; + boost::asio::io_context io; + boost::asio::ip::tcp::resolver resolver{io}; + boost::asio::ip::tcp::endpoint endpoint; + boost::asio::strand strand{io.get_executor()}; + std::queue q; + int cnt = 0; + int MAX_THREADS = 0; + int open_ports = 0; + int closed_ports = 0; + int filtered_ports = 0; + + std::string domainName; + std::uint16_t startPort = 1; + std::uint16_t endPort = MAX_PORT; + + std::uint8_t expiry_time; + + void scan(); + void setup_queue(); + void parse_port(std::string& port); +public: + PortScanner(std::string& ip_address, std::string& port, int max_threads, std::uint8_t expiry_time); + PortScanner(){} + ~PortScanner() { + + } + + void set_options(std::string& domainName, std::string& port, int max_threads, std::uint8_t expiry_time); + void set_max_port(std::uint16_t port); + void set_max_threads(int value); + void set_ip_address(std::string ip); + void set_expiry_time(std::uint8_t value); + + void start(); + void run(); +}; + +#endif //PORTSCANNER_HPP