diff options
| author | Will Hawkins <[email protected]> | 2022-06-22 13:29:11 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-06-22 13:29:11 -0400 |
| commit | 8595ff279dc46e3f974661e29686af3de5e01026 (patch) | |
| tree | c6bb8a9ec1c2bdbec37f978ab9d5ebd34a76a5b8 | |
| parent | 462bd5f6998e7922fb568dd50c52435370e8ecdb (diff) | |
| parent | 21b8689fa96f54350bf218b804bba58fda781fca (diff) | |
Merge pull request #30 from network-quality/extended-stats-windows
[Feature] Add extendedstats support for Windows
| -rw-r--r-- | extendedstats/other.go | 4 | ||||
| -rw-r--r-- | extendedstats/windows.go | 148 | ||||
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 2 |
4 files changed, 153 insertions, 3 deletions
diff --git a/extendedstats/other.go b/extendedstats/other.go index d313826..960ee85 100644 --- a/extendedstats/other.go +++ b/extendedstats/other.go @@ -1,5 +1,5 @@ -//go:build !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !darwin -// +build !dragonfly,!freebsd,!linux,!netbsd,!openbsd,!darwin +//go:build !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !darwin && !windows +// +build !dragonfly,!freebsd,!linux,!netbsd,!openbsd,!darwin,!windows package extendedstats diff --git a/extendedstats/windows.go b/extendedstats/windows.go new file mode 100644 index 0000000..cc2fca7 --- /dev/null +++ b/extendedstats/windows.go @@ -0,0 +1,148 @@ +//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 +} @@ -4,7 +4,7 @@ go 1.18 require ( golang.org/x/net v0.0.0-20220225172249-27dd8689420f - golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c + golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 ) require golang.org/x/text v0.3.7 // indirect @@ -2,5 +2,7 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= |
