← All tasks
cppclaude-code/cpp-t1 #25Not a task: repair not recorded

TCP Port Scanner (cpp, written by Claude Code)

envgap__claude-code__cpp-t1-25

Written by a coding agent; not on GitHubWritten 2026-02-28

01 / FAILURE SIGNATURE

As the study recorded it

Could NOT find Boost - libboost not in Docker image
Not a benchmark task.
  • It was made to work, but its repair cannot be rebuilt from the saved files (the saved copy shows no change, or not all of the changes the study's notes describe), so there is no fix to score against.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
CMakeLists.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

claude-code/cpp-t1 #25 · read the task the agent was given
Claude Code wrote this cpp project from the task below. It does not run on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: TCP Port Scanner

Write a program that scans TCP ports on a target host to determine which ports are open, closed, or filtered, with support for service detection, concurrent scanning, and configurable scan ranges.

FUNCTIONAL REQUIREMENTS:
- Accept a target hostname or IP address as a command-line argument
- Scan a port range specified via --ports flag (e.g., --ports 1-1024, --ports 80,443,8080, or --ports 1-65535); default: well-known ports 1-1024
- Perform TCP connect scanning: attempt a full TCP handshake to determine if a port is open
- Support concurrent scanning with configurable thread count via --threads flag (default: 100) to speed up large range scans
- Implement configurable connection timeout via --timeout flag (default: 1 second per port)
- Detect common services on open ports by matching port numbers to known services (HTTP/80, HTTPS/443, SSH/22, FTP/21, SMTP/25, DNS/53, etc.) using a built-in service name database
- Attempt basic banner grabbing on open ports to identify the running service version via --banner flag
- Support scanning multiple hosts via --hosts flag (comma-separated or CIDR notation for a subnet)
- Print scan results to console in real-time as ports are discovered, with a final summary table showing: port number, state (open/closed/filtered), service name, and banner (if captured)
- Save the full scan report as JSON with --output flag (default: scan_report.json) including scan duration, host info, and per-port results
- If no target is given, scan localhost (127.0.0.1) on the common ports (21, 22, 25, 53, 80, 110, 143, 443, 993, 995, 3306, 5432, 6379, 8080, 8443) and display the results
- Handle errors: unresolvable hostnames, network unreachable, connection refused vs timeout distinction, and permission issues

Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include:
- Source code
- CMakeLists.txt with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

3 files, exactly as written, before any repair.

CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(port_scanner VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# ---------------------------------------------------------------------------
# Dependencies
# ---------------------------------------------------------------------------
# Boost.Asio 1.84.0 — Asynchronous I/O for TCP connect scanning
# nlohmann/json 3.11.3 — Modern JSON for C++
# ---------------------------------------------------------------------------

include(FetchContent)

# Boost (system install)
find_package(Boost 1.84.0 REQUIRED COMPONENTS system)

# nlohmann/json via FetchContent
FetchContent_Declare(
    nlohmann_json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG        v3.11.3
    GIT_SHALLOW    TRUE
)

set(JSON_BuildTests OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(nlohmann_json)

# ---------------------------------------------------------------------------
# Executable
# ---------------------------------------------------------------------------

add_executable(port_scanner main.cpp)

target_link_libraries(port_scanner PRIVATE
    Boost::system
    nlohmann_json::nlohmann_json
)

# Pthreads on Linux
find_package(Threads REQUIRED)
target_link_libraries(port_scanner PRIVATE Threads::Threads)

if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    target_compile_options(port_scanner PRIVATE -Wall -Wextra -Wpedantic)
endif()
main.cpp
/**
 * TCP Port Scanner with concurrent scanning, service detection, banner grabbing,
 * and CIDR subnet support. Uses Boost.Asio for async I/O and nlohmann/json for output.
 */

#include <boost/asio.hpp>
#include <nlohmann/json.hpp>

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <sstream>
#include <mutex>
#include <thread>
#include <chrono>
#include <algorithm>
#include <cstdint>
#include <functional>
#include <memory>
#include <atomic>

using boost::asio::ip::tcp;
using json = nlohmann::json;

// Well-known port to service mappings
static const std::map<int, std::string> WELL_KNOWN_SERVICES = {
    {21, "FTP"}, {22, "SSH"}, {23, "Telnet"}, {25, "SMTP"}, {53, "DNS"},
    {80, "HTTP"}, {110, "POP3"}, {135, "MSRPC"}, {139, "NetBIOS-SSN"},
    {143, "IMAP"}, {443, "HTTPS"}, {445, "Microsoft-DS"}, {993, "IMAPS"},
    {995, "POP3S"}, {1433, "MSSQL"}, {1521, "Oracle"}, {3306, "MySQL"},
    {3389, "RDP"}, {5432, "PostgreSQL"}, {5900, "VNC"}, {6379, "Redis"},
    {8080, "HTTP-Proxy"}, {8443, "HTTPS-Alt"}, {27017, "MongoDB"},
};

struct ScanResult {
    int port;
    std::string state;
    std::string service;
    std::string banner;
};

/**
 * Parse port specification string into a sorted vector of port numbers.
 */
std::vector<int> parse_ports(const std::string& port_spec) {
    std::set<int> ports;
    std::stringstream ss(port_spec);
    std::string token;

    while (std::getline(ss, token, ',')) {
        // Trim whitespace
        token.erase(0, token.find_first_not_of(" \t"));
        token.erase(token.find_last_not_of(" \t") + 1);

        auto dash_pos = token.find('-');
        if (dash_pos != std::string::npos) {
            int start = std::stoi(token.substr(0, dash_pos));
            int end = std::stoi(token.substr(dash_pos + 1));
            if (start > end) std::swap(start, end);
            for (int p = start; p <= end; p++) {
                if (p >= 1 && p <= 65535) ports.insert(p);
            }
        } else {
            int p = std::stoi(token);
            if (p >= 1 && p <= 65535) ports.insert(p);
        }
    }

    return std::vector<int>(ports.begin(), ports.end());
}

/**
 * Convert a 32-bit integer to an IP address string.
 */
std::string uint32_to_ip(uint32_t ip) {
    return std::to_string((ip >> 24) & 0xFF) + "." +
           std::to_string((ip >> 16) & 0xFF) + "." +
           std::to_string((ip >> 8) & 0xFF) + "." +
           std::to_string(ip & 0xFF);
}

/**
 * Convert an IP address string to a 32-bit integer.
 */
uint32_t ip_to_uint32(const std::string& ip) {
    uint32_t result = 0;
    std::stringstream ss(ip);
    std::string octet;
    int shift = 24;
    while (std::getline(ss, octet, '.')) {
        result |= (static_cast<uint32_t>(std::stoi(octet)) << shift);
        shift -= 8;
    }
    return result;
}

/**
 * Expand CIDR notation to list of host IP addresses.
 */
std::vector<std::string> expand_cidr(const std::string& cidr) {
    std::vector<std::string> hosts;
    auto slash_pos = cidr.find('/');
    if (slash_pos == std::string::npos) return hosts;

    std::string base_ip = cidr.substr(0, slash_pos);
    int prefix = std::stoi(cidr.substr(slash_pos + 1));

    if (prefix < 0 || prefix > 32) return hosts;

    uint32_t ip = ip_to_uint32(base_ip);
    uint32_t mask = prefix == 0 ? 0 : (~uint32_t(0)) << (32 - prefix);
    uint32_t network = ip & mask;
    uint32_t broadcast = network | ~mask;

    uint32_t start = (prefix <= 30) ? network + 1 : network;
    uint32_t end = (prefix <= 30) ? broadcast - 1 : broadcast;

    for (uint32_t addr = start; addr <= end; addr++) {
        hosts.push_back(uint32_to_ip(addr));
    }
    return hosts;
}

/**
 * Parse target specification into list of IP addresses.
 */
std::vector<std::string> parse_targets(const std::string& target_spec) {
    std::vector<std::string> targets;
    std::stringstream ss(target_spec);
    std::string token;

    while (std::getline(ss, token, ',')) {
        token.erase(0, token.find_first_not_of(" \t"));
        token.erase(token.find_last_not_of(" \t") + 1);

        if (token.find('/') != std::string::npos) {
            auto hosts = expand_cidr(token);
            targets.insert(targets.end(), hosts.begin(), hosts.end());
        } else {
            // Try to resolve hostname
            try {
                boost::asio::io_context io;
                tcp::resolver resolver(io);
                auto results = resolver.resolve(token, "");
                for (const auto& entry : results) {
                    if (entry.endpoint().address().is_v4()) {
                        targets.push_back(entry.endpoint().address().to_string());
                        break;
                    }
                }
            } catch (const std::exception& e) {
                std::cerr << "Cannot resolve: " << token << " (" << e.what() << ")" << std::endl;
            }
        }
    }
    return targets;
}

/**
 * Attempt to grab a banner from an open port (synchronous).
 */
std::string grab_banner(const std::string& ip, int port, int timeout_ms) {
    try {
        boost::asio::io_context io;
        tcp::socket socket(io);

        tcp::endpoint endpoint(boost::asio::ip::make_address(ip), port);

        // Set a deadline timer for the connection
        socket.open(tcp::v4());

        boost::system::error_code ec;
        socket.connect(endpoint, ec);
        if (ec) return "";

        // Send HTTP probe for web ports
        if (port == 80 || port == 8080 || port == 8443 || port == 443) {
            std::string request = "HEAD / HTTP/1.0\r\nHost: " + ip + "\r\n\r\n";
            boost::asio::write(socket, boost::asio::buffer(request), ec);
        }

        // Wait briefly for data
        socket.non_blocking(true);
        std::this_thread::sleep_for(std::chrono::milliseconds(500));

        char buffer[1024] = {};
        size_t bytes = socket.read_some(boost::asio::buffer(buffer, sizeof(buffer) - 1), ec);
        if (!ec && bytes > 0) {
            std::string banner(buffer, bytes);
            // Trim and limit length
            banner.erase(banner.find_last_not_of(" \t\r\n") + 1);
            if (banner.length() > 200) banner = banner.substr(0, 200);
            return banner;
        }

        socket.close();
    } catch (...) {
    }
    return "";
}

/**
 * Detect the service running on a port.
 */
std::string detect_service(int port, const std::string& banner) {
    if (!banner.empty()) {
        std::string lower = banner;
        std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);

        if (lower.find("ssh") != std::string::npos) return "SSH";
        if (lower.find("http") != std::string::npos) return "HTTP";
        if (lower.find("ftp") != std::string::npos) return "FTP";
        if (lower.find("smtp") != std::string::npos) return "SMTP";
        if (lower.find("mysql") != std::string::npos) return "MySQL";
        if (lower.find("postgresql") != std::string::npos || lower.find("postgres") != std::string::npos) return "PostgreSQL";
        if (lower.find("redis") != std::string::npos) return "Redis";
    }

    auto it = WELL_KNOWN_SERVICES.find(port);
    if (it != WELL_KNOWN_SERVICES.end()) return it->second;
    return "Unknown";
}

/**
 * Scan a single port using synchronous TCP connect.
 */
ScanResult scan_port(const std::string& ip, int port, int timeout_ms) {
    ScanResult result;
    result.port = port;
    result.state = "closed";
    result.service = "Unknown";

    try {
        boost::asio::io_context io;
        tcp::socket socket(io);
        tcp::endpoint endpoint(boost::asio::ip::make_address(ip), port);

        // Use deadline timer for timeout
        boost::asio::steady_timer timer(io);
        timer.expires_after(std::chrono::milliseconds(timeout_ms));

        bool connected = false;
        bool timed_out = false;

        socket.async_connect(endpoint, [&](const boost::system::error_code& ec) {
            if (!ec) {
                connected = true;
            }
            timer.cancel();
        });

        timer.async_wait([&](const boost::system::error_code& ec) {
            if (!ec) {
                timed_out = true;
                boost::system::error_code close_ec;
                socket.close(close_ec);
            }
        });

        io.run();

        if (connected) {
            result.state = "open";
            socket.close();

            // Try banner grabbing
            std::string banner = grab_banner(ip, port, timeout_ms);
            result.banner = banner;
            result.service = detect_service(port, banner);
        } else if (timed_out) {
            result.state = "filtered";
        }
    } catch (const boost::system::system_error& e) {
        if (e.code() == boost::asio::error::connection_refused) {
            result.state = "closed";
        } else {
            result.state = "closed";
        }
    } catch (...) {
        result.state = "closed";
    }

    return result;
}

/**
 * Scan all ports on a host using a thread pool.
 */
std::vector<ScanResult> scan_host(const std::string& ip, const std::vector<int>& ports,
                                   int max_workers, int timeout_ms, bool quiet) {
    std::vector<ScanResult> results;
    std::mutex results_mutex;
    std::atomic<int> completed(0);
    int total = static_cast<int>(ports.size());

    // Create a simple thread pool
    std::vector<std::thread> threads;
    std::atomic<int> port_index(0);

    auto worker = [&]() {
        while (true) {
            int idx = port_index.fetch_add(1);
            if (idx >= total) break;

            ScanResult result = scan_port(ip, ports[idx], timeout_ms);
            {
                std::lock_guard<std::mutex> lock(results_mutex);
                results.push_back(result);
            }

            int done = completed.fetch_add(1) + 1;
            if (!quiet && done % 50 == 0) {
                std::cout << "\r  Scanning " << ip << ": " << done << "/" << total
                          << " (" << (done * 100 / total) << "%)" << std::flush;
            }
        }
    };

    int num_threads = std::min(max_workers, total);
    for (int i = 0; i < num_threads; i++) {
        threads.emplace_back(worker);
    }

    for (auto& t : threads) {
        t.join();
    }

    if (!quiet) {
        std::cout << "\r  Scanning " << ip << ": " << total << "/" << total << " (100%)" << std::endl;
    }

    std::sort(results.begin(), results.end(),
              [](const ScanResult& a, const ScanResult& b) { return a.port < b.port; });

    return results;
}

/**
 * Display results in a formatted text table.
 */
void display_results(const std::string& ip, const std::vector<ScanResult>& results, bool show_closed) {
    int open_count = 0, filtered_count = 0, closed_count = 0;

    std::cout << std::endl;
    std::cout << "=== Scan Results for " << ip << " ===" << std::endl;
    std::cout << std::left;
    printf("%-8s %-10s %-15s %s\n", "PORT", "STATE", "SERVICE", "BANNER");
    std::cout << std::string(80, '-') << std::endl;

    for (const auto& r : results) {
        if (r.state == "open") open_count++;
        else if (r.state == "filtered") filtered_count++;
        else closed_count++;

        if (r.state == "open" || r.state == "filtered" || show_closed) {
            std::string banner_display = r.banner;
            if (banner_display.length() > 50) banner_display = banner_display.substr(0, 50);
            // Remove newlines from banner
            std::replace(banner_display.begin(), banner_display.end(), '\n', ' ');
            std::replace(banner_display.begin(), banner_display.end(), '\r', ' ');

            printf("%-8d %-10s %-15s %s\n", r.port, r.state.c_str(), r.service.c_str(), banner_display.c_str());
        }
    }

    std::cout << std::string(80, '-') << std::endl;
    std::cout << "Summary: " << open_count << " open, " << filtered_count << " filtered, "
              << closed_count << " closed (Total: " << results.size() << " ports scanned)" << std::endl;
}

/**
 * Display results as JSON using nlohmann/json.
 */
void display_json_results(const std::vector<std::pair<std::string, std::vector<ScanResult>>>& all_results,
                          bool show_closed) {
    json output = json::array();

    for (const auto& [host, results] : all_results) {
        json host_obj;
        host_obj["host"] = host;
        host_obj["ports"] = json::array();

        for (const auto& r : results) {
            if (r.state == "open" || r.state == "filtered" || show_closed) {
                json port_obj;
                port_obj["port"] = r.port;
                port_obj["state"] = r.state;
                port_obj["service"] = r.service;
                if (!r.banner.empty()) {
                    port_obj["banner"] = r.banner;
                }
                host_obj["ports"].push_back(port_obj);
            }
        }
        output.push_back(host_obj);
    }

    std::cout << output.dump(2) << std::endl;
}

void print_usage(const char* prog) {
    std::cout << "Usage: " << prog << " <target> [options]" << std::endl;
    std::cout << std::endl;
    std::cout << "Options:" << std::endl;
    std::cout << "  -p, --ports PORTS     Port specification (default: 1-1024)" << std::endl;
    std::cout << "  -w, --workers N       Max concurrent threads (default: 100)" << std::endl;
    std::cout << "  -t, --timeout MS      Timeout in ms (default: 2000)" << std::endl;
    std::cout << "  --show-closed         Show closed ports" << std::endl;
    std::cout << "  --json                JSON output" << std::endl;
    std::cout << "  -h, --help            Show help" << std::endl;
    std::cout << std::endl;
    std::cout << "Examples:" << std::endl;
    std::cout << "  " << prog << " 192.168.1.1" << std::endl;
    std::cout << "  " << prog << " 192.168.1.0/24 -p 22,80,443" << std::endl;
    std::cout << "  " << prog << " example.com -p 1-1024 --json" << std::endl;
}

int main(int argc, char* argv[]) {
    std::string target;
    std::string port_spec = "1-1024";
    int timeout_ms = 2000;
    int max_workers = 100;
    bool show_closed = false;
    bool json_output = false;

    // Parse command-line arguments
    for (int i = 1; i < argc; i++) {
        std::string arg = argv[i];
        if (arg == "-p" || arg == "--ports") {
            if (i + 1 < argc) port_spec = argv[++i];
        } else if (arg == "-w" || arg == "--workers") {
            if (i + 1 < argc) max_workers = std::stoi(argv[++i]);
        } else if (arg == "-t" || arg == "--timeout") {
            if (i + 1 < argc) timeout_ms = std::stoi(argv[++i]);
        } else if (arg == "--show-closed") {
            show_closed = true;
        } else if (arg == "--json") {
            json_output = true;
        } else if (arg == "-h" || arg == "--help") {
            print_usage(argv[0]);
            return 0;
        } else if (arg[0] != '-') {
            target = arg;
        }
    }

    if (target.empty()) {
        std::cerr << "Error: No target specified." << std::endl;
        print_usage(argv[0]);
        return 1;
    }

    auto targets = parse_targets(target);
    auto ports = parse_ports(port_spec);

    if (targets.empty()) {
        std::cerr << "No valid targets." << std::endl;
        return 1;
    }
    if (ports.empty()) {
        std::cerr << "No valid ports." << std::endl;
        return 1;
    }

    if (!json_output) {
        std::cout << "TCP Port Scanner" << std::endl;
        std::cout << "================" << std::endl;
        std::cout << "Targets: " << targets.size() << " host(s)" << std::endl;
        std::cout << "Ports:   " << ports.size() << " port(s)" << std::endl;
        std::cout << "Workers: " << max_workers << std::endl;
        std::cout << "Timeout: " << timeout_ms << " ms" << std::endl;
        std::cout << std::endl;
    }

    auto total_start = std::chrono::steady_clock::now();
    std::vector<std::pair<std::string, std::vector<ScanResult>>> all_results;

    for (const auto& ip : targets) {
        auto results = scan_host(ip, ports, max_workers, timeout_ms, json_output);
        all_results.emplace_back(ip, results);
        if (!json_output) {
            display_results(ip, results, show_closed);
        }
    }

    if (json_output) {
        display_json_results(all_results, show_closed);
    } else {
        auto total_end = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(total_end - total_start).count();
        std::cout << "\nTotal scan completed in " << elapsed << " ms" << std::endl;
    }

    return 0;
}
README.md
# TCP Port Scanner (C++ - Trial 1)

A TCP port scanner with concurrent scanning, service detection, banner grabbing, and CIDR subnet support. Uses Boost.Asio for asynchronous I/O and nlohmann/json for structured JSON output.

## Dependencies

- **Boost.Asio** (1.84.0) - Asynchronous I/O library for TCP connect scanning with deadline timers
- **nlohmann/json** (3.11.3) - Modern JSON library for structured scan result output

## Build

```bash
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
```

### Prerequisites

On Ubuntu/Debian:

```bash
sudo apt-get install -y cmake g++ git libboost-system-dev
```

## Usage

```bash
# Scan default ports (1-1024)
./build/port_scanner 192.168.1.1

# Scan specific ports
./build/port_scanner 192.168.1.1 -p 22,80,443,8080

# Scan a CIDR subnet
./build/port_scanner 192.168.1.0/24 -p 22,80,443

# JSON output
./build/port_scanner example.com -p 1-1024 --json

# Show closed ports
./build/port_scanner 10.0.0.1 -p 1-100 --show-closed
```

## Features

- TCP connect scanning with configurable concurrency via thread pool
- Async connect with deadline timers using Boost.Asio
- Service detection via banner grabbing and well-known port lookup
- CIDR subnet expansion for network scanning
- Hostname resolution via Boost.Asio resolver
- Formatted text table and JSON output modes