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