Files
cloud-ip-validator/internal/checkrunner/icmp.go
T

82 lines
2.1 KiB
Go
Raw Normal View History

2026-08-21 07:34:45 +03:00
package checkrunner
import (
"context"
"fmt"
"net"
"os"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
// ICMPEcho sends up to `count` ICMP echo requests to host and reports
// success if at least one echo reply is received before timeout. Uses a
// raw ICMP socket (ip4:icmp), which requires either running as root or
// (on Linux, as deployed here) the CAP_NET_RAW capability — see the
// validator-agent and prober systemd units.
func ICMPEcho(host string, count int, timeout time.Duration) func(ctx context.Context) Result {
return run("icmp", host, func(ctx context.Context) error {
conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return fmt.Errorf("open icmp socket (needs CAP_NET_RAW or root): %w", err)
}
defer conn.Close()
dst, err := net.ResolveIPAddr("ip4", host)
if err != nil {
return fmt.Errorf("resolve %s: %w", host, err)
}
id := os.Getpid() & 0xffff
var lastErr error
for seq := 1; seq <= count; seq++ {
if err := ctx.Err(); err != nil {
return err
}
msg := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: id,
Seq: seq,
Data: []byte("cloud-ip-validator"),
},
}
wb, err := msg.Marshal(nil)
if err != nil {
return fmt.Errorf("marshal echo request: %w", err)
}
if _, err := conn.WriteTo(wb, dst); err != nil {
lastErr = fmt.Errorf("write echo request: %w", err)
continue
}
perAttempt := timeout / time.Duration(count)
if perAttempt <= 0 {
perAttempt = timeout
}
conn.SetReadDeadline(time.Now().Add(perAttempt))
rb := make([]byte, 1500)
n, _, err := conn.ReadFrom(rb)
if err != nil {
lastErr = fmt.Errorf("read echo reply: %w", err)
continue
}
rm, err := icmp.ParseMessage(1 /* protocolICMP */, rb[:n])
if err != nil {
lastErr = fmt.Errorf("parse reply: %w", err)
continue
}
if rm.Type == ipv4.ICMPTypeEchoReply {
return nil
}
lastErr = fmt.Errorf("unexpected icmp type %v", rm.Type)
}
if lastErr == nil {
lastErr = fmt.Errorf("no reply received")
}
return lastErr
})
}