Initial revisin of healthchecker, inspired by HAProxy
This commit is contained in:
118
internal/prober/icmp.go
Normal file
118
internal/prober/icmp.go
Normal file
@@ -0,0 +1,118 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user