github.com/network-quality/goresponsiveness@v0.0.0-20240129151524-343954285090/extendedstats/windows.go (about) 1 //go:build windows 2 // +build windows 3 4 /* 5 * This file is part of Go Responsiveness. 6 * 7 * Go Responsiveness is free software: you can redistribute it and/or modify it under 8 * the terms of the GNU General Public License as published by the Free Software Foundation, 9 * either version 2 of the License, or (at your option) any later version. 10 * Go Responsiveness is distributed in the hope that it will be useful, but WITHOUT ANY 11 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 12 * PARTICULAR PURPOSE. See the GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License along 15 * with Go Responsiveness. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18 package extendedstats 19 20 import ( 21 "crypto/tls" 22 "errors" 23 "fmt" 24 "net" 25 "unsafe" 26 27 "github.com/network-quality/goresponsiveness/utilities" 28 "golang.org/x/sys/windows" 29 ) 30 31 type AggregateExtendedStats struct { 32 MaxMss uint64 33 TotalBytesSent uint64 34 TotalBytesReceived uint64 35 TotalBytesReordered uint64 36 TotalBytesRetransmitted uint64 37 38 RetransmitRatio float64 39 AverageRtt float64 40 rtt_measurements uint64 41 total_rtt float64 42 } 43 44 type TCPINFO_BASE struct { 45 State uint32 46 Mss uint32 47 ConnectionTimeMs uint64 48 TimestampsEnabled bool 49 RttUs uint32 50 MinRttUs uint32 51 BytesInFlight uint32 52 Cwnd uint32 53 SndWnd uint32 54 RcvWnd uint32 55 RcvBuf uint32 56 BytesOut uint64 57 BytesIn uint64 58 BytesReordered uint32 59 BytesRetrans uint32 60 FastRetrans uint32 61 DupAcksIn uint32 62 TimeoutEpisodes uint32 63 SynRetrans byte // UCHAR 64 } 65 66 // https://github.com/tpn/winsdk-10/blob/9b69fd26ac0c7d0b83d378dba01080e93349c2ed/Include/10.0.16299.0/shared/mstcpip.h#L289 67 type TCPINFO_V0 struct { 68 TCPINFO_BASE 69 } 70 71 // https://docs.microsoft.com/en-us/windows/win32/api/mstcpip/ns-mstcpip-tcp_info_v1 72 type TCPINFO_V1 struct { 73 TCPINFO_BASE 74 SndLimTransRwin uint32 75 SndLimTimeRwin uint32 76 SndLimBytesRwin uint64 77 SndLimTransCwnd uint32 78 SndLimTimeCwnd uint32 79 SndLimBytesCwnd uint64 80 SndLimTransSnd uint32 81 SndLimTimeSnd uint32 82 SndLimBytesSnd uint64 83 } 84 85 // https://pkg.go.dev/golang.org/x/sys/unix#TCPInfo 86 // Used to allow access to TCPInfo in like manner to unix. 87 type TCPInfo struct { 88 State uint8 89 Ca_state uint8 90 Retransmits uint8 91 Probes uint8 92 Backoff uint8 93 Options uint8 94 Rto uint32 95 Ato uint32 96 Snd_mss uint32 97 Rcv_mss uint32 98 Unacked uint32 99 Sacked uint32 100 Lost uint32 101 Retrans uint32 102 Fackets uint32 103 Last_data_sent uint32 104 Last_ack_sent uint32 105 Last_data_recv uint32 106 Last_ack_recv uint32 107 Pmtu uint32 108 Rcv_ssthresh uint32 109 Rtt uint32 110 Rttvar uint32 111 Snd_ssthresh uint32 112 Snd_cwnd uint32 113 Advmss uint32 114 Reordering uint32 115 Rcv_rtt uint32 116 Rcv_space uint32 117 Total_retrans uint32 118 } 119 120 func ExtendedStatsAvailable() bool { 121 return true 122 } 123 124 func (es *AggregateExtendedStats) IncorporateConnectionStats(basicConn net.Conn) error { 125 if info, err := getTCPInfoRaw(basicConn); err != nil { 126 return fmt.Errorf("OOPS: Could not get the TCP info for the connection: %v", err) 127 } else { 128 es.MaxMss = utilities.Max(es.MaxMss, uint64(info.Mss)) 129 es.TotalBytesReordered += uint64(info.BytesReordered) 130 es.TotalBytesRetransmitted += uint64(info.BytesRetrans) 131 es.TotalBytesSent += info.BytesOut 132 es.TotalBytesReceived += info.BytesIn 133 134 es.total_rtt += float64(info.RttUs) 135 es.rtt_measurements += 1 136 es.AverageRtt = es.total_rtt / float64(es.rtt_measurements) 137 es.RetransmitRatio = (float64(es.TotalBytesRetransmitted) / float64(es.TotalBytesSent)) * 100.0 138 } 139 return nil 140 } 141 142 func (es *AggregateExtendedStats) Repr() string { 143 return fmt.Sprintf(`Extended Statistics: 144 Maximum Segment Size: %v 145 Total Bytes Retransmitted: %v 146 Retransmission Ratio: %.2f%% 147 Total Bytes Reordered: %v 148 Average RTT: %v 149 `, es.MaxMss, es.TotalBytesRetransmitted, es.RetransmitRatio, es.TotalBytesReordered, es.AverageRtt) 150 } 151 152 func getTCPInfoRaw(basicConn net.Conn) (*TCPINFO_V1, error) { 153 tlsConn, ok := basicConn.(*tls.Conn) 154 if !ok { 155 return nil, fmt.Errorf("OOPS: Outermost connection is not a TLS connection") 156 } 157 tcpConn, ok := tlsConn.NetConn().(*net.TCPConn) 158 if !ok { 159 return nil, fmt.Errorf( 160 "OOPS: Could not get the TCP info for the connection (not a TCP connection)", 161 ) 162 } 163 rawConn, err := tcpConn.SyscallConn() 164 if err != nil { 165 return nil, err 166 } 167 168 // SIO_TCP_INFO 169 // https://docs.microsoft.com/en-us/windows/win32/winsock/sio-tcp-info 170 // https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/shared/mstcpip.h 171 iocc := uint32(windows.IOC_INOUT | windows.IOC_VENDOR | 39) 172 173 // Should be a DWORD, 0 for version 0, 1 for version 1 tcp_info: 174 // 0: https://docs.microsoft.com/en-us/windows/win32/api/mstcpip/ns-mstcpip-tcp_info_v0 175 // 1: https://docs.microsoft.com/en-us/windows/win32/api/mstcpip/ns-mstcpip-tcp_info_v1 176 inbuf := uint32(1) 177 178 // Size of the inbuf variable 179 cbif := uint32(4) 180 181 outbuf := TCPINFO_V1{} 182 183 cbob := uint32(unsafe.Sizeof(outbuf)) // Size = 136 for V1 and 88 for V0 184 185 // Size pointer of return object 186 cbbr := uint32(0) 187 188 overlapped := windows.Overlapped{} 189 190 eventHandle, err := windows.CreateEvent(nil, 0, 0, nil) 191 192 if err != nil { 193 return nil, fmt.Errorf("OOPS: CreateEvent failed during extended stats capture: %v", err) 194 } 195 overlapped.HEvent = eventHandle 196 overlapped.HEvent = (windows.Handle)((uintptr)(eventHandle)) | 0x1 197 198 completionRoutine := uintptr(0) 199 200 rawConn.Control(func(fd uintptr) { 201 err = windows.WSAIoctl( 202 windows.Handle(fd), 203 iocc, 204 (*byte)(unsafe.Pointer(&inbuf)), 205 cbif, 206 (*byte)(unsafe.Pointer(&outbuf)), 207 cbob, 208 &cbbr, 209 &overlapped, 210 completionRoutine, 211 ) 212 }) 213 214 if err != nil { 215 /* An error might just indicate that the result is not immediately available. 216 * If that is the case, we will wait until it is. 217 */ 218 if errors.Is(err, windows.ERROR_IO_PENDING /*AKA, WSA_IO_PENDING*/) { 219 _, err = windows.WaitForSingleObject(overlapped.HEvent, windows.INFINITE) 220 if err != nil { 221 return nil, fmt.Errorf("OOPS: WaitForSingleObject failed during extended stats capture: %v", err) 222 } 223 rawConn.Control(func(fd uintptr) { 224 err = windows.GetOverlappedResult( 225 windows.Handle(fd), 226 &overlapped, 227 &cbbr, 228 false, 229 ) 230 }) 231 if err != nil { 232 return nil, fmt.Errorf("OOPS: GetOverlappedResult failed during extended stats capture: %v", err) 233 } 234 } else { 235 return nil, fmt.Errorf("OOPS: WSAIoctl failed: %v", err) 236 } 237 } 238 239 windows.CloseHandle(overlapped.HEvent) 240 241 if cbbr != cbob { 242 return nil, fmt.Errorf("WSAIoctl did not get valid information about the TCP connection") 243 } 244 245 return &outbuf, err 246 } 247 248 func GetTCPInfo(connection net.Conn) (*TCPInfo, error) { 249 info, err := getTCPInfoRaw(connection) 250 if err != nil { 251 return nil, err 252 } 253 // Uncertain on all the statistic correlation so only transferring the needed 254 return &TCPInfo{ 255 Rtt: info.RttUs, 256 Snd_cwnd: info.Cwnd, 257 }, err 258 }