Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added experiential icmp based ping functionality #105

Merged
merged 2 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.8.1
go.uber.org/zap v1.27.0
golang.org/x/net v0.19.0
)

require (
Expand All @@ -15,6 +16,6 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
Expand Down
104 changes: 103 additions & 1 deletion httpclient/httpclient_ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@

import (
"fmt"
"net"
"net/http"
"os"
"time"

"go.uber.org/zap"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)

// DoPing performs an HTTP "ping" to the specified endpoint using the given HTTP method, body,
Expand Down Expand Up @@ -57,7 +61,7 @@
// Loop until a successful response is received or maximum retries are reached
for retryCount <= maxRetries {
// Use the existing 'do' function for sending the request
resp, err := c.executeRequest(method, endpoint, body, out)
resp, err := c.executeRequestWithRetries(method, endpoint, body, out)

// If request is successful and returns 200 status code, return the response
if err == nil && resp.StatusCode == http.StatusOK {
Expand All @@ -78,3 +82,101 @@
log.Error("Ping failed after maximum retries", zap.String("method", method), zap.String("endpoint", endpoint))
return nil, fmt.Errorf("ping failed after %d retries", maxRetries)
}

// DoPing performs an ICMP "ping" to the specified host. It sends ICMP echo requests and waits for echo replies.
// This function is useful for checking the availability or health of a host, particularly in environments where
// network reliability might be an issue.

// Parameters:
// - host: The target host for the ping request.
// - timeout: The timeout for waiting for a ping response.

// Returns:
// - error: An error object indicating failure during the execution of the ping operation or nil if the ping was successful.

// Usage:
// This function is intended for use in scenarios where it's necessary to confirm the availability or health of a host.
// The caller is responsible for handling the error according to their needs.

// Example:
// err := client.DoPing("www.example.com", 3*time.Second)
// if err != nil {
// // Handle error
// }

func (c *Client) DoPingV2(host string, timeout time.Duration) error {
log := c.Logger

// Listen for ICMP replies
conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
log.Error("Failed to listen for ICMP packets", zap.Error(err))

Check warning

Code scanning / gosec

Errors unhandled. Warning

Errors unhandled.
return fmt.Errorf("failed to listen for ICMP packets: %w", err)
}
defer conn.Close()

// Resolve the IP address of the host
dst, err := net.ResolveIPAddr("ip4", host)
if err != nil {
log.Error("Failed to resolve IP address", zap.String("host", host), zap.Error(err))

Check warning

Code scanning / gosec

Errors unhandled. Warning

Errors unhandled.
return fmt.Errorf("failed to resolve IP address for host %s: %w", host, err)
}

// Create an ICMP Echo Request message
msg := icmp.Message{
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{
ID: os.Getpid() & 0xffff, Seq: 1, // Use PID as ICMP ID
Data: []byte("HELLO"), // Data payload
},
}

// Marshal the message into bytes
msgBytes, err := msg.Marshal(nil)
if err != nil {
log.Error("Failed to marshal ICMP message", zap.Error(err))

Check warning

Code scanning / gosec

Errors unhandled. Warning

Errors unhandled.
return fmt.Errorf("failed to marshal ICMP message: %w", err)
}

// Send the ICMP Echo Request message
if _, err := conn.WriteTo(msgBytes, dst); err != nil {
log.Error("Failed to send ICMP message", zap.Error(err))

Check warning

Code scanning / gosec

Errors unhandled. Warning

Errors unhandled.
return fmt.Errorf("failed to send ICMP message: %w", err)
}

// Set read timeout
if err := conn.SetReadDeadline(time.Now().Add(timeout)); err != nil {
log.Error("Failed to set read deadline", zap.Error(err))

Check warning

Code scanning / gosec

Errors unhandled. Warning

Errors unhandled.
return fmt.Errorf("failed to set read deadline: %w", err)
}

// Wait for an ICMP Echo Reply message
reply := make([]byte, 1500)
n, _, err := conn.ReadFrom(reply)
if err != nil {
log.Error("Failed to receive ICMP reply", zap.Error(err))

Check warning

Code scanning / gosec

Errors unhandled. Warning

Errors unhandled.
return fmt.Errorf("failed to receive ICMP reply: %w", err)
}

// Parse the ICMP message from the reply
parsedMsg, err := icmp.ParseMessage(1, reply[:n])
if err != nil {
log.Error("Failed to parse ICMP message", zap.Error(err))

Check warning

Code scanning / gosec

Errors unhandled. Warning

Errors unhandled.
return fmt.Errorf("failed to parse ICMP message: %w", err)
}

// Check if the message is an ICMP Echo Reply
if echoReply, ok := parsedMsg.Type.(*ipv4.ICMPType); ok {
if *echoReply != ipv4.ICMPTypeEchoReply {
log.Error("Did not receive ICMP Echo Reply", zap.String("received_type", echoReply.String()))

Check warning

Code scanning / gosec

Errors unhandled. Warning

Errors unhandled.
return fmt.Errorf("did not receive ICMP Echo Reply, received type: %s", echoReply.String())
}
} else {
// Handle the case where the type assertion fails
log.Error("Failed to assert ICMP message type")

Check warning

Code scanning / gosec

Errors unhandled. Warning

Errors unhandled.
return fmt.Errorf("failed to assert ICMP message type")
}

log.Info("Ping successful", zap.String("host", host))
return nil
}
Loading