← All tasks
javacodex/java-t1 #25Not a task: already works

TCP Port Scanner (java, written by Codex)

envgap__codex__java-t1-25

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

01 / FAILURE SIGNATURE

As the study recorded it

None
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
pom.xml
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/java-t1 #25 · read the task the agent was given
Codex wrote this java 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 Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include:
- Source code
- pom.xml 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.

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.tmlr</groupId>
    <artifactId>tcp-port-scanner</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.11.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <release>17</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.4.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>TcpPortScanner</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
README.md
# TCP Port Scanner (Java)

Runs TCP connect scans for one or multiple hosts with configurable port ranges, concurrency, timeout, service mapping, optional banner grabbing, CIDR host expansion, real-time discovery output, and JSON reporting.

## Requirements

- Ubuntu 22.04
- JDK 17+
- Maven 3.8+

## Dependency (Pinned)

- `com.google.code.gson:gson:2.11.0`

## Build

```bash
mvn clean package
```

## Run

```bash
java -cp target/tcp-port-scanner-1.0.0.jar TcpPortScanner scanme.nmap.org
java -cp target/tcp-port-scanner-1.0.0.jar TcpPortScanner 192.168.1.10 --ports 1-1024 --threads 200 --timeout 1
java -cp target/tcp-port-scanner-1.0.0.jar TcpPortScanner --hosts 192.168.1.0/24 --ports 22,80,443 --threads 100
java -cp target/tcp-port-scanner-1.0.0.jar TcpPortScanner example.com --banner --output scan_report.json
java -cp target/tcp-port-scanner-1.0.0.jar TcpPortScanner
```

## Notes

