Skip to content

Commit

Permalink
Pull request: 4120 validate services names
Browse files Browse the repository at this point in the history
Merge in DNS/golibs from 4120-service-labels to master

Updates AdguardTeam/AdGuardHome#4120.

Squashed commit of the following:

commit b6d2b5d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jan 18 13:20:06 2022 +0300

    netutil: add parallel

commit 09b6f23
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jan 17 20:40:11 2022 +0300

    netutil: imp code quality

commit b75314e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jan 17 18:57:44 2022 +0300

    netutil: validate services names
  • Loading branch information
EugeneOne1 committed Jan 18, 2022
1 parent a986e5c commit 8e3d818
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 0 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
}
114 changes: 114 additions & 0 deletions netutil/addr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,3 +275,117 @@ 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) {
t.Parallel()

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

0 comments on commit 8e3d818

Please sign in to comment.