Skip to content

Commit

Permalink
Add PiHole IPv6/AAAA tests/documentation
Browse files Browse the repository at this point in the history
Signed-off-by: PseudoResonance <kaio11604@gmail.com>
  • Loading branch information
PseudoResonance committed Apr 10, 2024
1 parent 7b43890 commit a48287b
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 27 deletions.
4 changes: 2 additions & 2 deletions docs/tutorials/pihole.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Setting up ExternalDNS for Pi-hole

This tutorial describes how to setup ExternalDNS to sync records with Pi-hole's Custom DNS.
Pi-hole has an internal list it checks last when resolving requests. This list can contain any number of arbitrary A or CNAME records.
Pi-hole has an internal list it checks last when resolving requests. This list can contain any number of arbitrary A, AAAA or CNAME records.
There is a pseudo-API exposed that ExternalDNS is able to use to manage these records.

__NOTE:__ Your Pi-hole must be running [version 5.9 or newer](https://pi-hole.net/blog/2022/02/12/pi-hole-ftl-v5-14-web-v5-11-and-core-v5-9-released).
Expand Down Expand Up @@ -91,7 +91,7 @@ spec:
args:
- --source=service
- --source=ingress
# Pihole only supports A/CNAME records so there is no mechanism to track ownership.
# Pihole only supports A/AAAA/CNAME records so there is no mechanism to track ownership.
# You don't need to set this flag, but if you leave it unset, you will receive warning
# logs when ExternalDNS attempts to create TXT records.
- --registry=noop
Expand Down
80 changes: 79 additions & 1 deletion provider/pihole/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,16 @@ func TestListRecords(t *testing.T) {
`))
return
}
// Pihole makes no distinction between A and AAAA records
w.Write([]byte(`
{
"data": [
["test1.example.com", "192.168.1.1"],
["test2.example.com", "192.168.1.2"],
["test3.match.com", "192.168.1.3"]
["test3.match.com", "192.168.1.3"],
["test1.example.com", "fc00::1:192:168:1:1"],
["test2.example.com", "fc00::1:192:168:1:2"],
["test3.match.com", "fc00::1:192:168:1:3"]
]
}
`))
Expand Down Expand Up @@ -157,6 +161,29 @@ func TestListRecords(t *testing.T) {
}
}

// Test retrieve AAAA records unfiltered
arecs, err = cl.listRecords(context.Background(), endpoint.RecordTypeAAAA)
if err != nil {
t.Fatal(err)
}
if len(arecs) != 3 {
t.Fatal("Expected 3 AAAA records returned, got:", len(arecs))
}
// Ensure records were parsed correctly
expected = [][]string{
{"test1.example.com", "fc00::1:192:168:1:1"},
{"test2.example.com", "fc00::1:192:168:1:2"},
{"test3.match.com", "fc00::1:192:168:1:3"},
}
for idx, rec := range arecs {
if rec.DNSName != expected[idx][0] {
t.Error("Got invalid DNS Name:", rec.DNSName, "expected:", expected[idx][0])
}
if rec.Targets[0] != expected[idx][1] {
t.Error("Got invalid target:", rec.Targets[0], "expected:", expected[idx][1])
}
}

// Test retrieve CNAME records unfiltered
cnamerecs, err := cl.listRecords(context.Background(), endpoint.RecordTypeCNAME)
if err != nil {
Expand Down Expand Up @@ -209,6 +236,27 @@ func TestListRecords(t *testing.T) {
}
}

// Test retrieve AAAA records filtered
arecs, err = cl.listRecords(context.Background(), endpoint.RecordTypeAAAA)
if err != nil {
t.Fatal(err)
}
if len(arecs) != 1 {
t.Fatal("Expected 1 AAAA record returned, got:", len(arecs))
}
// Ensure records were parsed correctly
expected = [][]string{
{"test3.match.com", "fc00::1:192:168:1:3"},
}
for idx, rec := range arecs {
if rec.DNSName != expected[idx][0] {
t.Error("Got invalid DNS Name:", rec.DNSName, "expected:", expected[idx][0])
}
if rec.Targets[0] != expected[idx][1] {
t.Error("Got invalid target:", rec.Targets[0], "expected:", expected[idx][1])
}
}

// Test retrieve CNAME records filtered
cnamerecs, err = cl.listRecords(context.Background(), endpoint.RecordTypeCNAME)
if err != nil {
Expand Down Expand Up @@ -246,6 +294,11 @@ func TestCreateRecord(t *testing.T) {
if r.Form.Get("ip") != ep.Targets[0] {
t.Error("Invalid ip in form:", r.Form.Get("ip"), "Expected:", ep.Targets[0])
}
// Pihole makes no distinction between A and AAAA records
case endpoint.RecordTypeAAAA:
if r.Form.Get("ip") != ep.Targets[0] {
t.Error("Invalid ip in form:", r.Form.Get("ip"), "Expected:", ep.Targets[0])
}
case endpoint.RecordTypeCNAME:
if r.Form.Get("target") != ep.Targets[0] {
t.Error("Invalid target in form:", r.Form.Get("target"), "Expected:", ep.Targets[0])
Expand Down Expand Up @@ -281,6 +334,16 @@ func TestCreateRecord(t *testing.T) {
t.Fatal(err)
}

// Test create AAAA record
ep = &endpoint.Endpoint{
DNSName: "test.example.com",
Targets: []string{"fc00::1:192:168:1:1"},
RecordType: endpoint.RecordTypeAAAA,
}
if err := cl.createRecord(context.Background(), ep); err != nil {
t.Fatal(err)
}

// Test create CNAME record
ep = &endpoint.Endpoint{
DNSName: "test.example.com",
Expand All @@ -307,6 +370,11 @@ func TestDeleteRecord(t *testing.T) {
if r.Form.Get("ip") != ep.Targets[0] {
t.Error("Invalid ip in form:", r.Form.Get("ip"), "Expected:", ep.Targets[0])
}
// Pihole makes no distinction between A and AAAA records
case endpoint.RecordTypeAAAA:
if r.Form.Get("ip") != ep.Targets[0] {
t.Error("Invalid ip in form:", r.Form.Get("ip"), "Expected:", ep.Targets[0])
}
case endpoint.RecordTypeCNAME:
if r.Form.Get("target") != ep.Targets[0] {
t.Error("Invalid target in form:", r.Form.Get("target"), "Expected:", ep.Targets[0])
Expand Down Expand Up @@ -342,6 +410,16 @@ func TestDeleteRecord(t *testing.T) {
t.Fatal(err)
}

// Test delete AAAA record
ep = &endpoint.Endpoint{
DNSName: "test.example.com",
Targets: []string{"fc00::1:192:168:1:1"},
RecordType: endpoint.RecordTypeAAAA,
}
if err := cl.deleteRecord(context.Background(), ep); err != nil {
t.Fatal(err)
}

// Test delete CNAME record
ep = &endpoint.Endpoint{
DNSName: "test.example.com",
Expand Down
134 changes: 110 additions & 24 deletions provider/pihole/pihole_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@ func TestProvider(t *testing.T) {
Targets: []string{"192.168.1.3"},
RecordType: endpoint.RecordTypeA,
},
{
DNSName: "test1.example.com",
Targets: []string{"fc00::1:192:168:1:1"},
RecordType: endpoint.RecordTypeAAAA,
},
{
DNSName: "test2.example.com",
Targets: []string{"fc00::1:192:168:1:2"},
RecordType: endpoint.RecordTypeAAAA,
},
{
DNSName: "test3.example.com",
Targets: []string{"fc00::1:192:168:1:3"},
RecordType: endpoint.RecordTypeAAAA,
},
}
if err := p.ApplyChanges(context.Background(), &plan.Changes{
Create: records,
Expand All @@ -125,11 +140,11 @@ func TestProvider(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if len(newRecords) != 3 {
t.Fatal("Expected list of 3 records, got:", records)
if len(newRecords) != 6 {
t.Fatal("Expected list of 6 records, got:", records)
}
if len(requests.createRequests) != 3 {
t.Fatal("Expected 3 create requests, got:", requests.createRequests)
if len(requests.createRequests) != 6 {
t.Fatal("Expected 6 create requests, got:", requests.createRequests)
}
if len(requests.deleteRequests) != 0 {
t.Fatal("Expected no delete requests, got:", requests.deleteRequests)
Expand Down Expand Up @@ -163,15 +178,37 @@ func TestProvider(t *testing.T) {
Targets: []string{"192.168.1.2"},
RecordType: endpoint.RecordTypeA,
},
{
DNSName: "test1.example.com",
Targets: []string{"fc00::1:192:168:1:1"},
RecordType: endpoint.RecordTypeAAAA,
},
{
DNSName: "test2.example.com",
Targets: []string{"fc00::1:192:168:1:2"},
RecordType: endpoint.RecordTypeAAAA,
},
}
recordToDelete := endpoint.Endpoint{
recordToDeleteA := endpoint.Endpoint{
DNSName: "test3.example.com",
Targets: []string{"192.168.1.3"},
RecordType: endpoint.RecordTypeA,
}
if err := p.ApplyChanges(context.Background(), &plan.Changes{
Delete: []*endpoint.Endpoint{
&recordToDelete,
&recordToDeleteA,
},
}); err != nil {
t.Fatal(err)
}
recordToDeleteAAAA := endpoint.Endpoint{
DNSName: "test3.example.com",
Targets: []string{"fc00::1:192:168:1:3"},
RecordType: endpoint.RecordTypeAAAA,
}
if err := p.ApplyChanges(context.Background(), &plan.Changes{
Delete: []*endpoint.Endpoint{
&recordToDeleteAAAA,
},
}); err != nil {
t.Fatal(err)
Expand All @@ -182,14 +219,14 @@ func TestProvider(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if len(newRecords) != 2 {
t.Fatal("Expected list of 2 records, got:", records)
if len(newRecords) != 4 {
t.Fatal("Expected list of 4 records, got:", records)
}
if len(requests.createRequests) != 0 {
t.Fatal("Expected no create requests, got:", requests.createRequests)
}
if len(requests.deleteRequests) != 1 {
t.Fatal("Expected 1 delete request, got:", requests.deleteRequests)
if len(requests.deleteRequests) != 2 {
t.Fatal("Expected 2 delete request, got:", requests.deleteRequests)
}

for idx, record := range records {
Expand All @@ -201,8 +238,11 @@ func TestProvider(t *testing.T) {
}
}

if !reflect.DeepEqual(requests.deleteRequests[0], &recordToDelete) {
t.Error("Unexpected delete request, got:", requests.deleteRequests[0], "expected:", recordToDelete)
if !reflect.DeepEqual(requests.deleteRequests[0], &recordToDeleteA) {
t.Error("Unexpected delete request, got:", requests.deleteRequests[0], "expected:", recordToDeleteA)
}
if !reflect.DeepEqual(requests.deleteRequests[1], &recordToDeleteAAAA) {
t.Error("Unexpected delete request, got:", requests.deleteRequests[0], "expected:", recordToDeleteAAAA)
}

requests.clear()
Expand All @@ -220,6 +260,16 @@ func TestProvider(t *testing.T) {
Targets: []string{"10.0.0.1"},
RecordType: endpoint.RecordTypeA,
},
{
DNSName: "test1.example.com",
Targets: []string{"fc00::1:192:168:1:1"},
RecordType: endpoint.RecordTypeAAAA,
},
{
DNSName: "test2.example.com",
Targets: []string{"fc00::1:10:0:0:1"},
RecordType: endpoint.RecordTypeAAAA,
},
}
if err := p.ApplyChanges(context.Background(), &plan.Changes{
UpdateOld: []*endpoint.Endpoint{
Expand All @@ -233,6 +283,16 @@ func TestProvider(t *testing.T) {
Targets: []string{"192.168.1.2"},
RecordType: endpoint.RecordTypeA,
},
{
DNSName: "test1.example.com",
Targets: []string{"fc00::1:192:168:1:1"},
RecordType: endpoint.RecordTypeAAAA,
},
{
DNSName: "test2.example.com",
Targets: []string{"fc00::1:192:168:1:2"},
RecordType: endpoint.RecordTypeAAAA,
},
},
UpdateNew: []*endpoint.Endpoint{
{
Expand All @@ -245,6 +305,16 @@ func TestProvider(t *testing.T) {
Targets: []string{"10.0.0.1"},
RecordType: endpoint.RecordTypeA,
},
{
DNSName: "test1.example.com",
Targets: []string{"fc00::1:192:168:1:1"},
RecordType: endpoint.RecordTypeAAAA,
},
{
DNSName: "test2.example.com",
Targets: []string{"fc00::1:10:0:0:1"},
RecordType: endpoint.RecordTypeAAAA,
},
},
}); err != nil {
t.Fatal(err)
Expand All @@ -255,14 +325,14 @@ func TestProvider(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if len(newRecords) != 2 {
t.Fatal("Expected list of 2 records, got:", records)
if len(newRecords) != 4 {
t.Fatal("Expected list of 4 records, got:", records)
}
if len(requests.createRequests) != 1 {
t.Fatal("Expected 1 create request, got:", requests.createRequests)
if len(requests.createRequests) != 2 {
t.Fatal("Expected 2 create request, got:", requests.createRequests)
}
if len(requests.deleteRequests) != 1 {
t.Fatal("Expected 1 delete request, got:", requests.deleteRequests)
if len(requests.deleteRequests) != 2 {
t.Fatal("Expected 2 delete request, got:", requests.deleteRequests)
}

for idx, record := range records {
Expand All @@ -274,22 +344,38 @@ func TestProvider(t *testing.T) {
}
}

expectedCreate := endpoint.Endpoint{
expectedCreateA := endpoint.Endpoint{
DNSName: "test2.example.com",
Targets: []string{"10.0.0.1"},
RecordType: endpoint.RecordTypeA,
}
expectedDelete := endpoint.Endpoint{
expectedDeleteA := endpoint.Endpoint{
DNSName: "test2.example.com",
Targets: []string{"192.168.1.2"},
RecordType: endpoint.RecordTypeA,
}
expectedCreateAAAA := endpoint.Endpoint{
DNSName: "test2.example.com",
Targets: []string{"fc00::1:10:0:0:1"},
RecordType: endpoint.RecordTypeAAAA,
}
expectedDeleteAAAA := endpoint.Endpoint{
DNSName: "test2.example.com",
Targets: []string{"fc00::1:192:168:1:2"},
RecordType: endpoint.RecordTypeAAAA,
}

if !reflect.DeepEqual(requests.createRequests[0], &expectedCreate) {
t.Error("Unexpected create request, got:", requests.createRequests[0], "expected:", &expectedCreate)
if !reflect.DeepEqual(requests.createRequests[0], &expectedCreateA) {
t.Error("Unexpected create request, got:", requests.createRequests[0], "expected:", &expectedCreateA)
}
if !reflect.DeepEqual(requests.deleteRequests[0], &expectedDeleteA) {
t.Error("Unexpected delete request, got:", requests.deleteRequests[0], "expected:", &expectedDeleteA)
}
if !reflect.DeepEqual(requests.createRequests[1], &expectedCreateAAAA) {
t.Error("Unexpected create request, got:", requests.createRequests[0], "expected:", &expectedCreateAAAA)
}
if !reflect.DeepEqual(requests.deleteRequests[0], &expectedDelete) {
t.Error("Unexpected delete request, got:", requests.deleteRequests[0], "expected:", &expectedDelete)
if !reflect.DeepEqual(requests.deleteRequests[1], &expectedDeleteAAAA) {
t.Error("Unexpected delete request, got:", requests.deleteRequests[0], "expected:", &expectedDeleteAAAA)
}

requests.clear()
Expand Down

0 comments on commit a48287b

Please sign in to comment.