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  }