Add per-node ping monitoring with unreachable error tracking

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ian Gulliver
2026-01-25 18:56:12 -08:00
parent a94f816f3d
commit c8be46b739
4 changed files with 172 additions and 17 deletions

89
ping.go Normal file
View File

@@ -0,0 +1,89 @@
package tendrils
import (
"net"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
func (t *Tendrils) pingNode(node *Node) {
t.nodes.mu.RLock()
var ips []string
for _, iface := range node.Interfaces {
for ipStr := range iface.IPs {
ip := net.ParseIP(ipStr)
if ip != nil && ip.To4() != nil {
ips = append(ips, ipStr)
}
}
}
t.nodes.mu.RUnlock()
if len(ips) == 0 {
return
}
for _, ipStr := range ips {
reachable := t.pingIP(ipStr)
if reachable {
t.errors.ClearUnreachable(node, ipStr)
} else {
t.errors.SetUnreachable(node, ipStr)
}
}
}
func (t *Tendrils) pingIP(ipStr string) bool {
conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return false
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(500 * time.Millisecond))
ip := net.ParseIP(ipStr)
seq := uint16(time.Now().UnixNano() & 0xFFFF)
msg := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: int(seq),
Seq: 1,
Data: []byte("tendrils"),
},
}
msgBytes, err := msg.Marshal(nil)
if err != nil {
return false
}
_, err = conn.WriteTo(msgBytes, &net.IPAddr{IP: ip})
if err != nil {
return false
}
buf := make([]byte, 1500)
for {
n, peer, err := conn.ReadFrom(buf)
if err != nil {
return false
}
parsed, err := icmp.ParseMessage(1, buf[:n])
if err != nil {
continue
}
if parsed.Type == ipv4.ICMPTypeEchoReply {
if ipAddr, ok := peer.(*net.IPAddr); ok {
if ipAddr.IP.String() == ipStr {
return true
}
}
}
}
}