-
Notifications
You must be signed in to change notification settings - Fork 10
/
ping.go
159 lines (147 loc) · 4.01 KB
/
ping.go
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Package tlsping measures the time needed for establishing TLS connections
package tlsping
import (
"crypto/tls"
"crypto/x509"
"net"
"sync"
"time"
)
// Config is used to configure how to time the TLS connection
type Config struct {
// Dont perform TLS handshake. Only measure the time for
// estasblishing the TCP connection
AvoidTLSHandshake bool
// Don't verify server certificate. Used relevant if
// the TLS handshake is performed
InsecureSkipVerify bool
// Set of root certificate authorities to use to verify the server
// certificate. This is only relevant when measuring the time spent
// in the TLS handshake.
// If nil, the host's set of root certificate authorities is used.
RootCAs *x509.CertPool
// Number of times to connect. The time spent by every connection will
// be measured and the results will be summarized.
Count int
}
// Ping establishes network connections to the specified network addr
// and returns summary statistics of the time spent establishing those
// connections. The operation is governed by the provided configuration.
// It returns an error if at least one of the connections fails.
// addr is of the form 'hostname:port'
// The returned results do not include the time spent calling the
// DNS for translating the host name to IP address. This resolution
// is performed once and a single of retrieved IP addresses is used for all
// connections.
func Ping(addr string, config *Config) (PingResult, error) {
if config.Count == 0 {
config.Count = 1
}
host, ipAddr, port, err := resolveAddr(addr)
if err != nil {
return PingResult{}, err
}
result := PingResult{
Host: host,
IPAddr: ipAddr,
Address: addr,
}
target := net.JoinHostPort(ipAddr, port)
var f func() error
d := &net.Dialer{
Timeout: 5 * time.Second,
}
if config.AvoidTLSHandshake {
f = func() error {
conn, err := d.Dial("tcp", target)
if err == nil {
conn.Close()
}
return err
}
} else {
tlsConfig := tls.Config{
ServerName: host,
InsecureSkipVerify: config.InsecureSkipVerify,
RootCAs: config.RootCAs,
}
f = func() error {
conn, err := tls.DialWithDialer(d, "tcp", target, &tlsConfig)
if err == nil {
conn.Close()
}
return err
}
}
// Launch workers to perform the timing
results := make(chan connectDuration, config.Count)
var wg sync.WaitGroup
wg.Add(config.Count)
for i := 0; i < config.Count; i++ {
go func() {
defer wg.Done()
d, err := timeit(f)
results <- connectDuration{
seconds: d,
err: err,
}
}()
}
// Wait for workers to finish
go func() {
wg.Wait()
close(results)
}()
// Collect workers' results
durations := make([]float64, 0, config.Count)
for res := range results {
if res.err != nil {
return result, res.err
}
durations = append(durations, res.seconds)
}
result.setSummaryStats(summarize(durations))
return result, nil
}
type connectDuration struct {
seconds float64
err error
}
// timeit measures the time spent executing the argument function f
// It returns the elapsed time spent as a floating point number of seconds
func timeit(f func() error) (float64, error) {
start := time.Now()
err := f()
end := time.Now()
if err != nil {
return 0, err
}
return end.Sub(start).Seconds(), nil
}
// resolveAddr queries the DNS to resolve the name of the host
// in addr and returns the hostname, IP address and port.
// If the DNS responds with more than one IP address associated
// to the given host, the first address to which a TCP connection
// can be established is returned.
func resolveAddr(addr string) (string, string, string, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return "", "", "", err
}
if len(host) == 0 {
host = "localhost"
}
addrs, err := net.LookupHost(host)
if err != nil {
return "", "", "", err
}
d := net.Dialer{Timeout: 3 * time.Second}
for _, a := range addrs {
conn, err := d.Dial("tcp", net.JoinHostPort(a, port))
if err == nil {
conn.Close()
return host, a, port, nil
}
}
return host, addrs[0], port, nil
}