summaryrefslogtreecommitdiff
path: root/extendedstats/unix.go
blob: fd537ee8e97fa49297706914eb2d8dda6d21bdcd (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
//go:build dragonfly || freebsd || linux || netbsd || openbsd
// +build dragonfly freebsd linux netbsd openbsd

/*
 * This file is part of Go Responsiveness.
 *
 * Go Responsiveness is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software Foundation,
 * either version 2 of the License, or (at your option) any later version.
 * Go Responsiveness is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with Go Responsiveness. If not, see <https://www.gnu.org/licenses/>.
 */

package extendedstats

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

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

type AggregateExtendedStats struct {
	MaxPathMtu           uint64
	MaxSendMss           uint64
	MaxRecvMss           uint64
	TotalRetransmissions uint64
	TotalReorderings     uint64
	AverageRtt           float64
	rtt_measurements     uint64
	total_rtt            float64
}

func ExtendedStatsAvailable() bool {
	return true
}

func (es *AggregateExtendedStats) IncorporateConnectionStats(basicConn net.Conn) error {
	if info, err := GetTCPInfo(basicConn); err != nil {
		return fmt.Errorf("OOPS: Could not get the TCP info for the connection: %v", err)
	} else {
		es.MaxPathMtu = utilities.Max(es.MaxPathMtu, uint64(info.Pmtu))
		es.MaxRecvMss = utilities.Max(es.MaxRecvMss, uint64(info.Rcv_mss))
		es.MaxSendMss = utilities.Max(es.MaxSendMss, uint64(info.Snd_mss))
		// https://lkml.iu.edu/hypermail/linux/kernel/1705.0/01790.html
		es.TotalRetransmissions += uint64(info.Total_retrans)
		es.TotalReorderings += uint64(info.Reordering)
		es.total_rtt += float64(info.Rtt)
		es.rtt_measurements += 1
		es.AverageRtt = es.total_rtt / float64(es.rtt_measurements)
	}
	return nil
}

func (es *AggregateExtendedStats) Repr() string {
	return fmt.Sprintf(`Extended Statistics:
	Maximum Path MTU:      %v
	Maximum Send MSS:      %v
	Maximum Recv MSS:      %v
	Total Retransmissions: %v
	Total Reorderings:     %v
	Average RTT:           %v
`, es.MaxPathMtu, es.MaxSendMss, es.MaxRecvMss, es.TotalRetransmissions, es.TotalReorderings, es.AverageRtt)
}

func GetTCPInfo(basicConn net.Conn) (*unix.TCPInfo, error) {
	tlsConn, ok := basicConn.(*tls.Conn)
	if !ok {
		return nil, fmt.Errorf("OOPS: Outermost connection is not a TLS connection")
	}
	tcpConn, ok := tlsConn.NetConn().(*net.TCPConn)
	if !ok {
		return nil, fmt.Errorf(
			"OOPS: Could not get the TCP info for the connection (not a TCP connection)",
		)
	}
	rawConn, err := tcpConn.SyscallConn()
	if err != nil {
		return nil, err
	}
	var info *unix.TCPInfo = nil
	rawConn.Control(func(fd uintptr) {
		info, err = unix.GetsockoptTCPInfo(int(fd), unix.SOL_TCP, unix.TCP_INFO)
	})
	return info, err
}