tailscale/util/dnsname/dnsname_test.go
Anton Tolchanov 2a933c1903
cmd/tailscale: extend hostname validation (#7678)
In addition to checking the total hostname length, validate characters used in each DNS label and label length.

Updates https://github.com/tailscale/corp/issues/10012

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2023-03-27 18:21:58 +01:00

233 lines
5.9 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package dnsname
import (
"strings"
"testing"
)
func TestFQDN(t *testing.T) {
tests := []struct {
in string
want FQDN
wantErr bool
wantLabels int
}{
{"", ".", false, 0},
{".", ".", false, 0},
{"foo.com", "foo.com.", false, 2},
{"foo.com.", "foo.com.", false, 2},
{".foo.com.", "foo.com.", false, 2},
{".foo.com", "foo.com.", false, 2},
{"com", "com.", false, 1},
{"www.tailscale.com", "www.tailscale.com.", false, 3},
{"_ssh._tcp.tailscale.com", "_ssh._tcp.tailscale.com.", false, 4},
{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com", "", true, 0},
{strings.Repeat("aaaaa.", 60) + "com", "", true, 0},
{"foo..com", "", true, 0},
}
for _, test := range tests {
t.Run(test.in, func(t *testing.T) {
got, err := ToFQDN(test.in)
if got != test.want {
t.Errorf("ToFQDN(%q) got %q, want %q", test.in, got, test.want)
}
if (err != nil) != test.wantErr {
t.Errorf("ToFQDN(%q) err %v, wantErr=%v", test.in, err, test.wantErr)
}
if err != nil {
return
}
gotDot := got.WithTrailingDot()
if gotDot != string(test.want) {
t.Errorf("ToFQDN(%q).WithTrailingDot() got %q, want %q", test.in, gotDot, test.want)
}
gotNoDot := got.WithoutTrailingDot()
wantNoDot := string(test.want)[:len(test.want)-1]
if gotNoDot != wantNoDot {
t.Errorf("ToFQDN(%q).WithoutTrailingDot() got %q, want %q", test.in, gotNoDot, wantNoDot)
}
if gotLabels := got.NumLabels(); gotLabels != test.wantLabels {
t.Errorf("ToFQDN(%q).NumLabels() got %v, want %v", test.in, gotLabels, test.wantLabels)
}
})
}
}
func TestFQDNContains(t *testing.T) {
tests := []struct {
a, b string
want bool
}{
{"", "", true},
{"", "foo.com", true},
{"foo.com", "", false},
{"tailscale.com", "www.tailscale.com", true},
{"www.tailscale.com", "tailscale.com", false},
{"scale.com", "tailscale.com", false},
{"foo.com", "foo.com", true},
}
for _, test := range tests {
t.Run(test.a+"_"+test.b, func(t *testing.T) {
a, err := ToFQDN(test.a)
if err != nil {
t.Fatalf("ToFQDN(%q): %v", test.a, err)
}
b, err := ToFQDN(test.b)
if err != nil {
t.Fatalf("ToFQDN(%q): %v", test.b, err)
}
if got := a.Contains(b); got != test.want {
t.Errorf("ToFQDN(%q).Contains(%q) got %v, want %v", a, b, got, test.want)
}
})
}
}
func TestSanitizeLabel(t *testing.T) {
tests := []struct {
name string
in string
want string
}{
{"empty", "", ""},
{"space", " ", ""},
{"upper", "OBERON", "oberon"},
{"mixed", "Avery's iPhone 4(SE)", "averys-iphone-4se"},
{"dotted", "mon.ipn.dev", "mon-ipn-dev"},
{"email", "admin@example.com", "admin-example-com"},
{"boundary", ".bound.ary.", "bound-ary"},
{"bad_trailing", "a-", "a"},
{"bad_leading", "-a", "a"},
{"bad_both", "-a-", "a"},
{
"overlong",
strings.Repeat("test.", 20),
"test-test-test-test-test-test-test-test-test-test-test-test-tes",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := SanitizeLabel(tt.in)
if got != tt.want {
t.Errorf("want %q; got %q", tt.want, got)
}
})
}
}
func TestTrimCommonSuffixes(t *testing.T) {
tests := []struct {
hostname string
want string
}{
{"computer.local", "computer"},
{"computer.localdomain", "computer"},
{"computer.lan", "computer"},
{"computer.mynetwork", "computer.mynetwork"},
}
for _, tt := range tests {
got := TrimCommonSuffixes(tt.hostname)
if got != tt.want {
t.Errorf("TrimCommonSuffixes(%q) = %q; want %q", tt.hostname, got, tt.want)
}
}
}
func TestHasSuffix(t *testing.T) {
tests := []struct {
name, suffix string
want bool
}{
{"foo.com", "com", true},
{"foo.com.", "com", true},
{"foo.com.", "com.", true},
{"foo.com", ".com", true},
{"", "", false},
{"foo.com.", "", false},
{"foo.com.", "o.com", false},
}
for _, tt := range tests {
got := HasSuffix(tt.name, tt.suffix)
if got != tt.want {
t.Errorf("HasSuffix(%q, %q) = %v; want %v", tt.name, tt.suffix, got, tt.want)
}
}
}
func TestTrimSuffix(t *testing.T) {
tests := []struct {
name string
suffix string
want string
}{
{"foo.magicdnssuffix.", "magicdnssuffix", "foo"},
{"foo.magicdnssuffix", "magicdnssuffix", "foo"},
{"foo.magicdnssuffix", ".magicdnssuffix", "foo"},
{"foo.anothersuffix", "magicdnssuffix", "foo.anothersuffix"},
{"foo.anothersuffix.", "magicdnssuffix", "foo.anothersuffix"},
{"a.b.c.d", "c.d", "a.b"},
{"name.", "foo", "name"},
}
for _, tt := range tests {
got := TrimSuffix(tt.name, tt.suffix)
if got != tt.want {
t.Errorf("TrimSuffix(%q, %q) = %q; want %q", tt.name, tt.suffix, got, tt.want)
}
}
}
func TestValidHostname(t *testing.T) {
tests := []struct {
hostname string
wantErr string
}{
{"example", ""},
{"example.com", ""},
{" example", `must start with a letter or number`},
{"example-.com", `must end with a letter or number`},
{strings.Repeat("a", 63), ""},
{strings.Repeat("a", 64), `is too long, max length is 63 bytes`},
{strings.Repeat(strings.Repeat("a", 63)+".", 4), "is too long to be a DNS name"},
{"www.what🤦lol.example.com", "contains invalid character"},
}
for _, test := range tests {
t.Run(test.hostname, func(t *testing.T) {
err := ValidHostname(test.hostname)
if (err == nil) != (test.wantErr == "") || (err != nil && !strings.Contains(err.Error(), test.wantErr)) {
t.Fatalf("ValidHostname(%s)=%v; expected %v", test.hostname, err, test.wantErr)
}
})
}
}
var sinkFQDN FQDN
func BenchmarkToFQDN(b *testing.B) {
tests := []string{
"www.tailscale.com.",
"www.tailscale.com",
".www.tailscale.com",
"_ssh._tcp.www.tailscale.com.",
"_ssh._tcp.www.tailscale.com",
}
for _, test := range tests {
b.Run(test, func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
sinkFQDN, _ = ToFQDN(test)
}
})
}
}