mirror of
https://github.com/golang/net.git
synced 2026-03-31 18:37:08 +09:00
Updates #43790 Change-Id: Id9f5bdc3e17a6f7d2c9b7b8a4e48c0c66a6a6964 Reviewed-on: https://go-review.googlesource.com/c/net/+/712080 Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Vinicius Fortuna <fortuna@google.com> Reviewed-by: Sean Liao <sean@liao.dev> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
394 lines
14 KiB
Go
394 lines
14 KiB
Go
// Copyright 2025 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package dnsmessage
|
|
|
|
import (
|
|
"bytes"
|
|
"math"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestSVCBParamsRoundTrip(t *testing.T) {
|
|
testSVCBParam := func(t *testing.T, p *SVCParam) {
|
|
t.Helper()
|
|
rr := &SVCBResource{
|
|
Priority: 1,
|
|
Target: MustNewName("svc.example.com."),
|
|
Params: []SVCParam{*p},
|
|
}
|
|
buf, err := rr.pack([]byte{}, nil, 0)
|
|
if err != nil {
|
|
t.Fatalf("pack() = %v", err)
|
|
}
|
|
got, n, err := unpackResourceBody(buf, 0, ResourceHeader{Type: TypeSVCB, Length: uint16(len(buf))})
|
|
if err != nil {
|
|
t.Fatalf("unpackResourceBody() = %v", err)
|
|
}
|
|
if n != len(buf) {
|
|
t.Fatalf("unpacked different amount than packed: got = %d, want = %d", n, len(buf))
|
|
}
|
|
if !reflect.DeepEqual(got, rr) {
|
|
t.Fatalf("roundtrip mismatch: got = %#v, want = %#v", got, rr)
|
|
}
|
|
}
|
|
|
|
testSVCBParam(t, &SVCParam{Key: SVCParamMandatory, Value: []byte{0x00, 0x01, 0x00, 0x03, 0x00, 0x05}})
|
|
testSVCBParam(t, &SVCParam{Key: SVCParamALPN, Value: []byte{0x02, 'h', '2', 0x02, 'h', '3'}})
|
|
testSVCBParam(t, &SVCParam{Key: SVCParamNoDefaultALPN, Value: []byte{}})
|
|
testSVCBParam(t, &SVCParam{Key: SVCParamPort, Value: []byte{0x1f, 0x90}}) // 8080
|
|
testSVCBParam(t, &SVCParam{Key: SVCParamIPv4Hint, Value: []byte{192, 0, 2, 1, 198, 51, 100, 2}})
|
|
testSVCBParam(t, &SVCParam{Key: SVCParamECH, Value: []byte{0x01, 0x02, 0x03, 0x04}})
|
|
testSVCBParam(t, &SVCParam{Key: SVCParamIPv6Hint, Value: []byte{0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}})
|
|
testSVCBParam(t, &SVCParam{Key: SVCParamDOHPath, Value: []byte("/dns-query{?dns}")})
|
|
testSVCBParam(t, &SVCParam{Key: SVCParamOHTTP, Value: []byte{0x00, 0x01, 0x02, 0x03}})
|
|
testSVCBParam(t, &SVCParam{Key: SVCParamTLSSupportedGroups, Value: []byte{0x00, 0x1d, 0x00, 0x17}})
|
|
}
|
|
|
|
func TestSVCBParsingAllocs(t *testing.T) {
|
|
name := MustNewName("foo.bar.example.com.")
|
|
msg := Message{
|
|
Header: Header{Response: true, Authoritative: true},
|
|
Questions: []Question{{Name: name, Type: TypeA, Class: ClassINET}},
|
|
Answers: []Resource{{
|
|
Header: ResourceHeader{Name: name, Type: TypeSVCB, Class: ClassINET, TTL: 300},
|
|
Body: &SVCBResource{
|
|
Priority: 1,
|
|
Target: MustNewName("svc.example.com."),
|
|
Params: []SVCParam{
|
|
{Key: SVCParamMandatory, Value: []byte{0x00, 0x01, 0x00, 0x03, 0x00, 0x05}},
|
|
{Key: SVCParamALPN, Value: []byte{0x02, 'h', '2', 0x02, 'h', '3'}},
|
|
{Key: SVCParamPort, Value: []byte{0x1f, 0x90}}, // 8080
|
|
{Key: SVCParamIPv4Hint, Value: []byte{192, 0, 2, 1, 198, 51, 100, 2}},
|
|
{Key: SVCParamIPv6Hint, Value: []byte{0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}},
|
|
},
|
|
},
|
|
}},
|
|
}
|
|
buf, err := msg.Pack()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
allocs := int(testing.AllocsPerRun(1, func() {
|
|
var p Parser
|
|
if _, err := p.Start(buf); err != nil {
|
|
t.Fatal("Parser.Start(non-nil) =", err)
|
|
}
|
|
if err := p.SkipAllQuestions(); err != nil {
|
|
t.Fatal("Parser.SkipAllQuestions(non-nil) =", err)
|
|
}
|
|
if _, err = p.AnswerHeader(); err != nil {
|
|
t.Fatal("Parser.AnswerHeader(non-nil) =", err)
|
|
}
|
|
if _, err = p.SVCBResource(); err != nil {
|
|
t.Fatal("Parser.SVCBResource(non-nil) =", err)
|
|
}
|
|
}))
|
|
|
|
// Make sure we have only two allocations: one for the SVCBResource.Params slice, and one
|
|
// for the SVCParam Values.
|
|
if allocs != 2 {
|
|
t.Errorf("allocations during parsing: got = %d, want 2", allocs)
|
|
}
|
|
}
|
|
|
|
func TestHTTPSBuildAllocs(t *testing.T) {
|
|
b := NewBuilder([]byte{}, Header{Response: true, Authoritative: true})
|
|
b.EnableCompression()
|
|
if err := b.StartQuestions(); err != nil {
|
|
t.Fatalf("StartQuestions() = %v", err)
|
|
}
|
|
if err := b.Question(Question{Name: MustNewName("foo.bar.example.com."), Type: TypeHTTPS, Class: ClassINET}); err != nil {
|
|
t.Fatalf("Question() = %v", err)
|
|
}
|
|
if err := b.StartAnswers(); err != nil {
|
|
t.Fatalf("StartAnswers() = %v", err)
|
|
}
|
|
|
|
header := ResourceHeader{Name: MustNewName("foo.bar.example.com."), Type: TypeHTTPS, Class: ClassINET, TTL: 300}
|
|
resource := HTTPSResource{SVCBResource{Priority: 1, Target: MustNewName("svc.example.com.")}}
|
|
|
|
// AllocsPerRun runs the function once to "warm up" before running the measurement.
|
|
// So technically this function is running twice, on different data, which can potentially
|
|
// make the measurement inaccurate (e.g. by using the name cache the second time).
|
|
// So we make sure we don't run in the warm-up phase.
|
|
warmUp := true
|
|
allocs := int(testing.AllocsPerRun(1, func() {
|
|
if warmUp {
|
|
warmUp = false
|
|
return
|
|
}
|
|
if err := b.HTTPSResource(header, resource); err != nil {
|
|
t.Fatalf("HTTPSResource() = %v", err)
|
|
}
|
|
}))
|
|
if allocs != 1 {
|
|
t.Fatalf("unexpected allocations: got = %d, want = 1", allocs)
|
|
}
|
|
}
|
|
|
|
func TestSVCBParams(t *testing.T) {
|
|
rr := SVCBResource{Priority: 1, Target: MustNewName("svc.example.com.")}
|
|
if _, ok := rr.GetParam(SVCParamALPN); ok {
|
|
t.Fatal("GetParam found non-existent param")
|
|
}
|
|
rr.SetParam(SVCParamIPv4Hint, []byte{192, 0, 2, 1})
|
|
inALPN := []byte{0x02, 'h', '2', 0x02, 'h', '3'}
|
|
rr.SetParam(SVCParamALPN, inALPN)
|
|
|
|
// Check sorting of params
|
|
packed, err := rr.pack([]byte{}, nil, 0)
|
|
if err != nil {
|
|
t.Fatal("pack() =", err)
|
|
}
|
|
expectedBytes := []byte{
|
|
0x00, 0x01, // priority
|
|
0x03, 0x73, 0x76, 0x63, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, // target
|
|
0x00, 0x01, // key 1
|
|
0x00, 0x06, // length 6
|
|
0x02, 'h', '2', 0x02, 'h', '3', // value
|
|
0x00, 0x04, // key 4
|
|
0x00, 0x04, // length 4
|
|
192, 0, 2, 1, // value
|
|
}
|
|
if !reflect.DeepEqual(packed, expectedBytes) {
|
|
t.Fatalf("pack() produced unexpected output: want = %v, got = %v", expectedBytes, packed)
|
|
}
|
|
|
|
// Check GetParam and DeleteParam.
|
|
if outALPN, ok := rr.GetParam(SVCParamALPN); !ok || !bytes.Equal(outALPN, inALPN) {
|
|
t.Fatal("GetParam failed to retrieve set param")
|
|
}
|
|
if !rr.DeleteParam(SVCParamALPN) {
|
|
t.Fatal("DeleteParam failed to remove existing param")
|
|
}
|
|
if _, ok := rr.GetParam(SVCParamALPN); ok {
|
|
t.Fatal("GetParam found deleted param")
|
|
}
|
|
if len(rr.Params) != 1 || rr.Params[0].Key != SVCParamIPv4Hint {
|
|
t.Fatalf("DeleteParam removed wrong param: got = %#v, want = [%#v]", rr.Params, SVCParam{Key: SVCParamIPv4Hint, Value: []byte{192, 0, 2, 1}})
|
|
}
|
|
}
|
|
|
|
func TestSVCBWireFormat(t *testing.T) {
|
|
testRecord := func(bytesInput []byte, parsedInput *SVCBResource) {
|
|
parsedOutput, n, err := unpackResourceBody(bytesInput, 0, ResourceHeader{Type: TypeSVCB, Length: uint16(len(bytesInput))})
|
|
if err != nil {
|
|
t.Fatalf("unpackResourceBody() = %v", err)
|
|
}
|
|
if n != len(bytesInput) {
|
|
t.Fatalf("unpacked different amount than packed: got = %d, want = %d", n, len(bytesInput))
|
|
}
|
|
if !reflect.DeepEqual(parsedOutput, parsedInput) {
|
|
t.Fatalf("unpack mismatch: got = %#v, want = %#v", parsedOutput, parsedInput)
|
|
}
|
|
|
|
bytesOutput, err := parsedInput.pack([]byte{}, nil, 0)
|
|
if err != nil {
|
|
t.Fatalf("pack() = %v", err)
|
|
}
|
|
if !reflect.DeepEqual(bytesOutput, bytesInput) {
|
|
t.Fatalf("pack mismatch: got = %#v, want = %#v", bytesOutput, bytesInput)
|
|
}
|
|
}
|
|
// Test examples from https://datatracker.ietf.org/doc/html/rfc9460#name-test-vectors
|
|
|
|
// Example D.1. Alias Mode
|
|
|
|
// Figure 2: AliasMode
|
|
// example.com. HTTPS 0 foo.example.com.
|
|
bytes := []byte{
|
|
0x00, 0x00, // priority
|
|
0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, // target: foo.example.com.
|
|
}
|
|
parsed := &SVCBResource{
|
|
Priority: 0,
|
|
Target: MustNewName("foo.example.com."),
|
|
Params: []SVCParam{},
|
|
}
|
|
testRecord(bytes, parsed)
|
|
|
|
// Example D.2. Service Mode
|
|
|
|
// Figure 3: TargetName Is "."
|
|
// example.com. SVCB 1 .
|
|
bytes = []byte{
|
|
0x00, 0x01, // priority
|
|
0x00, // target (root label)
|
|
}
|
|
parsed = &SVCBResource{
|
|
Priority: 1,
|
|
Target: MustNewName("."),
|
|
Params: []SVCParam{},
|
|
}
|
|
testRecord(bytes, parsed)
|
|
|
|
// Figure 4: Specifies a Port
|
|
// example.com. SVCB 16 foo.example.com. port=53
|
|
bytes = []byte{
|
|
0x00, 0x10, // priority
|
|
0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, // target
|
|
0x00, 0x03, // key 3
|
|
0x00, 0x02, // length 2
|
|
0x00, 0x35, // value
|
|
}
|
|
parsed = &SVCBResource{
|
|
Priority: 16,
|
|
Target: MustNewName("foo.example.com."),
|
|
Params: []SVCParam{{Key: SVCParamPort, Value: []byte{0x00, 0x35}}},
|
|
}
|
|
testRecord(bytes, parsed)
|
|
|
|
// Figure 5: A Generic Key and Unquoted Value
|
|
// example.com. SVCB 1 foo.example.com. key667=hello
|
|
bytes = []byte{
|
|
0x00, 0x01, // priority
|
|
0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, // target
|
|
0x02, 0x9b, // key 667
|
|
0x00, 0x05, // length 5
|
|
0x68, 0x65, 0x6c, 0x6c, 0x6f, // value
|
|
}
|
|
parsed = &SVCBResource{
|
|
Priority: 1,
|
|
Target: MustNewName("foo.example.com."),
|
|
Params: []SVCParam{{Key: 667, Value: []byte("hello")}},
|
|
}
|
|
testRecord(bytes, parsed)
|
|
|
|
// Figure 6: A Generic Key and Quoted Value with a Decimal Escape
|
|
// example.com. SVCB 1 foo.example.com. key667="hello\210qoo"
|
|
bytes = []byte{
|
|
0x00, 0x01, // priority
|
|
0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, // target
|
|
0x02, 0x9b, // key 667
|
|
0x00, 0x09, // length 9
|
|
0x68, 0x65, 0x6c, 0x6c, 0x6f, 0xd2, 0x71, 0x6f, 0x6f, // value
|
|
}
|
|
parsed = &SVCBResource{
|
|
Priority: 1,
|
|
Target: MustNewName("foo.example.com."),
|
|
Params: []SVCParam{{Key: 667, Value: []byte("hello\xd2qoo")}},
|
|
}
|
|
testRecord(bytes, parsed)
|
|
|
|
// Figure 7: Two Quoted IPv6 Hints
|
|
// example.com. SVCB 1 foo.example.com. (
|
|
// ipv6hint="2001:db8::1,2001:db8::53:1"
|
|
// )
|
|
bytes = []byte{
|
|
0x00, 0x01, // priority
|
|
0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, // target
|
|
0x00, 0x06, // key 6
|
|
0x00, 0x20, // length 32
|
|
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // first address
|
|
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00, 0x01, // second address
|
|
}
|
|
parsed = &SVCBResource{
|
|
Priority: 1,
|
|
Target: MustNewName("foo.example.com."),
|
|
Params: []SVCParam{{Key: SVCParamIPv6Hint, Value: []byte{0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00, 0x01}}},
|
|
}
|
|
testRecord(bytes, parsed)
|
|
|
|
// Figure 8: An IPv6 Hint Using the Embedded IPv4 Syntax
|
|
// example.com. SVCB 1 example.com. (
|
|
// ipv6hint="2001:db8:122:344::192.0.2.33"
|
|
// )
|
|
bytes = []byte{
|
|
0x00, 0x01, // priority
|
|
0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, // target
|
|
0x00, 0x06, // key 6
|
|
0x00, 0x10, // length 16
|
|
0x20, 0x01, 0x0d, 0xb8, 0x01, 0x22, 0x03, 0x44, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x02, 0x21, // address
|
|
}
|
|
parsed = &SVCBResource{
|
|
Priority: 1,
|
|
Target: MustNewName("example.com."),
|
|
Params: []SVCParam{{Key: SVCParamIPv6Hint, Value: []byte{0x20, 0x01, 0x0d, 0xb8, 0x01, 0x22, 0x03, 0x44, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x02, 0x21}}},
|
|
}
|
|
testRecord(bytes, parsed)
|
|
|
|
// Figure 9: SvcParamKey Ordering Is Arbitrary in Presentation Format but Sorted in Wire Format
|
|
// example.com. SVCB 16 foo.example.org. (
|
|
// alpn=h2,h3-19 mandatory=ipv4hint,alpn
|
|
// ipv4hint=192.0.2.1
|
|
// )
|
|
bytes = []byte{
|
|
0x00, 0x10, // priority
|
|
0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x6f, 0x72, 0x67, 0x00, // target
|
|
0x00, 0x00, // key 0
|
|
0x00, 0x04, // param length 4
|
|
0x00, 0x01, // value: key 1
|
|
0x00, 0x04, // value: key 4
|
|
0x00, 0x01, // key 1
|
|
0x00, 0x09, // param length 9
|
|
0x02, // alpn length 2
|
|
0x68, 0x32, // alpn value
|
|
0x05, // alpn length 5
|
|
0x68, 0x33, 0x2d, 0x31, 0x39, // alpn value
|
|
0x00, 0x04, // key 4
|
|
0x00, 0x04, // param length 4
|
|
0xc0, 0x00, 0x02, 0x01, // param value
|
|
}
|
|
parsed = &SVCBResource{
|
|
Priority: 16,
|
|
Target: MustNewName("foo.example.org."),
|
|
Params: []SVCParam{
|
|
{Key: SVCParamMandatory, Value: []byte{0x00, 0x01, 0x00, 0x04}},
|
|
{Key: SVCParamALPN, Value: []byte{0x02, 0x68, 0x32, 0x05, 0x68, 0x33, 0x2d, 0x31, 0x39}},
|
|
{Key: SVCParamIPv4Hint, Value: []byte{0xc0, 0x00, 0x02, 0x01}},
|
|
},
|
|
}
|
|
testRecord(bytes, parsed)
|
|
|
|
// Figure 10: An "alpn" Value with an Escaped Comma and an Escaped Backslash in Two Presentation Formats
|
|
// example.com. SVCB 16 foo.example.org. alpn=f\\\092oo\092,bar,h2
|
|
bytes = []byte{
|
|
0x00, 0x10, // priority
|
|
0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x6f, 0x72, 0x67, 0x00, // target
|
|
0x00, 0x01, // key 1
|
|
0x00, 0x0c, // param length 12
|
|
0x08, // alpn length 8
|
|
0x66, 0x5c, 0x6f, 0x6f, 0x2c, 0x62, 0x61, 0x72, // alpn value
|
|
0x02, // alpn length 2
|
|
0x68, 0x32, // alpn value
|
|
}
|
|
parsed = &SVCBResource{
|
|
Priority: 16,
|
|
Target: MustNewName("foo.example.org."),
|
|
Params: []SVCParam{
|
|
{Key: SVCParamALPN, Value: []byte{0x08, 0x66, 0x5c, 0x6f, 0x6f, 0x2c, 0x62, 0x61, 0x72, 0x02, 0x68, 0x32}},
|
|
},
|
|
}
|
|
testRecord(bytes, parsed)
|
|
}
|
|
|
|
func TestSVCBPackLongValue(t *testing.T) {
|
|
b := NewBuilder(nil, Header{})
|
|
b.StartQuestions()
|
|
b.StartAnswers()
|
|
|
|
res := SVCBResource{
|
|
Target: MustNewName("example.com."),
|
|
Params: []SVCParam{
|
|
{
|
|
Key: SVCParamMandatory,
|
|
Value: make([]byte, math.MaxUint16+1),
|
|
},
|
|
},
|
|
}
|
|
|
|
err := b.SVCBResource(ResourceHeader{Name: MustNewName("example.com.")}, res)
|
|
if err == nil || err.Error() != "ResourceBody: SVCBResource.Params: value too long (>65535 bytes)" {
|
|
t.Fatalf(`b.SVCBResource() = %v; want = "ResourceBody: SVCBResource.Params: value too long (>65535 bytes)"`, err)
|
|
}
|
|
|
|
err = b.HTTPSResource(ResourceHeader{Name: MustNewName("example.com.")}, HTTPSResource{res})
|
|
if err == nil || err.Error() != "ResourceBody: SVCBResource.Params: value too long (>65535 bytes)" {
|
|
t.Fatalf(`b.HTTPSResource() = %v; want = "ResourceBody: SVCBResource.Params: value too long (>65535 bytes)"`, err)
|
|
}
|
|
}
|