Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions endpoint/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,9 @@ func NewEndpointWithTTL(dnsName, recordType string, ttl TTL, targets ...string)
for idx, target := range targets {
// Only trim trailing dots for domain name record types, not for TXT or NAPTR records
// TXT records can contain arbitrary text including multiple dots
// SRV can contain dots in their target part (RFC2782)
switch recordType {
case RecordTypeTXT, RecordTypeNAPTR:
case RecordTypeTXT, RecordTypeNAPTR, RecordTypeSRV:
cleanTargets[idx] = target
default:
cleanTargets[idx] = strings.TrimSuffix(target, ".")
Expand Down Expand Up @@ -484,11 +485,15 @@ func (t Targets) ValidateMXRecord() bool {

func (t Targets) ValidateSRVRecord() bool {
for _, target := range t {
// SRV records must have a priority, weight, and port value, e.g. "10 5 5060 example.com"
// as per https://www.rfc-editor.org/rfc/rfc2782.txt
// SRV records must have a priority, weight, a port value and a target e.g. "10 5 5060 example.com."
// as per https://www.rfc-editor.org/rfc/rfc2782.txt the target host has to end with a dot.
targetParts := strings.Fields(strings.TrimSpace(target))
if len(targetParts) != 4 {
log.Debugf("Invalid SRV record target: %s. SRV records must have a priority, weight, and port value, e.g. '10 5 5060 example.com'", target)
log.Debugf("Invalid SRV record target: %s. SRV records must have a priority, weight, a port value and a target host, e.g. '10 5 5060 example.com.'", target)
return false
}
if !strings.HasSuffix(targetParts[3], ".") {
log.Debugf("Invalid SRV record target: %s. Target host does not end with a dot.'", target)
return false
}

Expand Down
17 changes: 13 additions & 4 deletions endpoint/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,7 @@ func TestPDNScheckEndpoint(t *testing.T) {
endpoint: Endpoint{
DNSName: "_service._tls.example.com",
RecordType: RecordTypeSRV,
Targets: Targets{"10 20 5060 service.example.com"},
Targets: Targets{"10 20 5060 service.example.com."},
},
expected: true,
},
Expand All @@ -805,7 +805,16 @@ func TestPDNScheckEndpoint(t *testing.T) {
endpoint: Endpoint{
DNSName: "_service._tls.example.com",
RecordType: RecordTypeSRV,
Targets: Targets{"10 20 abc service.example.com"},
Targets: Targets{"10 20 abc service.example.com."},
},
expected: false,
},
{
description: "Invalid SRV record with missing dot for target host",
endpoint: Endpoint{
DNSName: "_service._tls.example.com",
RecordType: RecordTypeSRV,
Targets: Targets{"10 20 5060 service.example.com"},
},
expected: false,
},
Expand Down Expand Up @@ -895,7 +904,7 @@ func TestCheckEndpoint(t *testing.T) {
endpoint: Endpoint{
DNSName: "_service._tcp.example.com",
RecordType: RecordTypeSRV,
Targets: Targets{"10 5 5060 example.com"},
Targets: Targets{"10 5 5060 example.com."},
},
expected: true,
},
Expand All @@ -904,7 +913,7 @@ func TestCheckEndpoint(t *testing.T) {
endpoint: Endpoint{
DNSName: "_service._tcp.example.com",
RecordType: RecordTypeSRV,
Targets: Targets{"10 5 example.com"},
Targets: Targets{"10 5 example.com."},
},
expected: false,
},
Expand Down
3 changes: 2 additions & 1 deletion source/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"

"sigs.k8s.io/external-dns/provider"
"sigs.k8s.io/external-dns/source/informers"

"sigs.k8s.io/external-dns/source/annotations"
Expand Down Expand Up @@ -855,7 +856,7 @@ func (sc *serviceSource) extractNodePortEndpoints(svc *v1.Service, hostname stri
// see https://en.wikipedia.org/wiki/SRV_record

// build a target with a priority of 0, weight of 50, and pointing the given port on the given host
target := fmt.Sprintf("0 50 %d %s", port.NodePort, hostname)
target := fmt.Sprintf("0 50 %d %s", port.NodePort, provider.EnsureTrailingDot(hostname))
Copy link
Copy Markdown
Member

@ivankatliarchuk ivankatliarchuk Sep 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a struct similar to

type MXTarget struct {

so we do target := NewSRVRecord()

And importing provider libraries in sources may lead to potential circular dependencies.

We may better move EnsureTrailingDot to endpoint package, in this or follow-up PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once there is a working PR in regards to the RFC 2782 issue, I gladly add another commit moving the EnsureTrailingDot function to endpoint.


// take the service name from the K8s Service object
// it is safe to use since it is DNS compatible
Expand Down
20 changes: 10 additions & 10 deletions source/service_fqdn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ func TestServiceSourceFqdnTemplatingExamples(t *testing.T) {
},
},
},
fqdnTemplate: `{{ $name := .Name }}{{ range .Spec.Ports -}}{{ $name }}{{ if eq .Name "http2" }}.http2{{ else if eq .Name "debug" }}.debug{{ end }}.example.tld{{printf "," }}{{ end }}`,
fqdnTemplate: `{{ $name := .Name }}{{ range .Spec.Ports -}}{{ $name }}{{ if eq .Name "http2" }}.http2{{ else if eq .Name "debug" }}.debug{{ end }}.example.tld.{{printf "," }}{{ end }}`,
expected: []*endpoint.Endpoint{
// TODO: This test shows that there is a bug that needs to be fixed in the external-dns logic.
{DNSName: "", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"192.51.100.22", "192.51.100.5"}},
Expand Down Expand Up @@ -736,15 +736,15 @@ func TestServiceSourceFqdnTemplatingExamples(t *testing.T) {
expected: []*endpoint.Endpoint{
// TODO: This test shows that there is a bug that needs to be fixed in the external-dns logic. Not a critical issue, as will be filtered out by the source.
{DNSName: "", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.96.41.132", "203.0.113.10"}},
{DNSName: "_service-one._tcp", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 30080 ", "0 50 30082 "}}, // TODO: wrong SRV target format
{DNSName: "_service-one._tcp.debug.host.tld", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 30080 debug.host.tld", "0 50 30082 debug.host.tld"}},
{DNSName: "_service-one._tcp.http.host.tld", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 30080 http.host.tld", "0 50 30082 http.host.tld"}},
{DNSName: "_service-three._tcp", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 25565 "}}, // TODO: wrong SRV target format
{DNSName: "_service-three._tcp.debug.host.tld", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 25565 debug.host.tld"}},
{DNSName: "_service-three._tcp.minecraft.host.tld", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 25565 minecraft.host.tld"}},
{DNSName: "_service-three._udp", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 30083 "}}, // TODO: wrong SRV target format
{DNSName: "_service-three._udp.debug.host.tld", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 30083 debug.host.tld"}},
{DNSName: "_service-three._udp.minecraft.host.tld", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 30083 minecraft.host.tld"}},
{DNSName: "_service-one._tcp", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 30080 .", "0 50 30082 ."}}, // TODO: wrong SRV target format
{DNSName: "_service-one._tcp.debug.host.tld", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 30080 debug.host.tld.", "0 50 30082 debug.host.tld."}},
{DNSName: "_service-one._tcp.http.host.tld", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 30080 http.host.tld.", "0 50 30082 http.host.tld."}},
{DNSName: "_service-three._tcp", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 25565 ."}}, // TODO: wrong SRV target format
{DNSName: "_service-three._tcp.debug.host.tld", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 25565 debug.host.tld."}},
{DNSName: "_service-three._tcp.minecraft.host.tld", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 25565 minecraft.host.tld."}},
{DNSName: "_service-three._udp", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 30083 ."}}, // TODO: wrong SRV target format
{DNSName: "_service-three._udp.debug.host.tld", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 30083 debug.host.tld."}},
{DNSName: "_service-three._udp.minecraft.host.tld", RecordType: endpoint.RecordTypeSRV, Targets: endpoint.Targets{"0 50 30083 minecraft.host.tld."}},
{DNSName: "debug.host.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"203.0.113.10"}},
{DNSName: "http.host.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"203.0.113.10"}},
{DNSName: "minecraft.host.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"203.0.113.10"}},
Expand Down
20 changes: 10 additions & 10 deletions source/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1741,7 +1741,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
annotations.HostnameKey: "foo.example.org.",
},
expected: []*endpoint.Endpoint{
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
},
Expand Down Expand Up @@ -1816,7 +1816,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
fqdnTemplate: "{{.Name}}.bar.example.com",
expected: []*endpoint.Endpoint{
{DNSName: "_foo._tcp.foo.bar.example.com", Targets: endpoint.Targets{"0 50 30192 foo.bar.example.com"}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "_foo._tcp.foo.bar.example.com", Targets: endpoint.Targets{"0 50 30192 foo.bar.example.com."}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
},
Expand Down Expand Up @@ -1856,7 +1856,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
annotations.HostnameKey: "foo.example.org.",
},
expected: []*endpoint.Endpoint{
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
},
Expand Down Expand Up @@ -1892,7 +1892,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
annotations.HostnameKey: "foo.example.org.",
},
expected: []*endpoint.Endpoint{
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
},
Expand Down Expand Up @@ -1938,7 +1938,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
annotations.HostnameKey: "foo.example.org.",
},
expected: []*endpoint.Endpoint{
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
},
Expand Down Expand Up @@ -1987,7 +1987,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
annotations.HostnameKey: "foo.example.org.",
},
expected: []*endpoint.Endpoint{
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1"}, RecordType: endpoint.RecordTypeA},
},
nodes: []*v1.Node{{
Expand Down Expand Up @@ -2031,7 +2031,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
annotations.HostnameKey: "foo.example.org.",
},
expected: []*endpoint.Endpoint{
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1"}, RecordType: endpoint.RecordTypeA},
},
nodes: []*v1.Node{{
Expand Down Expand Up @@ -2087,7 +2087,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
annotations.AccessKey: "private",
},
expected: []*endpoint.Endpoint{
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
},
Expand Down Expand Up @@ -2127,7 +2127,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
annotations.AccessKey: "public",
},
expected: []*endpoint.Endpoint{
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
},
Expand Down Expand Up @@ -2170,7 +2170,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
},
exposeInternalIPv6: true,
expected: []*endpoint.Endpoint{
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2", "2001:DB8::3", "2001:DB8::4"}, RecordType: endpoint.RecordTypeAAAA},
},
Expand Down
Loading