blob: 982740c075865a8463d673913d19d3745411a07d [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Jan Schär39f4f5c2024-10-29 09:41:50 +01004package node
5
6import (
7 "errors"
8 "fmt"
9 "regexp"
10)
11
12const (
13 // domainNameMaxLength is the maximum length of a domain name supported by DNS
14 // when represented without a trailing dot.
15 domainNameMaxLength = 253
16
17 // clusterDomainMaxLength is the maximum length of a cluster domain. Limiting
18 // this to 80 allows for constructing subdomains of the cluster domain, where
19 // the subdomain part can have length up to 172. With the joining dot, this
20 // adds up to 253.
21 clusterDomainMaxLength = 80
22)
23
24var (
25 fmtDomainNameLabel = `[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?`
26 reDomainName = regexp.MustCompile(`^` + fmtDomainNameLabel + `(\.` + fmtDomainNameLabel + `)*$`)
27 reDomainNameEndsInNumber = regexp.MustCompile(`(^|\.)([0-9]+|0x[0-9a-f]*)$`)
28
29 errDomainNameTooLong = fmt.Errorf("too long, must have length at most %d", domainNameMaxLength)
30 errDomainNameInvalid = errors.New("must consist of labels separated by '.', where each label has between 1 and 63 lowercase letters, digits or '-', and must not start or end with '-'")
31 errDomainNameEndsInNumber = errors.New("must not end in a number")
32
33 errClusterDomainTooLong = fmt.Errorf("too long, must have length at most %d", clusterDomainMaxLength)
34)
35
36// validateDomainName returns an error if the passed string is not a valid
37// domain name, according to these rules: The name must be a valid DNS name
38// without a trailing dot. Labels must only consist of lowercase letters, digits
39// or '-', and must not start or end with '-'. Additionally, the name must not
40// end in a number, so that it won't be parsed as an IPv4 address.
41func validateDomainName(d string) error {
42 if len(d) > domainNameMaxLength {
43 return errDomainNameTooLong
44 }
45 // This implements RFC 1123 domain validation. Additionally, it does not allow
46 // uppercase, so that we don't need to implement case-insensitive matching.
47 if !reDomainName.MatchString(d) {
48 return errDomainNameInvalid
49 }
50 // This implements https://url.spec.whatwg.org/#ends-in-a-number-checker
51 if reDomainNameEndsInNumber.MatchString(d) {
52 return errDomainNameEndsInNumber
53 }
54 return nil
55}
56
57// ValidateClusterDomain returns an error if the passed string is not a valid
58// cluster domain.
59func ValidateClusterDomain(d string) error {
60 if len(d) > clusterDomainMaxLength {
61 return errClusterDomainTooLong
62 }
63 return validateDomainName(d)
64}