82 lines
2.1 KiB
Go
82 lines
2.1 KiB
Go
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
|
||
|
|
})
|
||
|
|
}
|