summaryrefslogtreecommitdiff
path: root/extendedstats/windows.go
blob: f7ce5801141ffc4ffd2980ca0aa449ea2dc4739f (plain)
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
}