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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
|
//go:build windows
// +build windows
/*
* This file is part of Go Responsiveness.
*
* Go Responsiveness is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software Foundation,
* either version 2 of the License, or (at your option) any later version.
* Go Responsiveness is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with Go Responsiveness. If not, see <https://www.gnu.org/licenses/>.
*/
package extendedstats
import (
"crypto/tls"
"errors"
"fmt"
"net"
"unsafe"
"github.com/network-quality/goresponsiveness/utilities"
"golang.org/x/sys/windows"
)
type AggregateExtendedStats struct {
MaxMss uint64
TotalBytesSent uint64
TotalBytesReceived uint64
TotalBytesReordered uint64
TotalBytesRetransmitted uint64
RetransmitRatio float64
AverageRtt float64
rtt_measurements uint64
total_rtt float64
}
type TCPINFO_BASE struct {
State uint32
Mss uint32
ConnectionTimeMs uint64
TimestampsEnabled bool
RttUs uint32
MinRttUs uint32
BytesInFlight uint32
Cwnd uint32
SndWnd uint32
RcvWnd uint32
RcvBuf uint32
BytesOut uint64
BytesIn uint64
BytesReordered uint32
BytesRetrans uint32
FastRetrans uint32
DupAcksIn uint32
TimeoutEpisodes uint32
SynRetrans byte // UCHAR
}
// https://github.com/tpn/winsdk-10/blob/9b69fd26ac0c7d0b83d378dba01080e93349c2ed/Include/10.0.16299.0/shared/mstcpip.h#L289
type TCPINFO_V0 struct {
TCPINFO_BASE
}
// https://docs.microsoft.com/en-us/windows/win32/api/mstcpip/ns-mstcpip-tcp_info_v1
type TCPINFO_V1 struct {
TCPINFO_BASE
SndLimTransRwin uint32
SndLimTimeRwin uint32
SndLimBytesRwin uint64
SndLimTransCwnd uint32
SndLimTimeCwnd uint32
SndLimBytesCwnd uint64
SndLimTransSnd uint32
SndLimTimeSnd uint32
SndLimBytesSnd uint64
}
// https://pkg.go.dev/golang.org/x/sys/unix#TCPInfo
// Used to allow access to TCPInfo in like manner to unix.
type TCPInfo struct {
State uint8
Ca_state uint8
Retransmits uint8
Probes uint8
Backoff uint8
Options uint8
Rto uint32
Ato uint32
Snd_mss uint32
Rcv_mss uint32
Unacked uint32
Sacked uint32
Lost uint32
Retrans uint32
Fackets uint32
Last_data_sent uint32
Last_ack_sent uint32
Last_data_recv uint32
Last_ack_recv uint32
Pmtu uint32
Rcv_ssthresh uint32
Rtt uint32
Rttvar uint32
Snd_ssthresh uint32
Snd_cwnd uint32
Advmss uint32
Reordering uint32
Rcv_rtt uint32
Rcv_space uint32
Total_retrans uint32
}
func ExtendedStatsAvailable() bool {
return true
}
func (es *AggregateExtendedStats) IncorporateConnectionStats(basicConn net.Conn) error {
if info, err := getTCPInfoRaw(basicConn); err != nil {
return fmt.Errorf("OOPS: Could not get the TCP info for the connection: %v", err)
} else {
es.MaxMss = utilities.Max(es.MaxMss, uint64(info.Mss))
es.TotalBytesReordered += uint64(info.BytesReordered)
es.TotalBytesRetransmitted += uint64(info.BytesRetrans)
es.TotalBytesSent += info.BytesOut
es.TotalBytesReceived += info.BytesIn
es.total_rtt += float64(info.RttUs)
es.rtt_measurements += 1
es.AverageRtt = es.total_rtt / float64(es.rtt_measurements)
es.RetransmitRatio = (float64(es.TotalBytesRetransmitted) / float64(es.TotalBytesSent)) * 100.0
}
return nil
}
func (es *AggregateExtendedStats) Repr() string {
return fmt.Sprintf(`Extended Statistics:
Maximum Segment Size: %v
Total Bytes Retransmitted: %v
Retransmission Ratio: %.2f%%
Total Bytes Reordered: %v
Average RTT: %v
`, es.MaxMss, es.TotalBytesRetransmitted, es.RetransmitRatio, es.TotalBytesReordered, es.AverageRtt)
}
func getTCPInfoRaw(basicConn net.Conn) (*TCPINFO_V1, error) {
tlsConn, ok := basicConn.(*tls.Conn)
if !ok {
return nil, fmt.Errorf("OOPS: Outermost connection is not a TLS connection")
}
tcpConn, ok := tlsConn.NetConn().(*net.TCPConn)
if !ok {
return nil, fmt.Errorf(
"OOPS: Could not get the TCP info for the connection (not a TCP connection)",
)
}
rawConn, err := tcpConn.SyscallConn()
if err != nil {
return nil, err
}
// SIO_TCP_INFO
// https://docs.microsoft.com/en-us/windows/win32/winsock/sio-tcp-info
// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/shared/mstcpip.h
iocc := uint32(windows.IOC_INOUT | windows.IOC_VENDOR | 39)
// Should be a DWORD, 0 for version 0, 1 for version 1 tcp_info:
// 0: https://docs.microsoft.com/en-us/windows/win32/api/mstcpip/ns-mstcpip-tcp_info_v0
// 1: https://docs.microsoft.com/en-us/windows/win32/api/mstcpip/ns-mstcpip-tcp_info_v1
inbuf := uint32(1)
// Size of the inbuf variable
cbif := uint32(4)
outbuf := TCPINFO_V1{}
cbob := uint32(unsafe.Sizeof(outbuf)) // Size = 136 for V1 and 88 for V0
// Size pointer of return object
cbbr := uint32(0)
overlapped := windows.Overlapped{}
eventHandle, err := windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
return nil, fmt.Errorf("OOPS: CreateEvent failed during extended stats capture: %v", err)
}
overlapped.HEvent = eventHandle
overlapped.HEvent = (windows.Handle)((uintptr)(eventHandle)) | 0x1
completionRoutine := uintptr(0)
rawConn.Control(func(fd uintptr) {
err = windows.WSAIoctl(
windows.Handle(fd),
iocc,
(*byte)(unsafe.Pointer(&inbuf)),
cbif,
(*byte)(unsafe.Pointer(&outbuf)),
cbob,
&cbbr,
&overlapped,
completionRoutine,
)
})
if err != nil {
/* An error might just indicate that the result is not immediately available.
* If that is the case, we will wait until it is.
*/
if errors.Is(err, windows.ERROR_IO_PENDING /*AKA, WSA_IO_PENDING*/) {
_, err = windows.WaitForSingleObject(overlapped.HEvent, windows.INFINITE)
if err != nil {
return nil, fmt.Errorf("OOPS: WaitForSingleObject failed during extended stats capture: %v", err)
}
rawConn.Control(func(fd uintptr) {
err = windows.GetOverlappedResult(
windows.Handle(fd),
&overlapped,
&cbbr,
false,
)
})
if err != nil {
return nil, fmt.Errorf("OOPS: GetOverlappedResult failed during extended stats capture: %v", err)
}
} else {
return nil, fmt.Errorf("OOPS: WSAIoctl failed: %v", err)
}
}
windows.CloseHandle(overlapped.HEvent)
if cbbr != cbob {
return nil, fmt.Errorf("WSAIoctl did not get valid information about the TCP connection")
}
return &outbuf, err
}
func GetTCPInfo(connection net.Conn) (*TCPInfo, error) {
info, err := getTCPInfoRaw(connection)
if err != nil {
return nil, err
}
// Uncertain on all the statistic correlation so only transferring the needed
return &TCPInfo{
Rtt: info.RttUs,
Snd_cwnd: info.Cwnd,
}, err
}
|