Construct IPv6 ingress URLs correctly

Fixes #19338

Signed-off-by: Aria Stewart <aredridel@dinhe.net>
This commit is contained in:
Aria Stewart
2026-05-20 17:21:35 -07:00
committed by James Tucker
parent c09407002f
commit 61277e3ad4
2 changed files with 90 additions and 1 deletions
+2 -1
View File
@@ -8,6 +8,7 @@ package main
import (
"context"
"fmt"
"net"
"slices"
"strings"
"sync"
@@ -364,7 +365,7 @@ func handlersForIngress(ctx context.Context, ing *networkingv1.Ingress, cl clien
proto = "https+insecure://"
}
mak.Set(&handlers, path, &ipn.HTTPHandler{
Proxy: proto + svc.Spec.ClusterIP + ":" + fmt.Sprint(port) + path,
Proxy: proto + net.JoinHostPort(svc.Spec.ClusterIP, fmt.Sprint(port)) + path,
})
}
addIngressBackend(ing.Spec.DefaultBackend, "/")
+88
View File
@@ -942,3 +942,91 @@ func TestTailscaleIngressWithHTTPRedirect(t *testing.T) {
t.Errorf("incorrect status ports after removing redirect: got %v, want %v", ing.Status.LoadBalancer.Ingress[0].Ports, wantPorts)
}
}
func TestTailscaleIngressIPv6(t *testing.T) {
fc := fake.NewFakeClient(ingressClass())
zl, err := zap.NewDevelopment()
if err != nil {
t.Fatal(err)
}
// Create a Service with an IPv6 ClusterIP
ipv6Svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ipv6",
Namespace: "default",
},
Spec: corev1.ServiceSpec{
ClusterIP: "fda9:e575:6e22:2::25",
Ports: []corev1.ServicePort{
{
Port: 2283,
Name: "http",
},
},
},
}
mustCreate(t, fc, ipv6Svc)
// Create an Ingress that routes to the IPv6 service
ing := &networkingv1.Ingress{
TypeMeta: metav1.TypeMeta{Kind: "Ingress", APIVersion: "networking.k8s.io/v1"},
ObjectMeta: metav1.ObjectMeta{
Name: "test-ipv6",
Namespace: "default",
UID: "1234-UID-IPV6",
},
Spec: networkingv1.IngressSpec{
IngressClassName: new("tailscale"),
DefaultBackend: &networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "test-ipv6",
Port: networkingv1.ServiceBackendPort{
Number: 2283,
},
},
},
},
}
mustCreate(t, fc, ing)
ingR := &IngressReconciler{
Client: fc,
ingressClassName: "tailscale",
ssr: &tailscaleSTSReconciler{
Client: fc,
clients: tsclient.NewProvider(&fakeTSClient{}),
tsnetServer: &fakeTSNetServer{certDomains: []string{"test-host"}},
defaultTags: []string{"tag:test"},
operatorNamespace: "operator-ns",
proxyImage: "tailscale/tailscale",
},
logger: zl.Sugar(),
}
expectReconciled(t, ingR, "default", "test-ipv6")
// Verify the generated serveConfig has properly bracketed IPv6 address
fullName, _ := findGenName(t, fc, "default", "test-ipv6", "ingress")
opts := configOpts{
replicas: new(int32(1)),
stsName: "tailscale-ipv6-ingress-test-ipv6",
secretName: fullName,
namespace: "default",
parentType: "ingress",
hostname: "default-test-ipv6-ingress",
app: kubetypes.AppIngressResource,
serveConfig: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"${TS_CERT_DOMAIN}:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://[fda9:e575:6e22:2::25]:2283/"},
}},
},
},
}
// expectedSecret hardcodes the parent-resource label to "test", so fix it for our IPv6 test
secret := expectedSecret(t, fc, opts)
secret.Labels[LabelParentName] = "test-ipv6"
expectEqual(t, fc, secret)
}