-
Notifications
You must be signed in to change notification settings - Fork 0
/
portscan.go
147 lines (132 loc) · 3.35 KB
/
portscan.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// +build windows linux
package main
import (
"fmt"
"log"
"net"
"strconv"
"strings"
"time"
)
// MAXWORKERS Need worker pool because running 1 goroutine per port exhausts file descriptors
const MAXWORKERS = 100
//PortRange is the range of ports
type PortRange struct {
Start uint64
End uint64
}
func (pr *PortRange) String() string {
return fmt.Sprintf("[%v,%v)", pr.Start, pr.End)
}
// Run the port scanner
func scan(host string, PortRange string, debug bool) string {
if host == "" || PortRange == "" {
return "Usage: scan <host> port|start-end,[port|start-end ...] [-debug]"
}
prs, err := parseRanges(PortRange)
if err != nil {
log.Fatal(err)
}
// Format results
report := ""
for _, pr := range prs {
results := ScanPorts(host, pr)
for port, success := range results {
if success || debug {
report += fmt.Sprintf("%v: %v\n", port, success)
}
}
}
return report
}
// Parse port ranges spec
func parseRanges(RangesStr string) ([]*PortRange, error) {
parts := strings.Split(RangesStr, ",")
ranges := make([]*PortRange, 0)
for _, part := range parts {
rg, err := parseRange(part)
if err != nil {
return nil, err
}
ranges = append(ranges, rg)
}
return ranges, nil
}
//TODO: check overflow
func parseRange(RangeStr string) (*PortRange, error) {
parts := strings.SplitN(RangeStr, "-", 2)
nums := make([]uint64, len(parts))
for i, v := range parts {
n, err := strconv.ParseUint(v, 10, 16)
if err != nil {
return nil, err
}
nums[i] = n
}
switch len(nums) {
case 1:
return &PortRange{
Start: nums[0],
End: nums[0] + 1,
}, nil
case 2:
return &PortRange{
Start: nums[0],
End: nums[1],
}, nil
default:
return nil, fmt.Errorf("Invalid Port Specification")
}
}
// ScanResult is the container for scan results from workers
type ScanResult struct {
Port uint64
Success bool
Err error
}
// ScanPorts runs the scan with a worker pool; memory usage grows in proportion
// with number of ports scanned to prevent deadlock from blocking channels
func ScanPorts(host string, pr *PortRange) map[uint64]bool {
numPorts := pr.End - pr.Start + 1
results := make(map[uint64]bool)
jobpipe := make(chan uint64, numPorts)
respipe := make(chan *ScanResult, numPorts)
// Start workers
for worker := 0; worker < MAXWORKERS; worker++ {
go scanWorker(host, jobpipe, respipe)
}
// Seed w/ jobs
for port := pr.Start; port < pr.End+1; port++ {
jobpipe <- port
}
// Receive results
received := uint64(0)
for received < pr.End-pr.Start {
res := <-respipe
results[res.Port] = res.Success
received++
}
return results
}
// Worker function; pull from job queue forever and return results on result
// queue
func scanWorker(host string, jobpipe chan uint64, respipe chan *ScanResult) {
for job := <-jobpipe; ; job = <-jobpipe {
respipe <- scanPort(host, job)
}
}
// Simple scan of a single port
// - Just tries to connect to <host>:<port> over TCP and checks for error
func scanPort(host string, port uint64) *ScanResult {
dialer := net.Dialer{Timeout: 2 * time.Second}
conn, err := dialer.Dial("tcp", fmt.Sprintf("%v:%v", host, port))
result := ScanResult{
Port: port,
Success: err == nil,
Err: err,
}
if conn != nil {
conn.Close()
}
return &result
}