Skip to content

Commit

Permalink
netutil: validate services names
Browse files Browse the repository at this point in the history
  • Loading branch information
EugeneOne1 committed Jan 17, 2022
1 parent a986e5c commit b75314e
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 8 deletions.
82 changes: 82 additions & 0 deletions netutil/addr.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,85 @@ func ValidateDomainName(name string) (err error) {

return nil
}

// MaxServiceLabelLen is the maximum allowed length of a service name label
// according to RFC 6335.
const MaxServiceLabelLen = 16

// ValidateServiceNameLabel returns an error if label is not a valid label of
// a service domain name. An empty label is considered invalid.
//
// Any error returned will have the underlying type of *AddrError.
func ValidateServiceNameLabel(label string) (err error) {
defer makeAddrError(&err, label, AddrKindSRVLabel)

if label == "" {
return ErrLabelIsEmpty
} else if r := rune(label[0]); r != '_' {
return &RuneError{
Kind: AddrKindSRVLabel,
Rune: r,
}
}

l := len(label)
if l > MaxServiceLabelLen {
return &LengthError{
Kind: AddrKindSRVLabel,
Max: MaxServiceLabelLen,
Length: l,
}
}

// TODO(e.burkov): Validate adjacent hyphens since service labels can't be
// internationalized. See RFC 6336 Section 5.1.
if err := ValidateDomainNameLabel(label[1:]); err != nil {
err = errors.Unwrap(err)
if rerr, ok := err.(*RuneError); ok {
rerr.Kind = AddrKindSRVLabel
}

return err
}

return nil
}

// ValidateSRVDomainName validates of domain name assuming it belongs to the
// superset of service domain names in accordance to RFC 2782 and RFC 6763. It
// doesn't validate against two or more hyphens to allow punycode and
// internationalized domains.
//
// Any error returned will have the underlying type of *AddrError.
func ValidateSRVDomainName(name string) (err error) {
defer makeAddrError(&err, name, AddrKindSRVName)

name, err = idna.ToASCII(name)
if err != nil {
return err
}

if name == "" {
return ErrAddrIsEmpty
} else if l := len(name); l > MaxDomainNameLen {
return &LengthError{
Kind: AddrKindSRVName,
Max: MaxDomainNameLen,
Length: l,
}
}

labels := strings.Split(name, ".")
for _, l := range labels {
if l != "" && l[0] == '_' {
err = ValidateServiceNameLabel(l)
} else {
err = ValidateDomainNameLabel(l)
}
if err != nil {
return err
}
}

return nil
}
112 changes: 112 additions & 0 deletions netutil/addr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,3 +275,115 @@ func TestValidateDomainName(t *testing.T) {
})
}
}

