Repository: kubernetes-sigs/external-dns (language: go)

Issue:
external-dns is ignoring punycode domains
**What happened**:

external-dns is completely ignoring one of my domains, as if it can't find it.

```
DEBU[0004] Skipping record a.xn--ccka2b6azt.xn--q9jyb4c because no hosted zone matching record DNS Name was detected
DEBU[0004] Skipping record a.xn--ccka2b6azt.xn--q9jyb4c because no hosted zone matching record DNS Name was detected
```

My domain is registered and active within Cloudflare:

![Image](https://github.com/user-attachments/assets/e528f45d-6079-4289-8f64-e93c5037403f)

**What you expected to happen**:

I expect it to find the right zone and create DNS records.

**How to reproduce it (as minimally and precisely as possible)**:

I am not sure how to describe a reproduction scenario, but this is my Ingress manifest (which I use as sources for external-dns):
<details>
<summary>ingress.yml</summary>

```
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: alyxiaminna
  namespace: alyxiaminna
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: traefik
  rules:
    - host: a.xn--ccka2b6azt.xn--q9jyb4c
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: alyxiaminna
                port:
                  number: 80

  tls:
    - hosts:
        - a.xn--ccka2b6azt.xn--q9jyb4c
      secretName: alyxiaminna-a-cert
```
</details>

**Anything else we need to know?**:

**Environment**:

- External-DNS version (use `external-dns --version`): `v0.15.0`
- DNS provider: Cloudflare



Discussion / hints:

Fix patch:
diff --git a/provider/zonefinder.go b/provider/zonefinder.go
index 18396000b8..6a07646c18 100644
--- a/provider/zonefinder.go
+++ b/provider/zonefinder.go
@@ -16,7 +16,12 @@ limitations under the License.
 
 package provider
 
-import "strings"
+import (
+	"strings"
+
+	log "github.com/sirupsen/logrus"
+	"golang.org/x/net/idna"
+)
 
 type ZoneIDName map[string]string
 
@@ -25,8 +30,13 @@ func (z ZoneIDName) Add(zoneID, zoneName string) {
 }
 
 func (z ZoneIDName) FindZone(hostname string) (suitableZoneID, suitableZoneName string) {
+	name, err := idna.Lookup.ToUnicode(hostname)
+	if err != nil {
+		log.Warnf("Failed to convert hostname '%s' to its Unicode form: %v", hostname, err)
+		name = hostname
+	}
 	for zoneID, zoneName := range z {
-		if hostname == zoneName || strings.HasSuffix(hostname, "."+zoneName) {
+		if name == zoneName || strings.HasSuffix(name, "."+zoneName) {
 			if suitableZoneName == "" || len(zoneName) > len(suitableZoneName) {
 				suitableZoneID = zoneID
 				suitableZoneName = zoneName


Test patch:
diff --git a/provider/zonefinder_test.go b/provider/zonefinder_test.go
index 592ef65fb2..0037edadd3 100644
--- a/provider/zonefinder_test.go
+++ b/provider/zonefinder_test.go
@@ -19,7 +19,9 @@ package provider
 import (
 	"testing"
 
+	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/assert"
+	"sigs.k8s.io/external-dns/internal/testutils"
 )
 
 func TestZoneIDName(t *testing.T) {
@@ -27,10 +29,12 @@ func TestZoneIDName(t *testing.T) {
 	z.Add("123456", "foo.bar")
 	z.Add("123456", "qux.baz")
 	z.Add("654321", "foo.qux.baz")
+	z.Add("987654", "エイミー.みんな")
 
 	assert.Equal(t, ZoneIDName{
 		"123456": "qux.baz",
 		"654321": "foo.qux.baz",
+		"987654": "エイミー.みんな",
 	}, z)
 
 	// simple entry in a domain
@@ -62,4 +66,13 @@ func TestZoneIDName(t *testing.T) {
 	zoneID, zoneName = z.FindZone("foo.qux.baz")
 	assert.Equal(t, "foo.qux.baz", zoneName)
 	assert.Equal(t, "654321", zoneID)
+
+	// entry gets normalized before finding
+	zoneID, zoneName = z.FindZone("xn--eckh0ome.xn--q9jyb4c")
+	assert.Equal(t, "エイミー.みんな", zoneName)
+	assert.Equal(t, "987654", zoneID)
+
+	b := testutils.LogsToBuffer(log.WarnLevel, t)
+	zoneID, zoneName = z.FindZone("???")
+	assert.Contains(t, b.String(), "level=warning msg=\"Failed to convert hostname '???' to its Unicode form: idna: disallowed rune U+003F\"")
 }