Let jarld recognize this host's addresses under systemd
release / release (push) Successful in 1m58s

The unit's RestrictAddressFamilies lacked AF_NETLINK, which listing the
host's addresses needs, so the name server checks didn't know this
server's own addresses: the Name servers tab queried them like another
server's, and the delegation check didn't sign them with the view key.
Allow AF_NETLINK, and fall back to /proc/net without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-11 18:35:06 +01:00
1 parent 9d9ae37367
commit ecffc2d5c1
4 files changed
+117 -10

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
package nscheck
import (
"net/netip"
"os"
"path/filepath"
"slices"
"testing"
)
// Without netlink (a sandboxed jarld), the host's addresses come from /proc.
func TestProcAddrs(t *testing.T) {
dir := t.TempDir()
os.WriteFile(filepath.Join(dir, "if_inet6"), []byte(
"00000000000000000000000000000001 01 80 10 80 lo\n"+
"2a0280137f3d00100000000000000005 02 40 00 80 eth0\n"), 0o644)
os.WriteFile(filepath.Join(dir, "fib_trie"), []byte(`Main:
+-- 0.0.0.0/0 3 0 5
|-- 0.0.0.0
/0 universe UNICAST
+-- 10.0.10.0/24 2 0 2
|-- 10.0.10.0
/24 link UNICAST
|-- 10.0.10.5
/32 host LOCAL
|-- 10.0.10.255
/32 link BROADCAST
Local:
+-- 0.0.0.0/0 3 0 5
|-- 10.0.10.5
/32 host LOCAL
`), 0o644)
got := procAddrs(dir)
for _, want := range []string{"::1", "2a02:8013:7f3d:10::5", "10.0.10.5"} {
if !slices.Contains(got, netip.MustParseAddr(want)) {
t.Errorf("%s missing from %v", want, got)
}
}
if len(got) != 3 {
t.Errorf("got %v (10.0.10.5 once, no broadcast or network addresses)", got)
}
// And the real ones on this machine: at least loopback.
if len(procAddrs("/proc/net")) == 0 {
t.Log("no addresses in /proc/net here")
}
}
+59 -9
View File
@@ -15,10 +15,13 @@ package nscheck
import (
"context"
"encoding/hex"
"errors"
"fmt"
"net"
"net/netip"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -257,20 +260,67 @@ func IsLocalAddr(a netip.Addr) bool {
if a.IsLoopback() {
return true
}
addrs, err := net.InterfaceAddrs()
if err != nil {
return false
}
for _, ia := range addrs {
if n, ok := ia.(*net.IPNet); ok {
if b, ok := netip.AddrFromSlice(n.IP); ok && b.Unmap() == a.Unmap() {
return true
}
for _, b := range LocalAddrs() {
if b == a.Unmap() {
return true
}
}
return false
}
// LocalAddrs returns this host's interface addresses. Listing them needs a
// netlink socket; where that is not allowed (a systemd unit without
// AF_NETLINK in RestrictAddressFamilies), they are read from /proc.
func LocalAddrs() []netip.Addr {
var out []netip.Addr
if addrs, err := net.InterfaceAddrs(); err == nil {
for _, ia := range addrs {
if n, ok := ia.(*net.IPNet); ok {
if b, ok := netip.AddrFromSlice(n.IP); ok {
out = append(out, b.Unmap())
}
}
}
return out
}
return procAddrs("/proc/net")
}
// procAddrs reads the host's addresses from /proc/net: IPv6 from
// if_inet6, IPv4 from the local (/32 host LOCAL) entries of fib_trie.
func procAddrs(dir string) []netip.Addr {
var out []netip.Addr
if b, err := os.ReadFile(filepath.Join(dir, "if_inet6")); err == nil {
for _, line := range strings.Split(string(b), "\n") {
f := strings.Fields(line)
if len(f) == 0 || len(f[0]) != 32 {
continue
}
raw, err := hex.DecodeString(f[0])
if err != nil {
continue
}
if a, ok := netip.AddrFromSlice(raw); ok {
out = append(out, a)
}
}
}
if b, err := os.ReadFile(filepath.Join(dir, "fib_trie")); err == nil {
seen := map[netip.Addr]bool{}
var last netip.Addr
for _, line := range strings.Split(string(b), "\n") {
t := strings.TrimSpace(line)
if strings.HasPrefix(t, "|-- ") {
last, _ = netip.ParseAddr(strings.TrimPrefix(t, "|-- "))
} else if strings.HasPrefix(t, "/32 host LOCAL") && last.IsValid() && !seen[last] {
seen[last] = true
out = append(out, last)
}
}
}
return out
}
// SystemResolver returns the first nameserver in /etc/resolv.conf as
// host:port.
func SystemResolver() (string, error) {