diff options
| author | Shulhan <ms@kilabit.info> | 2024-04-01 05:02:42 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2024-04-12 06:28:16 +0700 |
| commit | 6361b4a088ee67e34887430b4a57f330c08d15b3 (patch) | |
| tree | 76e6f3ebd3b922c26ff49c31359370cdef9ef821 | |
| parent | 77b2da671a29eff58925c13ea0e690c3977698a5 (diff) | |
| download | pakakeh.go-6361b4a088ee67e34887430b4a57f330c08d15b3.tar.xz | |
lib/dns: fix packing and unpacking OPT record
The RDATA in OPT records can contains zero or _more_ options.
Previously, we only handle unpacking and packing one option, now we
handle multiple options.
| -rw-r--r-- | _doc/RFC_6891_EDNS0.adoc | 76 | ||||
| -rw-r--r-- | _doc/index.adoc | 2 | ||||
| -rw-r--r-- | lib/dns/message.go | 34 | ||||
| -rw-r--r-- | lib/dns/message_test.go | 54 | ||||
| -rw-r--r-- | lib/dns/rdata_opt.go | 80 | ||||
| -rw-r--r-- | lib/dns/rdata_opt_var.go | 16 | ||||
| -rw-r--r-- | lib/dns/resource_record.go | 28 | ||||
| -rw-r--r-- | lib/dns/testdata/message/UnpackMessage_OPT_test.txt | 171 |
8 files changed, 388 insertions, 73 deletions
diff --git a/_doc/RFC_6891_EDNS0.adoc b/_doc/RFC_6891_EDNS0.adoc new file mode 100644 index 00000000..966cbff3 --- /dev/null +++ b/_doc/RFC_6891_EDNS0.adoc @@ -0,0 +1,76 @@ += Extension Mechanisms for DNS - EDNS(0) +:toc: +:sectlinks: + +The +https://datatracker.ietf.org/doc/html/rfc6891[RFC 6891] +define the pseudo resource record (RR) or meta RR for DNS named OPT. + +The OPT record provides an extension to DNS, nicknamed as "EDNS(0)", which +was previously called "EDNS0" specified in +https://datatracker.ietf.org/doc/html/rfc2671/[RFC 2671]. + +The OPT RR has RR type 41 (0x21). + +The OPT record can only be added to the additional section of DNS response. + +== Implementation requirements + +OPT RRs MUST NOT be cached, forwarded, or stored in or loaded from master +files. + +When an OPT RR is included within any DNS message, it MUST be the +only OPT RR in that message. +If a query message with more than one OPT RR is received, a FORMERR (format +error with response code (RCODE) value 1) MUST be returned. + +== Record format + +The OPT RR changes the definition of CLASS and TTL from normal DNS RR. + +---- ++--------+ +| NAME | ; 2-octets, MUST be 0 (an empty label). ++--------+ +| TYPE | ; 16-bit unsigned integer, with value 0x0029 (or 41) ++--------+ +| CLASS | ; 16-bit unsigned integer, requester's UDP payload size. ++--------+ +| TTL | ; 32-bit unsigned integer, extended RCODE and flags. +| | ++--------+ +| RDLEN | ; 16-bit unsigned integer, length of RDATA. ++--------+ +/ RDATA / ; Arbitrary length based on RDLEN. ++--------+ +---- + +Inside the TTL, the extended RCODE and flags define as below, + +---- ++----------------+ +| EXTENDED-RCODE | 1-octet, the extended RCODE. ++----------------+ +| VERSION | 1-octet, version of implementation. ++----------------+ +| DO | 1-bit. ++----------------+ +| Z | 15-bit, zero bits. ++----------------+ +---- + +Note that EXTENDED-RCODE value 0 indicates that an unextended RCODE is in +use. + +The RDATA contains zero or more options as a pair of code-value in the +following format, + +---- ++---------------+ +| OPTION-CODE | ; 2-octets. ++---------------+ +| OPTION-LENGTH | ; 2-octets, the length of value in octets. ++---------------+ +/ OPTION-VALUE / ; Arbitrary length of value based on OPTION-LENGTH; ++---------------+ +---- diff --git a/_doc/index.adoc b/_doc/index.adoc index 7db30f9a..5f71f567 100644 --- a/_doc/index.adoc +++ b/_doc/index.adoc @@ -102,6 +102,8 @@ SPF:: DNS:: + -- +* link:RFC_6891_EDNS0.html[RFC 6891: Extension Mechanisms for DNS (EDNS(0))^] + * link:RFC_9460__SVCB_and_HTTP_RR.html[RFC 9460 Service Binding and Parameter Specification via the DNS (SVCB and HTTPS Resource Records)] -- diff --git a/lib/dns/message.go b/lib/dns/message.go index 7a43a7c8..a35473b0 100644 --- a/lib/dns/message.go +++ b/lib/dns/message.go @@ -730,37 +730,15 @@ func (msg *Message) packAAAA(rr *ResourceRecord) { } } +// packOPT pack the RDLEN and RDATA of OPT record. func (msg *Message) packOPT(rr *ResourceRecord) { - var ( - rrOPT, _ = rr.Value.(*RDataOPT) - off = uint(len(msg.packet)) - - n uint16 - ) - - // Reserve two octets for rdlength. - msg.packet = libbytes.AppendUint16(msg.packet, 0) + var rrOPT *RDataOPT - if rrOPT.Length == 0 { - return - } - - // Pack OPT rdata - msg.packet = libbytes.AppendUint16(msg.packet, rrOPT.Code) - - // Values of less than 512 bytes MUST be treated as equal to 512 - // bytes (RFC6891 P11). - if rrOPT.Length < 512 { - msg.packet = libbytes.AppendUint16(msg.packet, 512) - } else { - msg.packet = libbytes.AppendUint16(msg.packet, rrOPT.Length) - } + rrOPT, _ = rr.Value.(*RDataOPT) - msg.packet = append(msg.packet, rrOPT.Data[:rrOPT.Length]...) - - // Write rdlength. - n = 4 + rrOPT.Length - libbytes.WriteUint16(msg.packet, off, n) + var rdata = rrOPT.pack() + msg.packet = libbytes.AppendUint16(msg.packet, uint16(len(rdata))) + msg.packet = append(msg.packet, rdata...) } func (msg *Message) packSVCB(rr *ResourceRecord) { diff --git a/lib/dns/message_test.go b/lib/dns/message_test.go index bd8ead34..9d833389 100644 --- a/lib/dns/message_test.go +++ b/lib/dns/message_test.go @@ -2078,6 +2078,59 @@ func TestUnpackMessage(t *testing.T) { } } +func TestUnpackMessage_OPT(t *testing.T) { + var ( + tdata *test.Data + err error + ) + + tdata, err = test.LoadData(`testdata/message/UnpackMessage_OPT_test.txt`) + if err != nil { + t.Fatal(err) + } + + var listCase = []string{ + `cgv5hi6hcnsdsf1etkyqzvuzdyus.multi.surbl.org:00`, + `cgv5hi6hcnsdsf1etkyqzvuzdyus.multi.surbl.org:01`, + } + + var ( + tcase string + stream []byte + msg *Message + bbuf bytes.Buffer + ) + for _, tcase = range listCase { + stream, err = libbytes.ParseHexDump(tdata.Input[tcase], true) + if err != nil { + t.Fatal(err) + } + + msg, err = UnpackMessage(stream) + if err != nil { + t.Fatal(tcase, err) + } + + stream, err = json.MarshalIndent(&msg, ``, ` `) + if err != nil { + t.Fatal(err) + } + test.Assert(t, tcase, string(tdata.Output[tcase]), string(stream)) + + // Pack the unpacked message again to generate hexdump that + // should be equal with input. + + stream, err = msg.Pack() + if err != nil { + t.Fatal(err) + } + bbuf.Reset() + libbytes.DumpPrettyTable(&bbuf, msg.Question.String(), stream) + tcase += `.hexdump` + test.Assert(t, tcase, string(tdata.Output[tcase]), bbuf.String()) + } +} + func TestUnpackMessage_SVCB(t *testing.T) { var ( logp = `TestUnpackMessage_SVCB` @@ -2108,7 +2161,6 @@ func TestUnpackMessage_SVCB(t *testing.T) { msg *Message ) for _, name = range listCase { - stream, err = libbytes.ParseHexDump(tdata.Input[name], true) if err != nil { t.Fatal(logp, err) diff --git a/lib/dns/rdata_opt.go b/lib/dns/rdata_opt.go index 329c9c77..fffba7ec 100644 --- a/lib/dns/rdata_opt.go +++ b/lib/dns/rdata_opt.go @@ -7,35 +7,33 @@ package dns import ( "fmt" "strings" + + libbytes "git.sr.ht/~shulhan/pakakeh.go/lib/bytes" ) // RDataOPT define format of RDATA for OPT. // // The extended RCODE and flags, which OPT stores in the RR Time to Live -// (TTL) field, contains ExtRCode, Version +// (TTL) field, contains ExtRCode, Version, and DNSSEC OK flag. type RDataOPT struct { - // Varies per OPTION-CODE. MUST be treated as a bit field. - Data []byte - - // Assigned by the Expert Review process as defined by the DNSEXT - // working group and the IESG. - Code uint16 + // ListVar list of pair of code-value inside the RDATA. + ListVar []RDataOPTVar - // Size (in octets) of OPTION-DATA. - Length uint16 - - // Forms the upper 8 bits of extended 12-bit RCODE (together with the - // 4 bits defined in [RFC1035]. Note that EXTENDED-RCODE value 0 - // indicates that an unextended RCODE is in use (values 0 through 15). + // Forms the upper 8 bits of extended 12-bit RCODE (together with + // the 4 bits defined message header). + // Note that the value of 0 indicates that the RCODE in message + // header is in use (values 0 through 15). ExtRCode byte - // Indicates the implementation level of the setter. Full conformance - // with this specification is indicated by version '0'. Requestors - // are encouraged to set this to the lowest implemented level capable - // of expressing a transaction, to minimise the responder and network - // load of discovering the greatest common implementation level - // between requestor and responder. A requestor's version numbering - // strategy MAY ideally be a run-time configuration option. + // Indicates the implementation level of the setter. + // Full conformance with this specification is indicated by version + // '0'. + // Requestors are encouraged to set this to the lowest implemented + // level capable of expressing a transaction, to minimise the + // responder and network load of discovering the greatest common + // implementation level between requestor and responder. + // A requestor's version numbering strategy MAY ideally be a + // run-time configuration option. Version byte // DNSSEC OK bit as defined by [RFC3225]. @@ -46,9 +44,45 @@ type RDataOPT struct { func (opt *RDataOPT) String() string { var b strings.Builder - fmt.Fprintf(&b, "{ExtRCode:%d Version:%d DO:%v Code:%d Length:%d Data:%s}", - opt.ExtRCode, opt.Version, opt.DO, opt.Code, opt.Length, - opt.Data) + fmt.Fprintf(&b, "{ExtRCode:%d Version:%d DO:%v}", + opt.ExtRCode, opt.Version, opt.DO) return b.String() } + +// pack the ListVar for RDATA. +func (opt *RDataOPT) pack() (rdata []byte) { + var optvar RDataOPTVar + for _, optvar = range opt.ListVar { + rdata = libbytes.AppendUint16(rdata, optvar.Code) + rdata = libbytes.AppendUint16(rdata, uint16(len(optvar.Data))) + rdata = append(rdata, optvar.Data...) + } + return rdata +} + +// unpack extended-rcode with flags from ext (RR TTL), and RDATA from raw +// packet. +func (opt *RDataOPT) unpack(rdlen int, packet []byte) (err error) { + var x int + + for x < rdlen { + var optvar = RDataOPTVar{} + + optvar.Code = libbytes.ReadUint16(packet, uint(x)) + x += 2 + + var optlen = int(libbytes.ReadUint16(packet, uint(x))) + x += 2 + + if x+optlen > len(packet) { + return fmt.Errorf(`option-length is out of range (want=%d, got=%d)`, optlen, len(packet)) + } + + optvar.Data = append(optvar.Data, packet[x:x+optlen]...) + x += optlen + + opt.ListVar = append(opt.ListVar, optvar) + } + return nil +} diff --git a/lib/dns/rdata_opt_var.go b/lib/dns/rdata_opt_var.go new file mode 100644 index 00000000..c08ef7b2 --- /dev/null +++ b/lib/dns/rdata_opt_var.go @@ -0,0 +1,16 @@ +// Copyright 2024, Shulhan <ms@kilabit.info>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dns + +// RDataOPTVar contains the option in OPT RDATA. +type RDataOPTVar struct { + // Varies per Code. + // MUST be treated as a bit field. + Data []byte + + // Assigned by the Expert Review process as defined by the DNSEXT + // working group and the IESG. + Code uint16 +} diff --git a/lib/dns/resource_record.go b/lib/dns/resource_record.go index f32ab334..4f620222 100644 --- a/lib/dns/resource_record.go +++ b/lib/dns/resource_record.go @@ -5,7 +5,6 @@ package dns import ( - "errors" "fmt" "log" "net" @@ -188,8 +187,6 @@ func (rr *ResourceRecord) unpack(packet []byte, startIdx uint) (x uint, err erro var ( logp = "ResourceRecord.unpack" lenPacket = uint(len(packet)) - - lenXRdata uint ) x = startIdx @@ -209,7 +206,7 @@ func (rr *ResourceRecord) unpack(packet []byte, startIdx uint) (x uint, err erro rr.rdlen = libbytes.ReadUint16(packet, x) x += 2 - lenXRdata = x + uint(rr.rdlen) + var lenXRdata uint = x + uint(rr.rdlen) if lenPacket < lenXRdata { return x, fmt.Errorf("%s: %s %d: packet length %d smaller than index+rdata %d+%d (%d)", logp, rr.Name, rr.Type, lenPacket, x, rr.rdlen, lenXRdata) @@ -513,37 +510,26 @@ func (rr *ResourceRecord) unpackSRV(packet []byte, x uint) (err error) { return } -func (rr *ResourceRecord) unpackOPT(packet []byte, x uint) error { - var ( - rrOPT = &RDataOPT{} - - endIdx uint - ) +func (rr *ResourceRecord) unpackOPT(packet []byte, x uint) (err error) { + var rrOPT = &RDataOPT{} rr.Value = rrOPT - // Unpack extended RCODE and flags from TTL. rrOPT.ExtRCode = byte(rr.TTL >> 24) rrOPT.Version = byte(rr.TTL >> 16) if rr.TTL&maskOPTDO == maskOPTDO { rrOPT.DO = true } - if rr.rdlen == 0 { return nil } - // Unpack the RDATA - rrOPT.Code = libbytes.ReadUint16(packet, x) - x += 2 - rrOPT.Length = libbytes.ReadUint16(packet, x) - x += 2 - endIdx = x + uint(rr.rdlen) - if int(endIdx) >= len(packet) { - return errors.New("unpackOPT: data length is out of range") + err = rrOPT.unpack(int(rr.rdlen), packet[x:]) + if err != nil { + return fmt.Errorf(`unpackOPT: %w`, err) } - rrOPT.Data = append(rrOPT.Data, packet[x:endIdx]...) + return nil } diff --git a/lib/dns/testdata/message/UnpackMessage_OPT_test.txt b/lib/dns/testdata/message/UnpackMessage_OPT_test.txt new file mode 100644 index 00000000..244df80a --- /dev/null +++ b/lib/dns/testdata/message/UnpackMessage_OPT_test.txt @@ -0,0 +1,171 @@ +>>> cgv5hi6hcnsdsf1etkyqzvuzdyus.multi.surbl.org:00 +0000000 4d2d 8182 0001 0000 0000 0001 1c43 4756 +0000010 3548 6936 6843 6e73 4453 6631 6574 4b59 +0000020 515a 7655 7a64 7975 7305 6d75 6c74 6905 +0000030 7375 7262 6c03 6f72 6700 0001 0001 0000 +0000040 2904 d000 0000 0000 1900 0f00 1500 1674 +0000050 696d 6520 6c69 6d69 7420 6578 6365 6564 +0000060 6564 + +<<< cgv5hi6hcnsdsf1etkyqzvuzdyus.multi.surbl.org:00 +{ + "Answer": null, + "Authority": null, + "Additional": [ + { + "Value": { + "ListVar": [ + { + "Data": "ABZ0aW1lIGxpbWl0IGV4Y2VlZGVk", + "Code": 15 + } + ], + "ExtRCode": 0, + "Version": 0, + "DO": false + }, + "Name": "", + "Type": 41, + "Class": 1232, + "TTL": 0 + } + ], + "Question": { + "Name": "cgv5hi6hcnsdsf1etkyqzvuzdyus.multi.surbl.org", + "Type": 1, + "Class": 1 + }, + "Header": { + "ID": 19757, + "IsQuery": false, + "Op": 0, + "IsAA": false, + "IsTC": false, + "IsRD": true, + "IsRA": true, + "RCode": 2, + "QDCount": 1, + "ANCount": 0, + "NSCount": 0, + "ARCount": 1 + } +} + +<<< cgv5hi6hcnsdsf1etkyqzvuzdyus.multi.surbl.org:00.hexdump +{Name:cgv5hi6hcnsdsf1etkyqzvuzdyus.multi.surbl.org Type:A} + | 0 1 2 3 4 5 6 7 | 01234567 | 0 1 2 3 4 5 6 7 | + | 8 9 A B C D E F | 89ABCDEF | 8 9 A B C D E F | +0x00000000| 4d 2d 81 82 00 01 00 00 | M-...... | 77 45 129 130 0 1 0 0 |0 +0x00000008| 00 00 00 01 1c 63 67 76 | .....cgv | 0 0 0 1 28 99 103 118 |8 +0x00000010| 35 68 69 36 68 63 6e 73 | 5hi6hcns | 53 104 105 54 104 99 110 115 |16 +0x00000018| 64 73 66 31 65 74 6b 79 | dsf1etky | 100 115 102 49 101 116 107 121 |24 +0x00000020| 71 7a 76 75 7a 64 79 75 | qzvuzdyu | 113 122 118 117 122 100 121 117 |32 +0x00000028| 73 05 6d 75 6c 74 69 05 | s.multi. | 115 5 109 117 108 116 105 5 |40 +0x00000030| 73 75 72 62 6c 03 6f 72 | surbl.or | 115 117 114 98 108 3 111 114 |48 +0x00000038| 67 00 00 01 00 01 00 00 | g....... | 103 0 0 1 0 1 0 0 |56 +0x00000040| 29 04 d0 00 00 00 00 00 | )....... | 41 4 208 0 0 0 0 0 |64 +0x00000048| 19 00 0f 00 15 00 16 74 | .......t | 25 0 15 0 21 0 22 116 |72 +0x00000050| 69 6d 65 20 6c 69 6d 69 | ime.limi | 105 109 101 32 108 105 109 105 |80 +0x00000058| 74 20 65 78 63 65 65 64 | t.exceed | 116 32 101 120 99 101 101 100 |88 +0x00000060| 65 64 | ed | 101 100 |96 + +>>> cgv5hi6hcnsdsf1etkyqzvuzdyus.multi.surbl.org:01 +0000000 4d2d 8182 0001 0000 +0000008 0000 0001 1c43 4756 +0000010 3548 6936 6843 6e73 +0000018 4453 6631 6574 4b59 +0000020 515a 7655 7a64 7975 +0000028 7305 6d75 6c74 6905 +0000030 7375 7262 6c03 6f72 +0000038 6700 0001 0001 0000 +0000040 2904 d000 0000 0000 +0000048 6d00 0f00 1500 1674 +0000050 696d 6520 6c69 6d69 +0000058 7420 6578 6365 6564 +0000060 6564 000f 0050 0017 +0000068 3134 352e 3130 302e +0000070 3138 382e 3232 3a35 +0000078 3320 7469 6d65 6420 +0000080 6f75 7420 666f 7220 +0000088 4347 5635 4869 3668 +0000090 436e 7344 5366 3165 +0000098 744b 5951 5a76 557a +00000a0 6479 7573 2e6d 756c +00000a8 7469 2e73 7572 626c +00000b0 2e6f 7267 2041 + +<<< cgv5hi6hcnsdsf1etkyqzvuzdyus.multi.surbl.org:01 +{ + "Answer": null, + "Authority": null, + "Additional": [ + { + "Value": { + "ListVar": [ + { + "Data": "ABZ0aW1lIGxpbWl0IGV4Y2VlZGVk", + "Code": 15 + }, + { + "Data": "ABcxNDUuMTAwLjE4OC4yMjo1MyB0aW1lZCBvdXQgZm9yIENHVjVIaTZoQ25zRFNmMWV0S1lRWnZVemR5dXMubXVsdGkuc3VyYmwub3JnIEE=", + "Code": 15 + } + ], + "ExtRCode": 0, + "Version": 0, + "DO": false + }, + "Name": "", + "Type": 41, + "Class": 1232, + "TTL": 0 + } + ], + "Question": { + "Name": "cgv5hi6hcnsdsf1etkyqzvuzdyus.multi.surbl.org", + "Type": 1, + "Class": 1 + }, + "Header": { + "ID": 19757, + "IsQuery": false, + "Op": 0, + "IsAA": false, + "IsTC": false, + "IsRD": true, + "IsRA": true, + "RCode": 2, + "QDCount": 1, + "ANCount": 0, + "NSCount": 0, + "ARCount": 1 + } +} + +<<< cgv5hi6hcnsdsf1etkyqzvuzdyus.multi.surbl.org:01.hexdump +{Name:cgv5hi6hcnsdsf1etkyqzvuzdyus.multi.surbl.org Type:A} + | 0 1 2 3 4 5 6 7 | 01234567 | 0 1 2 3 4 5 6 7 | + | 8 9 A B C D E F | 89ABCDEF | 8 9 A B C D E F | +0x00000000| 4d 2d 81 82 00 01 00 00 | M-...... | 77 45 129 130 0 1 0 0 |0 +0x00000008| 00 00 00 01 1c 63 67 76 | .....cgv | 0 0 0 1 28 99 103 118 |8 +0x00000010| 35 68 69 36 68 63 6e 73 | 5hi6hcns | 53 104 105 54 104 99 110 115 |16 +0x00000018| 64 73 66 31 65 74 6b 79 | dsf1etky | 100 115 102 49 101 116 107 121 |24 +0x00000020| 71 7a 76 75 7a 64 79 75 | qzvuzdyu | 113 122 118 117 122 100 121 117 |32 +0x00000028| 73 05 6d 75 6c 74 69 05 | s.multi. | 115 5 109 117 108 116 105 5 |40 +0x00000030| 73 75 72 62 6c 03 6f 72 | surbl.or | 115 117 114 98 108 3 111 114 |48 +0x00000038| 67 00 00 01 00 01 00 00 | g....... | 103 0 0 1 0 1 0 0 |56 +0x00000040| 29 04 d0 00 00 00 00 00 | )....... | 41 4 208 0 0 0 0 0 |64 +0x00000048| 6d 00 0f 00 15 00 16 74 | m......t | 109 0 15 0 21 0 22 116 |72 +0x00000050| 69 6d 65 20 6c 69 6d 69 | ime.limi | 105 109 101 32 108 105 109 105 |80 +0x00000058| 74 20 65 78 63 65 65 64 | t.exceed | 116 32 101 120 99 101 101 100 |88 +0x00000060| 65 64 00 0f 00 50 00 17 | ed...P.. | 101 100 0 15 0 80 0 23 |96 +0x00000068| 31 34 35 2e 31 30 30 2e | 145.100. | 49 52 53 46 49 48 48 46 |104 +0x00000070| 31 38 38 2e 32 32 3a 35 | 188.22:5 | 49 56 56 46 50 50 58 53 |112 +0x00000078| 33 20 74 69 6d 65 64 20 | 3.timed. | 51 32 116 105 109 101 100 32 |120 +0x00000080| 6f 75 74 20 66 6f 72 20 | out.for. | 111 117 116 32 102 111 114 32 |128 +0x00000088| 43 47 56 35 48 69 36 68 | CGV5Hi6h | 67 71 86 53 72 105 54 104 |136 +0x00000090| 43 6e 73 44 53 66 31 65 | CnsDSf1e | 67 110 115 68 83 102 49 101 |144 +0x00000098| 74 4b 59 51 5a 76 55 7a | tKYQZvUz | 116 75 89 81 90 118 85 122 |152 +0x000000a0| 64 79 75 73 2e 6d 75 6c | dyus.mul | 100 121 117 115 46 109 117 108 |160 +0x000000a8| 74 69 2e 73 75 72 62 6c | ti.surbl | 116 105 46 115 117 114 98 108 |168 +0x000000b0| 2e 6f 72 67 20 41 | .org.A | 46 111 114 103 32 65 |176 |
