-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprotocol_linux.go
More file actions
133 lines (115 loc) · 2.8 KB
/
Copy pathprotocol_linux.go
File metadata and controls
133 lines (115 loc) · 2.8 KB
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
//go:build linux
package gomap
import (
"context"
"fmt"
"net"
"sync"
"time"
)
// IPProtocolScan sends raw IP packets for each protocol number (0-255)
// and determines which IP protocols are supported by the target.
// Matches nmap's -sO scan type.
func IPProtocolScan(ctx context.Context, host string, opts ScanOptions) ([]ProtocolResult, error) {
opts.defaults()
laddr, err := GetLocalAddr(host)
if err != nil {
return nil, fmt.Errorf("getting local address: %w", err)
}
if !canSocketBind(laddr) {
return nil, fmt.Errorf("IP protocol scan requires raw socket privileges")
}
protocols := defaultProtocols()
if len(opts.Ports) > 0 {
protocols = opts.Ports
}
type job struct {
proto int
}
in := make(chan job, len(protocols))
resultCh := make(chan ProtocolResult, len(protocols))
go func() {
for _, p := range protocols {
select {
case in <- job{proto: p}:
case <-ctx.Done():
close(in)
return
}
}
close(in)
}()
workers := opts.Workers
if workers > len(protocols) {
workers = len(protocols)
}
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := range in {
if ctx.Err() != nil {
return
}
result := probeProtocol(ctx, laddr, host, j.proto, opts.Timeout)
if opts.TraceWriter != nil {
tr := newTracer(opts.TraceWriter)
tr.tracePacket(PacketSent, fmt.Sprintf("IP-PROTO-%d", j.proto), laddr, 0, host, 0, "")
}
resultCh <- result
}
}()
}
go func() {
wg.Wait()
close(resultCh)
}()
var results []ProtocolResult
for r := range resultCh {
results = append(results, r)
}
return results, nil
}
func probeProtocol(ctx context.Context, laddr, host string, proto int, timeout time.Duration) ProtocolResult {
result := ProtocolResult{
Protocol: proto,
Name: lookupProtocolName(proto),
}
// Send a minimal IP packet with the given protocol number
network := fmt.Sprintf("ip4:%d", proto)
conn, err := net.DialTimeout(network, host, timeout)
if err != nil {
result.State = PortFiltered
result.Reason = "no-response"
return result
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(timeout))
// Send a single byte
_, err = conn.Write([]byte{0})
if err != nil {
result.State = PortFiltered
result.Reason = "no-response"
return result
}
// Try to read — any response means protocol is supported
buf := make([]byte, readBufferSize)
_, err = conn.Read(buf)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
result.State = PortOpenFiltered
result.Open = true
result.Reason = "no-response"
return result
}
// ICMP protocol unreachable = closed
result.State = PortClosed
result.Reason = "proto-unreach"
return result
}
result.State = PortOpen
result.Open = true
result.Reason = "proto-response"
return result
}