← All tasks
cppcodex/cpp-t1 #25Not a task: already works

TCP Port Scanner (cpp, written by Codex)

envgap__codex__cpp-t1-25

Written by a coding agent; not on GitHubWritten 2026-03-03

01 / FAILURE SIGNATURE

As the study recorded it

No identifying execution failure has been captured.
Not a benchmark task.
  • The project already builds and runs before the fix, so there is nothing to repair.

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

codex/cpp-t1 #25 · read the task the agent was given
Codex wrote this cpp project from the task below. It installed and ran 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.22)
project(tcp_port_scanner_cpp VERSION 1.0.0 LANGUAGES C CXX)

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

include(FetchContent)

FetchContent_Declare(
  nlohmann_json
  URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
)
FetchContent_MakeAvailable(nlohmann_json)

add_executable(tcp_port_scanner src/main.cpp)
target_link_libraries(tcp_port_scanner PRIVATE nlohmann_json::nlohmann_json)
README.md
# TCP Port Scanner (C++)

Performs TCP connect scans across configurable port ranges and hosts (including CIDR expansion), supports concurrent workers, timeout tuning, optional banner grabbing, real-time result output, and JSON reporting.

## Requirements

- Ubuntu 22.04
- G++ 12+
- CMake 3.22+

## Dependency (Pinned)

- nlohmann/json `3.11.3`

## Build

```bash
cmake -S . -B build
cmake --build build -j
```

## Run

```bash
./build/tcp_port_scanner scanme.nmap.org
./build/tcp_port_scanner 192.168.1.10 --ports 1-1024 --threads 200 --timeout 1
./build/tcp_port_scanner --hosts 192.168.1.0/24 --ports 22,80,443 --threads 100
./build/tcp_port_scanner example.com --banner --output scan_report.json
./build/tcp_port_scanner
```

## Notes

- Default explicit scan range is `1-1024`.
- With no target/hosts, scanner checks localhost common ports.
- `--hosts` supports comma-separated hosts and IPv4 CIDR notation.
src/main.cpp
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>

#include <nlohmann/json.hpp>

#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <mutex>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>

namespace fs = std::filesystem;
using json = nlohmann::json;

struct CliOptions {
    std::map<std::string, std::string> kv;
    std::vector<std::string> positional;
};

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

const std::vector<int> COMMON_LOCALHOST_PORTS = {21, 22, 25, 53, 80, 110, 143, 443, 993, 995, 3306, 5432, 6379, 8080, 8443};
const std::map<int, std::string> SERVICES = {
    {20, "ftp-data"}, {21, "ftp"}, {22, "ssh"}, {23, "telnet"}, {25, "smtp"}, {53, "dns"}, {80, "http"}, {110, "pop3"},
    {123, "ntp"}, {143, "imap"}, {443, "https"}, {465, "smtps"}, {587, "submission"}, {993, "imaps"}, {995, "pop3s"},
    {1433, "mssql"}, {1521, "oracle"}, {3306, "mysql"}, {3389, "rdp"}, {5432, "postgresql"}, {6379, "redis"},
    {8080, "http-alt"}, {8443, "https-alt"}
};

CliOptions parseArgs(int argc, char** argv) {
    CliOptions out;
    for (int i = 1; i < argc; ++i) {
        std::string t = argv[i];
        if (t.rfind("--", 0) == 0) {
            std::string key = t.substr(2);
            std::string value = "true";
            if (i + 1 < argc) {
                std::string next = argv[i + 1];
                if (next.rfind("--", 0) != 0) {
                    value = next;
                    ++i;
                }
            }
            out.kv[key] = value;
        } else out.positional.push_back(t);
    }
    return out;
}

std::string trim(std::string s) {
    while (!s.empty() && std::isspace(static_cast<unsigned char>(s.front()))) s.erase(s.begin());
    while (!s.empty() && std::isspace(static_cast<unsigned char>(s.back()))) s.pop_back();
    return s;
}

std::vector<int> parsePorts(const std::string& raw) {
    std::set<int> out;
    std::stringstream ss(raw);
    std::string part;
    while (std::getline(ss, part, ',')) {
        part = trim(part);
        if (part.empty()) continue;
        auto dash = part.find('-');
        if (dash != std::string::npos) {
            int a = std::stoi(part.substr(0, dash));
            int b = std::stoi(part.substr(dash + 1));
            if (a < 1 || b > 65535 || a > b) throw std::invalid_argument("Invalid port range: " + part);
            for (int p = a; p <= b; ++p) out.insert(p);
        } else {
            int p = std::stoi(part);
            if (p < 1 || p > 65535) throw std::invalid_argument("Invalid port: " + part);
            out.insert(p);
        }
    }
    return std::vector<int>(out.begin(), out.end());
}

uint32_t ipToInt(const std::string& ip) {
    in_addr a{};
    if (inet_pton(AF_INET, ip.c_str(), &a) != 1) throw std::invalid_argument("Invalid IPv4: " + ip);
    return ntohl(a.s_addr);
}

