Tres defeitos na comparacao, todos encontrados contra a zona real. Cada um fazia a consola declarar em falta um registo que existia e estava a funcionar - o que e pior do que nao dizer nada, porque manda o operador procurar um problema que nao ha. MX. A especificacao guarda a prioridade num campo proprio, portanto o valor desejado e so o host. A comparacao era feita contra "10 mail.x." como texto, e por isso o MX de um dominio que recebe correio todos os dias aparecia como inexistente. TXT longo. Um valor acima de 255 bytes e publicado em varias strings, e o resolver devolve-as entre aspas. Tirar as aspas e juntar tudo com espacos punha um espaco no meio de uma chave DKIM: todos os registos DKIM RSA da zona eram dados como em falta. As strings sao agora unidas ponta a ponta, e os espacos dentro de cada uma sobrevivem. Registos atras do proxy. Um CNAME proxied nao e publicado como CNAME - o proxy responde A. Perguntar pelo CNAME devolvia resposta vazia, e o registo era dado como em falta apesar de estar la a servir trafego. A pergunta passa a ser do tipo que o proxy responde de facto. A comparacao passa a ser por tipo em vez de uma normalizacao unica para tudo, que era a origem comum dos tres. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
97 lines
3.8 KiB
Go
97 lines
3.8 KiB
Go
package netdns
|
|
|
|
import "testing"
|
|
|
|
// Comparing a desired record against what a resolver actually answers.
|
|
//
|
|
// Every case here comes from a real zone where the console reported a record
|
|
// as missing while it was published and correct. Telling an operator that a
|
|
// record they can see in their DNS does not exist is worse than saying
|
|
// nothing: they go looking for a problem that is not there.
|
|
|
|
func TestMxIgnoresThePriorityAndTrailingDot(t *testing.T) {
|
|
// The spec carries the host in Value and the priority in its own field, so
|
|
// the desired value is the host alone. A resolver answers "10 mail.x." -
|
|
// comparing the two as strings said the MX of a domain that receives mail
|
|
// every day did not exist.
|
|
if !answerMatchesValue("MX", "10 mail.lucasrguerra.dev.br.", "mail.lucasrguerra.dev.br") {
|
|
t.Error("a published MX was reported as missing")
|
|
}
|
|
if !answerMatchesValue("MX", "20 mail.lucasrguerra.dev.br.", "mail.lucasrguerra.dev.br") {
|
|
t.Error("the priority should not decide whether the host matches")
|
|
}
|
|
// A different host is still a real difference.
|
|
if answerMatchesValue("MX", "10 outro.example.test.", "mail.lucasrguerra.dev.br") {
|
|
t.Error("a different mail host was accepted")
|
|
}
|
|
}
|
|
|
|
// A TXT value longer than 255 bytes is published as several strings, and a
|
|
// resolver renders them quoted and separated. Joining them with a space - which
|
|
// is what stripping the quotes and collapsing whitespace did - inserted a space
|
|
// into the middle of a DKIM public key, so every RSA DKIM record on the zone
|
|
// was reported as missing.
|
|
func TestLongTxtIsJoinedWithoutASeparator(t *testing.T) {
|
|
published := `"v=DKIM1; k=rsa; p=AAAA" "BBBB"`
|
|
if !answerMatchesValue("TXT", published, "v=DKIM1; k=rsa; p=AAAABBBB") {
|
|
t.Error("a split TXT record was reported as missing")
|
|
}
|
|
}
|
|
|
|
func TestShortTxtStillMatches(t *testing.T) {
|
|
if !answerMatchesValue("TXT", `"v=spf1 mx -all"`, "v=spf1 mx -all") {
|
|
t.Error("a single-string TXT was reported as missing")
|
|
}
|
|
}
|
|
|
|
// The spaces inside a policy string are meaningful and must survive.
|
|
func TestTxtKeepsItsOwnSpaces(t *testing.T) {
|
|
if !answerMatchesValue("TXT", `"v=spf1 include:_spf.example.test -all"`,
|
|
"v=spf1 include:_spf.example.test -all") {
|
|
t.Error("a TXT with real spaces stopped matching")
|
|
}
|
|
if answerMatchesValue("TXT", `"v=spf1 mx -all"`, "v=spf1 mx ~all") {
|
|
t.Error("a genuinely different policy was accepted")
|
|
}
|
|
}
|
|
|
|
func TestCnameIgnoresTheTrailingDot(t *testing.T) {
|
|
if !answerMatchesValue("CNAME", "mail.lucasrguerra.dev.br.", "mail.lucasrguerra.dev.br") {
|
|
t.Error("a published CNAME was reported as missing")
|
|
}
|
|
}
|
|
|
|
func TestCaseDoesNotDecide(t *testing.T) {
|
|
if !answerMatchesValue("CNAME", "MAIL.LUCASRGUERRA.DEV.BR.", "mail.lucasrguerra.dev.br") {
|
|
t.Error("a name in another case was reported as missing")
|
|
}
|
|
}
|
|
|
|
func TestAValueIsCompared(t *testing.T) {
|
|
if !answerMatchesValue("A", "203.0.113.10", "203.0.113.10") {
|
|
t.Error("a matching A record was reported as missing")
|
|
}
|
|
if answerMatchesValue("A", "203.0.113.99", "203.0.113.10") {
|
|
t.Error("a different address was accepted")
|
|
}
|
|
}
|
|
|
|
// A record behind the provider's proxy is not published as the type it was
|
|
// created as: a proxied CNAME answers A, carrying the proxy's own addresses.
|
|
// Asking for the CNAME therefore comes back empty, and the console reported
|
|
// the record as missing while it was there and serving traffic.
|
|
func TestProxiedCnameIsQueriedAsTheTypeTheProxyAnswers(t *testing.T) {
|
|
proxied := queryTypeFor("CNAME", true)
|
|
if proxied != "A" {
|
|
t.Errorf("a proxied CNAME is queried as %s; the proxy answers A", proxied)
|
|
}
|
|
// Without the proxy it is still a CNAME question.
|
|
if plain := queryTypeFor("CNAME", false); plain != "CNAME" {
|
|
t.Errorf("an ordinary CNAME is queried as %s", plain)
|
|
}
|
|
// Other types are unaffected: a proxied A is still an A.
|
|
if a := queryTypeFor("A", true); a != "A" {
|
|
t.Errorf("a proxied A is queried as %s", a)
|
|
}
|
|
}
|