Building your own port scanner in Go ;-)
Overview
Building your own tools in Go offers a perfect balance of performance, simplicity, and scalability. Go’s efficient concurrency model with Goroutines enables high-speed parallel processing for demanding tasks. Its cross-platform portability allows creating standalone binaries with no external dependencies. The rich standard library and straightforward syntax make development quick and efficient. Ultimately, Go empowers you to craft highly reliable and customized solutions tailored to your exact needs.
This port scanner is designed to quickly scan TCP and UDP ports on a target IP address or hostname. It is highly efficient, leveraging Goroutines for concurrent scans and allowing users to specify a port range and timeout. It supports:
Code Documentation
Imports
import (
"flag" // Handles command-line flags
"fmt" // Used for formatted I/O
"net" // Provides networking functionality
"time" // Used for timeout handling
"sync" // For concurrency synchronization
)
Main Function
The main function:
Worker Functions
tcpWorker:
udpWorker:
Helper Functions
isTimeout:
displayHelp:
Enhanced Code
package main
import (
"flag"
"fmt"
"net"
"sync"
"time"
)
// PortStatus represents the state of a port
type PortStatus struct {
Port int
Protocol string
Status string
}
func main() {
// Define command-line flags
ip := flag.String("ip", "", "Target IP address or hostname")
startPort := flag.Int("start", 1, "Start of port range")
endPort := flag.Int("end", 65535, "End of port range")
timeout := flag.Int("timeout", 500, "Timeout in milliseconds")
workers := flag.Int("workers", 100, "Number of concurrent workers")
scanUDP := flag.Bool("udp", false, "Enable UDP scanning")
help := flag.Bool("help", false, "Display help")
// Parse flags
flag.Parse()
// Display help if no parameters are provided or if help is explicitly requested
if *help || flag.NFlag() == 0 {
displayHelp()
return
}
// Validate IP address or hostname
if *ip == "" {
fmt.Println("Error: Target IP address or hostname is required.")
displayHelp()
return
}
// Print scan summary
fmt.Printf("Starting scan on %s from port %d to %d with %d workers\n", ip, startPort, endPort, workers)
// Channels for ports and results
ports := make(chan int, *workers)
results := make(chan PortStatus)
// WaitGroup for workers
var wg sync.WaitGroup
// Start TCP workers
for i := 0; i < *workers; i++ {
wg.Add(1)
go tcpWorker(*ip, ports, results, &wg, time.Duration(*timeout)*time.Millisecond)
}
// Start UDP workers if enabled
if *scanUDP {
for i := 0; i < *workers; i++ {
wg.Add(1)
go udpWorker(*ip, ports, results, &wg, time.Duration(*timeout)*time.Millisecond)
}
}
// Goroutine to close results channel after all workers are done
go func() {
wg.Wait()
close(results)
}()
// Goroutine to send ports to the ports channel
go func() {
for port := startPort; port <= endPort; port++ {
ports <- port
}
close(ports)
}()
// Display results as they are received
for result := range results {
fmt.Printf("Port %d (%s): %s\n", result.Port, result.Protocol, result.Status)
}
fmt.Println("Scan complete!")
}
// TCP worker scans ports using TCP
func tcpWorker(ip string, ports chan int, results chan PortStatus, wg *sync.WaitGroup, timeout time.Duration) {
defer wg.Done()
for port := range ports {
address := fmt.Sprintf("%s:%d", ip, port)
conn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
if isTimeout(err) {
results <- PortStatus{Port: port, Protocol: "TCP", Status: "filtered"}
} else {
results <- PortStatus{Port: port, Protocol: "TCP", Status: "closed"}
}
continue
}
conn.Close()
results <- PortStatus{Port: port, Protocol: "TCP", Status: "open"}
}
}
// UDP worker scans ports using UDP
func udpWorker(ip string, ports chan int, results chan PortStatus, wg *sync.WaitGroup, timeout time.Duration) {
defer wg.Done()
for port := range ports {
address := fmt.Sprintf("%s:%d", ip, port)
conn, err := net.DialTimeout("udp", address, timeout)
if err != nil {
results <- PortStatus{Port: port, Protocol: "UDP", Status: "closed"}
continue
}
conn.Close()
results <- PortStatus{Port: port, Protocol: "UDP", Status: "open"}
}
}
// Helper to check if an error is a timeout
func isTimeout(err error) bool {
netErr, ok := err.(net.Error)
return ok && netErr.Timeout()
}
// Display help message
func displayHelp() {
fmt.Println("\n== Go Port Scanner ==")
fmt.Println("Usage: portscanner [options]")
fmt.Println("Options:")
fmt.Println(" -ip <target_ip> Target IP address or hostname (required)")
fmt.Println(" -start <port> Start of port range (default: 1)")
fmt.Println(" -end <port> End of port range (default: 65535)")
fmt.Println(" -timeout <ms> Timeout in milliseconds (default: 500)")
fmt.Println(" -workers <count> Number of concurrent workers (default: 100)")
fmt.Println(" -udp Enable UDP scanning")
fmt.Println(" -help Display this help message")
fmt.Println("\nExample:")
fmt.Println(" go run portscanner.go -ip 192.168.1.1 -start 20 -end 80 -timeout 200 -workers 50 -udp")
}
How the code works
Help System:
Worker Design:
Concurrency:
Timeouts and Errors:
领英推荐
How to use
Display Help:
go run portscanner.go -help
Scan TCP Ports:
go run portscanner.go -ip <target_ip> -start 20 -end 80 -timeout 200 -workers 50
Scan Both TCP and UDP:
go run portscanner.go -ip <target_ip> -start 20 -end 80 -timeout 200 -workers 50 -udp
Key factors contributing to performance
Concurrency with Goroutines:
The use of workers allows multiple ports to be scanned simultaneously.
You can adjust the number of workers (default: 100) to optimize for hardware and network capabilities.
Efficient Network Dialing:
Using net.DialTimeout ensures that the scanner doesn't wait indefinitely on unresponsive ports, improving overall speed.
Minimal Overhead:
The tool avoids unnecessary computations, focusing only on sending requests and processing responses.
Recommendations for further optimization or testing
Increase Worker Count:
You can experiment with higher worker counts for even faster scans if your system can handle it:
go run portscanner.go -ip <target_ip> -start 1 -end 65535 -workers 500
Test on Remote Hosts:
Scanning a remote host over the network introduces latency. Measure the time for such scenarios:
time go run portscanner.go -ip <target_ip> -start 1 -end 65535
Enable UDP Scanning:
UDP scans are inherently slower since they require additional steps (sending packets and waiting for replies). Include the -udp flag:
time go run portscanner.go -ip <target_ip> -start 1 -end 65535 -udp
Adjust Timeout:
A shorter timeout can speed up scans, but it may lead to false negatives for slower networks. Try:
go run portscanner.go -ip <target_ip> -start 1 -end 65535 -timeout 100
Save Results to a File:
Extend the program to save scan results to a file for large scans. For example:
./portscanner -ip <target_ip> -start 1 -end 65535 > scan_results.txt
Hoping you liked this tutorial. If you find any mistakes or have suggestions to improve this tool, please don’t hesitate to let me know, drop me an email to [email protected] —I’m always open to feedback! ??