Skip to content

Commit 5866efb

Browse files
committed
Add force IPv4/IPv6 option
1 parent 30c5ffb commit 5866efb

File tree

7 files changed

+99
-33
lines changed

7 files changed

+99
-33
lines changed

defs/options.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package defs
22

33
const (
44
OptionHelp = "help"
5+
OptionIPv4 = "ipv4"
6+
OptionIPv4Alt = "4"
7+
OptionIPv6 = "ipv6"
8+
OptionIPv6Alt = "6"
59
OptionNoDownload = "no-download"
610
OptionNoUpload = "no-upload"
711
OptionBytes = "bytes"

defs/server.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func (s *Server) IsUp() bool {
4343
u.Path = path.Join(u.Path, s.PingURL)
4444
resp, err := http.Get(u.String())
4545
if err != nil {
46+
log.Debugf("Error checking for server status: %s", err)
4647
return false
4748
}
4849
defer resp.Body.Close()
@@ -52,7 +53,7 @@ func (s *Server) IsUp() bool {
5253
}
5354

5455
// ICMPPingAndJitter pings the server via ICMP echos and calculate the average ping and jitter
55-
func (s *Server) ICMPPingAndJitter(count int, srcIp string) (float64, float64, error) {
56+
func (s *Server) ICMPPingAndJitter(count int, srcIp, network string) (float64, float64, error) {
5657
t := time.Now()
5758
defer func() {
5859
s.TLog.Logf("ICMP ping took %s", time.Now().Sub(t).String())
@@ -68,7 +69,8 @@ func (s *Server) ICMPPingAndJitter(count int, srcIp string) (float64, float64, e
6869
log.Debugf("Failed to get server URL: %s", err)
6970
return 0, 0, err
7071
}
71-
p, err := ping.NewPinger(u.Hostname())
72+
73+
p, err := ping.NewPinger(u.Hostname(), network)
7274
if err != nil {
7375
log.Debugf("Failed to initialize pinger: %s", err)
7476
return 0, 0, err
@@ -214,7 +216,7 @@ func (s *Server) Download(silent bool, useBytes bool) (float64, int, error) {
214216
defer resp.Body.Close()
215217

216218
if _, err = io.Copy(ioutil.Discard, io.TeeReader(resp.Body, counter)); err != nil {
217-
if !errors.Is(err, context.Canceled) {
219+
if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) {
218220
log.Debugf("Failed when reading HTTP response: %s", err)
219221
}
220222
}
@@ -299,7 +301,7 @@ func (s *Server) Upload(noPrealloc, silent, useBytes bool) (float64, int, error)
299301

300302
doUpload := func() {
301303
resp, err := http.DefaultClient.Do(req)
302-
if err != nil && !errors.Is(err, context.Canceled) {
304+
if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) {
303305
log.Debugf("Failed when making HTTP request: %s", err)
304306
} else if err == nil {
305307
defer resp.Body.Close()

go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ module librespeed-cli
22

33
go 1.14
44

5-
replace github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c => github.com/maddie/go-ping v0.0.0-20200305135031-f8c069280206
5+
replace github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c => github.com/maddie/go-ping v0.0.0-20200311033510-5e9a13ec8da6
66

77
require (
88
github.com/briandowns/spinner v1.9.0
99
github.com/gocarina/gocsv v0.0.0-20200302151839-87c60d755c58
1010
github.com/sirupsen/logrus v1.4.2
1111
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c
12-
github.com/stretchr/testify v1.3.0 // indirect
1312
github.com/urfave/cli/v2 v2.1.1
1413
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect
1514
)

go.sum

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ github.com/gocarina/gocsv v0.0.0-20200302151839-87c60d755c58 h1:rRQm5os6ffGTukb4
1212
github.com/gocarina/gocsv v0.0.0-20200302151839-87c60d755c58/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
1313
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
1414
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
15-
github.com/maddie/go-ping v0.0.0-20200305135031-f8c069280206 h1:wcRUWd5aN1arYHnxsAXx56VEV9RKQxVay24fKBv0s8M=
16-
github.com/maddie/go-ping v0.0.0-20200305135031-f8c069280206/go.mod h1:IYhVIzcOSIO1fLDdEz4JFidYhLPmmW3dchKuengmL9s=
15+
github.com/maddie/go-ping v0.0.0-20200311033510-5e9a13ec8da6 h1:ApVK0ZXs0wyZmj4dSelnorxkJguUhVLXxT+ghrXtNQY=
16+
github.com/maddie/go-ping v0.0.0-20200311033510-5e9a13ec8da6/go.mod h1:IYhVIzcOSIO1fLDdEz4JFidYhLPmmW3dchKuengmL9s=
1717
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
1818
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
1919
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
@@ -26,8 +26,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I
2626
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
2727
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
2828
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
29-
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c h1:gqEdF4VwBu3lTKGHS9rXE9x1/pEaSwCXRLOZRF6qtlw=
30-
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0=
3129
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
3230
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
3331
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -36,13 +34,22 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
3634
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
3735
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
3836
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
37+
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
38+
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
39+
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
40+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
3941
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
4042
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
43+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
4144
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
4245
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
4346
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
4447
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
4548
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
4649
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
50+
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
51+
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c h1:IGkKhmfzcztjm6gYkykvu/NiS8kaqbCWAEWWAyf8J5U=
52+
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
53+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
4754
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
4855
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

main.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ func main() {
3737
Name: defs.OptionVersion,
3838
Usage: "Show the version number and exit",
3939
},
40+
&cli.BoolFlag{
41+
Name: defs.OptionIPv4,
42+
Aliases: []string{defs.OptionIPv4Alt},
43+
Usage: "Force IPv4 only",
44+
},
45+
&cli.BoolFlag{
46+
Name: defs.OptionIPv6,
47+
Aliases: []string{defs.OptionIPv6Alt},
48+
Usage: "Force IPv6 only",
49+
},
4050
&cli.BoolFlag{
4151
Name: defs.OptionNoDownload,
4252
Usage: "Do not perform download test",

speedtest/helper.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const (
2727
)
2828

2929
// doSpeedTest is where the actual speed test happens
30-
func doSpeedTest(c *cli.Context, servers []defs.Server, telemetryServer defs.TelemetryServer, silent bool) error {
30+
func doSpeedTest(c *cli.Context, servers []defs.Server, telemetryServer defs.TelemetryServer, network string, silent bool) error {
3131
if serverCount := len(servers); serverCount > 1 {
3232
log.Infof("Testing against %d servers", serverCount)
3333
}
@@ -61,7 +61,7 @@ func doSpeedTest(c *cli.Context, servers []defs.Server, telemetryServer defs.Tel
6161
pb.Start()
6262
}
6363

64-
p, jitter, err := currentServer.ICMPPingAndJitter(pingCount, c.String(defs.OptionSource))
64+
p, jitter, err := currentServer.ICMPPingAndJitter(pingCount, c.String(defs.OptionSource), network)
6565
if err != nil {
6666
log.Errorf("Failed to get ping and jitter: %s", err)
6767
return err

speedtest/speedtest.go

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package speedtest
22

33
import (
4+
"context"
45
"encoding/json"
56
"errors"
67
"io/ioutil"
78
"net"
89
"net/http"
10+
"strings"
911
"sync"
1012
"time"
1113

@@ -124,28 +126,70 @@ func SpeedTest(c *cli.Context) error {
124126
// HTTP requests timeout
125127
http.DefaultClient.Timeout = time.Duration(c.Int(defs.OptionTimeout)) * time.Second
126128

127-
// bind to source IP address if given
128-
if src := c.String(defs.OptionSource); src != "" {
129-
// first we parse the IP to see if it's valid
130-
localAddr, err := net.ResolveIPAddr("ip", src)
131-
if err != nil {
132-
log.Errorf("Error parsing source IP: %s", err)
133-
return err
129+
forceIPv4 := c.Bool(defs.OptionIPv4)
130+
forceIPv6 := c.Bool(defs.OptionIPv6)
131+
132+
var network string
133+
switch {
134+
case forceIPv4:
135+
network = "ip4"
136+
case forceIPv6:
137+
network = "ip6"
138+
default:
139+
network = "ip"
140+
}
141+
142+
// bind to source IP address if given, or if ipv4/ipv6 is forced
143+
if src := c.String(defs.OptionSource); src != "" || (forceIPv4 || forceIPv6) {
144+
var localTCPAddr *net.TCPAddr
145+
if src != "" {
146+
// first we parse the IP to see if it's valid
147+
addr, err := net.ResolveIPAddr(network, src)
148+
if err != nil {
149+
if strings.Contains(err.Error(), "no suitable address") {
150+
if forceIPv6 {
151+
log.Errorf("Address %s is not a valid IPv6 address", src)
152+
} else {
153+
log.Errorf("Address %s is not a valid IPv4 address", src)
154+
}
155+
} else {
156+
log.Errorf("Error parsing source IP: %s", err)
157+
}
158+
return err
159+
}
160+
161+
log.Debugf("Using %s as source IP", src)
162+
localTCPAddr = &net.TCPAddr{IP: addr.IP}
134163
}
135164

136-
localTCPAddr := net.TCPAddr{IP: localAddr.IP}
165+
var dialContext func(context.Context, string, string) (net.Conn, error)
166+
defaultDialer := &net.Dialer{
167+
Timeout: 30 * time.Second,
168+
KeepAlive: 30 * time.Second,
169+
}
170+
171+
if localTCPAddr != nil {
172+
defaultDialer.LocalAddr = localTCPAddr
173+
}
174+
175+
switch {
176+
case forceIPv4:
177+
dialContext = func(ctx context.Context, network, address string) (conn net.Conn, err error) {
178+
return defaultDialer.DialContext(ctx, "tcp4", address)
179+
}
180+
case forceIPv6:
181+
dialContext = func(ctx context.Context, network, address string) (conn net.Conn, err error) {
182+
return defaultDialer.DialContext(ctx, "tcp6", address)
183+
}
184+
default:
185+
dialContext = defaultDialer.DialContext
186+
}
137187

138188
// set default HTTP client's Transport to the one that binds the source address
139189
// this is modified from http.DefaultTransport
140190
transport := &http.Transport{
141-
Proxy: http.ProxyFromEnvironment,
142-
DialContext: (&net.Dialer{
143-
LocalAddr: &localTCPAddr,
144-
Timeout: 30 * time.Second,
145-
KeepAlive: 30 * time.Second,
146-
// although this option is marked deprecated, but it's still used in http.DefaultTransport, keeping as-is
147-
DualStack: true,
148-
}).DialContext,
191+
Proxy: http.ProxyFromEnvironment,
192+
DialContext: dialContext,
149193
ForceAttemptHTTP2: true,
150194
MaxIdleConns: 100,
151195
IdleConnTimeout: 90 * time.Second,
@@ -188,7 +232,7 @@ func SpeedTest(c *cli.Context) error {
188232

189233
// if --server is given, do speed tests with all of them
190234
if len(c.IntSlice(defs.OptionServer)) > 0 {
191-
return doSpeedTest(c, servers, telemetryServer, silent)
235+
return doSpeedTest(c, servers, telemetryServer, network, silent)
192236
} else {
193237
// else select the fastest server from the list
194238
log.Info("Selecting the fastest server based on ping")
@@ -202,7 +246,7 @@ func SpeedTest(c *cli.Context) error {
202246

203247
// spawn 10 concurrent pingers
204248
for i := 0; i < 10; i++ {
205-
go pingWorker(jobs, results, &wg, c.String(defs.OptionSource))
249+
go pingWorker(jobs, results, &wg, c.String(defs.OptionSource), network)
206250
}
207251

208252
// send ping jobs to workers
@@ -239,11 +283,11 @@ func SpeedTest(c *cli.Context) error {
239283
}
240284

241285
// do speed test on the server
242-
return doSpeedTest(c, []defs.Server{servers[serverIdx]}, telemetryServer, silent)
286+
return doSpeedTest(c, []defs.Server{servers[serverIdx]}, telemetryServer, network, silent)
243287
}
244288
}
245289

246-
func pingWorker(jobs <-chan PingJob, results chan<- PingResult, wg *sync.WaitGroup, srcIp string) {
290+
func pingWorker(jobs <-chan PingJob, results chan<- PingResult, wg *sync.WaitGroup, srcIp, network string) {
247291
for {
248292
job := <-jobs
249293
server := job.Server
@@ -258,7 +302,7 @@ func pingWorker(jobs <-chan PingJob, results chan<- PingResult, wg *sync.WaitGro
258302
// check the server is up by accessing the ping URL and checking its returned value == empty and status code == 200
259303
if server.IsUp() {
260304
// if server is up, get ping
261-
ping, _, err := server.ICMPPingAndJitter(1, srcIp)
305+
ping, _, err := server.ICMPPingAndJitter(1, srcIp, network)
262306
if err != nil {
263307
log.Debugf("Can't ping server %s (%s), skipping", server.Name, u.Hostname())
264308
wg.Done()

0 commit comments

Comments
 (0)