1
1
package ping
2
2
3
3
import (
4
+ "bufio"
5
+ "bytes"
4
6
"context"
5
7
"fmt"
8
+ "log"
9
+ "os/exec"
10
+ "runtime"
6
11
"strings"
7
12
"time"
8
13
9
14
"github.com/ycd/dstp/pkg/common"
10
15
)
11
16
12
17
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 ) {
13
22
var output string
14
23
15
24
pinger , err := createPinger (addr .String ())
@@ -19,52 +28,158 @@ func RunTest(ctx context.Context, addr common.Address, count int, timeout int) (
19
28
20
29
pinger .Count = count
21
30
if timeout == - 1 {
22
- pinger .Timeout = time .Duration (10 * count ) * time .Second
31
+ pinger .Timeout = time .Duration (2 * count ) * time .Second
23
32
} else {
24
33
pinger .Timeout = time .Duration (timeout ) * time .Second
25
34
}
26
35
err = pinger .Run ()
27
36
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
+ }
34
42
} 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
+ }
36
53
}
37
54
38
55
return common .Output (output ), nil
39
56
}
40
57
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 ())
44
63
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
47
75
}
48
76
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
51
80
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 ()
53
93
if err != nil {
54
94
return "" , err
55
95
}
56
96
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 "
62
100
}
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 ())
66
104
}
67
105
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 , " " )
70
185
}
0 commit comments