summaryrefslogtreecommitdiff
path: root/extendedstats/windows.go
blob: c1c80f8e6ad25aaf4111e84300497f1bc339d022 (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
//go:build windows
// +build windows

package extendedstats

import (
	"crypto/tls"
	"fmt"
	"net"
	"unsafe"

	"github.com/network-quality/goresponsiveness/utilities"
	"golang.org/x/sys/windows"
)

type ExtendedStats 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
}

func (es *ExtendedStats) IncorporateConnectionStats(rawConn net.Conn) error {
	tlsConn, ok := rawConn.(*tls.Conn)
	if !ok {
		return fmt.Errorf(
			"OOPS: Could not get the TCP info for the connection (not a TLS connection)",
		)
	}
	tcpConn, ok := tlsConn.NetConn().(*net.TCPConn)
	if !ok {
		return fmt.Errorf(
			"OOPS: Could not get the TCP info for the connection (not a TCP connection)",
		)
	}
	if info, err := getTCPInfo(tcpConn); 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 *ExtendedStats) 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 ExtendedStatsAvailable() bool {
	return true
}

func getTCPInfo(connection net.Conn) (*TCPINFO_V1, error) {
	tcpConn, ok := connection.(*net.TCPConn)
	if !ok {
		return nil, fmt.Errorf("connection is not a net.TCPConn")
	}
	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{}

	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,
		)
	})
	return &outbuf, err
}