std::string intToIp(uint32_t n) {
    in_addr a{};
    a.s_addr = htonl(n);
    char buf[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &a, buf, sizeof(buf));
    return std::string(buf);
}

std::vector<std::string> expandCidr(const std::string& cidr) {
    auto slash = cidr.find('/');
    if (slash == std::string::npos) throw std::invalid_argument("Invalid CIDR: " + cidr);
    std::string base = cidr.substr(0, slash);
    int prefix = std::stoi(cidr.substr(slash + 1));
    if (prefix < 0 || prefix > 32) throw std::invalid_argument("Invalid CIDR prefix: " + cidr);
    uint32_t baseInt = ipToInt(base);
    uint32_t mask = prefix == 0 ? 0 : (0xffffffffu << (32 - prefix));
    uint32_t net = baseInt & mask;
    uint64_t count = 1ull << (32 - prefix);
    std::vector<std::string> hosts;
    for (uint64_t i = 0; i < count && hosts.size() < 4096; ++i) {
        if (prefix <= 30 && (i == 0 || i == count - 1)) continue;
        hosts.push_back(intToIp(static_cast<uint32_t>(net + i)));
    }
    return hosts;
}

std::vector<std::string> parseHosts(const std::string& target, const std::string& hostsArg) {
    std::vector<std::string> seeds;
    if (!target.empty()) seeds.push_back(target);
    if (!hostsArg.empty()) {
        std::stringstream ss(hostsArg);
        std::string part;
        while (std::getline(ss, part, ',')) {
            part = trim(part);
            if (!part.empty()) seeds.push_back(part);
        }
    }
    if (seeds.empty()) return {"127.0.0.1"};

    std::set<std::string> out;
    for (const auto& s : seeds) {
        if (s.find('/') != std::string::npos) {
            auto ex = expandCidr(s);
            out.insert(ex.begin(), ex.end());
        } else out.insert(s);
    }
    return std::vector<std::string>(out.begin(), out.end());
}

std::string resolveHost(const std::string& host) {
    addrinfo hints{};
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    addrinfo* res = nullptr;
    int rc = getaddrinfo(host.c_str(), nullptr, &hints, &res);
    if (rc != 0 || !res) throw std::runtime_error("unresolvable host: " + host);
    char ip[INET_ADDRSTRLEN];
    auto* addr = reinterpret_cast<sockaddr_in*>(res->ai_addr);
    inet_ntop(AF_INET, &(addr->sin_addr), ip, sizeof(ip));
    freeaddrinfo(res);
    return std::string(ip);
}

std::string normalizeBanner(const std::string& s) {
    std::string out;
    bool space = false;
    for (char c : s) {
        if (std::isspace(static_cast<unsigned char>(c))) {
            if (!space) out.push_back(' ');
            space = true;
        } else {
            out.push_back(c);
            space = false;
        }
        if (out.size() >= 160) break;
    }
    return trim(out);
}

ScanRow scanPort(const std::string& host, const std::string& ip, int port, int timeoutMs, bool banner, std::mutex& outMutex) {
    ScanRow row;
    row.port = port;
    row.service = SERVICES.count(port) ? SERVICES.at(port) : "unknown";
    row.state = "filtered";
    row.banner = "";

    int fd = ::socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        row.state = "filtered";
    } else {
        fcntl(fd, F_SETFL, O_NONBLOCK);
        sockaddr_in addr{};
        addr.sin_family = AF_INET;
        addr.sin_port = htons(static_cast<uint16_t>(port));
        inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);

        int rc = ::connect(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
        if (rc == 0) {
            row.state = "open";
        } else if (errno == EINPROGRESS) {
            fd_set wfds;
            FD_ZERO(&wfds);
            FD_SET(fd, &wfds);
            timeval tv{};
            tv.tv_sec = timeoutMs / 1000;
            tv.tv_usec = (timeoutMs % 1000) * 1000;
            int sel = ::select(fd + 1, nullptr, &wfds, nullptr, &tv);
            if (sel == 0) {
                row.state = "filtered";
            } else if (sel > 0) {
                int err = 0;
                socklen_t len = sizeof(err);
                getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len);
                if (err == 0) row.state = "open";
                else if (err == ECONNREFUSED) row.state = "closed";
                else if (err == ENETUNREACH || err == EHOSTUNREACH || err == ETIMEDOUT) row.state = "filtered";
                else row.state = "filtered";
            } else {
                row.state = "filtered";
            }
        } else if (errno == ECONNREFUSED) {
            row.state = "closed";
        } else {
            row.state = "filtered";
        }

        if (row.state == "open" && banner) {
            timeval t{};
            t.tv_sec = std::max(1, timeoutMs / 1000);
            t.tv_usec = 0;
            setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t));
            setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof(t));
            if (port == 80 || port == 8080 || port == 8000 || port == 443 || port == 8443) {
                const char* probe = "HEAD / HTTP/1.0\r\n\r\n";
                send(fd, probe, std::strlen(probe), 0);
            }
            char buf[256];
            int n = recv(fd, buf, sizeof(buf) - 1, 0);
            if (n > 0) {
                buf[n] = '\0';
                row.banner = normalizeBanner(std::string(buf));
            }
        }
        close(fd);
    }

    {
        std::lock_guard<std::mutex> lk(outMutex);
        std::cout << host << ":" << port << " " << row.state;
        if (row.state == "open") std::cout << " service=" << row.service;
        if (!row.banner.empty()) std::cout << " banner=\"" << row.banner << "\"";
        std::cout << "\n";
    }
    return row;
}

