Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions dns/dnsmessage/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,21 @@ import (
)

// Message formats

// A Type is a type of DNS request and response.
//
// To add a new Resource Record type:
// 1. Create Resource Record types
// 1.1. Add a Type constant named "Type<name>"
// 1.2. Add the corresponding entry to the typeNames map
// 1.3. Add a [ResourceBody] implementation named "<name>Resource"
// 2. Implement packing
// 2.1. Implement Builder.<name>Resource()
// 3. Implement unpacking
// 3.1. Add the unpacking code to unpackResourceBody()
// 3.2. Implement Parser.<name>Resource()

// A Type is the type of a DNS Resource Record, as defined in the [IANA registry].
//
// [IANA registry]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4
type Type uint16

const (
Expand All @@ -33,6 +46,8 @@ const (
TypeAAAA Type = 28
TypeSRV Type = 33
TypeOPT Type = 41
TypeSVCB Type = 64
TypeHTTPS Type = 65

// Question.Type
TypeWKS Type = 11
Expand All @@ -53,6 +68,8 @@ var typeNames = map[Type]string{
TypeAAAA: "TypeAAAA",
TypeSRV: "TypeSRV",
TypeOPT: "TypeOPT",
TypeSVCB: "TypeSVCB",
TypeHTTPS: "TypeHTTPS",
TypeWKS: "TypeWKS",
TypeHINFO: "TypeHINFO",
TypeMINFO: "TypeMINFO",
Expand Down Expand Up @@ -273,6 +290,7 @@ var (
errTooManyAdditionals = errors.New("too many Additionals to pack (>65535)")
errNonCanonicalName = errors.New("name is not in canonical format (it must end with a .)")
errStringTooLong = errors.New("character string exceeds maximum length (255)")
errParamOutOfOrder = errors.New("parameter out of order")
)

// Internal constants.
Expand Down Expand Up @@ -2220,6 +2238,16 @@ func unpackResourceBody(msg []byte, off int, hdr ResourceHeader) (ResourceBody,
rb, err = unpackSRVResource(msg, off)
r = &rb
name = "SRV"
case TypeSVCB:
var rb SVCBResource
rb, err = unpackSVCBResource(msg, off, hdr.Length)
r = &rb
name = "SVCB"
case TypeHTTPS:
var rb HTTPSResource
rb.SVCBResource, err = unpackSVCBResource(msg, off, hdr.Length)
r = &rb
name = "HTTPS"
case TypeOPT:
var rb OPTResource
rb, err = unpackOPTResource(msg, off, hdr.Length)
Expand Down
80 changes: 75 additions & 5 deletions dns/dnsmessage/message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,49 @@ func TestResourceNotStarted(t *testing.T) {
}
}

func buildTestSVCBMsg() Message {
svcb := &SVCBResource{
Priority: 1,
Target: MustNewName("svc.example.com."),
Params: []SVCParam{{Key: SVCParamALPN, Value: []byte("h2")}},
}

https := &HTTPSResource{
SVCBResource{
Priority: 2,
Target: MustNewName("https.example.com."),
Params: []SVCParam{
{Key: SVCParamPort, Value: []byte{0x01, 0xbb}},
{Key: SVCParamIPv4Hint, Value: []byte{192, 0, 2, 1}},
},
},
}

return Message{
Questions: []Question{},
Answers: []Resource{
{
ResourceHeader{
Name: MustNewName("foo.bar.example.com."),
Type: TypeSVCB,
Class: ClassINET,
},
svcb,
},
{
ResourceHeader{
Name: MustNewName("foo.bar.example.com."),
Type: TypeHTTPS,
Class: ClassINET,
},
https,
},
},
Authorities: []Resource{},
Additionals: []Resource{},
}
}

func TestDNSPackUnpack(t *testing.T) {
wants := []Message{
{
Expand All @@ -378,6 +421,7 @@ func TestDNSPackUnpack(t *testing.T) {
Additionals: []Resource{},
},
largeTestMsg(),
buildTestSVCBMsg(),
}
for i, want := range wants {
b, err := want.Pack()
Expand All @@ -390,7 +434,14 @@ func TestDNSPackUnpack(t *testing.T) {
t.Fatalf("%d: Message.Unapck() = %v", i, err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("%d: Message.Pack/Unpack() roundtrip: got = %+v, want = %+v", i, &got, &want)
t.Errorf("%d: Message.Pack/Unpack() roundtrip: got = %#v, want = %#v", i, &got, &want)
if len(got.Answers) > 0 && len(want.Answers) > 0 {
if !reflect.DeepEqual(got.Answers[0].Body, want.Answers[0].Body) {
t.Errorf("Answer 0 Body mismatch")
t.Errorf("got: %#v", got.Answers[0].Body)
t.Errorf("want: %#v", want.Answers[0].Body)
}
}
}
}
}
Expand Down Expand Up @@ -684,16 +735,19 @@ func TestBuilderResourceError(t *testing.T) {
name string
fn func(*Builder) error
}{
// Keep it sorted by resource type name.
{"AResource", func(b *Builder) error { return b.AResource(ResourceHeader{}, AResource{}) }},
{"AAAAResource", func(b *Builder) error { return b.AAAAResource(ResourceHeader{}, AAAAResource{}) }},
{"CNAMEResource", func(b *Builder) error { return b.CNAMEResource(ResourceHeader{}, CNAMEResource{}) }},
{"HTTPSResource", func(b *Builder) error { return b.HTTPSResource(ResourceHeader{}, HTTPSResource{}) }},
{"MXResource", func(b *Builder) error { return b.MXResource(ResourceHeader{}, MXResource{}) }},
{"NSResource", func(b *Builder) error { return b.NSResource(ResourceHeader{}, NSResource{}) }},
{"OPTResource", func(b *Builder) error { return b.OPTResource(ResourceHeader{}, OPTResource{}) }},
{"PTRResource", func(b *Builder) error { return b.PTRResource(ResourceHeader{}, PTRResource{}) }},
{"SOAResource", func(b *Builder) error { return b.SOAResource(ResourceHeader{}, SOAResource{}) }},
{"TXTResource", func(b *Builder) error { return b.TXTResource(ResourceHeader{}, TXTResource{}) }},
{"SRVResource", func(b *Builder) error { return b.SRVResource(ResourceHeader{}, SRVResource{}) }},
{"AResource", func(b *Builder) error { return b.AResource(ResourceHeader{}, AResource{}) }},
{"AAAAResource", func(b *Builder) error { return b.AAAAResource(ResourceHeader{}, AAAAResource{}) }},
{"OPTResource", func(b *Builder) error { return b.OPTResource(ResourceHeader{}, OPTResource{}) }},
{"SVCBResource", func(b *Builder) error { return b.SVCBResource(ResourceHeader{}, SVCBResource{}) }},
{"TXTResource", func(b *Builder) error { return b.TXTResource(ResourceHeader{}, TXTResource{}) }},
{"UnknownResource", func(b *Builder) error { return b.UnknownResource(ResourceHeader{}, UnknownResource{}) }},
}

Expand Down Expand Up @@ -785,6 +839,14 @@ func TestBuilder(t *testing.T) {
if err := b.SRVResource(a.Header, *a.Body.(*SRVResource)); err != nil {
t.Fatalf("Builder.SRVResource(%#v) = %v", a, err)
}
case TypeSVCB:
if err := b.SVCBResource(a.Header, *a.Body.(*SVCBResource)); err != nil {
t.Fatalf("Builder.SVCBResource(%#v) = %v", a, err)
}
case TypeHTTPS:
if err := b.HTTPSResource(a.Header, *a.Body.(*HTTPSResource)); err != nil {
t.Fatalf("Builder.HTTPSResource(%#v) = %v", a, err)
}
case privateUseType:
if err := b.UnknownResource(a.Header, *a.Body.(*UnknownResource)); err != nil {
t.Fatalf("Builder.UnknownResource(%#v) = %v", a, err)
Expand Down Expand Up @@ -1262,6 +1324,14 @@ func benchmarkParsing(tb testing.TB, buf []byte) {
if _, err := p.NSResource(); err != nil {
tb.Fatal("Parser.NSResource() =", err)
}
case TypeSVCB:
if _, err := p.SVCBResource(); err != nil {
tb.Fatal("Parser.SVCBResource() =", err)
}
case TypeHTTPS:
if _, err := p.HTTPSResource(); err != nil {
tb.Fatal("Parser.HTTPSResource() =", err)
}
case TypeOPT:
if _, err := p.OPTResource(); err != nil {
tb.Fatal("Parser.OPTResource() =", err)
Expand Down
Loading