package prober import ( "context" "fmt" "math/rand/v2" "net" "time" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" "git.ipng.ch/ipng/vpp-maglev/internal/health" ) // ICMPProbe sends an ICMP echo request to cfg.Target inside the healthcheck // netns and waits for a matching reply. func ICMPProbe(ctx context.Context, cfg ProbeConfig) health.ProbeResult { isV6 := cfg.Target.To4() == nil var ok bool err := inNetns(cfg.HealthCheckNetns, func() error { var network string var proto int var msgType icmp.Type if isV6 { network = "ip6:ipv6-icmp" proto = 58 msgType = ipv6.ICMPTypeEchoRequest } else { network = "ip4:icmp" proto = 1 msgType = ipv4.ICMPTypeEcho } listenAddr := "" if cfg.ProbeSrc != nil { listenAddr = cfg.ProbeSrc.String() } pc, err := net.ListenPacket(network, listenAddr) if err != nil { return fmt.Errorf("listen icmp (%s): %w", network, err) } defer pc.Close() id := rand.IntN(0xffff) + 1 seq := rand.IntN(0xffff) + 1 msg := icmp.Message{ Type: msgType, Code: 0, Body: &icmp.Echo{ ID: id, Seq: seq, Data: []byte("maglev-hc"), }, } b, err := msg.Marshal(nil) if err != nil { return fmt.Errorf("marshal icmp: %w", err) } dst := &net.IPAddr{IP: cfg.Target} if _, err := pc.WriteTo(b, dst); err != nil { return fmt.Errorf("write icmp: %w", err) } deadline := time.Now().Add(cfg.Timeout) pc.SetDeadline(deadline) //nolint:errcheck buf := make([]byte, 1500) for time.Now().Before(deadline) { select { case <-ctx.Done(): return ctx.Err() default: } n, _, err := pc.ReadFrom(buf) if err != nil { if isTimeout(err) { return nil } return fmt.Errorf("read icmp: %w", err) } reply, err := icmp.ParseMessage(proto, buf[:n]) if err != nil { continue } echo, ok2 := reply.Body.(*icmp.Echo) if !ok2 { continue } if echo.ID == id && echo.Seq == seq { ok = true return nil } } return nil }) if err != nil { return health.ProbeResult{OK: false, Layer: health.LayerL7, Code: "L7TOUT", Detail: err.Error()} } if ok { return health.ProbeResult{OK: true, Layer: health.LayerL7, Code: "L7OK"} } return health.ProbeResult{OK: false, Layer: health.LayerL7, Code: "L7TOUT", Detail: "no reply received"} } func isTimeout(err error) bool { if netErr, ok := err.(net.Error); ok { return netErr.Timeout() } return false }