json scanHost(const std::string& host, const std::vector<int>& ports, int threads, int timeoutMs, bool banner, std::mutex& outMutex) {
    std::string ip;
    try {
        ip = resolveHost(host);
    } catch (const std::exception& ex) {
        return {{"host", host}, {"resolved_ip", ""}, {"error", ex.what()}, {"results", json::array()}};
    }

    std::vector<ScanRow> rows;
    rows.reserve(ports.size());
    std::mutex queueMutex;
    std::mutex rowsMutex;
    size_t next = 0;
    auto worker = [&]() {
        while (true) {
            int port = 0;
            {
                std::lock_guard<std::mutex> lk(queueMutex);
                if (next >= ports.size()) break;
                port = ports[next++];
            }
            auto row = scanPort(host, ip, port, timeoutMs, banner, outMutex);
            std::lock_guard<std::mutex> lk(rowsMutex);
            rows.push_back(std::move(row));
        }
    };

    std::vector<std::thread> pool;
    for (int i = 0; i < std::max(1, threads); ++i) pool.emplace_back(worker);
    for (auto& t : pool) t.join();
    std::sort(rows.begin(), rows.end(), [](const ScanRow& a, const ScanRow& b) { return a.port < b.port; });

    json jrows = json::array();
    for (const auto& r : rows) jrows.push_back({{"port", r.port}, {"state", r.state}, {"service", r.service}, {"banner", r.banner}});
    return {{"host", host}, {"resolved_ip", ip}, {"results", jrows}};
}

void printSummary(const json& report) {
    for (const auto& host : report["hosts"]) {
        std::cout << "\nHost: " << host.value("host", "") << " (" << host.value("resolved_ip", "") << ")\n";
        if (host.contains("error")) {
            std::cout << "Error: " << host["error"] << "\n";
            continue;
        }
        std::cout << "Port | State | Service | Banner\n";
        std::cout << "---- | ----- | ------- | ------\n";
        for (const auto& r : host["results"]) {
            std::cout << r["port"] << " | " << r["state"] << " | " << r["service"] << " | " << r["banner"] << "\n";
        }
    }
}

std::string nowIso() {
    auto now = std::chrono::system_clock::now();
    std::time_t t = std::chrono::system_clock::to_time_t(now);
    std::tm tm = *gmtime(&t);
    char buf[32];
    std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", &tm);
    return std::string(buf);
}

int main(int argc, char** argv) {
    try {
        CliOptions opts = parseArgs(argc, argv);
        std::string target = opts.positional.empty() ? "" : opts.positional.front();
        std::vector<std::string> hosts = parseHosts(target, opts.kv.count("hosts") ? opts.kv.at("hosts") : "");
        std::vector<int> ports = (target.empty() && !opts.kv.count("hosts"))
            ? COMMON_LOCALHOST_PORTS
            : parsePorts(opts.kv.count("ports") ? opts.kv.at("ports") : "1-1024");
        int threads = std::max(1, std::stoi(opts.kv.count("threads") ? opts.kv.at("threads") : "100"));
        int timeoutMs = static_cast<int>(std::max(0.05, std::stod(opts.kv.count("timeout") ? opts.kv.at("timeout") : "1")) * 1000.0);
        bool banner = opts.kv.count("banner") > 0;

        auto startedClock = std::chrono::steady_clock::now();
        std::mutex outMutex;
        json hostReports = json::array();
        for (const auto& h : hosts) {
            hostReports.push_back(scanHost(h, ports, threads, timeoutMs, banner, outMutex));
        }

        json report = {
            {"started_at", nowIso()},
            {"duration_sec", std::chrono::duration<double>(std::chrono::steady_clock::now() - startedClock).count()},
            {"port_spec", (target.empty() && !opts.kv.count("hosts")) ? "common-localhost" : (opts.kv.count("ports") ? opts.kv.at("ports") : "1-1024")},
            {"hosts", hostReports}
        };
        printSummary(report);

        fs::path out = fs::absolute(opts.kv.count("output") ? opts.kv.at("output") : "scan_report.json");
        fs::create_directories(out.parent_path());
        std::ofstream ofs(out);
        ofs << report.dump(2) << "\n";
        std::cout << "\nReport saved: " << out << "\n";
        return 0;
    } catch (const std::exception& ex) {
        std::cerr << "Error: " << ex.what() << "\n";
        return 1;
    }
}