- Default explicit scan range is `1-1024`.
- Without target/hosts, scanner checks localhost common ports.
- `--hosts` supports comma-separated values and IPv4 CIDR.
src/main/java/TcpPortScanner.java
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public final class TcpPortScanner {
    private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
    private static final List<Integer> COMMON_LOCALHOST_PORTS = List.of(21, 22, 25, 53, 80, 110, 143, 443, 993, 995, 3306, 5432, 6379, 8080, 8443);
    private static final Map<Integer, String> SERVICES = Map.ofEntries(
        Map.entry(20, "ftp-data"), Map.entry(21, "ftp"), Map.entry(22, "ssh"), Map.entry(23, "telnet"), Map.entry(25, "smtp"),
        Map.entry(53, "dns"), Map.entry(80, "http"), Map.entry(110, "pop3"), Map.entry(123, "ntp"), Map.entry(143, "imap"),
        Map.entry(443, "https"), Map.entry(465, "smtps"), Map.entry(587, "submission"), Map.entry(993, "imaps"), Map.entry(995, "pop3s"),
        Map.entry(1433, "mssql"), Map.entry(1521, "oracle"), Map.entry(3306, "mysql"), Map.entry(3389, "rdp"),
        Map.entry(5432, "postgresql"), Map.entry(6379, "redis"), Map.entry(8080, "http-alt"), Map.entry(8443, "https-alt")
    );

    private record ParsedArgs(Map<String, String> options, List<String> positional) {}

    private TcpPortScanner() {}

    public static void main(String[] args) {
        try {
            ParsedArgs parsed = parseArgs(args);
            String target = parsed.positional.isEmpty() ? "" : parsed.positional.get(0);
            List<String> hosts = parseHosts(target, parsed.options.get("hosts"));
            List<Integer> ports = (target.isEmpty() && !parsed.options.containsKey("hosts"))
                ? COMMON_LOCALHOST_PORTS
                : parsePorts(parsed.options.getOrDefault("ports", "1-1024"));

            int threads = Math.max(1, Integer.parseInt(parsed.options.getOrDefault("threads", "100")));
            int timeoutMs = (int) Math.max(50, Double.parseDouble(parsed.options.getOrDefault("timeout", "1")) * 1000.0);
            boolean banner = parsed.options.containsKey("banner");

            long started = System.currentTimeMillis();
            List<Map<String, Object>> hostReports = new ArrayList<>();
            for (String host : hosts) hostReports.add(scanHost(host, ports, threads, timeoutMs, banner));

            Map<String, Object> report = new LinkedHashMap<>();
            report.put("started_at", Instant.ofEpochMilli(started).toString());
            report.put("duration_sec", (System.currentTimeMillis() - started) / 1000.0);
            report.put("port_spec", (target.isEmpty() && !parsed.options.containsKey("hosts"))
                ? joinPorts(COMMON_LOCALHOST_PORTS)
                : parsed.options.getOrDefault("ports", "1-1024"));
            report.put("hosts", hostReports);

            printSummary(report);
            Path out = Paths.get(parsed.options.getOrDefault("output", "scan_report.json")).toAbsolutePath();
            Files.writeString(out, GSON.toJson(report), StandardCharsets.UTF_8);
            System.out.println("\nReport saved: " + out);
        } catch (Exception ex) {
            System.err.println("Error: " + ex.getMessage());
            System.exit(1);
        }
    }

    private static ParsedArgs parseArgs(String[] args) {
        Map<String, String> options = new LinkedHashMap<>();
        List<String> positional = new ArrayList<>();
        for (int i = 0; i < args.length; i++) {
            String t = args[i];
            if (t.startsWith("--")) {
                String key = t.substring(2);
                String value = "true";
                if (i + 1 < args.length && !args[i + 1].startsWith("--")) value = args[++i];
                options.put(key, value);
            } else positional.add(t);
        }
        return new ParsedArgs(options, positional);
    }

    private static List<Integer> parsePorts(String raw) {
        Set<Integer> out = new LinkedHashSet<>();
        for (String part : raw.split(",")) {
            String p = part.trim();
            if (p.isEmpty()) continue;
            if (p.contains("-")) {
                String[] ab = p.split("-", 2);
                int a = Integer.parseInt(ab[0]);
                int b = Integer.parseInt(ab[1]);
                if (a < 1 || b > 65535 || a > b) throw new IllegalArgumentException("Invalid port range: " + p);
                for (int x = a; x <= b; x++) out.add(x);
            } else {
                int n = Integer.parseInt(p);
                if (n < 1 || n > 65535) throw new IllegalArgumentException("Invalid port: " + p);
                out.add(n);
            }
        }
        return out.stream().sorted().toList();
    }

    private static List<String> parseHosts(String target, String hostsArg) {
        List<String> seeds = new ArrayList<>();
        if (target != null && !target.isBlank()) seeds.add(target);
        if (hostsArg != null && !hostsArg.isBlank()) seeds.addAll(Arrays.stream(hostsArg.split(",")).map(String::trim).filter(s -> !s.isBlank()).toList());
        if (seeds.isEmpty()) return List.of("127.0.0.1");

        Set<String> out = new LinkedHashSet<>();
        for (String s : seeds) {
            if (s.contains("/")) out.addAll(expandCidr(s));
            else out.add(s);
        }
        return new ArrayList<>(out);
    }

    private static List<String> expandCidr(String cidr) {
        String[] parts = cidr.split("/", 2);
        if (parts.length != 2) throw new IllegalArgumentException("Invalid CIDR: " + cidr);
        int prefix = Integer.parseInt(parts[1]);
        if (prefix < 0 || prefix > 32) throw new IllegalArgumentException("Invalid CIDR mask: " + cidr);
        long base = ipToLong(parts[0]);
        long mask = prefix == 0 ? 0L : (0xffffffffL << (32 - prefix)) & 0xffffffffL;
        long network = base & mask;
        long count = 1L << (32 - prefix);
        List<String> out = new ArrayList<>();
        for (long i = 0; i < count && out.size() < 4096; i++) {
            if (prefix <= 30 && (i == 0 || i == count - 1)) continue;
            out.add(longToIp((network + i) & 0xffffffffL));
        }
        return out;
    }

    private static long ipToLong(String ip) {
        String[] seg = ip.split("\\.");
        if (seg.length != 4) throw new IllegalArgumentException("Invalid IPv4: " + ip);
        long out = 0;
        for (String s : seg) {
            int n = Integer.parseInt(s);
            if (n < 0 || n > 255) throw new IllegalArgumentException("Invalid IPv4: " + ip);
            out = (out << 8) | n;
        }
        return out;
    }

    private static String longToIp(long v) {
        return ((v >>> 24) & 255) + "." + ((v >>> 16) & 255) + "." + ((v >>> 8) & 255) + "." + (v & 255);
    }

    private static Map<String, Object> scanHost(String host, List<Integer> ports, int threads, int timeoutMs, boolean banner) {
        String ip;
        try {
            ip = InetAddress.getByName(host).getHostAddress();
        } catch (Exception ex) {
            return Map.of("host", host, "resolved_ip", "", "error", "unresolvable host: " + ex.getMessage(), "results", List.of());
        }

        List<Map<String, Object>> rows = new ArrayList<>();
        ExecutorService pool = Executors.newFixedThreadPool(threads);
        try {
            List<Future<Map<String, Object>>> futures = new ArrayList<>();
            for (int port : ports) {
                futures.add(pool.submit(() -> scanPort(host, ip, port, timeoutMs, banner)));
            }
            for (Future<Map<String, Object>> f : futures) rows.add(f.get());
        } catch (Exception ex) {
            rows.add(Map.of("port", -1, "state", "filtered", "service", "unknown", "banner", "scan error: " + ex.getMessage()));
        } finally {
            pool.shutdown();
        }

        rows.sort(Comparator.comparingInt(r -> ((Number) r.get("port")).intValue()));
        return Map.of("host", host, "resolved_ip", ip, "results", rows);
    }

    private static Map<String, Object> scanPort(String host, String ip, int port, int timeoutMs, boolean bannerEnabled) {
        String state = "filtered";
        String banner = "";
        try (Socket socket = new Socket()) {
            socket.connect(new InetSocketAddress(ip, port), timeoutMs);
            state = "open";
            if (bannerEnabled) {
                socket.setSoTimeout(Math.min(1000, timeoutMs));
                try {
                    if (port == 80 || port == 8080 || port == 8000 || port == 443 || port == 8443) {
                        OutputStream out = socket.getOutputStream();
                        out.write("HEAD / HTTP/1.0\r\n\r\n".getBytes(StandardCharsets.UTF_8));
                        out.flush();
                    }
                    InputStream in = socket.getInputStream();
                    byte[] buf = new byte[200];
                    int n = in.read(buf);
                    if (n > 0) banner = normalizeBanner(new String(buf, 0, n, StandardCharsets.UTF_8));
                } catch (Exception ignored) {
                }
            }
        } catch (java.net.ConnectException ex) {
            state = "closed";
        } catch (java.net.SocketTimeoutException ex) {
            state = "filtered";
        } catch (Exception ex) {
            state = "filtered";
            banner = ex.getMessage() == null ? "" : ex.getMessage();
        }
        String service = SERVICES.getOrDefault(port, "unknown");
        System.out.println(host + ":" + port + " " + state + ("open".equals(state) ? " service=" + service : "") + (!banner.isBlank() ? " banner=\"" + banner + "\"" : ""));
        return Map.of("port", port, "state", state, "service", service, "banner", banner);
    }

    private static String normalizeBanner(String s) {
        return s.replaceAll("\\s+", " ").trim().substring(0, Math.min(160, s.replaceAll("\\s+", " ").trim().length()));
    }

    @SuppressWarnings("unchecked")
    private static void printSummary(Map<String, Object> report) {
        List<Map<String, Object>> hosts = (List<Map<String, Object>>) report.get("hosts");
        for (Map<String, Object> host : hosts) {
            System.out.println("\nHost: " + host.get("host") + " (" + host.get("resolved_ip") + ")");
            if (host.containsKey("error")) {
                System.out.println("Error: " + host.get("error"));
                continue;
            }
            System.out.println("Port | State | Service | Banner");
            System.out.println("---- | ----- | ------- | ------");
            List<Map<String, Object>> rows = (List<Map<String, Object>>) host.get("results");
            for (Map<String, Object> r : rows) {
                System.out.println(r.get("port") + " | " + r.get("state") + " | " + r.get("service") + " | " + r.get("banner"));
            }
        }
    }

    private static String joinPorts(List<Integer> ports) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < ports.size(); i++) {
            if (i > 0) sb.append(",");
            sb.append(ports.get(i));
        }
        return sb.toString();
    }
}