-
Notifications
You must be signed in to change notification settings - Fork 132
/
Copy pathping_windows.go
144 lines (133 loc) · 3.14 KB
/
ping_windows.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
package tun
import (
"context"
"errors"
"fmt"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/pipe"
"golang.org/x/sys/windows"
"sync"
"time"
"unsafe"
)
type wPinger struct {
options PingOptions
handle windows.Handle
recvChan chan *buf.Buffer
errChan chan error
readDeadline pipe.Deadline
readDeadlineTime atomic.TypedValue[time.Time]
closeOnce sync.Once
}
func NewPinger(options PingOptions) (Pinger, error) {
var (
handle windows.Handle
err error
)
if options.Destination.Is4() {
handle, err = icmpCreateFile()
} else {
handle, err = icmp6CreateFile()
}
if err != nil {
return nil, err
}
return &wPinger{
options: options,
handle: handle,
readDeadline: pipe.MakeDeadline(),
recvChan: make(chan *buf.Buffer, 1),
errChan: make(chan error, 1),
}, nil
}
func (w *wPinger) WritePacket(packet *buf.Buffer) error {
event, err := windows.CreateEvent(nil, 1, 0, nil)
if err != nil {
return err
}
defer windows.CloseHandle(event)
lAddr := w.options.Source.As4()
rAddr := w.options.Destination.As4()
response := buf.NewPacket()
options := IPOptionInformation{
Ttl: w.options.TTL,
Tos: w.options.TOS,
}
err = icmpSendEcho2Ex(w.handle, event, 0, 0, &lAddr, &rAddr, packet.Bytes(), &options, response.FreeBytes(), 0)
if !errors.Is(err, windows.ERROR_IO_PENDING) {
return err
}
var timeout uint32
if deadline := w.readDeadlineTime.Load(); !deadline.IsZero() {
timeout = uint32(time.Until(deadline).Milliseconds())
} else {
timeout = windows.INFINITE
}
go func() {
eventCode, err := windows.WaitForSingleObject(event, timeout)
if err == nil && eventCode == windows.WAIT_OBJECT_0 {
w.parseReplies(response)
} else {
if err == nil {
err = E.New("wait failed, code: ", fmt.Sprintf("%x", eventCode))
}
packet.Release()
select {
case w.errChan <- err:
default:
}
}
}()
return nil
}
func (w *wPinger) parseReplies(buffer *buf.Buffer) {
replyCount, err := icmpParseReplies(buffer.FreeBytes())
if err != nil {
buffer.Release()
w.errChan <- err
return
}
replies := unsafe.Slice((*IcmpEchoReply)(unsafe.Pointer(&buffer.Index(0)[0])), replyCount)
for _, reply := range replies {
}
}
func (w *wPinger) ReadPacket() (*buf.Buffer, error) {
select {
case packet := <-w.recvChan:
return packet, nil
case <-w.readDeadline.Wait():
return nil, context.DeadlineExceeded
case err := <-w.errChan:
return nil, err
}
}
func (w *wPinger) SetReadDeadline(deadline time.Time) error {
w.readDeadline.Set(deadline)
w.readDeadlineTime.Store(deadline)
return nil
}
func (w *wPinger) Close() error {
var err error
w.closeOnce.Do(func() {
err = windows.CloseHandle(w.handle)
})
return err
}
type IPOptionInformation struct {
Ttl byte
Tos byte
Flags byte
OptionsSize byte
OptionsData *byte
}
type IcmpEchoReply struct {
Address [4]byte
Status uint32
RoundTripTime uint32
DataSize uint16
Reserved uint16
Data unsafe.Pointer
Options IPOptionInformation
}