func TestValidateSRVDomainName(t *testing.T) {
t.Parallel()

longDomainName := strings.Repeat("a", 255)
longLabel := "_" + strings.Repeat("a", 16)
longLabelDomainName := longLabel + ".com"

testCases := []struct {
name string
in string
wantErrAs interface{}
wantErrMsg string
}{{
name: "success",
in: "_http.example.com",
wantErrAs: nil,
wantErrMsg: "",
}, {
name: "success_idna",
in: "_http.пример.рф",
wantErrAs: nil,
wantErrMsg: "",
}, {
name: "success_one",
in: "_u",
wantErrAs: nil,
wantErrMsg: "",
}, {
name: "empty",
in: "",
wantErrAs: new(errors.Error),
wantErrMsg: `bad service domain name "": address is empty`,
}, {
name: "bad_symbol",
in: "_!",
wantErrAs: new(*netutil.RuneError),
wantErrMsg: `bad service domain name "_!": ` +
`bad service name label "_!": bad service name label rune '!'`,
}, {
name: "bad_length",
in: longDomainName,
wantErrAs: new(*netutil.LengthError),
wantErrMsg: `bad service domain name "` + longDomainName + `": ` +
`service domain name is too long: got 255, max 253`,
}, {
name: "bad_label_length",
in: longLabelDomainName,
wantErrAs: new(*netutil.LengthError),
wantErrMsg: `bad service domain name "` + longLabelDomainName + `": ` +
`bad service name label "` + longLabel + `": ` +
`service name label is too long: got 17, max 16`,
}, {
name: "bad_label_empty",
in: "example..com",
wantErrAs: new(errors.Error),
wantErrMsg: `bad service domain name "example..com": ` +
`bad domain name label "": label is empty`,
}, {
name: "bad_label_first_symbol",
in: "example._-a.com",
wantErrAs: new(*netutil.RuneError),
wantErrMsg: `bad service domain name "example._-a.com": ` +
`bad service name label "_-a": bad service name label rune '-'`,
}, {
name: "bad_label_last_symbol",
in: "_example-.aa.com",
wantErrAs: new(*netutil.RuneError),
wantErrMsg: `bad service domain name "_example-.aa.com": ` +
`bad service name label "_example-": bad service name label rune '-'`,
}, {
name: "bad_label_unexpected_underscore",
in: "example._ht_tp.com",
wantErrAs: new(*netutil.RuneError),
wantErrMsg: `bad service domain name "example._ht_tp.com": ` +
`bad service name label "_ht_tp": bad service name label rune '_'`,
}, {
name: "bad_service_label_empty",
in: "example._.com",
wantErrAs: new(*netutil.AddrError),
wantErrMsg: `bad service domain name "example._.com": ` +
`bad service name label "_": label is empty`,
}}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

err := netutil.ValidateSRVDomainName(tc.in)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)

if tc.wantErrAs != nil {
require.Error(t, err)

assert.ErrorAs(t, err, new(*netutil.AddrError))
assert.ErrorAs(t, err, tc.wantErrAs)
}
})
}

t.Run("bad_service_label", func(t *testing.T) {
wantErrMsg := `bad service name label "non-service.com": ` +
`bad service name label rune 'n'`

err := netutil.ValidateServiceNameLabel("non-service.com")
testutil.AssertErrorMsg(t, wantErrMsg, err)

assert.ErrorAs(t, err, new(*netutil.AddrError))
assert.ErrorAs(t, err, new(*netutil.RuneError))
})
}
2 changes: 2 additions & 0 deletions netutil/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ const (
AddrKindIPPort AddrKind = "ipport address"
AddrKindIPv4 AddrKind = "ipv4 address"
AddrKindLabel AddrKind = "domain name label"
AddrKindSRVLabel AddrKind = "service name label"
AddrKindMAC AddrKind = "mac address"
AddrKindName AddrKind = "domain name"
AddrKindSRVName AddrKind = "service domain name"
)

// AddrError is the underlying type of errors returned from validation
Expand Down
13 changes: 5 additions & 8 deletions netutil/reversed.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,8 @@ func IPFromReversedAddr(arpa string) (ip net.IP, err error) {
defer makeAddrError(&err, arpa, AddrKindARPA)

// TODO(a.garipov): Add stringutil.HasSuffixFold and remove this.
arpa = strings.ToLower(arpa)

if strings.HasSuffix(arpa, arpaV4Suffix) {
switch arpa = strings.ToLower(arpa); {
case strings.HasSuffix(arpa, arpaV4Suffix):
ipStr := arpa[:len(arpa)-len(arpaV4Suffix)]
ip, err = ParseIPv4(ipStr)
if err != nil {
Expand All @@ -128,9 +127,7 @@ func IPFromReversedAddr(arpa string) (ip net.IP, err error) {
reverseIP(ip)

return ip, nil
}

if strings.HasSuffix(arpa, arpaV6Suffix) {
case strings.HasSuffix(arpa, arpaV6Suffix):
if l := len(arpa); l != arpaV6MaxLen {
return nil, &LengthError{
Kind: AddrKindARPA,
Expand All @@ -145,9 +142,9 @@ func IPFromReversedAddr(arpa string) (ip net.IP, err error) {
}

return ip, nil
default:
return nil, ErrNotAReversedIP
}

return nil, ErrNotAReversedIP
}

// IPToReversedAddr returns the reversed ARPA address of ip suitable for reverse
Expand Down

0 comments on commit b75314e

Please sign in to comment.