Fix temperature monitoring on standalone Proxmox nodes (addresses #571)
Root cause: The systemd service hardening blocked AF_NETLINK sockets, preventing IP address discovery on standalone nodes. The proxy could only discover hostnames, causing node_not_cluster_member rejections when users configured Pulse with IP addresses. Changes: 1. Add AF_NETLINK to RestrictAddressFamilies in all systemd services - pulse-sensor-proxy.service - install-sensor-proxy.sh (both modes) - pulse-sensor-cleanup.service 2. Replace shell-based 'ip addr' with Go native net.Interfaces() API - More reliable and doesn't require external commands - Works even with strict systemd restrictions - Properly filters loopback, link-local, and down interfaces 3. Improve error logging and user guidance - Warn when no IP addresses can be discovered - Provide clear instructions about allowed_nodes workaround - Include address counts in logs for debugging This fix ensures standalone Proxmox nodes can properly validate temperature requests by IP address without requiring manual allowed_nodes configuration.
This commit is contained in:
parent
c8fc20dae0
commit
ca8cc0844a
4 changed files with 62 additions and 31 deletions
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -680,42 +681,64 @@ func discoverLocalHostAddresses() ([]string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all non-loopback IP addresses using ip command
|
// Get all non-loopback IP addresses using Go's native net.Interfaces API
|
||||||
cmd := exec.Command("ip", "-o", "addr", "show")
|
// This is more reliable than shelling out to 'ip addr' and works even with strict systemd restrictions
|
||||||
out, err := cmd.Output()
|
ipCount := 0
|
||||||
|
interfaces, err := net.Interfaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Msg("Failed to get IP addresses via 'ip addr'")
|
log.Warn().
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to enumerate network interfaces - temperature monitoring may require manual allowed_nodes configuration")
|
||||||
} else {
|
} else {
|
||||||
// Parse output lines like:
|
for _, iface := range interfaces {
|
||||||
// 2: eth0 inet 192.168.0.100/24 brd 192.168.0.255 scope global eth0
|
// Skip loopback interfaces
|
||||||
// 2: eth0 inet6 fe80::a00:27ff:fe4e:66a1/64 scope link
|
if iface.Flags&net.FlagLoopback != 0 {
|
||||||
lines := strings.Split(string(out), "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
fields := strings.Fields(line)
|
|
||||||
if len(fields) < 4 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Field 2 is interface, field 3 is inet/inet6, field 4 is addr/prefix
|
// Skip interfaces that are down
|
||||||
if fields[2] != "inet" && fields[2] != "inet6" {
|
if iface.Flags&net.FlagUp == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split addr/prefix (e.g., "192.168.0.100/24")
|
addrs, err := iface.Addrs()
|
||||||
addrWithPrefix := fields[3]
|
if err != nil {
|
||||||
addr := strings.Split(addrWithPrefix, "/")[0]
|
log.Debug().
|
||||||
|
Err(err).
|
||||||
// Skip loopback addresses
|
Str("interface", iface.Name).
|
||||||
if strings.HasPrefix(addr, "127.") || addr == "::1" {
|
Msg("Failed to get addresses for interface")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip link-local IPv6
|
for _, addr := range addrs {
|
||||||
if strings.HasPrefix(addr, "fe80:") {
|
var ip net.IP
|
||||||
continue
|
switch v := addr.(type) {
|
||||||
}
|
case *net.IPNet:
|
||||||
|
ip = v.IP
|
||||||
|
case *net.IPAddr:
|
||||||
|
ip = v.IP
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
addresses[addr] = struct{}{}
|
// Skip loopback addresses
|
||||||
|
if ip.IsLoopback() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip link-local IPv6 addresses
|
||||||
|
if ip.IsLinkLocalUnicast() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip unspecified addresses
|
||||||
|
if ip.IsUnspecified() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses[ip.String()] = struct{}{}
|
||||||
|
ipCount++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -729,9 +752,17 @@ func discoverLocalHostAddresses() ([]string, error) {
|
||||||
return nil, fmt.Errorf("no local host addresses found")
|
return nil, fmt.Errorf("no local host addresses found")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().
|
// Log helpful info about discovered addresses
|
||||||
|
logger := log.Info().
|
||||||
Strs("addresses", result).
|
Strs("addresses", result).
|
||||||
Msg("Discovered local host addresses for standalone node validation")
|
Int("ip_count", ipCount).
|
||||||
|
Int("hostname_count", len(result)-ipCount)
|
||||||
|
|
||||||
|
if ipCount == 0 {
|
||||||
|
logger.Msg("WARNING: No IP addresses discovered for standalone node - only hostnames available. If temperature monitoring fails with 'node_not_cluster_member' errors, add the node's IP to allowed_nodes in /etc/pulse-sensor-proxy/config.yaml")
|
||||||
|
} else {
|
||||||
|
logger.Msg("Discovered local host addresses for standalone node validation")
|
||||||
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -726,7 +726,7 @@ ProcSubset=pid
|
||||||
LockPersonality=true
|
LockPersonality=true
|
||||||
RemoveIPC=true
|
RemoveIPC=true
|
||||||
RestrictSUIDSGID=true
|
RestrictSUIDSGID=true
|
||||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||||
RestrictNamespaces=true
|
RestrictNamespaces=true
|
||||||
SystemCallFilter=@system-service
|
SystemCallFilter=@system-service
|
||||||
SystemCallErrorNumber=EPERM
|
SystemCallErrorNumber=EPERM
|
||||||
|
|
@ -786,7 +786,7 @@ ProcSubset=pid
|
||||||
LockPersonality=true
|
LockPersonality=true
|
||||||
RemoveIPC=true
|
RemoveIPC=true
|
||||||
RestrictSUIDSGID=true
|
RestrictSUIDSGID=true
|
||||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||||
RestrictNamespaces=true
|
RestrictNamespaces=true
|
||||||
SystemCallFilter=@system-service
|
SystemCallFilter=@system-service
|
||||||
SystemCallErrorNumber=EPERM
|
SystemCallErrorNumber=EPERM
|
||||||
|
|
@ -1237,7 +1237,7 @@ ProtectKernelModules=true
|
||||||
ProtectControlGroups=true
|
ProtectControlGroups=true
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
RestrictSUIDSGID=true
|
RestrictSUIDSGID=true
|
||||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||||
LimitNOFILE=1024
|
LimitNOFILE=1024
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ ProcSubset=pid
|
||||||
LockPersonality=true
|
LockPersonality=true
|
||||||
RemoveIPC=true
|
RemoveIPC=true
|
||||||
RestrictSUIDSGID=true
|
RestrictSUIDSGID=true
|
||||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||||
RestrictNamespaces=true
|
RestrictNamespaces=true
|
||||||
SystemCallFilter=@system-service
|
SystemCallFilter=@system-service
|
||||||
SystemCallErrorNumber=EPERM
|
SystemCallErrorNumber=EPERM
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ ProtectKernelModules=true
|
||||||
ProtectControlGroups=true
|
ProtectControlGroups=true
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
RestrictSUIDSGID=true
|
RestrictSUIDSGID=true
|
||||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||||
LimitNOFILE=1024
|
LimitNOFILE=1024
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue