mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-08 18:37:09 +02:00
The dial-time SSRF guard runs on the resolved IP, so the tests that prove a symbolic hostname cannot reach loopback used "localhost." — a trailing dot never matches /etc/hosts, so Go queries real DNS. Machines whose resolver does not answer "localhost." (a VPN DNS, for example) got "no such host" before the dial guard ever ran, failing three specs. Add tests.StubResolver, a net.Resolver backed by an in-memory DNS responder over net.Pipe, and let the plugin dialers take a resolver so tests can inject it. Name resolution in those specs no longer depends on the machine's DNS.
54 lines
1.5 KiB
Go
54 lines
1.5 KiB
Go
package tests
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"io"
|
|
"net"
|
|
"net/netip"
|
|
|
|
"golang.org/x/net/dns/dnsmessage"
|
|
)
|
|
|
|
// StubResolver returns a net.Resolver that answers A queries from records (absolute name to IPv4,
|
|
// e.g. "localhost." to "127.0.0.1") instead of the system DNS. Other names do not resolve.
|
|
func StubResolver(records map[string]string) *net.Resolver {
|
|
return &net.Resolver{
|
|
PreferGo: true,
|
|
Dial: func(context.Context, string, string) (net.Conn, error) {
|
|
client, server := net.Pipe()
|
|
go serveStubDNS(server, records)
|
|
return client, nil
|
|
},
|
|
}
|
|
}
|
|
|
|
// net.Pipe is not a PacketConn, so the resolver sends one query framed with a 2-byte length prefix.
|
|
func serveStubDNS(conn net.Conn, records map[string]string) {
|
|
defer conn.Close()
|
|
var size [2]byte
|
|
if _, err := io.ReadFull(conn, size[:]); err != nil {
|
|
return
|
|
}
|
|
buf := make([]byte, binary.BigEndian.Uint16(size[:]))
|
|
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
return
|
|
}
|
|
var msg dnsmessage.Message
|
|
if err := msg.Unpack(buf); err != nil || len(msg.Questions) != 1 {
|
|
return
|
|
}
|
|
msg.Response = true
|
|
q := msg.Questions[0]
|
|
if ip, err := netip.ParseAddr(records[q.Name.String()]); err == nil && q.Type == dnsmessage.TypeA {
|
|
msg.Answers = []dnsmessage.Resource{{
|
|
Header: dnsmessage.ResourceHeader{Name: q.Name, Type: q.Type, Class: q.Class},
|
|
Body: &dnsmessage.AResource{A: ip.As4()},
|
|
}}
|
|
}
|
|
resp, err := msg.Pack()
|
|
if err != nil {
|
|
return
|
|
}
|
|
_, _ = conn.Write(append(binary.BigEndian.AppendUint16(nil, uint16(len(resp))), resp...))
|
|
}
|