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;
}
}