summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWill Hawkins <[email protected]>2022-06-22 13:29:11 -0400
committerGitHub <[email protected]>2022-06-22 13:29:11 -0400
commit8595ff279dc46e3f974661e29686af3de5e01026 (patch)
treec6bb8a9ec1c2bdbec37f978ab9d5ebd34a76a5b8
parent462bd5f6998e7922fb568dd50c52435370e8ecdb (diff)
parent21b8689fa96f54350bf218b804bba58fda781fca (diff)
Merge pull request #30 from network-quality/extended-stats-windows
[Feature] Add extendedstats support for Windows
-rw-r--r--extendedstats/other.go4
-rw-r--r--extendedstats/windows.go148
-rw-r--r--go.mod2
-rw-r--r--go.sum2
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
+}
diff --git a/go.mod b/go.mod
index 40e148a..5fcee76 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index 1cbffce..3fe6d08 100644
--- a/go.sum
+++ b/go.sum
@@ -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=