@@ -3,10 +3,14 @@ package httpclient
3
3
4
4
import (
5
5
"fmt"
6
+ "net"
6
7
"net/http"
8
+ "os"
7
9
"time"
8
10
9
11
"go.uber.org/zap"
12
+ "golang.org/x/net/icmp"
13
+ "golang.org/x/net/ipv4"
10
14
)
11
15
12
16
// DoPing performs an HTTP "ping" to the specified endpoint using the given HTTP method, body,
@@ -57,7 +61,7 @@ func (c *Client) DoPing(method, endpoint string, body, out interface{}) (*http.R
57
61
// Loop until a successful response is received or maximum retries are reached
58
62
for retryCount <= maxRetries {
59
63
// Use the existing 'do' function for sending the request
60
- resp , err := c .executeRequest (method , endpoint , body , out )
64
+ resp , err := c .executeRequestWithRetries (method , endpoint , body , out )
61
65
62
66
// If request is successful and returns 200 status code, return the response
63
67
if err == nil && resp .StatusCode == http .StatusOK {
@@ -78,3 +82,101 @@ func (c *Client) DoPing(method, endpoint string, body, out interface{}) (*http.R
78
82
log .Error ("Ping failed after maximum retries" , zap .String ("method" , method ), zap .String ("endpoint" , endpoint ))
79
83
return nil , fmt .Errorf ("ping failed after %d retries" , maxRetries )
80
84
}
85
+
86
+ // DoPing performs an ICMP "ping" to the specified host. It sends ICMP echo requests and waits for echo replies.
87
+ // This function is useful for checking the availability or health of a host, particularly in environments where
88
+ // network reliability might be an issue.
89
+
90
+ // Parameters:
91
+ // - host: The target host for the ping request.
92
+ // - timeout: The timeout for waiting for a ping response.
93
+
94
+ // Returns:
95
+ // - error: An error object indicating failure during the execution of the ping operation or nil if the ping was successful.
96
+
97
+ // Usage:
98
+ // This function is intended for use in scenarios where it's necessary to confirm the availability or health of a host.
99
+ // The caller is responsible for handling the error according to their needs.
100
+
101
+ // Example:
102
+ // err := client.DoPing("www.example.com", 3*time.Second)
103
+ // if err != nil {
104
+ // // Handle error
105
+ // }
106
+
107
+ func (c * Client ) DoPingV2 (host string , timeout time.Duration ) error {
108
+ log := c .Logger
109
+
110
+ // Listen for ICMP replies
111
+ conn , err := icmp .ListenPacket ("ip4:icmp" , "0.0.0.0" )
112
+ if err != nil {
113
+ log .Error ("Failed to listen for ICMP packets" , zap .Error (err ))
114
+ return fmt .Errorf ("failed to listen for ICMP packets: %w" , err )
115
+ }
116
+ defer conn .Close ()
117
+
118
+ // Resolve the IP address of the host
119
+ dst , err := net .ResolveIPAddr ("ip4" , host )
120
+ if err != nil {
121
+ log .Error ("Failed to resolve IP address" , zap .String ("host" , host ), zap .Error (err ))
122
+ return fmt .Errorf ("failed to resolve IP address for host %s: %w" , host , err )
123
+ }
124
+
125
+ // Create an ICMP Echo Request message
126
+ msg := icmp.Message {
127
+ Type : ipv4 .ICMPTypeEcho , Code : 0 ,
128
+ Body : & icmp.Echo {
129
+ ID : os .Getpid () & 0xffff , Seq : 1 , // Use PID as ICMP ID
130
+ Data : []byte ("HELLO" ), // Data payload
131
+ },
132
+ }
133
+
134
+ // Marshal the message into bytes
135
+ msgBytes , err := msg .Marshal (nil )
136
+ if err != nil {
137
+ log .Error ("Failed to marshal ICMP message" , zap .Error (err ))
138
+ return fmt .Errorf ("failed to marshal ICMP message: %w" , err )
139
+ }
140
+
141
+ // Send the ICMP Echo Request message
142
+ if _ , err := conn .WriteTo (msgBytes , dst ); err != nil {
143
+ log .Error ("Failed to send ICMP message" , zap .Error (err ))
144
+ return fmt .Errorf ("failed to send ICMP message: %w" , err )
145
+ }
146
+
147
+ // Set read timeout
148
+ if err := conn .SetReadDeadline (time .Now ().Add (timeout )); err != nil {
149
+ log .Error ("Failed to set read deadline" , zap .Error (err ))
150
+ return fmt .Errorf ("failed to set read deadline: %w" , err )
151
+ }
152
+
153
+ // Wait for an ICMP Echo Reply message
154
+ reply := make ([]byte , 1500 )
155
+ n , _ , err := conn .ReadFrom (reply )
156
+ if err != nil {
157
+ log .Error ("Failed to receive ICMP reply" , zap .Error (err ))
158
+ return fmt .Errorf ("failed to receive ICMP reply: %w" , err )
159
+ }
160
+
161
+ // Parse the ICMP message from the reply
162
+ parsedMsg , err := icmp .ParseMessage (1 , reply [:n ])
163
+ if err != nil {
164
+ log .Error ("Failed to parse ICMP message" , zap .Error (err ))
165
+ return fmt .Errorf ("failed to parse ICMP message: %w" , err )
166
+ }
167
+
168
+ // Check if the message is an ICMP Echo Reply
169
+ if echoReply , ok := parsedMsg .Type .(* ipv4.ICMPType ); ok {
170
+ if * echoReply != ipv4 .ICMPTypeEchoReply {
171
+ log .Error ("Did not receive ICMP Echo Reply" , zap .String ("received_type" , echoReply .String ()))
172
+ return fmt .Errorf ("did not receive ICMP Echo Reply, received type: %s" , echoReply .String ())
173
+ }
174
+ } else {
175
+ // Handle the case where the type assertion fails
176
+ log .Error ("Failed to assert ICMP message type" )
177
+ return fmt .Errorf ("failed to assert ICMP message type" )
178
+ }
179
+
180
+ log .Info ("Ping successful" , zap .String ("host" , host ))
181
+ return nil
182
+ }
0 commit comments