2024-04-03 17:33:02 -04:00
|
|
|
package hcdns_test
|
2024-04-02 18:26:39 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
2024-04-02 18:51:13 -04:00
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
2024-04-02 18:26:39 -04:00
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"git.hatecomputers.club/hatecomputers/hatecomputers.club/args"
|
|
|
|
"git.hatecomputers.club/hatecomputers/hatecomputers.club/database"
|
|
|
|
"git.hatecomputers.club/hatecomputers/hatecomputers.club/hcdns"
|
2024-04-02 18:51:13 -04:00
|
|
|
"git.hatecomputers.club/hatecomputers/hatecomputers.club/utils"
|
2024-04-02 18:26:39 -04:00
|
|
|
"github.com/miekg/dns"
|
|
|
|
)
|
|
|
|
|
2024-04-02 18:51:13 -04:00
|
|
|
func randomPort() int {
|
2024-04-03 17:33:02 -04:00
|
|
|
return rand.Intn(3000) + 5192
|
2024-04-02 18:51:13 -04:00
|
|
|
}
|
2024-04-02 18:26:39 -04:00
|
|
|
|
2024-04-07 19:04:43 -04:00
|
|
|
func setup(arguments *args.Arguments) (*sql.DB, *dns.Server, string, func()) {
|
2024-04-02 18:51:13 -04:00
|
|
|
randomDb := utils.RandomId()
|
|
|
|
|
|
|
|
testDb := database.MakeConn(&randomDb)
|
2024-04-02 18:26:39 -04:00
|
|
|
database.Migrate(testDb)
|
|
|
|
testUser := &database.User{
|
|
|
|
ID: "test",
|
|
|
|
}
|
2024-04-09 18:39:14 -04:00
|
|
|
database.FindOrSaveBaseUser(testDb, testUser)
|
2024-04-02 18:26:39 -04:00
|
|
|
|
2024-04-07 19:04:43 -04:00
|
|
|
dnsArguments := arguments
|
|
|
|
if dnsArguments == nil {
|
|
|
|
dnsArguments = &args.Arguments{
|
|
|
|
DnsPort: randomPort(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-02 22:26:24 -04:00
|
|
|
waitLock := &sync.Mutex{}
|
2024-04-07 19:04:43 -04:00
|
|
|
server := hcdns.MakeServer(dnsArguments, testDb)
|
2024-04-02 22:26:24 -04:00
|
|
|
server.NotifyStartedFunc = func() {
|
|
|
|
waitLock.Unlock()
|
|
|
|
}
|
|
|
|
waitLock.Lock()
|
2024-04-02 18:26:39 -04:00
|
|
|
|
|
|
|
go func() {
|
|
|
|
server.ListenAndServe()
|
|
|
|
}()
|
2024-04-02 22:26:24 -04:00
|
|
|
waitLock.Lock()
|
2024-04-02 18:26:39 -04:00
|
|
|
|
2024-04-07 19:04:43 -04:00
|
|
|
address := fmt.Sprintf("127.0.0.1:%d", dnsArguments.DnsPort)
|
|
|
|
return testDb, server, address, func() {
|
|
|
|
waitLock.Unlock()
|
2024-04-02 22:26:24 -04:00
|
|
|
server.Shutdown()
|
|
|
|
|
2024-04-02 18:51:13 -04:00
|
|
|
testDb.Close()
|
|
|
|
os.Remove(randomDb)
|
|
|
|
}
|
2024-04-02 18:26:39 -04:00
|
|
|
}
|
|
|
|
|
2024-04-07 19:04:43 -04:00
|
|
|
func TestWhenCNAMEIsResolved(t *testing.T) {
|
|
|
|
testDb, _, addr, cleanup := setup(nil)
|
2024-04-02 18:51:13 -04:00
|
|
|
defer cleanup()
|
2024-04-02 18:26:39 -04:00
|
|
|
|
2024-04-03 17:33:02 -04:00
|
|
|
records := []*database.DNSRecord{
|
|
|
|
{
|
|
|
|
ID: "0",
|
|
|
|
UserID: "test",
|
|
|
|
Name: "cname.internal.example.com.",
|
|
|
|
Type: "CNAME",
|
|
|
|
Content: "next.internal.example.com.",
|
|
|
|
TTL: 300,
|
|
|
|
Internal: true,
|
|
|
|
}, {
|
|
|
|
ID: "1",
|
|
|
|
UserID: "test",
|
|
|
|
Name: "next.internal.example.com.",
|
|
|
|
Type: "CNAME",
|
|
|
|
Content: "res.example.com.",
|
|
|
|
TTL: 300,
|
|
|
|
Internal: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ID: "2",
|
|
|
|
UserID: "test",
|
|
|
|
Name: "res.example.com.",
|
|
|
|
Type: "A",
|
|
|
|
Content: "1.2.3.2",
|
|
|
|
TTL: 300,
|
|
|
|
Internal: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, record := range records {
|
|
|
|
database.SaveDNSRecord(testDb, record)
|
2024-04-02 18:26:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
qtype := dns.TypeA
|
2024-04-03 17:33:02 -04:00
|
|
|
domain := dns.Fqdn("cname.internal.example.com.")
|
2024-04-02 22:26:24 -04:00
|
|
|
client := &dns.Client{}
|
|
|
|
message := &dns.Msg{}
|
2024-04-02 18:26:39 -04:00
|
|
|
message.SetQuestion(domain, qtype)
|
|
|
|
|
2024-04-07 19:04:43 -04:00
|
|
|
in, _, err := client.Exchange(message, addr)
|
2024-04-02 18:26:39 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2024-04-03 17:33:02 -04:00
|
|
|
if len(in.Answer) != 3 {
|
|
|
|
t.Fatalf("expected 3 answers, got %d", len(in.Answer))
|
2024-04-02 18:26:39 -04:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:33:02 -04:00
|
|
|
for i, record := range records {
|
|
|
|
if in.Answer[i].Header().Name != record.Name {
|
|
|
|
t.Fatalf("expected %s, got %s", record.Name, in.Answer[i].Header().Name)
|
|
|
|
}
|
2024-04-02 18:26:39 -04:00
|
|
|
|
2024-04-03 17:33:02 -04:00
|
|
|
if in.Answer[i].Header().Rrtype != dns.StringToType[record.Type] {
|
|
|
|
t.Fatalf("expected %s, got %d", record.Type, in.Answer[i].Header().Rrtype)
|
|
|
|
}
|
2024-04-02 18:26:39 -04:00
|
|
|
|
2024-04-03 17:33:02 -04:00
|
|
|
if int(in.Answer[i].Header().Ttl) != record.TTL {
|
|
|
|
t.Fatalf("expected %d, got %d", record.TTL, in.Answer[i].Header().Ttl)
|
|
|
|
}
|
2024-04-02 18:26:39 -04:00
|
|
|
|
2024-04-03 17:33:02 -04:00
|
|
|
if !in.Authoritative {
|
|
|
|
t.Fatalf("expected authoritative response")
|
|
|
|
}
|
2024-04-02 18:26:39 -04:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:33:02 -04:00
|
|
|
if in.Answer[2].(*dns.A).A.String() != "1.2.3.2" {
|
|
|
|
t.Fatalf("expected final record to be the A record with correct IP")
|
2024-04-02 18:26:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestWhenNoRecordNxDomain(t *testing.T) {
|
2024-04-07 19:04:43 -04:00
|
|
|
_, _, addr, cleanup := setup(nil)
|
2024-04-02 18:51:13 -04:00
|
|
|
defer cleanup()
|
2024-04-02 18:26:39 -04:00
|
|
|
|
|
|
|
qtype := dns.TypeA
|
|
|
|
domain := dns.Fqdn("nonexistant.example.com.")
|
2024-04-02 22:26:24 -04:00
|
|
|
client := &dns.Client{}
|
|
|
|
message := &dns.Msg{}
|
2024-04-02 18:26:39 -04:00
|
|
|
message.SetQuestion(domain, qtype)
|
|
|
|
|
2024-04-07 19:04:43 -04:00
|
|
|
in, _, err := client.Exchange(message, addr)
|
2024-04-02 18:26:39 -04:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(in.Answer) != 0 {
|
|
|
|
t.Fatalf("expected 0 answers, got %d", len(in.Answer))
|
|
|
|
}
|
|
|
|
|
|
|
|
if in.Rcode != dns.RcodeNameError {
|
|
|
|
t.Fatalf("expected NXDOMAIN, got %d", in.Rcode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestWhenUnresolvingCNAME(t *testing.T) {
|
2024-04-07 19:04:43 -04:00
|
|
|
testDb, _, addr, cleanup := setup(nil)
|
2024-04-02 18:51:13 -04:00
|
|
|
defer cleanup()
|
2024-04-02 18:26:39 -04:00
|
|
|
|
|
|
|
cname := &database.DNSRecord{
|
|
|
|
ID: "1",
|
|
|
|
UserID: "test",
|
|
|
|
Name: "cname.internal.example.com.",
|
|
|
|
Type: "CNAME",
|
|
|
|
Content: "nonexistant.example.com.",
|
|
|
|
TTL: 300,
|
|
|
|
Internal: true,
|
|
|
|
}
|
|
|
|
database.SaveDNSRecord(testDb, cname)
|
|
|
|
|
|
|
|
qtype := dns.TypeA
|
|
|
|
domain := dns.Fqdn(cname.Name)
|
2024-04-02 22:26:24 -04:00
|
|
|
client := &dns.Client{}
|
|
|
|
message := &dns.Msg{}
|
2024-04-02 18:26:39 -04:00
|
|
|
message.SetQuestion(domain, qtype)
|
|
|
|
|
2024-04-07 19:04:43 -04:00
|
|
|
in, _, err := client.Exchange(message, addr)
|
2024-04-02 18:26:39 -04:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(in.Answer) != 1 {
|
|
|
|
t.Fatalf("expected 1 answer, got %d", len(in.Answer))
|
|
|
|
}
|
|
|
|
|
|
|
|
if !in.Authoritative {
|
|
|
|
t.Fatalf("expected authoritative response")
|
|
|
|
}
|
|
|
|
|
|
|
|
if in.Answer[0].Header().Name != cname.Name {
|
|
|
|
t.Fatalf("expected cname.internal.example.com., got %s", in.Answer[0].Header().Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
if in.Answer[0].Header().Rrtype != dns.TypeCNAME {
|
|
|
|
t.Fatalf("expected CNAME, got %d", in.Answer[0].Header().Rrtype)
|
|
|
|
}
|
|
|
|
|
|
|
|
if in.Answer[0].(*dns.CNAME).Target != cname.Content {
|
|
|
|
t.Fatalf("expected nonexistant.example.com., got %s", in.Answer[0].(*dns.CNAME).Target)
|
|
|
|
}
|
|
|
|
|
|
|
|
if in.Rcode == dns.RcodeNameError {
|
|
|
|
t.Fatalf("expected no NXDOMAIN, got %d", in.Rcode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestWhenUnresolvingCNAMEWithMaxDepth(t *testing.T) {
|
2024-04-07 19:04:43 -04:00
|
|
|
testDb, _, addr, cleanup := setup(nil)
|
2024-04-02 18:51:13 -04:00
|
|
|
defer cleanup()
|
2024-04-02 18:26:39 -04:00
|
|
|
|
|
|
|
cname := &database.DNSRecord{
|
|
|
|
ID: "1",
|
|
|
|
UserID: "test",
|
|
|
|
Name: "cname.internal.example.com.",
|
|
|
|
Type: "CNAME",
|
|
|
|
Content: "cname.internal.example.com.",
|
|
|
|
TTL: 300,
|
|
|
|
Internal: true,
|
|
|
|
}
|
|
|
|
database.SaveDNSRecord(testDb, cname)
|
|
|
|
|
|
|
|
qtype := dns.TypeA
|
|
|
|
domain := dns.Fqdn(cname.Name)
|
2024-04-02 22:26:24 -04:00
|
|
|
client := &dns.Client{}
|
|
|
|
message := &dns.Msg{}
|
2024-04-02 18:26:39 -04:00
|
|
|
message.SetQuestion(domain, qtype)
|
|
|
|
|
2024-04-07 19:04:43 -04:00
|
|
|
in, _, err := client.Exchange(message, addr)
|
2024-04-02 18:26:39 -04:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(in.Answer) > 0 {
|
|
|
|
t.Fatalf("expected 0 answers, got %d", len(in.Answer))
|
|
|
|
}
|
2024-04-02 22:26:24 -04:00
|
|
|
|
2024-04-02 18:26:39 -04:00
|
|
|
if in.Rcode != dns.RcodeServerFailure {
|
|
|
|
t.Fatalf("expected SERVFAIL, got %d", in.Rcode)
|
|
|
|
}
|
|
|
|
}
|
2024-04-07 21:02:42 -04:00
|
|
|
|
|
|
|
func TestWhenExternalDomain(t *testing.T) {
|
|
|
|
externalDb, _, externalAddr, externalCleanup := setup(nil)
|
|
|
|
internalDb, _, internalAddr, internalCleanup := setup(&args.Arguments{
|
|
|
|
DnsPort: randomPort(),
|
|
|
|
DnsResolvers: []string{externalAddr},
|
|
|
|
})
|
|
|
|
defer internalCleanup()
|
|
|
|
defer externalCleanup()
|
|
|
|
|
|
|
|
authoritativeRecords := []database.DNSRecord{
|
|
|
|
{
|
|
|
|
ID: "1",
|
|
|
|
UserID: "test",
|
|
|
|
Name: "external.example.com.",
|
|
|
|
Type: "CNAME",
|
|
|
|
Content: "external.internal.example.com.",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ID: "2",
|
|
|
|
UserID: "test",
|
|
|
|
Name: "final.example.com.",
|
|
|
|
Type: "A",
|
|
|
|
Content: "127.0.0.1",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
internalRecords := []database.DNSRecord{
|
|
|
|
{
|
|
|
|
ID: "1",
|
|
|
|
UserID: "test",
|
|
|
|
Name: "external.internal.example.com.",
|
|
|
|
Type: "CNAME",
|
|
|
|
Content: "final.example.com",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ID: "2",
|
|
|
|
UserID: "test",
|
|
|
|
Name: "test.internal.example.com.",
|
|
|
|
Type: "CNAME",
|
|
|
|
Content: "external.example.com.",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, record := range authoritativeRecords {
|
|
|
|
database.SaveDNSRecord(externalDb, &record)
|
|
|
|
}
|
|
|
|
for _, record := range internalRecords {
|
|
|
|
database.SaveDNSRecord(internalDb, &record)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensure that if the record doesn't exist in the internal database, it will
|
|
|
|
// go and query the external dns resolvers, then loop back to the internal
|
|
|
|
|
|
|
|
qtype := dns.TypeA
|
|
|
|
domain := dns.Fqdn("test.internal.example.com.")
|
|
|
|
client := &dns.Client{}
|
|
|
|
message := &dns.Msg{}
|
|
|
|
message.SetQuestion(domain, qtype)
|
|
|
|
|
|
|
|
in, _, err := client.Exchange(message, internalAddr)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(in.Answer) != 4 {
|
|
|
|
t.Fatalf("expected 4 answers, got %d", len(in.Answer))
|
|
|
|
}
|
|
|
|
|
|
|
|
aRecord := in.Answer[3]
|
|
|
|
if aRecord.Header().Name != authoritativeRecords[1].Name {
|
|
|
|
t.Fatalf("expected %s, got %s", domain, aRecord.Header().Name)
|
|
|
|
}
|
|
|
|
if aRecord.Header().Rrtype != dns.TypeA {
|
|
|
|
t.Fatalf("expected %s, got %s", dns.TypeToString[aRecord.Header().Rrtype], internalRecords[1].Type)
|
|
|
|
}
|
|
|
|
if aRecord.(*dns.A).A.String() != authoritativeRecords[1].Content {
|
|
|
|
t.Fatalf("expected %s, got %s", authoritativeRecords[1].Content, aRecord.(*dns.A).A.String())
|
|
|
|
}
|
|
|
|
if in.Authoritative {
|
|
|
|
t.Fatalf("expected non-authoritative response")
|
|
|
|
}
|
|
|
|
}
|