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 }) }