Concurrent TCP Port Scanner with Timeout Control
Concurrent TCP port scanner that sweeps ranges with per-connection timeouts and reports open ports quickly.
main.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
package main
import (
"fmt"
"net"
"sync"
"time"
)
// PortScanResult contains the result of a single port scan
type PortScanResult struct {
Port int
Open bool
Error error
Latency time.Duration
}
// PortScanner performs concurrent TCP port scanning
type PortScanner struct {
target string
ports []int
timeout time.Duration
concurrency int
results []PortScanResult
mu sync.Mutex
wg sync.WaitGroup
}
// NewPortScanner creates a new port scanner for the target
func NewPortScanner(target string, ports []int, timeout time.Duration, concurrency int) *PortScanner {
return &PortScanner{
target: target,
ports: ports,
timeout: timeout,
concurrency: concurrency,
}
}
// NewPortScannerFromRange creates a scanner for a port range (start to end inclusive)
func NewPortScannerFromRange(target string, start, end int, timeout time.Duration, concurrency int) *PortScanner {
ports := make([]int, 0, end-start+1)
for port := start; port <= end; port++ {
ports = append(ports, port)
}
return NewPortScanner(target, ports, timeout, concurrency)
}
// Scan performs the concurrent port scan
func (ps *PortScanner) Scan() []PortScanResult {
// Create a semaphore to control concurrency
sem := make(chan struct{}, ps.concurrency)
// Add all ports to the wait group
ps.wg.Add(len(ps.ports))
// Scan each port concurrently
for _, port := range ps.ports {
sem <- struct{}{} // Acquire semaphore
go func(p int) {
defer ps.wg.Done()
defer func() { <-sem }() // Release semaphore
result := ps.scanSinglePort(p)
// Safely add result to the slice
ps.mu.Lock()
ps.results = append(ps.results, result)
ps.mu.Unlock()
}(port)
}
// Wait for all scans to complete
ps.wg.Wait()
return ps.results
}
// scanSinglePort scans a single TCP port
func (ps *PortScanner) scanSinglePort(port int) PortScanResult {
result := PortScanResult{Port: port}
address := fmt.Sprintf("%s:%d", ps.target, port)
startTime := time.Now()
// Attempt TCP connection
conn, err := net.DialTimeout("tcp", address, ps.timeout)
if err != nil {
result.Open = false
result.Error = err
result.Latency = time.Since(startTime)
return result
}
// Connection successful - port is open
result.Open = true
result.Latency = time.Since(startTime)
// Close the connection
if err := conn.Close(); err != nil {
result.Error = fmt.Errorf("failed to close connection: %w", err)
}
return result
}
func main() {
// Configure scanner: scan localhost ports 1-1000, 50 concurrent goroutines, 1s timeout
scanner := NewPortScannerFromRange("localhost", 1, 1000, 1*time.Second, 50)
fmt.Printf("Scanning %s ports 1-1000...\n", scanner.target)
startTime := time.Now()
// Run scan
results := scanner.Scan()
// Calculate total scan time
totalTime := time.Since(startTime)
// Print open ports
fmt.Println("\nOpen Ports:")
fmt.Println("-----------")
openCount := 0
for _, res := range results {
if res.Open {
openCount++
fmt.Printf("Port %d: OPEN (Latency: %v)\n", res.Port, res.Latency)
}
}
if openCount == 0 {
fmt.Println("No open ports found")
}
fmt.Printf("\nScan completed in %v. Total ports scanned: %d. Open ports: %d\n", totalTime, len(results), openCount)
}How It Works
Concurrent TCP port scanner that sweeps ranges with per-connection timeouts and reports open ports quickly.
Creates a worker pool, dials target host and port combinations with deadlines, collects successes via channels, and prints progress until all ports are scanned.
Key Concepts
- 1Bounded goroutines prevent overwhelming the operating system.
- 2Dial timeouts keep slow or filtered ports from blocking the scan.
- 3Results are aggregated and sorted for readability.
When to Use This Pattern
- Operations or security checks for exposed services.
- CI smoke tests to verify ports are listening.
- Learning concurrency patterns with networking.
Best Practices
- Respect legal and ethical scanning rules and target ownership.
- Tune worker counts based on system limits and network latency.
- Handle rate limits or firewalls gracefully.
Go Version1.18
Difficultyintermediate
Production ReadyYes
Lines of Code135