Skip to content

Commit a764918

Browse files
authored
feat(ping): implement fallback for *nix and windows
feat(ping): implement fallback for *nix and windows
2 parents 819698c + 59199be commit a764918

File tree

6 files changed

+199
-34
lines changed

6 files changed

+199
-34
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ jobs:
2626
timeout-minutes: 2
2727
run: |
2828
go build cmd/dstp/main.go
29-
go test -v ./...
29+
go test -v -tags=integration ./...

config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Options:
2424
-o, --out <string> The type of the output, either json or plaintext [Default: plaintext]
2525
-c <bool> Run all the tests concurrently. [Default: false]
2626
-p <int> Number of ping packets [Default: 3]
27-
-t <int> Give up on ping after this many seconds [Default: 10s per ping packet]
27+
-t <int> Give up on ping after this many seconds [Default: 2s per ping packet]
2828
-h, --help Show this message and exit.
2929
`
3030

pkg/dstp/dstp_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build integration || darwin
1+
//go:build integration
22

33
package dstp
44

@@ -16,7 +16,7 @@ func TestRunAllTests(t *testing.T) {
1616
Output: "plaintext",
1717
ShowHelp: false,
1818
Concurrent: false,
19-
Timeout: 10,
19+
Timeout: 3,
2020
PingCount: 3,
2121
}
2222

@@ -25,7 +25,7 @@ func TestRunAllTests(t *testing.T) {
2525
Output: "plaintext",
2626
ShowHelp: false,
2727
Concurrent: false,
28-
Timeout: 10,
28+
Timeout: 3,
2929
PingCount: 3,
3030
}
3131

@@ -34,7 +34,7 @@ func TestRunAllTests(t *testing.T) {
3434
Output: "plaintext",
3535
ShowHelp: false,
3636
Concurrent: false,
37-
Timeout: 10,
37+
Timeout: 3,
3838
PingCount: 3,
3939
}
4040

@@ -43,7 +43,7 @@ func TestRunAllTests(t *testing.T) {
4343
Output: "plaintext",
4444
ShowHelp: false,
4545
Concurrent: false,
46-
Timeout: 10,
46+
Timeout: 3,
4747
PingCount: 3,
4848
}
4949

@@ -52,7 +52,7 @@ func TestRunAllTests(t *testing.T) {
5252
Output: "plaintext",
5353
ShowHelp: false,
5454
Concurrent: false,
55-
Timeout: 10,
55+
Timeout: 3,
5656
PingCount: 3,
5757
}
5858

pkg/ping/dns.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package ping
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/ycd/dstp/pkg/common"
7+
"time"
8+
)
9+
10+
func RunDNSTest(ctx context.Context, addr common.Address, count int, timeout int) (common.Output, error) {
11+
var output string
12+
13+
pinger, err := createPinger(addr.String())
14+
if err != nil {
15+
return "", err
16+
}
17+
18+
pinger.Count = count
19+
if timeout == -1 {
20+
pinger.Timeout = time.Duration(2*count) * time.Second
21+
} else {
22+
pinger.Timeout = time.Duration(timeout) * time.Second
23+
}
24+
err = pinger.Run()
25+
if err != nil {
26+
return "", fmt.Errorf("failed to run ping: %v", err.Error())
27+
}
28+
29+
output += joinS("resolving", pinger.IPAddr().String())
30+
return common.Output(output), nil
31+
}

pkg/ping/ping.go

Lines changed: 141 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
package ping
22

33
import (
4+
"bufio"
5+
"bytes"
46
"context"
57
"fmt"
8+
"log"
9+
"os/exec"
10+
"runtime"
611
"strings"
712
"time"
813

914
"github.com/ycd/dstp/pkg/common"
1015
)
1116

1217
func RunTest(ctx context.Context, addr common.Address, count int, timeout int) (common.Output, error) {
18+
return runPing(ctx, addr, count, timeout)
19+
}
20+
21+
func runPing(ctx context.Context, addr common.Address, count int, timeout int) (common.Output, error) {
1322
var output string
1423

1524
pinger, err := createPinger(addr.String())
@@ -19,52 +28,158 @@ func RunTest(ctx context.Context, addr common.Address, count int, timeout int) (
1928

2029
pinger.Count = count
2130
if timeout == -1 {
22-
pinger.Timeout = time.Duration(10*count) * time.Second
31+
pinger.Timeout = time.Duration(2*count) * time.Second
2332
} else {
2433
pinger.Timeout = time.Duration(timeout) * time.Second
2534
}
2635
err = pinger.Run()
2736
if err != nil {
28-
return "", fmt.Errorf("failed to run ping: %v", err.Error())
29-
}
30-
31-
stats := pinger.Statistics()
32-
if stats.PacketsRecv == 0 {
33-
output += "no response"
37+
if out, err := runPingFallback(ctx, addr, count, timeout); err == nil {
38+
output += out.String()
39+
} else {
40+
return "", fmt.Errorf("failed to run ping: %v", err.Error())
41+
}
3442
} else {
35-
output += joinS(joinC(stats.AvgRtt.String()))
43+
stats := pinger.Statistics()
44+
if stats.PacketsRecv == 0 {
45+
if out, err := runPingFallback(ctx, addr, count, timeout); err == nil {
46+
output += out.String()
47+
} else {
48+
output += "no response"
49+
}
50+
} else {
51+
output += joinS(joinC(stats.AvgRtt.String()))
52+
}
3653
}
3754

3855
return common.Output(output), nil
3956
}
4057

41-
func joinC(args ...string) string {
42-
return strings.Join(args, ",")
43-
}
58+
// runPingFallback executes the ping command from cli
59+
// Currently fallback is not implemented for windows.
60+
func runPingFallback(ctx context.Context, addr common.Address, count int, timeout int) (common.Output, error) {
61+
args := fmt.Sprintf("-c %v -t %v", count, timeout)
62+
command := fmt.Sprintf("ping %s %s", args, addr.String())
4463

45-
func joinS(args ...string) string {
46-
return strings.Join(args, " ")
64+
out, err := executeCommand("bash", command)
65+
if err != nil {
66+
return common.Output(""), err
67+
}
68+
69+
po, err := parsePingOutput(out)
70+
if err != nil {
71+
return common.Output(""), err
72+
}
73+
74+
return common.Output(po.AvgRTT + "ms"), nil
4775
}
4876

49-
func RunDNSTest(ctx context.Context, addr common.Address, count int, timeout int) (common.Output, error) {
50-
var output string
77+
func executeCommand(shell, command string) (string, error) {
78+
var errb bytes.Buffer
79+
var out string
5180

52-
pinger, err := createPinger(addr.String())
81+
var cmd *exec.Cmd
82+
if runtime.GOOS == "windows" {
83+
cmd = exec.Command("cmd", "/C", command)
84+
} else {
85+
cmd = exec.Command(shell, "-c", command)
86+
}
87+
cmd.Stderr = &errb
88+
stdout, err := cmd.StdoutPipe()
89+
if err != nil {
90+
log.Printf("got error while tracing pipe: %v", err)
91+
}
92+
err = cmd.Start()
5393
if err != nil {
5494
return "", err
5595
}
5696

57-
pinger.Count = count
58-
if timeout == -1 {
59-
pinger.Timeout = time.Duration(10*count) * time.Second
60-
} else {
61-
pinger.Timeout = time.Duration(timeout) * time.Second
97+
scanner := bufio.NewScanner(stdout)
98+
for scanner.Scan() {
99+
out += scanner.Text() + "\n"
62100
}
63-
err = pinger.Run()
64-
if err != nil {
65-
return "", fmt.Errorf("failed to run ping: %v", err.Error())
101+
102+
if err := cmd.Wait(); err != nil {
103+
return "", fmt.Errorf("got error: %v, stderr: %v", err, errb.String())
66104
}
67105

68-
output += joinS("resolving", pinger.IPAddr().String())
69-
return common.Output(output), nil
106+
return out, nil
107+
}
108+
109+
type pingOutput struct {
110+
PacketLoss string
111+
PacketReceived string
112+
PacketTransmitted string
113+
MinRTT string
114+
AvgRTT string
115+
MaxRTT string
116+
}
117+
118+
var (
119+
RequestTimeoutError = fmt.Errorf("requests timed out")
120+
PacketLossError = fmt.Errorf("timeout error: 100.0%% packet loss")
121+
)
122+
123+
// parsePingOutput parses the output of ping by parsing the stdout
124+
// example output:
125+
//
126+
// ping -c 3 jvns.ca
127+
// PING jvns.ca (104.21.91.206): 56 data bytes
128+
// 64 bytes from 104.21.91.206: icmp_seq=0 ttl=58 time=14.468 ms
129+
// 64 bytes from 104.21.91.206: icmp_seq=1 ttl=58 time=14.450 ms
130+
// 64 bytes from 104.21.91.206: icmp_seq=2 ttl=58 time=14.683 ms
131+
//
132+
// --- jvns.ca ping statistics ---
133+
// 3 packets transmitted, 3 packets received, 0.0% packet loss
134+
// round-trip min/avg/max/stddev = 14.450/14.534/14.683/0.106 ms
135+
func parsePingOutput(out string) (pingOutput, error) {
136+
var po pingOutput
137+
138+
lines := strings.Split(out, "\n")
139+
140+
for _, line := range lines {
141+
switch {
142+
case strings.Contains(line, "packets transmitted"):
143+
arr := strings.Split(line, ",")
144+
fmt.Println(arr)
145+
if len(arr) != 3 {
146+
continue
147+
}
148+
149+
po.PacketTransmitted, po.PacketReceived, po.PacketLoss = arr[0], arr[1], arr[2]
150+
151+
case strings.Contains(line, "round-trip min/avg/max"):
152+
l := strings.ReplaceAll(line, " = ", " ")
153+
arr := strings.Split(l, " ")
154+
155+
if len(arr) != 4 {
156+
continue
157+
}
158+
159+
rttArr := strings.Split(arr[2], "/")
160+
if len(rttArr) != 4 {
161+
continue
162+
}
163+
164+
po.MinRTT, po.AvgRTT, po.MaxRTT = rttArr[0], rttArr[1], rttArr[2]
165+
}
166+
}
167+
168+
if po.MinRTT == "" && po.AvgRTT == "" && po.MaxRTT == "" {
169+
return po, RequestTimeoutError
170+
}
171+
172+
if po.PacketLoss == "100.0% packet loss" {
173+
return po, PacketLossError
174+
}
175+
176+
return po, nil
177+
}
178+
179+
func joinC(args ...string) string {
180+
return strings.Join(args, ",")
181+
}
182+
183+
func joinS(args ...string) string {
184+
return strings.Join(args, " ")
70185
}

pkg/ping/ping_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//go:build integration
2+
3+
package ping
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"github.com/ycd/dstp/pkg/common"
9+
"testing"
10+
)
11+
12+
func TestPingFallback(t *testing.T) {
13+
out, err := runPingFallback(context.Background(), common.Address("8.8.8.8"), 3, 10)
14+
if err != nil {
15+
t.Fatal(err.Error())
16+
}
17+
18+
fmt.Println(out.String())
19+
}

0 commit comments

Comments
 (0)