@@ -3,10 +3,14 @@ package httpclient
33
44import (
55 "fmt"
6+ "net"
67 "net/http"
8+ "os"
79 "time"
810
911 "go.uber.org/zap"
12+ "golang.org/x/net/icmp"
13+ "golang.org/x/net/ipv4"
1014)
1115
1216// 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
5761 // Loop until a successful response is received or maximum retries are reached
5862 for retryCount <= maxRetries {
5963 // 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 )
6165
6266 // If request is successful and returns 200 status code, return the response
6367 if err == nil && resp .StatusCode == http .StatusOK {
@@ -78,3 +82,101 @@ func (c *Client) DoPing(method, endpoint string, body, out interface{}) (*http.R
7882 log .Error ("Ping failed after maximum retries" , zap .String ("method" , method ), zap .String ("endpoint" , endpoint ))
7983 return nil , fmt .Errorf ("ping failed after %d retries" , maxRetries )
8084}
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