Compare commits

...

447 Commits

Author SHA1 Message Date
Spencer Comfort d8fbd0a755
Update actions & add go 1.19.x and 1.20.x to go workflow (#1414)
* Update actions

* Update go.yml

* Update go.yml

* Update go.yml
2023-02-02 19:52:49 +01:00
dnschecktool f8a185d39e
readme: add some very thankful users (#1415) 2023-01-22 16:52:13 +01:00
Kian-Meng Ang 0089167cae
Fix typos (#1413)
Found via `codespell -L ede,ans,te,crasher`
2023-01-14 08:19:09 +01:00
Ali Mosajjal fe20d5d323
Update edns.go (#1409) 2023-01-06 08:54:28 +01:00
Tom Thorogood 41a7730f43
Remove space between NextLabel func and doc comment (#1410)
This was mistakenly added in #1406.
2023-01-06 08:51:52 +01:00
Miek Gieben 4bd038eb76
Run gofmt -w -s *.go (#1408)
Signed-off-by: Miek Gieben <miek@miek.nl>

Signed-off-by: Miek Gieben <miek@miek.nl>
2023-01-05 07:53:57 +01:00
Sam Therapy caa3fe0583
edns: add missing dig options (#1389)
* edns: add missing dig options

Signed-off-by: Sam Therapy <sam@samtherapy.net>

* Apply suggested change

Signed-off-by: Sam Therapy <sam@samtherapy.net>

Signed-off-by: Sam Therapy <sam@samtherapy.net>
2023-01-05 07:53:34 +01:00
Miek Gieben 3b7e0b9bdd
NextLabel: document neg. offset will panic (#1406)
Fixes: #1404

Signed-off-by: Miek Gieben <miek@miek.nl>

Signed-off-by: Miek Gieben <miek@miek.nl>
2023-01-05 07:52:29 +01:00
Caleb Jasik 8c643eba82
Add slackhq/nebula to users (#1403)
This is currently used to provide DNS server capabilities to the nebula lighthouses.
2022-12-30 18:08:40 +01:00
Simon Elsbrock 16b12df562
fix: example in docs with invalid syntax (#1401)
fixes invalid syntax in one of the examples of the README
2022-11-12 12:38:48 +01:00
Mike Schinkel 4822b271aa
Restructure license so Github will recognize it. (#1397) 2022-11-12 12:37:31 +01:00
Miek Gieben b3dfea0715 Release 1.1.50 2022-06-21 10:38:26 +02:00
Miek Gieben 69924a02cf
Make tsigGenerateProvider/TsigVerifyProvider public (#1382)
Make it public as TsigGenerateWithProvider and update the docs a little.
And TsigVerifyWithProvider also - tweak those docs also a little.

Signed-off-by: Miek Gieben <miek@miek.nl>
2022-06-21 10:37:36 +02:00
João Oliveirinha ff611cdc4b
Add back support for *net.UnixCon with seqpacket type (#1378)
This was broken by PR: https://github.com/miekg/dns/pull/1322
2022-06-08 14:03:24 +02:00
Miek Gieben eb4745b695
Add more detail to NSEC packing errors (#1374)
Add 'in the type bitmap' to make clear where in the RR the error occurs.
Also use 'NSEC(3)' - as this code is shared between NSEC and NSEC3, the
first error used NSECx.

Technically backwards incompatible, but checking strings in errors as
bad practice (although this lib lacks library types).

See #1373

Signed-off-by: Miek Gieben <miek@miek.nl>
2022-05-27 11:01:25 +02:00
Shane Kerr 7413c83334
Disallow names that start with '.' in IsDomainName() (#1376)
* Disallow names that start with '.' in IsDomainName()

* Also update packDomain()
2022-05-26 15:06:08 +02:00
Miek Gieben 5521648610 Release 1.1.49 2022-05-10 18:51:11 +02:00
Shane Kerr bfcbf0fd23
Fix dohpath parsing/stringify (#1366)
* dohpath escaped in String(), and parsed such values

* Update the test for dohpath with escaping

* Fix cut & paste error with svcdohpath error
2022-05-10 18:40:32 +02:00
Shane Kerr feda877277
Properly parse alpn values in SVCB (#1363)
* Modify the SVCBAlpn to properly parse/print

* Remove debug

* Change SVCB test from reflect to loop

* Refactor SVCB code to reduce indentation

* When stringifying SVCBAlpn, use strings.Builder for whole process

* Update comment in svcb.go

Co-authored-by: Miek Gieben <miek@miek.nl>

* Describe why we use a specific size for the alpn buffer

Co-authored-by: Miek Gieben <miek@miek.nl>
2022-05-10 18:40:09 +02:00
Miek Gieben 0d2c95b99c
Upgrade our test Go version to 1.17 and 1.18 (#1369)
Signed-off-by: Miek Gieben <miek@miek.nl>
2022-04-15 10:16:27 +02:00
Miek Gieben c760d3c7f1
Fix examples by using net.JoinHostPort (#1368)
Small fix in the examples to properly work with v6 addresses.

Closes: #1365 #1367

Signed-off-by: Miek Gieben <miek@miek.nl>
2022-04-15 10:13:08 +02:00
Ainar Garipov 656b7409ac
Add notes about SVCB draft changes to SVCB-related API (#1364)
* Add notes about SVCB draft changes to SVCB-related API

* Decrease number of warnings, rephrase
2022-04-14 20:36:11 +02:00
Ainar Garipov 08c2616301
Add SVCB dohpath key (#1359)
* Add SVCB dohpath key

The parameter is being added in [its own IETF draft][1] and also being used in
the [IETF draft about Descovery of Designated Resolvers][2].

Additionally, the mappings of the numeric key values to strings are exported,
under names consistent with the already existing exported mappings, to make it
easier for the clients of the module to validate and print SVCB keys.

Testing was done by sending SVCB queries for the "_dns.resolver.arpa" domain to
OpenDNS's 146.112.41.2 server.

[1]: https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02
[2]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-06.html

* Fix template length, docs; reverse some changes

* Remove incorrect validations; improve docs
2022-04-12 17:49:30 +02:00
Miek Gieben dedee46bd4
Fix ExamplePrivateHandle (#1354)
Signed-off-by: Miek Gieben <miek@miek.nl>
2022-04-02 19:16:31 +02:00
Miek Gieben 49c1b2e20f Release 1.1.48 2022-04-01 14:02:37 +02:00
Miek Gieben 045ac4ec6c
Fix RSAMD5 keytag calucation. (#1353)
Of course the wording was changed (for the better) in an errata:
https://www.rfc-editor.org/errata/eid193

We still followed the original RFC4034 text. Note I haven't given this
much thought, just changed the 2 into a 3 and ran the test.

Fixes: #1352

Signed-off-by: Miek Gieben <miek@miek.nl>
2022-04-01 14:01:47 +02:00
Olivier Poitrey 57e2e627a6
Invalid NSEC/3 bitmap on non-zero buffer (#1338)
* Invalid NSEC/3 bitmap on non-zero buffer

If the PackBuffer is used to encode an NSEC/3 record, the bitmap is
xored with the content of the buffer instead of being zeroed first.

The algorithm has been changed so it is able zero bytes without
losing too much performance (around 2x slower).

* Add some comments + rename some vars to make algo clearer

* Revert to previous algo with window length compute+0 on new window

* Use typeBitMapLen to compute the bitmap length to zero
2022-04-01 14:01:05 +02:00
DesWurstes 2f577ca35d
Update SVCB (#1341)
* Rename ECH, bump draft number

* AliasForm new treatment

* alpn is no longer mandatory by default

* Document the non-empty value requirement

* new test cases

* more test cases

* Continue forbidding v4-map-v6 but not v4-embed-v6

https://github.com/miekg/dns/pull/1067#discussion_r495556735 and https://github.com/MikeBishop/dns-alt-svc/issues/361

* Update documentation

* revert rename ech

* Reword AliasMode with key=value pairs
2022-04-01 14:00:53 +02:00
Miek Gieben d70eb7b9e1 Release 1.1.47 2022-03-12 10:49:06 +01:00
Dimitris Mavrommatis d48e92a0e6
Handle packing of empty RDATA correctly for EDNS0_EXPIRE (Resolves #1292) (#1293)
* Change EDNS_EXPIRE field to support zero length option data (Resolves #1292)

As per [RFC7134](https://datatracker.ietf.org/doc/html/rfc7314#section-2) the Expire
Option in queries should be zero-length. In the current implementation the field is
uint32 which always instatiates 4bytes for that field when packing to wire format.
For that reason we change the field to []uint8 so it can support 0-length and 4-byte
length option data.

* addressed comments

* addressed comments

* make change backwards compatible

* add comment for Empty field
2022-03-12 09:42:42 +01:00
Miek Gieben 05140a3136
Add indentityHash for algos that don't need hashing (#1340)
This adds hash.go and creates a identityHash that is used for algorithms
that do their own hashing (ED25519) for instance.

This unifies the hash variable naming between dnssec and sig(0) signing
and removes the special casing that existed for ED25519.

This unifies the variable naming between sig(0) and dnssec signing and
verifying.

I didn't want to used crypto.RegisterHash as not to fiddle with the
global namespaces of hashes, so the value of '0' from AlgorithmsToHash
is handled specially in dnssec and sig(0) code.

Note that ED448 isn't implemented at all.

Signed-off-by: Miek Gieben <miek@miek.nl>
2022-03-12 09:41:21 +01:00
Miek Gieben af1ebf55eb
Id check tests: stop using Exchange (#1343)
Using Exchange doesn't add anything, as it just wraps client.Exchange
with a default client.

Remove them and speed up the tests, goes from 3s to 1s (for the entire
test suite).

Signed-off-by: Miek Gieben <miek@miek.nl>
2022-03-02 13:52:49 +01:00
Miek Gieben 84af068d46 Release 1.1.46 2022-02-07 08:05:09 +01:00
Tom Thorogood 33e64002b6
Support TsigProvider for Server and Transfer (#1331)
Automatically submitted.
2022-02-05 00:23:49 +00:00
JeremyRand 51afb90ed3
Fix doc typo (#1328) 2022-01-17 08:58:28 +01:00
Tom Thorogood 0544c8bb11
Only treat a *net.UnixConn of unixgram as a packet conn (#1322)
* Refactor net.PacketConn checks into helper function

* Only treat a *net.UnixConn of unixgram as a packet conn

* Handle wrapped net.Conn types in isPacketConn

* Use Error instead of Fatal where appropriate in TestIsPacketConn
2021-12-28 13:52:38 +00:00
bshea3 af5144a5ca
Fix location of opt pseudosection and prevent empty additional section in dig-like output (#1320)
Automatically submitted.
2021-12-24 17:12:14 +00:00
Mark 32b1ed5f32
Add https://github.com/markdingo/autoreverse into list of users (#1319) 2021-12-24 08:09:22 +01:00
Miek Gieben 294c41a1d8 Release 1.1.45 2021-12-23 09:46:38 +01:00
Andrey Meshkov ba44371638
Fix edns keepalive (#1317)
* Fix un/packing of EDNS0 TCP keepalive extension

The current code is assuming the OPT code and length are part of the
data passed to the unpacker and that it should be appenedded during
packing while those fields are parsed by the caller.

This change fixes the parsing and make sure a request with a keepalive
won't fail.

* added tests for EDNS0_TCP_KEEPALIVE pack/unpack

* mark Length as deprecated

* removed named returns

* restored String

Co-authored-by: Olivier Poitrey <rs@rhapsodyk.net>
2021-12-23 09:45:02 +01:00
Miek Gieben f4af58267c Release 1.1.44 2021-12-21 09:32:39 +01:00
Chris O'Haver 3a58872b63
Do not sign BADKEY and BADSIG TSIG error responses (#1316)
* Per RFC 8945 5.3.2, responses with BADKEY and BADSIG errors must not be signed.

Signed-off-by: Chris O'Haver <cohaver@infoblox.com>

* refactor to remove else block

Signed-off-by: Chris O'Haver <cohaver@infoblox.com>

* skip signing only for BADKEY and BADSIG

Signed-off-by: Chris O'Haver <cohaver@infoblox.com>
2021-12-20 10:31:57 +01:00
Konstantine 3b8982ccc6
fixed documentation for DefaultMsgAcceptFunc (#1308) 2021-10-15 09:02:59 +02:00
Tom Sellers 7318b01e11
APL: adjust error handling and tests (#1302) 2021-10-12 13:06:29 +02:00
Alexandru Ionut Tripon 1630ffe2ca
Added support for ENUM Source-URI Extension (#1301) 2021-09-27 15:50:21 +02:00
Johan Knutzen 4e8fe099f4
add fleetdeck project (#1298)
Co-authored-by: Johan Knutzen <johan@fleetdeck.io>
2021-09-15 08:58:44 +02:00
Tom Thorogood df84acab71
Fix go generate missing required go.mod entry (#1290)
* Fix go generate missing required go.mod entry

There were no non-excluded files importing golang.org/x/tools so it's
require was missing from go.mod. This caused the go run commands
invoked by go generate to fail.

tools.go is currently the recommended way to force non-build
dependencies to be retained by the go mod commands. If that changes in
the future, we can update then. As nothing from the import is actually
used, it won't impact anyone's build at all (except requiring an extra
download by the go tool which is unavoidable).

Pulling in the @master version of golang.org/x/tools (which doesn't yet
have a stable release version) also updates a number of other
dependencies, but not quite to latest, so while I'm here just update
them all.

* Add explanatory comment and build tag to tools.go
2021-09-14 16:59:49 +02:00
Miek Gieben ab67aa6423
Fix race condition in ExchangeContext. (#1281)
Automatically submitted.
2021-08-04 16:16:52 +00:00
Manabu Sonoda af0c865ab3
fix Msg.Used TTL must be zero (#1280)
* fix Msg.Used RRset exists (value dependent -- with rdata) TTL must be zero

* Used(): change ttl using Header()
2021-07-18 09:24:05 +02:00
Aveline c23d8b3ce0
readme: add misaka.io & ping.sx (#1278) 2021-07-07 19:22:17 +02:00
Miek Gieben 996478ee91 Release 1.1.43 2021-06-22 14:03:06 +02:00
Miek Gieben bd70190c4d
Move makeDataOpt into edns.go (#1273)
Make it more obvious that these two lists (const, and case) need to be
in sync.

Also sort the list to match the const sorting.

Signed-off-by: Miek Gieben <miek@miek.nl>
2021-06-22 14:00:25 +02:00
Malte Granderath 595ee5aa98
Adding TCPKeepAlive to makeDataOpt (#1272) 2021-06-22 09:41:30 +02:00
Miek Gieben ce48a4b9ef
small cleans from go report card (#1268)
I went through the list and cleaned things up here and there.

Signed-off-by: Miek Gieben <miek@miek.nl>
2021-06-17 11:05:49 +02:00
joseph-stanton-ax 9922549621
Incorrect HIP RR public key length decode (#1262)
When decoding a HIP resource record, 'base64.StdEncoding.DecodedLen' can return a length larger than the length of the decoded public key. This change decodes the public key and retrieves the correct length. In our tests, the public key length was being set to 33, instead of 32. Below is our offending resource record:
'23b5993f649c0827.a.b.c.      3600    IN      HIP     5 200100100020001523B5993F649C0827 Cm6k4jhir9YYoKq9JDqD3Ob1hBfCuwbWam1igFPhkGg='
2021-05-13 09:33:12 +02:00
Miek Gieben d2b5d38d4f
Remove cifuzz (#1260)
The existing of the file signifies it needs to run, so now it errors
that there is nothing to run (everything is commented out).

Remove the entire file.

Signed-off-by: Miek Gieben <miek@miek.nl>
2021-05-10 09:32:16 +02:00
Miek Gieben 9f8f2e3a3c Release 1.1.42 2021-05-07 09:31:52 +02:00
Miek Gieben 21ccaf84aa
Bump go.mod and rename reuseport files (#1259)
Go 1.11 was a long time ago, so rename the reuseport go files to
something more sane. Contemplated _unix, or _posix but that didn't
really cut it. Went with reuseport and no_reuseport.

Also bump go.mod to 1.14

Signed-off-by: Miek Gieben <miek@miek.nl>
2021-05-07 09:30:50 +02:00
Miek Gieben cce7f43db4
OPT: add Z and SetZ methods (#1246)
* OPT: add Z and SetZ methods

Fixes: #1169

Signed-off-by: Miek Gieben <miek@miek.nl>

* code review feedback

Change the mask and use and not (&^)

Signed-off-by: Miek Gieben <miek@miek.nl>

* Code review changes

Signed-off-by: Miek Gieben <miek@miek.nl>
2021-05-05 15:38:29 +02:00
Fredrik Lönnegren 2a9acc8d83
Check if DNSKEY Zone-Key bit is set on RRSIG Verify (#1258) 2021-05-05 15:38:06 +02:00
Andrey Meshkov c99ea652e3
Fix copy() in SVCBIPv4Hint and SVCBIPv6Hint (#1256)
* Fix copy() in SVCBIPv4Hint and SVCBIPv6Hint

The problem with the current implementation is that it is not a real deep copy,
it points to the same base arrays behind the slices. This was causing some
issues in real-life application.

* Address review comments
2021-04-23 09:09:34 +02:00
Miek Gieben 88913150f0
Remove HmacMD5 from the documention (#1255)
* Remove HmacMD5 from the documention

The aglo is deprecated.

Signed-off-by: Miek Gieben <miek@miek.nl>

* bump to sha256

Signed-off-by: Miek Gieben <miek@miek.nl>
2021-04-19 12:10:52 +02:00
Daniel Tang 83b388a80c
Add support for extended DNS errors (RFC 8914). (#1253)
Automatically submitted.
2021-04-08 15:11:06 +00:00
Miek Gieben 40060b4a4b
zoneparser: document not checking syntax (#1245)
* zoneparser: document not checking syntax

Add a couple of lines that we're not syntax checking.

Closes: #948

Signed-off-by: Miek Gieben <miek@miek.nl>

* Update wording after review.

Signed-off-by: Miek Gieben <miek@miek.nl>
2021-03-22 16:21:34 +01:00
Miek Gieben ad89e5bc70
stop doing any fuzzing (#1247)
The whole fuzzing pipeline is broken. Comment it out for now.
See #1186 and #1223

Signed-off-by: Miek Gieben <miek@miek.nl>
2021-03-22 15:47:47 +01:00
Miek Gieben 797f1f2953 Release 1.1.41 2021-03-19 07:46:51 +01:00
cesarkuroiwa a614451ab3
Use ed25519 from Go standard lib (#1243)
* Use "crypto/ed25519"

* Remove unused dependencies

* Version bump

Co-authored-by: Cesar Kuroiwa <cesar@registro.br>
2021-03-16 15:37:10 +01:00
Tom Thorogood e5407eb800
Remove fuzz from .github/workflows/go.yml (#1238) 2021-03-04 16:40:54 +01:00
Tom Thorogood 40ce7c7df7
Bump go versions in .github/workflows/go.yml (#1237)
* Bump go versions in .github/workflows/go.yml

These are very out of date and it's causing persistent failures in the
fuzz CI step.

* Remove GOPATH=$GOROOT assignment

go1.16 shows a warning:
  "warning: GOPATH set to GOROOT (/opt/hostedtoolcache/go/1.16.0/x64) has no effect"
2021-03-04 07:32:55 +01:00
Tom Thorogood b694ab3d93
Update go.mod dependencies (#1236)
Automatically submitted.
2021-03-04 06:25:23 +00:00
Miek Gieben 4fdbc51bbd
Run a go fmt -w -s (#1235)
Noticed a non-gofmted `;` earlier, run gofmt -w -s on all files.

(mechanical change)

Signed-off-by: Miek Gieben <miek@miek.nl>
2021-03-01 15:30:32 +01:00
Miek Gieben db96610e5e
loc: use float64 to get enough precision (#1234)
Fixes: #1232

Signed-off-by: Miek Gieben <miek@miek.nl>
2021-03-01 15:19:31 +01:00
madestro 375601dc88
Implementation of zone digest (ZONEMD) (#1208)
* adding ZONEMD

* adding ZONEMD

* deleting extra cod

* updating constants

* updating mod

* updating release

* Moving ZONEMD Implementation to project structure

* re adding indirect tools import

* case-insensitive digest

* fixing if zone has rfc 3597 RRs

* remove .idea folder

* restore go.mod imports

* gofmt files

* pseudo rollback

* after go generate...

* parsing zonemd in rfc3597

* removing the check for a STRING as HAsh Algorithm in ZONEMD, RFC says only numbers go there

* adding ZONEMD constants

* Reverting changes in generate.go

un-gofmt ing generate.go file

* Reverting changes in generate.go

un-gofmt ing generate.go file

* remove ZoneMD reserved types

* remove zonemd RFC3597 branch in ZONEMD parser

* revert rfc3597 related modifications

* revert rfc3597 related modifications

* removing unintentional changes from go.sum and types.go

* add line break to go.sum

* removing spaces from types.go

* Use ZONEMD official RFC link as reference

* Add ZONEMD parsing test

* Update parse_test.go

Co-authored-by: Miek Gieben <miek@miek.nl>

Co-authored-by: Eduardo <eriveros@dcc.uchile.cl>
Co-authored-by: Eduardo <e.sdfbsadjhgskndwegit@xor.cl>
Co-authored-by: Eduardo <e.git@xor.cl>
Co-authored-by: Miek Gieben <miek@miek.nl>
2021-02-26 16:35:05 +01:00
Josh Soref 883641f4a9
Spelling (#1222)
* spelling: artifacts

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: encoding

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: exponent

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: ignoring

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: implemented

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: implements

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: next

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: numeric

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: previous

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: positions

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: presentation

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: resetting

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: stringifying

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: subsequent

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: validated

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

Co-authored-by: Miek Gieben <miek@miek.nl>
2021-02-25 17:08:05 +01:00
Shubhendra Singh Chauhan 2f14d104f3
improve code quality (#1228)
* Combine multiple `append`s into a single call

* Fix Yoda conditions

* Fix check for empty string

* revert "combine multiple `append`s"
2021-02-25 17:01:55 +01:00
Miek Gieben 9884b9f446 Release 1.1.40 2021-02-25 11:00:46 +01:00
R+ 2543d8bb2d
Close the UDPConn before returning if `setUDPSocketOptions` went wrong (#1227)
Detail: https://github.com/miekg/dns/issues/1226
2021-02-25 09:24:39 +01:00
Nikolay Nikolaev c08efdcc07
readme: adding Kuma.io (#1225)
Signed-off-by: Nikolay Nikolaev <nikolay.nikolaev@konghq.com>
2021-02-23 10:54:40 +01:00
Miek Gieben 4ec3e54a9e Release 1.1.39 2021-02-23 10:30:20 +01:00
Miek Gieben 4495f1939f
readme: add CAA and ZOMEMD RFCs (#1224)
Try to keep this list semi up to date.

Signed-off-by: Miek Gieben <miek@miek.nl>
2021-02-23 10:10:31 +01:00
Andrey Meshkov 67bd57debd
Send DNS query in one packet when using TCP/TLS (#1219)
* Send DNS query in one packet when using TCP/TLS

* fix review comments

* Removed net.Buffers

* Added unit-tests for writing messages over TCP in one go
2021-02-13 19:49:02 +01:00
unknowndev233 7d5e1ea350
README.md: Users list update: add v2fly/v2ray-core (#1220) 2021-02-10 07:42:07 +01:00
chantra ee8fef6743
Update Truncate doc with compress behaviour (#1217)
* Update Truncate doc with compress behaviour

This is a documentation update to highlight the behaviour of Truncate, which will reset dns.Compress to false when the message fits in the requested size without truncation, and make it the caller responsibility to set it back to true if they wish to compress, regardless of fitting, uncompressed, in the requested message size in the first place or not.
Fixes #1216

* address comments

* d/Note that/
* s/reset/set/
* s/caller/caller's/
* removed backticks

* regardless of size
2021-02-10 07:41:26 +01:00
Miek Gieben 35023fab5c Release 1.1.38 2021-02-01 09:20:37 +01:00
Tom Thorogood 2fd5af9f92
Validate Rdlength and off in UnpackRRWithHeader (#1215) 2021-02-01 09:15:50 +01:00
Tom Thorogood e6df8867af
Fix Rdlength related parsing bug in RFC3597 records (#1214)
* Set Rdlength in fromRFC3597

This was a bug found by oss-fuzz. My bad (#1211).

* Limit maximum length of Rdata in (*RFC3597).parse

RDATA must be a 16-bit unsigned integer.

* Validate Rdlength and off in UnpackRRWithHeader

* Revert "Validate Rdlength and off in UnpackRRWithHeader"

This reverts commit 2f6a8811b944b100af7605e53a6fb164944a6d65.

* Use hex.DecodedLen in (*RFC3597).fromRFC3597

While this isn't done elsewhere, it is clearer and more obvious.
2021-02-01 09:10:38 +01:00
Miek Gieben ba2d042a57 Release 1.1.37 2021-01-30 14:07:04 +01:00
Miek Gieben a1362108be Merge branch 'master' of github.com:miekg/dns 2021-01-30 14:06:35 +01:00
Miek Gieben a25c26b5ed Release 1.1.36 2021-01-30 14:06:14 +01:00
Tom Thorogood 13238cb6ad
Support parsing known RR types in RFC 3597 format (#1211)
* Support parsing known RR types in RFC 3597 format

This is the format used for "Unknown DNS Resource Records", but it's
also useful to support parsing known RR types in this way.

RFC 3597 says:

   An implementation MAY also choose to represent some RRs of known type
   using the above generic representations for the type, class and/or
   RDATA, which carries the benefit of making the resulting master file
   portable to servers where these types are unknown.  Using the generic
   representation for the RDATA of an RR of known type can also be
   useful in the case of an RR type where the text format varies
   depending on a version, protocol, or similar field (or several)
   embedded in the RDATA when such a field has a value for which no text
   format is known, e.g., a LOC RR [RFC1876] with a VERSION other than
   0.

   Even though an RR of known type represented in the \# format is
   effectively treated as an unknown type for the purpose of parsing the
   RDATA text representation, all further processing by the server MUST
   treat it as a known type and take into account any applicable type-
   specific rules regarding compression, canonicalization, etc.

* Correct mistakes in TestZoneParserAddressAAAA

This was spotted when writing TestParseKnownRRAsRFC3597.

* Eliminate canParseAsRR

This has the advantage that concrete types will now be returned for
parsed ANY, NULL, OPT and TSIG records.

* Expand TestDynamicUpdateParsing for RFC 3597

This ensures we're properly handling empty RDATA for RFC 3597 parsed
records.
2021-01-30 14:05:25 +01:00
Tom Thorogood f9dc403cff
Fix potentially truncated int casts in $GENERATE code (#1212)
These were flagged by GitHub CodeQL code scanning as potential
vulnerabilities or issues. Fixing them is easy and they are incorrect.

Adding tests is less easy because int is 64-bits on most systems,
including those we test on, so we can't consistently provoke a failure
here.
2021-01-30 10:07:06 +01:00
Matt Dainty 731b191cab
Add a link to https://github.com/bodgit/tsig (#1206) 2021-01-09 09:37:20 +01:00
Matt Dainty 59aea23afe
Add GSS-TSIG support (#1201)
Automatically submitted.
2021-01-07 14:28:20 +00:00
Janik at Cloudflare 23c4faca9d
Fix comment about SVCB values in AliasMode (#1202)
Automatically submitted.
2020-12-18 19:16:09 +00:00
Till! 428cef3187
Add a link for dnsbl_exporter (#1194)
Adding a shameless plug to my Prometheus exporter. ;) Thanks for sharing your code on here.
2020-11-06 18:31:18 +01:00
Miek Gieben 91eca25c30
Remove travis (#1193)
This also removed the codecov that was still done. We could potentially
re-add as an action, but I don't really miss it. Add testing for 1.14
and 1.15 (it's very fast now).

Fuzzing needs to stay on 1.14 due to brokeness of some kind.

Signed-off-by: Miek Gieben <miek@miek.nl>
2020-11-06 18:30:22 +01:00
Catena cyber 9732cfa6b8
Runs CIFuzz only when go files are modified (#1192)
and run it for 5 minutes instead of 10
2020-11-05 15:35:21 +01:00
Miek Gieben 1ba9487b18
make the client.Exchange doc work (#1190)
Remove the newline so the comment is picked up as documentation.

Signed-off-by: Miek Gieben <miek@miek.nl>
2020-10-26 14:10:31 +01:00
Miek Gieben 3b41a31342 Release 1.1.35 2020-10-25 09:22:05 +01:00
Tom Thorogood fa528cceb7
Fix PacketConnReader typo in DecorateReader docs (#1189) 2020-10-25 02:26:19 +10:30
Tom Thorogood 0e1c4e69dd
Support generic net.PacketConn's for the Server (#1174)
* Support generic net.PacketConn's for the Server

This commit adds support for listening on generic net.PacketConn's for
UDP DNS requests, previously *net.UDPConn was the only supported type.

In the event of a future v2 of this module, this should be streamlined.

* Eliminate wrapper functions around RunLocalXServerWithFinChan

* Eliminate RunLocalTCPServerWithTsig function

* Replace RunLocalTLSServer with a wrapper around RunLocalTCPServer

This reduces code duplication.

* Add net.PacketConn server tests

This provides coverage over nearly all of the newly added code (with
the unfortunate exception of (*response).RemoteAddr).

* Fix broken client_test.go tests

a433fbede4 was merged into master between this PR being opened and
being merged. This broke the CI tests in rather strange ways as the
code was being merged into master in a way that wasn't at all clear.
This commit fixes the two broken lines.
2020-10-25 02:23:01 +10:30
Tom Thorogood a3ad44419a
Fix XFR tests (#1188)
* Fix XFR tests

axfrTestingSuite returned the test function that was never actually
executed. These were broken from the beginning awkwardly, though the
test cases pass fine once fixed.

* Switch axfrTestingSuite argument order

*testing.T is customarily the first argument.
2020-10-24 13:58:44 +02:00
Tom Thorogood 93945c2844
Remove HMAC-MD5 support from TSIG (#1187) 2020-10-24 13:57:51 +02:00
Tom Thorogood db53c847ca
Remove RSAMD5 support from (*RRSIG).Verify (#1185)
AFAIK, the only way to get an RSAMD5 DNSKEY was to manually construct
one. This is ancient, just get rid of it.

The only remaining usage of md5 is in tsig.go. Hopefully that might be
removable as well.
2020-10-24 13:55:55 +02:00
Tom Thorogood be51022368
Remove the remnants of DSA support (#1184)
crypto/dsa is formally deprecated as of go1.16 and DSA support was
largely removed from this library in 9c315c51c3, but some remnants
remained.
2020-10-24 13:55:21 +02:00
Peter Wu 6d41f43022
svcb: documentation fixes (#1182)
`&dns.HTTPS{Hdr: ...}` fails due to `cannot use promoted field SVCB.Hdr
in struct literal of type dns.HTTPS`. Fix this, less readable
alternatives include using `dns.SVCB` directly, or `&dns.HTTPS{SVCB:
dns.SVCB{Hdr: ...}}`.

Fix the draft reference, draft-02 has not been published yet. Fix ECH,
its type has changed from base64 to a byte array.
2020-10-21 17:42:39 +02:00
Laurent Demailly 04c41addaf
add link to https://github.com/fortio/dnsping (#1181)
Thanks for the great library!

I couldn't quite figure out the sort order so I put my link at the end...
2020-10-21 08:14:38 +02:00
Miek Gieben 276b51d84b Release 1.1.34 2020-10-18 07:56:38 +02:00
Andrew Ayer a433fbede4
Ignore responses with unexpected IDs (#1155)
* Ignore replies with unexpected IDs

This fixes the following problem:

At time 0, we send a query with ID X from port P.

At time T, we time out the query due to lack of response, and then send
a different query with ID Y.  By coincidence, the new query is sent from
the same port number P (since port numbers are only 16 bits, this can happen
with non-negligible probability when making queries at a high rate).

At time T+epsilon, we receive a response to the original query.
Since the ID in this response is X, not Y, we would previously return
ErrId, preventing the second query from succeeding.

With this commit, we simply ignore the response with the mismatched ID
and return once we receive the response with the correct ID.

* Update test for bad ID

The new test sends two replies: the first one has a bad ID, which should
be ignored, and the second one has the correct ID.

* Add test to ensure query times out when server returns bad ID

* Avoid use of error string matching in test case

* Check for mismatched query IDs when using TCP

* Reduce timeout in TestClientSyncBadID
2020-10-18 07:55:24 +02:00
Miek Gieben 61a22d0ee6
svcb: doc updates (#1178)
The SVBC record didn't have a class, so add that and use struct literal
to put it all on 1 one. Use `s` for SVCB records, and `h` for HTTPS to
be more consistent.

Signed-off-by: Miek Gieben <miek@miek.nl>
2020-10-17 10:00:13 +02:00
Miek Gieben 95dddd3867
Make the 900 number lower in this test (#1176)
Original PR doesn't make clear why this is 900-now assuming we want to
cross the 14 bit length boundary.

Up to 900 creates a super large (>2^16) message. Not sure why that needs
testing.

Also remove the packs at lower sizes.

Signed-off-by: Miek Gieben <miek@miek.nl>
2020-10-16 11:55:29 +02:00
Miek Gieben 3b0ffe413f
tests: reduce timeouts and iterations (#1175)
This reduces the time it takes to run the test. Shorter timeouts on
clients to avoid awaiting for the detault timeouts.

It's also reduces the iterations in some test functions, this doesn't
seem to impact the tests indicating those numbers where random to begin
with.

Use shorter crypto keys, as we don't need to strength in tests.

Stop using Google Public DNS and other remotes in tests as well: it's
faster, keeps things local and avoids spilling info to Google.

This brings the test duration down from ~8s to ~2s on my machine, a 4x
reduction.

~~~
PASS
ok  	github.com/miekg/dns	2.046s

Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
PASS
ok  	github.com/miekg/dns	7.915s
~~~

Signed-off-by: Miek Gieben <miek@miek.nl>
2020-10-16 09:10:36 +02:00
Miek Gieben 10e0aeedbe Release 1.1.33 2020-10-14 08:44:55 +02:00
Miek Gieben ef286f8f39
Update doc for Truncate (#1173)
Follow up after #1171, update the docs about TC and use the MinMsgSize
constant for 512 in the code.

Signed-off-by: Miek Gieben <miek@miek.nl>
2020-10-14 08:42:57 +02:00
Chris O'Haver 5579226123
dont unset TC on a message that is already truncated (#1171)
Signed-off-by: Chris O'Haver <cohaver@infoblox.com>
2020-10-14 07:18:59 +02:00
Tom Thorogood 08cf611c2b
Bump CI tested go versions (#1172)
This bumps Travis CI to the last two supported golang releases, as
documented in the readme: go1.14 and go1.15.

It also bumps the GitHub Code Action CI to go1.14 as it doesn't yet
supported go1.15 (see actions/setup-go#74).
2020-10-14 07:16:31 +02:00
Miek Gieben eaab7b7e85 Release 1.1.32 2020-10-11 14:29:24 +02:00
DesWurstes 0972db6834
Implement SVCB (#1067)
* Implement SVCB

* Fix serialization and deserialization of double quotes

* More effort (?)

4 months old commit

* DEBUG

* _

* Presentation format serialization/deserialization

* _

Remove generated

* Progress on presentation format parse & write

* _

* Finish parsing presentation format

* Regenerate

* Pack unpack

* Move to svcb.go

Scan_rr.go and types.go should be untouched now

* 🐛

Thanks ghedo

* Definitions

* TypeHTTPSSVC

* Generated

and isDuplicate

* Goodbye lenient functions

Now private key=value pairs have to be defined as structs too. They are no longer automatically named as KeyNNNNN

* Encode/decode

* Experimental svc

* Read method

* Implement some of the methods, use trick...

to  report where the error is while reading it. This should be applied to EDNS too. Todo: Find if case can only contain e := new(SVC_ALPN) and rest moved out

Also fix two compile errors

* Add SVC_LOCAL methods, reorder, remove alpn value, bugs

* Errors

* Alpn, make it build

* Correct testsuite

* Fully implement parser

Change from keeping a state variable to reading in one iteration until the key=value pair is fully consumed

* Simplify and document

EDNS should be simplified too

* Attempt to fix fuzzer

And Alpn bug

* A bug and change type values to match @ghedo's implementation

* IP bug

Also there are two ip duplicating patterns, one with copy, one with append. Maybe change it to be consistent.

* Check for strictly increasing keys as required

* Don't panic on invalid alpn

* Redundant check, don't modify original array

* Size calculation

* Fix the fuzzer, match the style

* 65535 is reserved too, don't delay errors

* Check keyNNN, check for aliasform having values

* IPvNHint is an array

* Fix ipvNHint

* Rename everything

* Unrecognized keys according to the updated specification

* Skip zero-length structs in generators. Fix CI

* Doc cleanup

* Off by one

* Add parse tests

* Check if private key doesn't collide with known key, invalid tests

* Disallow IPv4 as IPv6. More tests.

Related #1107

* Style fixes

* More consistency, more tests

* 🐛 Deep copy as in the documentation

	a := make([]net.IP, 1)
	a[0] = net.ParseIP("1.1.1.1").To4()
	b := append(make([]net.IP, 0, 1), a...)
	b[0] = net.ParseIP("3.1.1.1").To4()
	fmt.Println(a[0][0])

* Make tests readable

* Move valid parse tests to different file

* 🐛 One of previous commits not fully committed

* Test binary single value encoding/decoding and full encode/decode

* Add worst-case grows to builders, 🐛 Wrong visible character range, redundant tests

* Testing improvements

And don't convert to IPv4 twice

* Doc update only

* Document worst case allocations

and ipv6 can be at most of length 39, not 40

* Redundant IP copy, consistent IPv6 behavior, fix deep copy

* isDuplicate for SVCB

* Optimizations

* echoconfig

* Svc => SVCB

* Fix CI

* Regenerate after REBASE (2)

Rebased twice on 15th and 20th May

* Rename svc, use escapeByte.

* Fix parsing whitespaces between quotes, rename ECHOHOConfig

* resolve

Remove svcbFieldLen
Use reverseInt
Uppercase SVCB
Rename key_value
"invalid" => bad
Alpn comments
> 65535 check
Unneeded slices

* a little more

read => parse
IP array meaning
Force pushed because forgot to change read in svcb_test.go

* HTTPSSVC -> HTTPS

* Use new values

* mandatory code

https://github.com/MikeBishop/dns-alt-svc/pull/205

* Resolve comments

Rename svcb-pairs
Remove SVCB_PRIVATE ranges
Comment on SVCB_KEY65535
ParseError return l.token
rename svcbKeyToString and svcbStringToKey
privatize SVCBKeyToString, SVCBStringToKey

* Refactor 1

Rename sorted, originalPairs
Use append instead of copy
Use svcb_RESERVED instead of 65535, with it now being private
"type SVCBKey uint16"

* Refactor 2

svcbKeyToString as method
svcbStringToKey updated after key 0
🐛 mandatory has missing key
Rename str
idx < 0

* Refactor 3

Use l.token as z
var key, value string
Comment wrap
0:
Sentences with '.'
keyValue => kv

* Refactor 4

* Refactor 5

len() int

* Refactor 6

* Refactor 7

* Test remove parsing

* Error messages

* Rewrite two estimate comments

* parse shouldn't modify original array 🐛

* Remove two unneeded comments

* Address review comments

Push 2 because can't build fuzzer python
Push 3 to try again

* Simplify argument duplication as per tmthrgd's suggestion

And add the relevant test
Force push edit: Make sorting code fit into one line

* Rewrite ECHConfig and address the review

* Remove the optional tab

* Add To4() Check

* More cleanup and fix mandatory not sorting bug
2020-10-11 09:09:36 +02:00
Frank Olbricht cec9156531
Set UDPSize in connections created with client.Dial() (#1165) 2020-10-11 09:06:33 +02:00
Miek Gieben 68df4402de
readme: remove too generic users (#1164)
These domain either nxdomain/404 or just generic sites selling some
service.

Remove them from the readme.

Signed-off-by: Miek Gieben <miek@miek.nl>
2020-10-05 09:13:52 +02:00
reuben honigwachs 7a5f1127f7
Update README.md (#1160)
Not sure whether router7 makes the cut for your impressive list, but it's worth a "go" I guess. Thanks.
2020-09-16 07:57:26 +02:00
Jacob Hoffman-Andrews efdec21496
Remove comment saying ExchangeContext is deprecated. (#1154) 2020-08-26 07:58:07 +02:00
Brian Shea 034f791cf8
answer queries with no matching handler with RcodeRefused (#1151)
* answer queries with no matching handler with RcodeRefused

* update documentation

* mark HandleFailed deprecated

* add handleRefused and use it to answer requests matching no handler

* silence noise maker

Co-authored-by: Brian <brian@pop-os.localdomain>
2020-08-20 08:41:45 +02:00
JINMEI Tatuya 9df839b2b4
make sure removing trailing zero octets in APL AFDPART (#1150)
* make sure removing trailing zero octets in APL AFDPART

* update code comment with a reference to RFC3123.
2020-08-17 19:51:22 +01:00
JINMEI Tatuya da812eed45
fix and enhance stringToCm to parse LOC RR optional fields (#1148)
Automatically submitted.
2020-08-17 07:08:03 +00:00
JINMEI Tatuya 81df27db17
validate LOC's lat/long field values not to be out of range (#1149)
Automatically submitted.
2020-08-17 07:07:46 +00:00
Disconnect3d 86044e4e05
Fixes a TODO to "error out on > MAX_UINT32" (#1147)
Automatically submitted.
2020-08-17 05:59:54 +00:00
Miek Gieben 34cecfe1b4 Release 1.1.31 2020-08-01 06:57:59 +02:00
JINMEI Tatuya de1def76d8
Add support for HMAC-SHA224 and HMAC-SHA384 TSIG algorithms (#1139) 2020-07-22 13:59:04 +09:30
JINMEI Tatuya f3da20bc00
fix TsigGenerate for non-0 TSIG error or non-empty other data (#1138)
Automatically submitted.
2020-07-21 15:55:03 +00:00
JINMEI Tatuya a7a0eafd7a
catch error from tsigBuffer, mainly to detect other data overflow (#1136)
* catch error from tsigBuffer, mainly to detect other data overflow

* hardcoded a constant string instead of a const var
2020-07-21 08:04:57 +02:00
JINMEI Tatuya 9093928550
make TsigVerify check time after signature per rfc2845bis (#1135)
Automatically submitted.
2020-07-18 06:06:18 +00:00
Eric Case 50b4756e47
Add domainr.com & zonedb.org to the Users list (#1134)
Thanks for making this great library, Miek!
2020-07-08 21:09:04 +02:00
Miek Gieben 54ab126a04
Create codeql-analysis.yml
Add codescanning - we're in the beta.

This is to try it out, this commit adds the default github comes up with
2020-07-08 09:23:39 +02:00
Miek Gieben f17e6c7171 Release 1.1.30 2020-07-06 22:17:46 +02:00
Richard Gibson 978b9a827a
Be consistent about domain name label character escaping (#1122)
* Improve sprintName tests

* Fix sprintName handling of escaped dots

* Make sprintName consistently drop dangling incomplete escapes

* Be consistent about domain name label character escaping

Fixes #1121

* Replace strings.IndexByte with faster special-purpose function
2020-07-06 10:07:56 +02:00
Michael Hudson-Doyle 064ba4b789
fix failing tests on 32 bit platforms (#1130)
* fix check for bad offset on 32 bit systems

* parse integers into int64 rather than platform dependent ints

Co-authored-by: Michael Hudson-Doyle <Michael Hudson-Doyle michael.hudson@ubuntu.com>
2020-07-02 09:54:07 +02:00
Richard Gibson 203ad2480b
Add test for forward compression pointer (#1123)
Automatically submitted.
2020-05-27 08:05:25 +00:00
DesWurstes 0ffcea3295
Generate copy() for derived types (#1118)
Automatically submitted.
2020-05-18 12:54:09 +00:00
Miek Gieben b7da9d95e0
Remove string(n) (#1117)
Automatically submitted.
2020-05-14 10:50:37 +00:00
Alex Fattouche b28dcc1849
Fix URI and CAA parsing on quotes and backslashes. (#1101) (#1104)
Automatically submitted.
2020-05-13 19:24:22 +00:00
DesWurstes 8f63c2d20c
Generate isDuplicate for derivatives (#1114) 2020-05-11 09:16:21 +02:00
DesWurstes b7703d0fa0
Cleanup EDNS (#1112) 2020-05-06 15:41:54 +02:00
yaakov kuperman 1fc9fa1db0
Adds function ExchangeWithConn (#1110)
* Implements ExchangeWithConn, a function that allows callers to pass in a connection instead of having the library create a new one for them.  Exchange now wraps around this, implementing the existing behavior by creating a new connection and passing it to ExchangeWithConn.  c.exchange has been updated to support this behavior as well.

* adding tab

* formatting problem

* adds test case for ExchangeWithConn
2020-05-04 10:22:21 +02:00
taciomcosta d128d10d17
refactor: remove ParseZone and parseZone (#1099) 2020-04-28 09:24:18 +02:00
Catena cyber 5bfe94bb6e
Efficient string concatenation (#1105)
Found by oss-fuzz
2020-04-28 09:21:06 +02:00
Manabu Sonoda 67373879ce
fix APL address length check. (#1095)
* fix APL address length check.

* add afdlen check, update check APL address bits

* revert error message

* revert error message
2020-04-10 21:20:01 +02:00
Catena cyber 2c9b7cfbaa
Continuous integration with GitHub workflow (#1091)
* Adds github workflow

* Add CIFuzz github workflow

* remove Gopkg.toml stuff
2020-04-09 08:50:30 +02:00
Adam Chalkley fd9c7eb788
RFCs allow multiple questions, but not in practice (#1097)
Paraphrase @miekg's response to emphasize that multiple
questions in the question section of a DNS message
is not supported in practice.

refs miekg/dns#1092

Co-authored-by: Adam Chalkley <atc0005@users.noreply.github.com>
2020-03-29 19:46:09 +02:00
Sijie Yang 923fc6bc72
Update README.md (#1096) 2020-03-26 17:24:37 +01:00
Miek Gieben f515aa579d Release 1.1.29 2020-03-18 11:22:29 +01:00
Jan Včelák 524a80c35d
CanonicalName function to return domain name in canonical form (#1073)
* add Canonical function to get name in canonical form

* replace strings.ToLower with Canonical

* rename Canonical to CanonicalName

* replace Fqdn with CanonicalName in ServeMux
2020-03-18 11:21:59 +01:00
Dominik Menke 438e446f5c
Ensure TSIG state is verified in TestServerRoundtripTsig (#1085)
Automatically submitted.
2020-03-11 14:18:07 +00:00
Miek Gieben 40ecd66164 Release 1.1.28 2020-03-11 10:25:26 +01:00
Manabu Sonoda 1d3a971542
Fix NSEC3PARAM SaltLength when parsing (#1088)
* fix parse nsec3param saltLength

* division inside into cast
2020-03-11 10:24:25 +01:00
Miek Gieben f0dca1ef05
Fix all cases of error halding (#1087)
Automatically submitted.
2020-03-10 06:42:28 +00:00
Pavel Rybintsev 418631f446
correct default values fields in LOC record (#1084)
* Fixed the default values of HorizPre and VertPre

According to RFC-1876 those fields should be:

"a pair of four-bit unsigned
integers, each ranging from zero to nine, with the most
significant four bits representing the base and the second
number representing the power of ten by which to multiply
the base.  This allows sizes from 0e0 (<1cm) to 9e9
(90,000km) to be expressed"

Current values for HorizPre and VertPre (165=0xA5 and 162=0xA2)
are incorrect because the first HEX digit is greater then 9

The default values should be:

HorizPre = 10000m = 10000 * 100 cm = 10^6 = 0x16
VertPre  = 10m    = 10 * 100 cm    = 10^3 = 0x13
Size     = 1m     = 1 * 100 cm     = 10^2 = 0x12

The value of Size was correct, but this PR changes it to HEX
representation to be more readable

* Informative comments

Made comments on LOC record default field values more informative

Co-Authored-By: Richard Gibson <richard.gibson@gmail.com>

Co-authored-by: Richard Gibson <richard.gibson@gmail.com>
2020-03-02 08:48:01 +00:00
Miek Gieben 9dcf47a409
Doc updates (#1075)
* Doc updates

Was reading https://pkg.go.dev/github.com/miekg/dns?tab=doc and spotted
some types and things to could be slightly better.

Make v unexported, as this version stuff should not be part of the
public API.

Signed-off-by: Miek Gieben <miek@miek.nl>

* fix test

Signed-off-by: Miek Gieben <miek@miek.nl>
2020-02-14 22:47:21 +01:00
Florian Lehner 7963800469
l is never used (#1071)
Signed-off-by: Lehner Florian <dev@der-flo.net>
2020-02-03 21:10:40 +01:00
Miek Gieben 6c0c4e6581 Release 1.1.27 2020-01-03 13:46:28 +01:00
Jan Včelák c9b62b4215 APL record support (#1058)
* APL record: add structure and code point

* APL record: add wire format support

* APL record: add presentation format support

* APL record: add isDuplicate implementation

* APL record: add copy implementation

* APL record: add len implementation

* APL record: run go generate

* APL record: fix condition checking for equality

* APL record: use switches to map family to address length

* APL record: check bounds of individual fields rather than whole header

* APL record: stylistic changes

* APL record: remove APLPrefix methods from public interface

* APL record: update README

* APL record: additional cleanup for code review

* APL record: change return type from pointer to struct

* APL record: refactor of pack and unpack to eliminate extra variables
2020-01-03 13:41:45 +01:00
Ask Bjørn Hansen e636c10380 Support the zero length EDNS0 EXPIRE option (#1065)
* Support the zero length EDNS0 EXPIRE option

* EDNS0 EXPIRE: Just reference the RFC, don't link to it

Co-Authored-By: Miek Gieben <miek@miek.nl>

Co-authored-by: Miek Gieben <miek@miek.nl>
2019-12-30 14:42:48 +01:00
Jan Včelák ba5b1f0bae code generation compatible with go modules (#1050)
* code generation compatible with go modules

* build: ensure go generate makes no changes
2019-12-30 12:25:57 +01:00
Miek Gieben eda228adcf Release 1.1.26 2019-12-20 14:31:18 +00:00
Miek Gieben 711e0fd90d
doc: fix xfr example. (#1062)
* doc: fix xfr example.

The currently example code has a data race, put in the proper code.

Fixes: #1061

Signed-off-by: Miek Gieben <miek@miek.nl>

* Feedback

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-12-19 13:11:22 +00:00
Miek Gieben bfd8601222
Doc fixes (#1060)
* Doc fixes

Polish the docs a bit; fix a link to miek.nl, remove edns0client subnet
draft link and point to the RFC. Some layout fixes and pull GOPATH from
the readme as we do go modules now.

Signed-off-by: Miek Gieben <miek@miek.nl>

* review comments

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-12-19 09:28:37 +00:00
Austin Oh b3cafcb268 Add missing EDNS0EXPIRE data unpack (#1054)
Automatically submitted.
2019-12-19 07:52:02 +00:00
Miek Gieben a72e5ceb18
Put added license bit at the bottom (#1056)
This license file is not being recognized by Github, nor by the new
google golang thingy recently launched. Don't remove those lines just
yet, but put them at the bottom

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-12-18 06:58:51 +00:00
Miek Gieben bd4ba36771 Add ; before printing TSIG (#1051)
Automatically submitted.
2019-12-17 15:18:05 +00:00
Miek Gieben aae7df65e6 Release 1.1.25 2019-12-11 07:31:45 +00:00
Jacob Hoffman-Andrews 8ebf2e419d Use crypto/rand for random id generation. (#1044)
* Use crypto/rand for random id generation.

Fixes #1043 and #1037

* Panic on rare crypto/rand error.

* Fixes in response to review.
2019-12-11 07:31:09 +00:00
Miek Gieben 6d0449f981
Add CODEOWNERS (#1042)
git shows:

 git shortlog -sne | head
  3311	Miek Gieben <miek@miek.nl>
   157	Tom Thorogood <me+github@tomthorogood.co.uk>
    39	Alex Sergeyev <asergeyev@dyn.com>
    37	Andrew Tunnell-Jones <andrew@tj.id.au>
    34	Filippo Valsorda <filippo@cloudflare.com>
    21	Rafael Dantas Justo <rafael@registro.br>
    19	Michael Haro <mharo@google.com>
    14	Alex Sergeyev <abc@alexsergeyev.com>
    13	chantra <chantra@users.noreply.github.com>
    11	Alex Ciuba <alexciuba@gmail.com>

I took the top 2, but happy to extend this obvs.

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-12-09 11:50:19 +00:00
Miek Gieben 0788ed5f4e Release 1.1.24 2019-12-06 21:24:08 +00:00
Miek Gieben 730ff1f016
Revert "Export EDNS0 interface (#1041)" (#1046)
This reverts commit a98e771ba5.

This is breaking people
2019-12-06 21:23:18 +00:00
Miek Gieben 78ecb5db60 Release 1.1.23 2019-12-06 15:22:00 +00:00
Omer Anson a98e771ba5 Export EDNS0 interface (#1041)
Replace all the private methods in the EDNS0 with public methods.
Additionally, as suggested in issue #857, made Pack receive a
pre-allocated byte array, introduce a Len method, and have Pack
and Unpack return the number of octets written and read (respectively)
if there was no error.

Closes #857
2019-12-06 10:56:57 +00:00
Julian Picht 22cda6dc4f Speed up NextLabel and PrevLabel (#1039)
* change NextLabel and PrevLabel to be faster

This reduces readability, but they are in the hot path of coredns.

* @redyeti pointed out, that my implementation disregarded triple backslashes

* add synthetic benchmark-tests for PrevLabel and NextLabel

* rename ii -> j

* invert compare

* PrevLabel: add empty string check + test case

* NextLabel: fix and add testcase for NextLabel("", offset>0)
2019-12-04 07:25:08 +00:00
chantra 9b7437f11d [zone parser] disallow nested $GENERATE directive (#1033)
While the range number of GENERATE is now limited, one can pass
a line with 2 $GENERATE directive that will exponentially increase the
time spent generating RRs.
Limit to only one per line.
Fixes #1020
2019-10-23 10:41:32 +01:00
Miek Gieben 4d4363a5dc
build: reduce testing output (#1031)
Use a better name in the fuzzing test to not spam the travis output,
move to Go 1.13.x in travis.yaml

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-10-20 07:12:43 +01:00
Andrew Tunnell-Jones 4334efe802 Update EDNS0_UL to draft-sekar-dns-ul-02 (#1028) 2019-10-14 09:29:56 +01:00
chantra 40eab7a196 [fuzzer] Avoid fuzzing parser with line that contains "$INCLUDE" (#1026)
Fixes #1025

```
GO111MODULE=off make -f Makefile.fuzz build
go-fuzz -bin=dns-fuzz.zip -workdir=fuzz -func Fuzz
GO111MODULE=off make -f Makefile.fuzz build-rr
go-fuzz -bin=dns-fuzz.zip -workdir=fuzz -func FuzzNewRR
```
2019-10-10 07:12:53 +01:00
Miek Gieben 997f079b75
Run gofmt (#1024)
Periodic go-fmt-ing.

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-10-07 10:33:11 +01:00
Florian Lehner 58a17b97a1 simplify loop (#1023)
Signed-off-by: Lehner Florian <dev@der-flo.net>
2019-10-05 14:24:46 +01:00
Miek Gieben 1e224ff5de Release 1.1.22 2019-10-04 10:33:17 +01:00
Miek Gieben 1cd342c79a
Revert "labels: eliminate most allocations and improve perf by 15-65% (#1007)" (#1022)
This reverts commit ab6ac402ba.

This break CoreDNS tests and probably a lot of other apps as well.
Reverted
2019-10-04 10:26:59 +01:00
Miek Gieben 041b4bc010 Release 1.1.21 2019-10-04 07:36:49 +01:00
Miek Gieben 76b57d0384
Limit $GENERATE range to 65535 steps (#1020)
* Limit $GENERATE range to 65535 steps

Having these checks means all test in TestCrasherString() are not
reached because we bail out earlier - removed that test all together.

Fixes #1019

Signed-off-by: Miek Gieben <miek@miek.nl>

* bring back testcase

Signed-off-by: Miek Gieben <miek@miek.nl>

* bring back crash test

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-10-03 20:01:28 +01:00
chantra 557870346a [scan] fix crashers when parsing comment (#1018)
* [scan] fix crashers when parsing comment

When dealing with comments the parsers was potentially incrementing comi
variable twice. During the second access to com[], comi was possibly
longer than maxTok, causing an out of bound error:
panic: runtime error: index out of range [2048] with length 2048

* * Keep only 1 crasher test string.
* move tests from scan_test.go to fuzz_test.go
2019-10-03 19:09:39 +01:00
Miek Gieben 046ae4ead6 Release 1.1.20 2019-10-03 10:08:22 +01:00
chantra 8ebfd8abbb [fuzz] Fix crashes when parsing GENERATE (#1016)
* [fuzz] Fix crashes when parsing GENERATE

Running the fuzzer on NewRR, some crashes came up that could be
prevented by checking that the token after the range is a Blank.
This diff checks that and return an error when the blank is not found.

* * s/Expect blank /garbage /
* get rid of if/else
2019-10-03 07:37:56 +01:00
Miek Gieben 93f749db12
Revert "pool the transient maps used for Msg Pack, Truncate and Len (#1006)" (#1017)
This reverts #1006, see discussion on the PR. Def. worth exploring this
furhter and pushing a more correct approach.

This reverts commit 9578caeab0.
2019-10-03 07:36:27 +01:00
Miek Gieben 1208fbdde0 Release 1.1.19 2019-09-27 18:04:44 +01:00
Miek Gieben bb79ca102d
Update deps (#1013)
Signed-off-by: Miek Gieben <miek@miek.nl>
2019-09-25 15:51:31 +01:00
Miek Gieben 513f7ec990 Release 1.1.18 2019-09-25 07:26:45 +01:00
Richard Gibson 9a6f1f2dc9 Simplify unpackString (#1012) 2019-09-25 06:53:47 +01:00
Charlie Vieth e393768b85 types: improve the performance of sprintName by ~30% and halve allocs (#1008)
```
benchmark                                          old ns/op     new ns/op     delta
BenchmarkSprintName-12                             174           117           -32.76%

benchmark                                          old allocs     new allocs     delta
BenchmarkSprintName-12                             2              1              -50.00%

benchmark                                          old bytes     new bytes     delta
BenchmarkSprintName-12                             48            32            -33.33%
```
2019-09-23 07:17:00 +01:00
Charlie Vieth 9578caeab0 pool the transient maps used for Msg Pack, Truncate and Len (#1006)
This improves runtime by 20-40% and more importantly significantly
reduces memory usage and allocations.
2019-09-23 07:16:26 +01:00
Tom Thorogood b733ad8671 Improve unpackString performance (#1011)
I'm not convinced this is really worth doing, but it does show a
performance improvement.

name                       old time/op    new time/op    delta
UnpackString/Escaped-12      83.7ns ± 7%    78.2ns ± 3%   -6.50%  (p=0.000 n=10+9)
UnpackString/Unescaped-12    57.8ns ± 9%    50.4ns ±13%  -12.74%  (p=0.000 n=10+10)

name                       old alloc/op   new alloc/op   delta
UnpackString/Escaped-12       48.0B ± 0%     32.0B ± 0%  -33.33%  (p=0.000 n=10+10)
UnpackString/Unescaped-12     32.0B ± 0%     32.0B ± 0%     ~     (all equal)

name                       old allocs/op  new allocs/op  delta
UnpackString/Escaped-12        2.00 ± 0%      1.00 ± 0%  -50.00%  (p=0.000 n=10+10)
UnpackString/Unescaped-12      1.00 ± 0%      1.00 ± 0%     ~     (all equal)
2019-09-22 08:59:05 +01:00
Tom Thorogood ba5bfd0295 Use strings.ToLower in ServeMux.match (#1010)
This has been a long standing TODO. golang.org/cl/137575 was included in
go1.12 which is the oldest officially supported release.
2019-09-21 08:50:53 +01:00
Charlie Vieth ab6ac402ba labels: eliminate most allocations and improve perf by 15-65% (#1007)
* labels: eliminate most allocations and improve perf by 15-65%

This commit changes the labels functions to step through labels
section-by-section instead of splitting them and thus allocating
a slice.

Where possible allocations are reduced to 0.

```
benchmark                         old ns/op     new ns/op     delta
BenchmarkSplitLabels-12           41.4          34.9          -15.70%
BenchmarkLenLabels-12             25.8          15.5          -39.92%
BenchmarkCompareDomainName-12     107           39.3          -63.27%
BenchmarkIsSubDomain-12           348           120           -65.52%

benchmark                         old allocs     new allocs     delta
BenchmarkSplitLabels-12           1              1              +0.00%
BenchmarkLenLabels-12             0              0              +0.00%
BenchmarkCompareDomainName-12     2              0              -100.00%
BenchmarkIsSubDomain-12           6              0              -100.00%

benchmark                         old bytes     new bytes     delta
BenchmarkSplitLabels-12           32            32            +0.00%
BenchmarkLenLabels-12             0             0             +0.00%
BenchmarkCompareDomainName-12     64            0             -100.00%
BenchmarkIsSubDomain-12           192           0             -100.00%
```

* labels: fix CompareDomainName and improve tests
2019-09-21 08:46:30 +01:00
Miek Gieben 2acbc9eff3 Release 1.1.17 2019-09-10 07:44:47 +01:00
Miek Gieben dcb849b337
update deps (#1005)
Update the deps to the latest versions.

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-09-09 14:34:25 +01:00
Miek Gieben c674456565 Release 1.1.16 2019-08-17 16:39:23 +01:00
Tiago Ilieve 5825da9f4d Migrate to Go modules (#980)
* Remove 'vendor/' directory

* Remove Go dep, add Go modules

* Travis: remove Go 1.10/GOPATH, add Go modules
2019-08-17 15:38:20 +00:00
Miek Gieben b13675009d Release 1.1.15 2019-06-28 16:40:26 +01:00
Tom Thorogood 7f2bf8764a Set the TC bit more aggressively in Truncate (#989)
* Set the TC bit more aggressively in Truncate

* Update Truncate documentation for TC bit changes
2019-06-24 16:59:43 +01:00
chantra d89f1e3d4b Reply with NOTIMPL when Opcode is not supported (#982)
One of the test from DNS Compliance testing validates that if the opcode
is not supported, a NOTIMPL rcode is returned.

e0884144dd/genreport.c (L293)

This diff makes the default acceptfunc support this case and reply with
NOTIMPL instead of FORMERR.
2019-06-17 16:13:02 +01:00
chantra ee62c8b086 go fmt (#986) 2019-06-13 07:24:10 +01:00
Miek Gieben 9cfcfb2209 Release 1.1.14 2019-06-10 07:39:39 +01:00
Tom Thorogood 25cacca8ca Prohibit newlines before record data in the ZoneParser (#979)
* Merge setRR into ZoneParser.Next

* Remove file argument from RR.parse

This was only used to fill in the ParseError file field. Instead we now
fill in that field in ZoneParser.Next.

* Move dynamic update check out of RR.parse

This consolidates all the dynamic update checks into one place.

* Check for unexpected newline before parsing RR data

* Move rr.parse call into if-statement

* Allow dynamic updates for TKEY and RFC3597 records

* Document that ParseError file field is unset from parse

* Inline allowDynamicUpdate into ZoneParser.Next

* Improve and simplify TestUnexpectedNewline
2019-06-10 07:38:54 +01:00
Frank Olbricht cbc52d2408 Add github.com/folbricht/routedns to users list (#984) 2019-06-10 07:35:38 +01:00
Miek Gieben 8a56deec68
Update deps (#981)
* update deps

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-06-06 07:36:43 +01:00
Miek Gieben d16ecb693e Release 1.1.13 2019-05-27 15:48:50 +01:00
Yaroslav Kolomiiets 1545072057 ignore Z flag in queries, clear Z flag in automatic replies (#976) 2019-05-23 20:54:24 +01:00
Pepijnvi fbd426fefa Handle all net.Conn connections correctly (#957)
* Change switch to if condition

* Update switch to if in read function
2019-05-22 14:38:57 +01:00
Miek Gieben a2c73fb86d Release 1.1.12 2019-05-21 09:42:26 +01:00
chantra ccd41ffaf8 [nsec3] fix crash in nsec3 packing (#973)
Both NSEC and NSEC3 use the same logic to pack the bitmap.
CSYNC.pack also appear to make use of `packDataNsec` so I am giving it
the same treatment by moving the logic in a helper function and making
all those types `len` call use that function.
2019-05-21 07:27:24 +01:00
Miek Gieben 77cba59d63 Release 1.1.11 2019-05-20 20:49:47 +01:00
Miek Gieben 9c315c51c3
Remove DSA* algorithms (#972)
This follows BIND9 and removed support for the DSA family of algorithms.
Any DNSSEC implementation should consider those zones using it,
insecure.

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-05-20 20:49:02 +01:00
Miek Gieben 087e486609
Run gofmt -w -s (#971)
mechanical run of gofmt.

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-05-20 07:44:53 +01:00
Miek Gieben 0930b62a13
DNSSEC: remove deprecated algorithms (RFC 6944) (#970)
This removes RSAMD5 as an algorithm you can use. BIND also has
deprecated *all* DSA algos which is more involved can removes more
helper codes as well, so that should be done in a new PR.

See #968

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-05-20 07:44:43 +01:00
Miek Gieben d49318b5a0 Release 1.1.10 2019-05-19 08:40:58 +01:00
chantra 37f455fa04 [nsec] compute NSEC.len() the same way that we would do in packDataNsec (#967)
The byte sequence, when Unpack()-ed and subsequential Pack()-ed created a
panic: runtime error: slice bounds out of range
github.com/miekg/dns.(*Msg).packBufferWithCompressionMap(0xc0000d4000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x100, 0x14, 0x14e80b, 0xbf2d4654d501a3c8, ...)
/Users/chantra/go/src/github.com/miekg/dns/msg.go:868 +0x13a8

Confirmed that Unpacking/Repacking payload described in TestCrashNSEC
did not raise a slice bound out of range panic, added unittests which
failed prior to this change.

```
go test -run TestCrashNSEC
--- FAIL: TestCrashNSEC (0.00s)
    types_test.go:135: expected length of 19, got 12
FAIL
exit status 1
FAIL	github.com/miekg/dns	0.067s
```
2019-05-19 08:40:22 +01:00
chantra 2f1ea90356 packDataOpt: fix overflow in packing opt (#966)
An option needs a minimum of 4 bytes that contains OPTION-CODE and OPTION-LENGTH
The code was checkign off + 3 > len(msg) instead of off + 4
2019-05-18 18:23:50 +01:00
Nick McKinney 77c7d907b4 Set TSIG on Transfer Out (#939)
* Call SetTsig() Msg `r` if q.IsTsig() != nil to enable TSIG on AXFR.

* Add tests for xfr.go

* Fix data race condition setting server.TsigSecret

* Test cleanup: xfr_test.go

* Xfr Test cleanup: use exported `IsDuplicate()`, len(xfrTestData)
2019-05-12 09:15:21 +01:00
Miek Gieben 59b8e6b3db
Remove the word Scrub (#961)
The function is called Truncate, not Scrub (that was the old name).
Updated the function's documentation to rename this.

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-05-03 08:25:00 +01:00
Tom Thorogood 92185d1e17 Simplify PrivateRR copying (#960)
This eliminates mkPrivateRR and reduces the risk of panic.
2019-04-30 09:14:58 +01:00
Tom Thorogood 56c04f1fec Fix dns:"hex" field len being off-by-one (#959)
dns:"hex" fields are not packed with any extra trailing byte. This was
causing a mismatch between PackRR and Len.
2019-04-30 09:14:07 +01:00
Miek Gieben 8aa92d4e02 Release 1.1.9 2019-04-30 07:24:24 +01:00
Tom Thorogood 357af3038a Always return UDP buffers to pool (#958) 2019-04-30 07:12:45 +01:00
Tom Thorogood cfee849963 Change the single in flight key for Client.Exchange (#943)
Previously it was possible for two different questions to hit the same
single in flight entry if the type or class isn't in the relevant
XToString map. This could happen for a proxy server or similar.
2019-04-10 11:55:21 +01:00
Miek Gieben 73601d4aed Release 1.1.8 2019-03-31 09:30:53 +01:00
Miek Gieben 0460860e89 Merge branch 'master' of github.com:miekg/dns 2019-03-31 09:30:33 +01:00
Miek Gieben 19b4ba9c16 Release 1.1.7 2019-03-31 08:50:54 +01:00
Tom Thorogood d1c1f95f67 Fix Len(rr) for dynamic update for A/AAAA/L32 RRs (#951) 2019-03-28 21:53:54 +00:00
Tom Thorogood d051b464e9 Add a message truncation implementation (#854)
* Add a message truncation implementation

* Remove OPT if-statement at end of Scrub

* Impose RFC 6891 payload size limit in Scrub

* Remove *Msg receiver from truncateLoop

* Remove OPT record creation from Scrub

* Test that TestRequestScrubAnswerExact has correct record count

* Rename (*Msg).Scrub to Truncate

This better reflects it's purpose.

* Remove comment reference to scrubbing in Truncate

* Properly calculate the length of OPT record in Truncate

* Correct comment in IsEdns0 in regards to RFC 6891

* Handle the OPT record being anywhere in Truncate

* Slight cleanup of Msg.Truncate
2019-03-24 09:20:11 +00:00
Tom Thorogood d8ff986484 Use for range loops instead of manual for loops (#937)
* Use for range loops instead of manual loops

* Use for range loop in Msg.CopyTo

This is a separate commit as the change is slightly more than just
switching the loop style.

* Use for range loop in DNSKEY.publicKeyRSA

* Add explen comment to DNSKEY.publicKeyRSA
2019-03-18 07:06:44 +00:00
Tom Thorogood bc7d5a495c Remove pointless cast in Conn.Read (#942)
This was accidentally added in a recent PR.
2019-03-18 07:01:10 +00:00
Tom Thorogood 1f99ca2fa4 Use new(big.Int) instead of big.NewInt(0) (#938)
* Use new(big.Int) instead of big.NewInt(0)

* Make big.NewInt(1) global for DNSKEY.PrivateKeyString
2019-03-13 07:36:34 +00:00
Christoffer Fjellström d49c86087e Add checks on data length for A and AAAA records (#919)
* Add checks on data length for A and AAAA records

Fixes panic when parsing A or AAAA records with no data

* Add tests Field() on empty A/AAAA data

* Refactor format test

* Add return value check on format test
2019-03-12 16:31:33 +00:00
Miek Gieben cc8cd02140 Release 1.1.6 2019-03-11 11:04:46 +00:00
Tom Thorogood 834f456fff Simplify TCP reading (#935)
* Simplify Server.readTCP

This slightly alters the error behaviour, but it should not be
observable outside of a decorated reader. I don't believe the old
behaviour was either obvious, documented or correct.

* Simplify TCP reading in client Conn

This alters the error behaviour in possibly observable ways, though
this is quite subtle and may not actually be readily observable.

Conn.ReadMsgHeader should behave the same way and still returns
ErrShortRead for length being too short.

Conn.Read will no longer return ErrShortRead if the length == 0,
otherwise it should be largely similar.

* Remove redundant error check in Conn.ReadMsgHeader
2019-03-11 10:59:25 +00:00
Tom Thorogood 337216f9a7 Use net.Buffers for writing TCP message (#934) 2019-03-10 13:46:14 +00:00
Tom Thorogood 1a5555c783 Split Server.serve into separate TCP and UDP methods (#933)
* Split Server.serve into separate TCP and UDP methods

* Merge reject cases in Server.serveDNS

* Inline Server.disposeBuffer method
2019-03-10 12:52:08 +00:00
Tom Thorogood 53b8a87e14 Correct Close() check in Server.serve (#932)
This was changed in ec3443f85d, but I
missed this function. Apparently no one noticed.
2019-03-10 11:59:36 +00:00
Miek Gieben eef2495fa3
Move srv.Handling selection to init() (#931)
Move this code to the server's init function to get it out of the
hotpath.

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-03-10 11:14:57 +00:00
Miek Gieben 035891ab61 Release 1.1.5 2019-03-09 15:15:13 +00:00
Miek Gieben 284bad20d8
Manually revert go workers (98a1ef45) (#928)
Manually revert the worker model.

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-03-09 13:34:22 +00:00
Tom F 487e4636d5 ZoneParser: error on parsing an IPv6 address in an A record (#923)
* ZoneParser: error on parsing an IPv6 address in an A record

And vice versa for IPv4 with AAAA.

The implementation of isIPv6 is inspired by e341bae08d/src/net/ip.go (L678-L681) .

* Fix benchmarks that try to use ::1 as A record.

* Test A/AAAA parsing via NewRR rather than zone parser.

* Document why we distinguish IPv4 vs IPv6 via existence of ":".
2019-03-09 09:02:18 +00:00
JINMEI Tatuya e838e1e3ce corrected default value of Server.MsgAcceptFunc as documented (#920)
the description says DefaultMsgAcceptFunc but actually defaultMsgAcceptFunc
was used.
2019-03-07 07:02:29 +00:00
Peter Banik 72df20724e Added github.com/peterzen/goresolver (#917)
goresolver is a DNSSEC validating resolver library based on miekg/dns
2019-03-04 07:36:23 +00:00
Jens Erat 092d7745b4 Ignore empty lines when parsing private keys (#911)
When migrating zones to CoreDNS, it did not accept private key files
the former bind setup gracefully accepted. It turned out they had a
trailing newline for whateveer reason. The dns library should handle
them gracefully, too.
2019-02-28 06:32:07 +00:00
Tom Thorogood 12ac8fb6e8 Add go1.12 to Travis CI (#915) 2019-02-28 06:29:30 +00:00
Tom Thorogood 39e689aa4a Fix String formatting of RP record (#914) 2019-02-28 06:28:44 +00:00
Tariq Ibrahim 164b22ef9a follow convention for deprecation notice in ExchangeConn (#908) 2019-02-07 07:30:09 +00:00
Dmitry Zubarev a288d199dc Fixed a misspelling. (#906) 2019-02-01 16:34:24 +00:00
Miek Gieben 8fc2e5773b Release 1.1.4 2019-01-30 18:12:07 +00:00
Francois Tur 896800ef1d fix OPT Record deep copy (#902)
* - implement deep-copy for OPT records + simple UT

* - adding ztypes.go (generated).

* - properly comment the specific behavior for EDNS0

* - remove too narrow UT + down-scope copy() method to package level only

* - tune comment
2019-01-30 18:11:33 +00:00
Anurag Goel 2df14c5c9f Add render to list (#904) 2019-01-29 07:32:45 +00:00
Miek Gieben 56be65265e Release 1.1.3 2019-01-12 10:18:55 +00:00
Miek Gieben 5c2ec9f7e4
RFC 1996 allows SOA in answer in notify (#900)
* RFC 1996 allows SOA in answer in notify

The answer section of a notify can contain a SOA record that we should
not ignore in the DefaultAcceptFunc.

* End sentence

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-01-12 10:11:11 +00:00
Tom Thorogood 5beb962416
Test for escaped dots in IsFqdn (#896)
This catches situations where the final dot in a name is actually
escaped. This was causing problems when packing these domain names.
2019-01-06 14:55:21 +10:30
Tom Thorogood c5493ad28d
Use an interface method for IsDuplicate (#883)
* Prevent IsDuplicate from panicing on wrong Rrtype

* Replace the isDuplicateRdata switch with an interface method

This is substantially simpler and avoids the need to use reflect.

* Add equal RR case to TestDuplicateWrongRrtype

* Move RR_Header duplicate checking back into IsDuplicate

This restores the previous behaviour that RR_Header's were never
equal to each other.

* Revert "Move RR_Header duplicate checking back into IsDuplicate"

This reverts commit a3d7eba50825d546074d81084e639bbecf9cbb57.

* Run go fmt on package

This was a mistake when I merged the master branch into this PR.

* Move isDuplicate method to end of RR interface
2019-01-06 14:42:38 +10:30
Tom Thorogood db3d0ce13b
Use an interface method for parsing zone file records (#886)
* Eliminate Variable bool from parserFunc

Instead we now check whether the last token read from the zlexer was
a zNewline or zEOF. The error check above should be tripped for any
record that ends prematurely.

* Use an interface method for parsing zone file records

* Prevent panic in TestOmittedTTL if no regexp match

* Move slurpRemainder into fixed length parse functions

This is consistent with the original logic in setRR and avoids potential
edge cases.

* Parse synthetic records according to RFC 3597

These records lack a presentation format and cannot be parsed otherwise.
This behaviour is consistent with how this previously operated.
2019-01-06 14:36:16 +10:30
Miek Gieben 44a8c5f8ba Release 1.1.2 2019-01-04 19:22:34 +00:00
Tom Thorogood 29b9bf368b Remove pointless casts (#895)
* Remove pointless casts

These are all casts where the value was already of the same type.

* Use var style for zero-value not cast style
2019-01-04 10:30:55 +00:00
Tom Thorogood 513c1ff221 Simplify and unify various returns (#893) 2019-01-04 10:19:42 +00:00
Tom Thorogood 57ca5ae8f4 Use headerSize const instead of hardcoded 12 (#894) 2019-01-04 10:19:01 +00:00
Tom Thorogood 34be74deeb Flatten goroutine inside goroutine in Transfer.In (#890)
* Flatten goroutine inside goroutine in Transfer.In

* Return an error for unknown question types

Previously this would just be silently ignored leaving nothing to close
the returned channel or return an error.
2019-01-04 08:14:40 +00:00
Tom Thorogood 09499bd07f Use IsFqdn and Fqdn helper functions more (#892) 2019-01-04 08:13:00 +00:00
Tom Thorogood b9e1e7529b Avoid calling RR.Header more than once per RR (#891)
* Avoid calling RR.Header more than once per RR

Header is an interface method so there's non-zero overhead when calling
it.

* Reset entire RR_Header in SIG.Sign

This is equivilant (while also clearing Rdlength) while being simpler.
2019-01-04 08:12:32 +00:00
Tom Thorogood e8b24e80da Move all reversed map creation into reverse.go (#889) 2019-01-04 08:10:15 +00:00
Tom Thorogood 5b818eed53 Use a value reciever for defaultReader (#888)
As defaultReader contains a single pointer, we can pass it by value and
avoid an allocation when it's put inside an interface{}.
2019-01-04 08:09:23 +00:00
Tom Thorogood ace4e60848 Make defaultMsgAcceptFunc a func not a var (#887) 2019-01-04 08:09:04 +00:00
Tom Thorogood b955100a79
Move RR header packing out of generated code (#885) 2019-01-04 10:09:14 +10:30
Tom Thorogood 813bd39114 Stop using packDomainName in IsDomainName (#873)
* Fork packDomainName for IsDomainName

* Eliminate msg buffer from packDomainName2

* Eliminate compression code from packDomainName2

* Remove off argument and return from packDomainName2

* Remove bs buffer from packDomainName2

* Merge packDomainName2 into IsDomainName

* Eliminate root label special case from IsDomainName

* Remove ls variable from IsDomainName

* Fixup comments in IsDomainName

* Remove msg == nil special cases from packDomainName

* Eliminate lenmsg variable from packDomainName

* Eliminate label counting from packDomainName

* Change off length check in IsDomainName

* Fix IsDomainName for escaped names

* Use strings.HasSuffix for IsFqdn

* Revert "Use strings.HasSuffix for IsFqdn"

I'll submit this as a seperate PR.

This reverts commit 80bf8c83700d121ea45edac0f00db52817498166.

* Cross reference IsDomainName and packDomainName

* Correct IsDomainName max length comment
2019-01-03 17:39:37 +01:00
Miek Gieben 1048d2bf96
Add newly released RFC to readme (#878)
Add RFC 8499 on DNS terminology. This is and will be used to name
various things in this package.

Signed-off-by: Miek Gieben <miek@miek.nl>
2019-01-03 17:37:57 +01:00
Tom Thorogood 57b81e0614 Use an interface method for unpacking records (#884)
* Use an interface method for unpacking records

* Eliminate err var declaration from unpack functions

* Remove pointless r.Data assignment in PrivateRR.unpack
2019-01-03 17:35:32 +01:00
Tom Thorogood 17dcb39074 Use (net.IP).Equal in isDuplicate functions (#882) 2019-01-03 17:32:22 +01:00
Tom Thorogood f9fcf1448b Return nil error from final return in unpack*() (#881)
This is consistent with the pack*() functions and is idiomatic Go.
2019-01-03 11:44:50 +01:00
Tom Thorogood bfd648e102
Rename NULL's Anything field to Data (#880) 2019-01-03 21:03:38 +10:30
Tom Thorogood 2533d75276 Move scanner comment handling out of scanRR (#877)
* Add comment getter to zlexer

* Use zlexer.Comment instead of lex.comment

* Move comment handling out of setRR code

* Move comment field from lex to zlexer

* Eliminate ZoneParser.com field

* Return empty string from zlexer.Comment on error

* Only reset zlexer.comment field once per Next

* Remove zlexer merge TODO

I'm pretty sure these have to remain separate which is okay.
2018-12-31 10:20:26 +01:00
Tom Thorogood ac5c421c48 Use binary.BigEndian.PutUint32 in EDNS0_EXPIRE.pack (#875) 2018-12-30 16:44:37 +01:00
Tom Thorogood 67bd05e2f4 Remove unused variable from switch in Field (#876) 2018-12-30 16:41:07 +01:00
Tom Thorogood 5913ad55e9 Use strings.HasSuffix for IsFqdn (#874) 2018-12-30 16:37:49 +01:00
Tom Thorogood b0835fab5e Reduce allocations in ReverseAddr (#872)
* Add ReverseAddr benchmarks

* Eliminate buffer allocation in ReverseAddr for v6

When the size of a slice is constant it can be allocated on the stack
rather than the heap.

name                old time/op    new time/op    delta
ReverseAddr/IP4-12     175ns ± 5%     173ns ± 4%     ~     (p=0.323 n=10+10)
ReverseAddr/IP6-12     364ns ±14%     218ns ±24%  -40.12%  (p=0.000 n=10+10)

name                old alloc/op   new alloc/op   delta
ReverseAddr/IP4-12     51.0B ± 0%     51.0B ± 0%     ~     (all equal)
ReverseAddr/IP6-12      176B ± 0%       96B ± 0%  -45.45%  (p=0.000 n=10+10)

name                old allocs/op  new allocs/op  delta
ReverseAddr/IP4-12      3.00 ± 0%      3.00 ± 0%     ~     (all equal)
ReverseAddr/IP6-12      3.00 ± 0%      2.00 ± 0%  -33.33%  (p=0.000 n=10+10)

* Reduce allocations in ReverseAddr for v4

name                old time/op    new time/op    delta
ReverseAddr/IP4-12     173ns ± 4%     140ns ±13%  -18.97%  (p=0.000 n=10+10)
ReverseAddr/IP6-12     218ns ±24%     218ns ±22%     ~     (p=0.838 n=10+10)

name                old alloc/op   new alloc/op   delta
ReverseAddr/IP4-12     51.0B ± 0%     48.0B ± 0%   -5.88%  (p=0.000 n=10+10)
ReverseAddr/IP6-12     96.0B ± 0%     96.0B ± 0%     ~     (all equal)

name                old allocs/op  new allocs/op  delta
ReverseAddr/IP4-12      3.00 ± 0%      2.00 ± 0%  -33.33%  (p=0.000 n=10+10)
ReverseAddr/IP6-12      2.00 ± 0%      2.00 ± 0%     ~     (all equal)

* Compare returned address in BenchmarkReverseAddr
2018-12-30 10:28:48 +00:00
Miek Gieben 56516cf4de
Add NULL record (#840)
Sorely missing from this library. Add it. As there is no presentation
format the String method for this type puts a comment in front of it.

Signed-off-by: Miek Gieben <miek@miek.nl>
2018-12-30 09:45:18 +00:00
cesarkuroiwa 5f5f2380fc Don't reject Nscount > 0 (#868)
* Don't reject Nscount > 0

IXFR request could have a SOA RR in the NS section
RFC 1995, section 3: https://tools.ietf.org/html/rfc1995

* Only one RR in the NS section is acceptable

* Remove URL from comment
2018-12-19 16:28:26 +00:00
Miek Gieben 450ab7d57f
Simplify TKEY presentation format (#856)
* Simplify TKEY presentation format

Just put add ";" in front of it, instead of the whole pseudo option
text.

Fixes #855

Signed-off-by: Miek Gieben <miek@miek.nl>

* Add more fields to presentation format - convert time using the RRSIG routines

Signed-off-by: Miek Gieben <miek@miek.nl>
2018-12-04 07:31:40 +00:00
Tom Thorogood 870a59089c Prevent timeout in TestConcurrentExchanges (#862)
This eliminates the possibility of deadlocking the handler and also
simplifies the test considerably.
2018-12-04 07:29:08 +00:00
Tom Thorogood 65b2aa0a63 Make TestTimeout less flaky (#863)
This test currently fails on occasion due to what appears to be
goroutine scheduling differences. This should eliminate those failures.
2018-12-03 07:40:52 +00:00
Miek Gieben 7586a3cbe8 Release 1.1.1 2018-12-02 09:00:23 +00:00
Tom Thorogood ff7d445081 Avoid setting the Rdlength field when packing records (#859)
* Avoid setting the Rdlength field when packing

The offset of the end of the header is now returned from the RR.pack
method, with the RDLENGTH record field being written in packRR.

To maintain compatability with callers of PackRR who might be relying
on this old behaviour, PackRR will now set rr.Header().Rdlength for
external callers. Care must be taken by callers to ensure this won't
cause a data-race.

* Prevent panic if TestClientLocalAddress fails

This came up during testing of the previous change.

* Change style of overflow check in packRR
2018-12-02 08:23:35 +00:00
Tom Thorogood 470f08e191
Reduce compression memory use with map[string]uint16 (#852)
* Reduce compression memory use with map[string]uint16

map[string]uint16 uses 25% less memory per-entry than a map[string]int
(16+2)/(16+8) = 0.75. All entries in the compression map are bound by
maxCompressionOffset which is 14-bits and fits within a uint16.

* Add PackMsg benchmark with more RRs

* Add a comment to the compressionMap struct
2018-12-02 08:50:51 +10:30
Tom Thorogood 5c9c0e7818 Pretty print test compression map differences (#853)
* Pretty print test compression map differences

* Use compressionMapsDifference in TestPackDomainNameCompressionMap

This isn't strictly needed as it only contains a small number of
entries, but is consistent nonetheless.

* Fix map ordering in compressionMapsDifference
2018-12-01 08:30:40 +00:00
Tom Thorogood 6b6e08b48c
Stop compressing names in RT records (#847)
* Stop compressing names in RT records

Although RFC 1183 allows names in the RT record to be compressed with:
 "The concrete encoding is identical to the MX RR."

RFC 3597 specifically prohibits compressing names in any record not
defined in RFC 1035.

* Add comment to RT struct regarding compression
2018-11-30 22:50:24 +10:30
Tom Thorogood 8d24af5fb5 Fix compressed length calculations for escaped names (#850)
* Fix compressed length calculations for escaped names

* Add Len benchmark for escaped name

* Fix length with escaping after compression point

* Avoid calling escapedNameLen multiple times in domainNameLen

* Use regular quotes for question in TestMsgCompressLengthEscaped
2018-11-30 07:50:49 +00:00
Tom Thorogood 6ade5b5fff Move compress=false out of packDataDomainNames into caller (#849)
This makes packDataDomainNames more consistent with PackDomainName where
it will only be called with compress = true for `dns:"cdomain-name"`.
2018-11-30 07:49:06 +00:00
Tom Thorogood 778aa4f83d
Properly calculate compressed message lengths (#833)
* Remove fullSize return from compressionLenSearch

This wasn't used anywhere but TestCompressionLenSearch, and was very
wrong.

* Add generated compressedLen functions and use them

This replaces the confusing and complicated compressionLenSlice
function.

* Use compressedLenWithCompressionMap even for uncompressed

This leaves the len() functions unused and they'll soon be removed.

This also fixes the off-by-one error of compressedLen when a (Q)NAME
is ".".

* Use Len helper instead of RR.len private method

* Merge len and compressedLen functions

* Merge compressedLen helper into Msg.Len

* Remove compress bool from compressedLenWithCompressionMap

* Merge map insertion into compressionLenSearch

This eliminates the need to loop over the domain name twice when we're
compressing the name.

* Use compressedNameLen for NSEC.NextDomain

This was a mistake.

* Remove compress from RR.len

* Add test case for multiple questions length

* Add test case for MINFO and SOA compression

These are the only RRs with multiple compressible names within the same
RR, and they were previously broken.

* Rename compressedNameLen to domainNameLen

It also handles the length of uncompressed domain names.

* Use off directly instead of len(s[:off])

* Move initial maxCompressionOffset check out of compressionLenMapInsert

This should allow us to avoid the call overhead of
compressionLenMapInsert in certain limited cases and may result in a
slight performance increase.

compressionLenMapInsert still has a maxCompressionOffset check inside
the for loop.

* Rename compressedLenWithCompressionMap to msgLenWithCompressionMap

This better reflects that it also calculates the uncompressed length.

* Merge TestMsgCompressMINFO with TestMsgCompressSOA

They're both testing the same thing.

* Remove compressionLenMapInsert

compressionLenSearch does everything compressionLenMapInsert did anyway.

* Only call compressionLenSearch in one place in domainNameLen

* Split if statement in domainNameLen

The last two commits worsened the performance of domainNameLen
noticably, this change restores it's original performance.

name                            old time/op    new time/op    delta
MsgLength-12                       550ns ±13%     510ns ±21%    ~     (p=0.050 n=10+10)
MsgLengthNoCompression-12         26.9ns ± 2%    27.0ns ± 1%    ~     (p=0.198 n=9+10)
MsgLengthPack-12                  2.30µs ±12%    2.26µs ±16%    ~     (p=0.739 n=10+10)
MsgLengthMassive-12               32.9µs ± 7%    32.0µs ±10%    ~     (p=0.243 n=9+10)
MsgLengthOnlyQuestion-12          9.60ns ± 1%    9.20ns ± 1%  -4.16%  (p=0.000 n=9+9)

* Remove stray newline from TestMsgCompressionMultipleQuestions

* Remove stray newline in length_test.go

This was introduced when resolving merge conflicts.
2018-11-30 10:03:41 +10:30
Tom Thorogood 2c039114d2 Use a table lookup for escaping unprintable bytes (#846) 2018-11-29 19:57:48 +00:00
Tom Thorogood c0747f060e Reduce allocations in UnpackDomainName by better sizing slice (#844)
* Reduce allocations in UnpackDomainName by better sizing slice

The maximum size of a domain name in presentation format is bounded by
the maximum length of a name in wire octet form and the maximum length
of a label. As s doesn't escape from UnpackDomainName, we can safely
give it the maximum capacity and it will never need to grow.

* Benchmark UnpackDomainName with lonest names possible

* Rename BenchmarkUnpackDomainNameLongestEscaped to match

* Improve maxDomainNamePresentationLength comment

* Further improve maxDomainNamePresentationLength comment
2018-11-29 19:55:51 +00:00
Tom Thorogood b1bf6f1b9b Simplify unprintable handling in UnpackDomainName (#845)
* Avoid the for loop for unprintable octets in UnpackDomainName

* Unify unprintable checking with writeTXTStringByte

These constants are identical.
2018-11-29 07:32:13 +00:00
Miek Gieben ed726faad6
Merge pull request #843 from tmthrgd/hashname
Reduce memory consumption in HashName
2018-11-29 07:19:50 +00:00
Tom Thorogood 71bbe52b67
Hoist SHA1 check in HashName
That's all that's supported by this function, and moving it makes the
code cleaner.
2018-11-29 10:44:01 +10:30
Tom Thorogood c2889aea81
Reduce wireSalt allocation in HashName 2018-11-29 10:30:02 +10:30
Tom Thorogood 2f8cf50b32
Add a HashName benchmark 2018-11-29 10:28:29 +10:30
Tom Thorogood 1afd10068a
Rename wire to wireSalt in HashName
This is slightly clearer.
2018-11-29 10:25:34 +10:30
Tom Thorogood 4058ac87fa
Remove saltWireFmt from nsecx.go
This isn't really needed and doesn't make the code any clearer.
2018-11-29 10:24:25 +10:30
Miek Gieben fa589750ad
Merge pull request #842 from tmthrgd/compression-map-escaped
Put escaped names into compression map
2018-11-28 23:39:22 +00:00
Tom Thorogood 56118562d7
Fix typo in TestMsgCompressLengthEscapingMatch comment 2018-11-29 09:58:18 +10:30
Tom Thorogood d27f0d3482
Add a test case to cover escaping in the compression map 2018-11-29 09:53:00 +10:30
Tom Thorogood 07ae768ab1
Put escaped names into compression map in PackDomainName 2018-11-29 09:49:18 +10:30
Tom Thorogood c1ad186588
Use compressionMapsEqual in TestPackDomainNameCompressionMap 2018-11-29 09:48:02 +10:30
Miek Gieben a220737569 Release 1.1.0 2018-11-28 23:08:28 +00:00
Miek Gieben 1c92765836
Merge pull request #830 from miekg/passfunc
Add MsgAcceptFunc
2018-11-28 23:07:35 +00:00
Miek Gieben f92da6fc6e Code review
Signed-off-by: Miek Gieben <miek@miek.nl>
2018-11-28 22:40:08 +00:00
Miek Gieben fef7963e99 remove newlines
Signed-off-by: Miek Gieben <miek@miek.nl>
2018-11-28 22:32:50 +00:00
Miek Gieben ab67d69d9b review
Signed-off-by: Miek Gieben <miek@miek.nl>
2018-11-28 22:25:28 +00:00
Tom Thorogood 6aa28be819
Bail early from UnpackDomainName when name is too long (#839)
* Simplify maxDomainNameWireOctets checking in UnpackDomainName

* Don't return too long name in UnpackDomainName

* Simplify root domain return in UnpackDomainName

* Bail early from UnpackDomainName when name is too long

This drastically reduces the amount of garbage created
in UnpackDomainName for certain malicious names.

The wire formatted name
 "\x3Faaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuu\xC0\x00"
would previously generate 1936B of garbage (36112B since maxCompressionPointers
was raised) before returning the "too many compression pointers" error, while
it now generates just 384B of garbage.

* Change +1 budget comment to reflect spec

This better reflects what maxDomainNameWireOctets is actually measuring.

* Remove budget check from after loop in UnpackDomainName

This can never be tripped as budget is always checked immediately after
subtracting inside the loop.

* Improve UnpackDomainName documentation
2018-11-29 08:26:30 +10:30
Miek Gieben 091d66a39f
Merge pull request #818 from tmthrgd/comp-opt
Improve PackDomainName performance
2018-11-28 18:53:23 +00:00
Miek Gieben db37038897 fix docs
Signed-off-by: Miek Gieben <miek@miek.nl>
2018-11-28 18:45:19 +00:00
Miek Gieben 74dbfccc11 Code Review
Signed-off-by: Miek Gieben <miek@miek.nl>
2018-11-28 18:42:48 +00:00
Miek Gieben 2c18e7259a Add MsgAcceptFunc in server
Generalize the srv.Unsafe and make it pluggeable. Also add a default
accept function that allows to discard malformed DNS messages very early
on. Before we allocate and parse anything furher.

Also re-use the client's message when sending a reply.

Signed-off-by: Miek Gieben <miek@miek.nl>
2018-11-28 18:42:48 +00:00
Miek Gieben 6bf402f3c4
Fix "too many compression points" for valid message (#835)
* Increase the maximum number of allowed compression pointers

* Add a Pack+Unpack test case for many compression pointers

* Clarify maxCompressionPointers comment
2018-11-28 11:45:22 +00:00
Tom Thorogood 64a73613cd Use range loop in packBufferWithCompressionMap (#837)
* Use range loops in Msg.packBufferWithCompressionMap

* Remove rr set variables in Msg.packBufferWithCompressionMap

* Move Header var down in Msg.packBufferWithCompressionMap

* Move stripTsig comment into Msg.Unpack
2018-11-28 11:44:23 +00:00
Tom Thorogood d193d08243
Clarify maxCompressionPointers comment 2018-11-28 21:38:37 +10:30
Tom Thorogood 32d8e33ba2
Add a Pack+Unpack test case for many compression pointers 2018-11-28 19:53:00 +10:30
Tom Thorogood c567cfc2bb
Increase the maximum number of allowed compression pointers 2018-11-28 19:52:41 +10:30
Tom Thorogood 7ae05cdcf8
Use map[string]struct{} for compression map in Len (#820)
* Use map[string]struct{} for compression map in Len

map[string]int requires 8 bytes per entry to store the unused position
information.

* Add MsgLength benchmark with more RRs
2018-11-28 08:02:08 +10:30
Tom Thorogood 34d23c00e1
Add bounds check comment to dddToByte 2018-11-28 07:42:44 +10:30
Miek Gieben a7e7488e1d
doc: Clean up README and doc.go (#817)
Cleans this up a bit.

Signed-off-by: Miek Gieben <miek@miek.nl>
2018-11-27 14:39:19 +00:00
Tom Thorogood 03d7306558 Fix NotImp RCode string (#819)
* Fix NOTIMP typo in RcodeToString

RFC 6895 lists RCODE 4 as NotImp.

* Accept legacy NOTIMPL spelling in StringToRcode
2018-11-27 14:38:33 +00:00
Tom Thorogood c03bc41f33 Remove pointless cast from unpackUint48 (#827) 2018-11-27 14:34:23 +00:00
Tom Thorogood e2f69345fd Avoid creating compression map for question only Msg (#823)
* Pass dns.Compress explicitly to packBufferWithCompressionMap

* Avoid creating compression map for question only Msg

This idea was inspired by:
  "Skip dname compression for replies with no answers."
 https://www.nlnetlabs.nl/bugs-script/show_bug.cgi?id=235

* Continue compressing multiple questions
2018-11-27 14:34:07 +00:00
Tom Thorogood 11fb61cb84 Use copy instead of loop in EDNS0_SUBNET.unpack (#825) 2018-11-27 14:32:41 +00:00
Tom Thorogood 8eab0120c4 Use FQDN for question in TestCompressLength (#831)
This was a bug.
2018-11-27 14:28:13 +00:00
Tom Thorogood e969cef252 Use t.Errorf not panic in TestCompressionLenSearch (#832) 2018-11-27 14:26:34 +00:00
Miek Gieben 1ff265a784
Remove ErrTruncated from the library (#815)
* Remove ErrTruncated from the library

ErrTruncated is removed. This (correctly) assume that a truncated
message will be fully formed. Any message that isn't fully formed will
return (most likely) an unpack error.

Any program using ErrTruncated will fail to compile when they update to
this version: this is by design: you're doing it wrong. For checking if
a message was truncated you should checked the msg.Truncated boolean;
assuming the unpack didn't fail.

Fixes #814

Signed-off-by: Miek Gieben <miek@miek.nl>

* Restore tests

Signed-off-by: Miek Gieben <miek@miek.nl>
2018-11-27 14:26:11 +00:00
Tom Thorogood 8f269a6b16
Use strings.EqualFold rather than strings.ToLower (#828)
strings.ToLower may allocate and will do more work than
strings.EqualFold.
2018-11-27 21:36:27 +10:30
Tom Thorogood 30d0133e57
Revert commits unrelated to PackDomainName
This reverts commit 2eeda8aabc,
                    8e6e188a87
                and 7bef528091.
2018-11-27 17:55:48 +10:30
Tom Thorogood f522504216
Eliminate roBs allocation from packDomainName
This allocation only occurred when s was escaped, but will no longer
occur.
2018-11-26 16:43:21 +10:30
Tom Thorogood 07ed56b1d6
Add isRootLabel helper for packDomainName
This handles the bs == nil case internally.
2018-11-26 16:38:15 +10:30
Tom Thorogood 149f3c884f
Move bs allocation above length check in packDomainName 2018-11-26 16:16:08 +10:30
Tom Thorogood 7f4b3bb806
Only copy once per \DDD in packDomainName
Previously the remainder of bs would be copied twice.
2018-11-26 16:13:32 +10:30
Tom Thorogood 6aa05940d5
Reset roBs even if compress is false in packDomainName
By only resetting roBs when compress is true, the compression map can
end up with inconsistent entries between compress being true and false.
2018-11-26 16:04:34 +10:30
Tom Thorogood 896cef4ce4
Replace bsFresh variable with bsDirty in packDomainName
This avoids needing to initialise it to true.
2018-11-26 16:00:37 +10:30
Tom Thorogood 5547fd63a0
Fix garbage after name in compression map
When packDomainName is called with an escaped domain name and compress
being true, bs wasn't be truncated to the correct length and would
include garbage that would be included in the compression map.
2018-11-26 15:53:29 +10:30
Tom Thorogood 260b5b401d
Only compute i-begin once in packDomainName 2018-11-26 15:09:47 +10:30
Tom Thorogood e5bc3b14fb
Use lenmsg rather than len(msg) in packDomainName
This is purely for consistency, they are always equal at this point.
2018-11-26 15:08:05 +10:30
Tom Thorogood 9358e95aef
Simplify final returns from packDomainName 2018-11-26 15:07:17 +10:30
Tom Thorogood 926752f160
Remove nameoffset variable from packDomainName
This is now always equal to off, so use that instead.
2018-11-26 15:05:51 +10:30
Tom Thorogood 03053758d4
Add whitespace to packDomainName 2018-11-26 15:04:35 +10:30
Tom Thorogood 4c43711692
Remove End goto in packDomainName 2018-11-26 15:03:49 +10:30
Tom Thorogood 36a30d2e58
Remove tainted zeroing from packDomainName
With the label copying now moved after the compression, the msg buffer
will no longer be tainted and need clearing.
2018-11-26 15:02:17 +10:30
Tom Thorogood 8995ae83e3
Move label copying below compression in packDomainName
When the dname is found in the compression map and compress is true,
this copy is as it will simply be overwritten later. This could provide
a very slight speedup.
2018-11-26 14:59:17 +10:30
Tom Thorogood ecef32b31b
Merge lenmsg checks in packDomainName 2018-11-26 14:53:33 +10:30
Tom Thorogood 3534784466
Reorder if-statements in packDomainName 2018-11-26 14:50:37 +10:30
Tom Thorogood 7bef528091
Remove pointless cast from unpackUint48 2018-11-26 14:40:27 +10:30
Tom Thorogood bf8065a091
Simplify double dot check in packDomainName 2018-11-26 12:32:35 +10:30
Tom Thorogood 0125cf9d0c
Simplify initial fqdn check in packDomainName 2018-11-26 12:25:11 +10:30
Tom Thorogood 77d26d8088
Avoid allocating copy of s in packDomainName
In the more common case, where the domain name has no escaping, this
avoids an allocation.
2018-11-26 12:21:09 +10:30
Tom Thorogood c12f225763
Use a switch statement in packDomainName 2018-11-26 12:07:46 +10:30
Tom Thorogood 8e6e188a87
Use NextLabel in compressionLenHelper
This avoids the allocation of Split and should have slightly better
performance.
2018-11-26 12:02:04 +10:30
Tom Thorogood 3b3a5b7c6a
Replace another for loop with copy in packDomainName
This change is included as a separate commit because this loop is not as
trivial as the others.
2018-11-26 12:00:33 +10:30
Tom Thorogood 8d08c56229
Replace simple loops with copy in packDomainName 2018-11-26 11:59:38 +10:30
Tom Thorogood ab9dd29c1d
Hoist bounds check in dddToByte
This eliminates two of the bounds checks in dddToByte and
dddStringToByte.
2018-11-26 11:52:43 +10:30
Tom Thorogood 2eeda8aabc
Use copy instead of loop in EDNS0_SUBNET.unpack 2018-11-26 11:51:55 +10:30
chantra 1c9c9bf4c9 properly set extended rcode when packing (#791)
* properly set extended rcode when packing

When calling `SetExtendedRcode`, we expect to get the full extended
rcode, not the rcode after we shift 4 bytes right.

* fix extended rcode

* fix TestOPTTtl test

* set error messages in TestPackExtendedBadCookie

* Set Rcode with extended rcode

* |=

* Set extended RCODE field to 0 when RCODE is not an extended one.
+ unittests

* Force setting extended rcode if we have an OPT available.

* go fmt + @tmthrgd comments

* comments and nits

* reformat comment
2018-11-17 10:30:14 +10:30
Yasar Alev 043a442757 nsec3 cover problems (#804)
* nsec3 cover fix

* nsec3 cover fix test

* nsec3 covered empty intervals

* nsec3 another condition

* nsec3 empty interval wildcard test

* nsec3 empty interval comment
2018-11-13 23:30:58 +00:00
Miek Gieben 7064f7248f Release 1.0.15 2018-11-03 09:44:52 +00:00
Tom Thorogood ec3443f85d Fix TCP connection tracking memory leak (#808)
* Add test that srv.conns is empty in checkInProgressQueriesAtShutdownServer

* Track ResponseWriter Close without nil-ing tcp

* Remove LocalAddr and RemoteAddr panic after Close

This is no longer needed as the tcp field is no longer set to nil in
Close.

* Add more explicit WriteMsg panic after Close

Previously this would panic with `dns: Write called after Close` which
is obviously less clear.

* Panic if Hijack is called after Close

Previously this worked, but later calls to Write would panic. This is
more explicit.

* Return an error if Close called multiple times

Neither io.Closer, nor ResponseWriter, provide any guarantees about the
behaviour of multiple calls to Close. This was made explicit in
https://golang.org/cl/8575043 and in practice implementations differ
wildly.

This matches ShutdownContext which returns an error if called multiple
times.

* Check map len under lock in checkInProgressQueriesAtShutdownServer

* Correct error message in checkInProgressQueriesAtShutdownServer

* Remove panic-after-Close from Hijack

* Return errors, not panic, on Write after Close
2018-11-03 09:44:07 +00:00
Miek Gieben 6ae357d393
Revert doh (#800)
* Revert "Require URLs for DOH addresses (#684)"

This reverts commit 8ccae88257.

* Revert "WIP: DNS-over-HTTPS support for Client.Exchange API (#671)"

This reverts commit 64746df23b.

Signed-off-by: Miek Gieben <miek@miek.nl>

* Finish revert of DoH

Signed-off-by: Miek Gieben <miek@miek.nl>

* Add back in the race condition comment

Signed-off-by: Miek Gieben <miek@miek.nl>
2018-11-01 20:16:39 +00:00
Miek Gieben 915ca3d5ff Release 1.0.14 2018-10-20 16:55:55 +01:00
Tom Thorogood 274da7d3ef
Add new ZoneParser API (#794)
* Improve ParseZone tests

* Add new ZoneParser API

* Use the ZoneParser API directly in ReadRR

* Merge parseZoneHelper into ParseZone

* Make generate string building slightly more efficient

* Add SetDefaultTTL method to ZoneParser

This makes it possible for external consumers to implement ReadRR.

* Make $INCLUDE directive opt-in

The $INCLUDE directive opens a user controlled file and parses it as
a DNS zone file. The error messages may reveal portions of sensitive
files, such as:
	/etc/passwd: dns: not a TTL: "root0:0:root:/root:/bin/bash" at line: 1:31
	/etc/shadow: dns: not a TTL: "root:$6$<redacted>::0:99999:7:::" at line: 1:125

Both ParseZone and ReadRR are currently opt-in for backward
compatibility.

* Disable $INCLUDE support in ReadRR

ReadRR and NewRR are often passed untrusted input. At the same time,
$INCLUDE isn't really useful for ReadRR as it only ever returns the
first record.

This is a breaking change, but it currently represents a slight
security risk.

* Document the need to drain the ParseZone chan

* Cleanup the documentation of NewRR, ReadRR and ParseZone

* Document the ZoneParser API

* Deprecated the ParseZone function

* Add whitespace to ZoneParser.Next

* Remove prevName field from ZoneParser

This doesn't track anything meaningful as both zp.prevName and h.Name
are only ever set at the same point and to the same value.

* Use uint8 for ZoneParser.include field

It has a maximum value of 7 which easily fits within uint8.

This reduces the size of ZoneParser from 160 bytes to 152 bytes.

* Add setParseError helper to ZoneParser

* Surface $INCLUDE os.Open error in error message

* Rename ZoneParser.include field to includeDepth

* Make maximum $INCLUDE depth a const

* Add ParseZone and ZoneParser benchmarks

* Parse $GENERATE directive with a single ZoneParser

This should be more efficient than calling NewRR for each generated
record.

* Run go fmt on generate_test.go

* Add a benchmark for $GENERATE directives

* Use a custom reader for generate

This avoids the overhead and memory usage of building the zone string.

name         old time/op    new time/op    delta
Generate-12     165µs ± 4%     157µs ± 2%   -5.06%  (p=0.000 n=25+25)

name         old alloc/op   new alloc/op   delta
Generate-12    42.1kB ± 0%    31.8kB ± 0%  -24.42%  (p=0.000 n=20+23)

name         old allocs/op  new allocs/op  delta
Generate-12     1.56k ± 0%     1.55k ± 0%   -0.38%  (p=0.000 n=25+25)

* Return correct ParseError from generateReader

The last commit made these regular errors while they had been
ParseErrors before.

* Return error message as string from modToPrintf

This is slightly simpler and they don't need to be errors.

* Skip setting includeDepth in generate

This sub parser isn't allowed to use $INCLUDE directives anyway.

Note: If generate is ever changed to allow $INCLUDE directives, then
      this line must be added back. Without doing that, it would be
      be possible to exceed maxIncludeDepth.

* Make generateReader errors sticky

ReadByte should not be called after an error has been returned, but
this is cheap insurance.

* Move file and lex fields to end of generateReader

These are only used for creating a ParseError and so are unlikely to be
accessed.

* Don't return offset with error in modToPrintf

Along for the ride, are some whitespace and style changes.

* Add whitespace to generate and simplify step

* Use a for loop instead of goto in generate

* Support $INCLUDE directives inside $GENERATE directives

This was previously supported and may be useful. This is now more
rigorous as the maximum include depth is respected and relative
$INCLUDE directives are now supported from within $GENERATE.

* Don't return any lexer tokens after read error

Without this, read errors are likely to be lost and become parse errors
of the remaining str. The $GENERATE code relies on surfacing errors from
the reader.

* Support $INCLUDE in NewRR and ReadRR

Removing $INCLUDE support from these is a breaking change and should
not be included in this pull request.

* Add test to ensure $GENERATE respects $INCLUDE support

* Unify TestZoneParserIncludeDisallowed with other tests

* Remove stray whitespace from TestGenerateSurfacesErrors

* Move ZoneParser SetX methods above Err method

* $GENERATE should not accept step of 0

If step is allowed to be 0, then generateReader (and the code it
replaced) will get stuck in an infinite loop.

This is a potential DOS vulnerability.

* Fix ReadRR comment for file argument

I missed this previosuly. The file argument is also used to
resolve relative $INCLUDE directives.

* Prevent test panics on nil error

* Rework ZoneParser.subNext

This is slightly cleaner and will close the underlying *os.File even
if an error occurs.

* Make ZoneParser.generate call subNext

This also moves the calls to setParseError into generate.

* Report errors when parsing rest of $GENERATE directive

* Report proper error location in $GENERATE directive

This makes error messages much clearer.

* Simplify modToPrintf func

Note: When width is 0, the leading 0 of the fmt string is now excluded.
      This should not alter the formatting of numbers in anyway.

* Add comment explaining sub field

* Remove outdated error comment from generate
2018-10-20 11:47:56 +10:30
Tom Thorogood 17c1bc6792
Eliminate lexer goroutines (#792)
* Eliminate zlexer goroutine

This replaces the zlexer goroutine and channels with a zlexer struct
that maintains state and provides a channel-like API.

* Eliminate klexer goroutine

This replaces the klexer goroutine and channels with a klexer struct
that maintains state and provides a channel-like API.

* Merge scan into zlexer and klexer

This does result in tokenText existing twice, but it's pretty simple
and small so it's not that bad.

* Avoid using text/scanner.Position to track position

* Track escape within zlexer.Next

* Avoid zl.commt check on space and tab in zlexer

* Track stri within zlexer.Next

* Track comi within zlexer.Next

There is one special case at the start of a comment that needs to be
handled, otherwise this is as simple as stri was.

* Use a single token buffer in zlexer

This is safe as there is never both a non-empty string buffer and a
non-empty comment buffer.

* Don't hardcode length of zl.tok in zlexer

* Eliminate lex.length field

This is always set to len(l.token) and is only queried in a few places.

It was added in 47cc5b052d without any
obvious need.

* Add whitespace to klexer.Next

* Track lex within klexer.Next

* Use a strings.Builder in klexer.Next

* Simplify : case in klexer.Next

* Add whitespace to zlexer.Next

* Change for loop style in zlexer.Next and klexer.Next

* Surface read errors in zlexer

* Surface read errors from klexer

* Remove debug line from parseKey

* Rename tokenText to readByte

* Make readByte return ok bool

Also change the for loop style to match the Next for loops.

* Make readByte errors sticky

klexer.Next calls readByte separately from within the loop. Without
readByte being sticky, an error that occurs during that readByte call
may be lost.

* Panic in testRR if the error is non-nil

* Add whitespace and unify field setting in zlexer.Next

* Remove eof fields from zlexer and klexer

With readByte having sticky errors, this no longer needed. zl.eof = true
was also in the wrong place and could mask an unbalanced brace error.

* Merge zl.tok blocks in zlexer.Next

* Split the tok buffer into separate string and comment buffers

The invariant of stri > 0 && comi > 0 never being true was broken when
x == '\n' && !zl.quote && zl.commt && zl.brace != 0 (the
"If not in a brace this ends the comment AND the RR" block).

Split the buffer back out into two separate buffers to avoid clobbering.

* Replace token slices with arrays in zlexer

* Add a NewRR benchmark

* Move token buffers into zlexer.Next

These don't need to be retained across Next calls and can be stack
allocated inside Next. This drastically reduces memory consumption as
they accounted for nearly half of all the memory used.

name      old alloc/op   new alloc/op   delta
NewRR-12    9.72kB ± 0%    4.98kB ± 0%  -48.72%  (p=0.000 n=10+10)

* Add a ReadRR benchmark

Unlike NewRR, this will use an io.Reader that does not implement any
methods aside from Read. In particular it does not implement
io.ByteReader.

* Avoid using a bufio.Reader for io.ByteReader readers

At the same time use a smaller buffer size of 1KiB rather than the
bufio.NewReader default of 4KiB.

name       old time/op    new time/op    delta
NewRR-12     11.0µs ± 3%     9.5µs ± 2%  -13.77%  (p=0.000 n=9+10)
ReadRR-12    11.2µs ±16%     9.8µs ± 1%  -13.03%  (p=0.000 n=10+10)

name       old alloc/op   new alloc/op   delta
NewRR-12     4.98kB ± 0%    0.81kB ± 0%  -83.79%  (p=0.000 n=10+10)
ReadRR-12    4.87kB ± 0%    1.82kB ± 0%  -62.73%  (p=0.000 n=10+10)

name       old allocs/op  new allocs/op  delta
NewRR-12       19.0 ± 0%      17.0 ± 0%  -10.53%  (p=0.000 n=10+10)
ReadRR-12      19.0 ± 0%      19.0 ± 0%     ~     (all equal)

ReadRR-12    11.2µs ±16%     9.8µs ± 1%  -13.03%  (p=0.000 n=10+10)

* Surface any remaining comment from zlexer.Next

* Improve comment handling in zlexer.Next

This both fixes a regression where comments could be lost under certain
circumstances and now emits comments that occur within braces.

* Remove outdated comment from zlexer.Next and klexer.Next

* Delay converting LF to space in braced comment

* Fixup TestParseZoneComments

* Remove tokenUpper field from lex

Not computing this for every token, and instead only
when needed is a substantial performance improvement.

name       old time/op    new time/op    delta
NewRR-12     9.56µs ± 0%    6.30µs ± 1%  -34.08%  (p=0.000 n=9+10)
ReadRR-12    9.93µs ± 1%    6.67µs ± 1%  -32.77%  (p=0.000 n=10+10)

name       old alloc/op   new alloc/op   delta
NewRR-12       824B ± 0%      808B ± 0%   -1.94%  (p=0.000 n=10+10)
ReadRR-12    1.83kB ± 0%    1.82kB ± 0%   -0.87%  (p=0.000 n=10+10)

name       old allocs/op  new allocs/op  delta
NewRR-12       17.0 ± 0%      17.0 ± 0%     ~     (all equal)
ReadRR-12      19.0 ± 0%      19.0 ± 0%     ~     (all equal)

* Update ParseZone documentation to match comment changes

The zlexer code was changed to return comments more often, so update the
ParseZone documentation to match.
2018-10-15 17:42:31 +10:30
Yasar Alev 4a9ca7e98d add user link (#790) 2018-10-12 22:15:26 +01:00
Miek Gieben d74956db7b Release 1.0.13 2018-10-10 07:39:49 +01:00
Tom Thorogood dad917d8de Test coverage for all packages and merge Travis builds (#781)
This halves the number of Travis builds that will run, while adding
coverage testing to the dnsutil package.
2018-10-09 18:46:58 +01:00
Tom Thorogood a3f088363b Hold srv.lock while calling SetReadDeadline (#780)
* Hold srv.lock while calling SetReadDeadline

* Only hold the read lock in readTCP and readUDP
2018-10-09 18:46:15 +01:00
Tom Thorogood e6cede5dc8 Use an atomic int32 in checkInProgressQueriesAtShutdownServer (#779) 2018-10-09 18:43:08 +01:00
Tom Thorogood 39265ac07f Prevent a checkInProgressQueriesAtShutdownServer panic (#778)
* Prevent a checkInProgressQueriesAtShutdownServer panic

* Fix typo in comment
2018-10-09 18:41:42 +01:00
Tom Thorogood ac339476d7 Remove RunLocalUDPServerUnsafe test method (#777)
Instead of having a separate RunLocalUDPServerUnsafe, we can use the
functional options added to RunLocalUDPServerWithFinChan in ab16005053.
2018-10-09 18:41:23 +01:00
Alexey Naidyonov c0283a2028 Allows larger offset in $GENERATE (#776)
Although BIND9 documentations does not specify the possible size of offset
in $GENERATE clause, it clearly says that range must be a positive integer
between 0 and (2^31)-1. Moreover, BIND perfectly supports large offsets
which might be really handy when you're dealing with large intranets. I.e.
consider following case

```
$GENERATE 0-255 dhcp-${0,4,d}   A 10.0.0.$
$GENERATE 0-255 dhcp-${256,4,d} A 10.0.1.$
$GENERATE 0-255 dhcp-${512,4,d} A 10.0.2.$
...
```

This change removes offset size check and introduces check that
0 >= (start + offset) and (end + offset) < (2^31)-1.
2018-10-05 18:30:27 +01:00
Tom Thorogood 7eca355503 Run dep ensure -update (#770)
This uses the latest dep as of golang/dep@3c04147.

While we're here, add the missing constraints to Gopkg.toml.
2018-10-05 18:29:45 +01:00
Tom Thorogood 0d29b283ac
Optimise sprintX functions in types.go (#757)
* Simplify appendByte

* Add test case and benchmark for sprintName

* Add test case and benchmark for sprintTxtOctet

* Add test case and benchmark for sprintTxt

* Use strings.Builder for sprint* functions in types.go

* Use writeByte helper in unpackString

* Rename writeByte to writeEscapedByte

This better captures the purpose of this function.
2018-10-06 02:06:59 +09:30
Tom Thorogood 36ffedf7d0
Avoid overriding aLongTimeAgo read deadline (#774)
* Avoid overriding aLongTimeAgo read deadline

The (*Server).readTCP and (*Server).readUDP methods both call
SetReadDeadline. If they race with ShutdownContext, they can override
the aLongTimeAgo read deadline that is set in ShutdownContext. That
will cause ShutdownContext to hang or timeout.

* Reword readTCP comment
2018-10-06 01:46:28 +09:30
Tom Thorogood 7ca2be95a9 NSEC type bitmap packing bug (#768)
* Add test case for NSEC after packing and unpacking

This is ported from:
https://gist.github.com/cesarkuroiwa/ebc2b4fb1103a7e88824865184f0c73c

* Clear msg data after pointer in packDomainName
2018-10-04 07:39:45 +01:00
Tom Thorogood 008c8ca764 Explicitly panic after (*response).Close (#769)
* Explicitly panic after (*response).Close

* Prefix panics with package name

* Harden TestResponseAfterClose by comparing panic message
2018-10-04 07:39:21 +01:00
Tom Thorogood c10ce5142a Fix race on loop variable in TestConcurrentExchanges (#773) 2018-10-04 07:24:09 +01:00
Miek Gieben ba6747e8a9 Release 1.0.12 2018-09-29 17:16:31 +01:00
Miek Gieben cef340ab2c Fix vendoring
Signed-off-by: Miek Gieben <miek@miek.nl>
2018-09-29 17:15:41 +01:00
Miek Gieben 26223ef466 Release 1.0.11 2018-09-29 16:53:57 +01:00
Evgenij Vrublevskij edac0c6ab3 Rollback PR #738 because it breaks compatibility with Windows (#765) 2018-09-29 10:25:59 +01:00
Tom Thorogood 068c7e7c81 Rework and optimise ServeMux (#754)
* Avoid using pointer to sync.RWMutex in ServeMux

* Initialize ServeMux.z only when needed.

This means the zero ServeMux is now valid and empty.

* Add benchmark for ServeMux.match

* Use strings.ToLower once in ServeMux.match

strings.ToLower has a special path for ASCII-only (which q always should
be), and avoids all allocations if the string is already lowercase
(which most DNS labels should be).

* Move NextLabel into for clause in ServeMux.match

* Make ServeMux.ServeDNS easier to read

* Fix the documentation of ServeMux.ServeDNS

* Invoke HandleFailed directly in ServeMux.ServeDNS

* Bail early in ServeMux.match if Handle never called

* Fix typo in ServeMux.match

* Improve documentation of ServeMux

This just splits the massive wall of text up so it's easier to follow.

* Fix typo in ServeMux.HandleRemove documentation

* Replace strings.ToLower with once-allocating version

strings.ToLower allocates twice for uppercase ASCII which causes an
overall regression for this changeset. By writing our own custom version
here we can avoid that allocation.

When https://go-review.googlesource.com/c/go/+/137575 lands in a go
release this can be removed.
2018-09-27 07:48:02 +01:00
Tom Thorogood 45e481ce44 Fix unpackString bug: 127 DEL is unprintable (#755)
This case previously differed from UnpackDomainName in
msg.go and both sprintTxtOctet and appendTXTStringByte in
types.go and was incorrect.
2018-09-27 07:47:48 +01:00
Tom Thorogood 7482521355 Replace the trigger type with chan in server_test.go (#760)
* Replace the trigger type with chan in server_test.go

This was a lot of code to do very little.

* Check the error from ActivateAndServe in TestHandlerCloseTCP

May as well add this missing error check in while we're here.
2018-09-26 21:04:11 +01:00
Tom Thorogood 7f61c6631b
Fix dominikh/go-tools nits (#758)
* Remove unused functions and consts

* Address gosimple nits

* Address staticcheck nits

This excludes several that were intentional or weren't actual errors.

* Reduce size of lex struct

This reduces the size of the lex struct by 8 bytes from:
  lex.token string: 0-16 (size 16, align 8)
  lex.tokenUpper string: 16-32 (size 16, align 8)
  lex.length int: 32-40 (size 8, align 8)
  lex.err bool: 40-41 (size 1, align 1)
  lex.value uint8: 41-42 (size 1, align 1)
  padding: 42-48 (size 6, align 0)
  lex.line int: 48-56 (size 8, align 8)
  lex.column int: 56-64 (size 8, align 8)
  lex.torc uint16: 64-66 (size 2, align 2)
  padding: 66-72 (size 6, align 0)
  lex.comment string: 72-88 (size 16, align 8)
to:
  lex.token string: 0-16 (size 16, align 8)
  lex.tokenUpper string: 16-32 (size 16, align 8)
  lex.length int: 32-40 (size 8, align 8)
  lex.err bool: 40-41 (size 1, align 1)
  lex.value uint8: 41-42 (size 1, align 1)
  lex.torc uint16: 42-44 (size 2, align 2)
  padding: 44-48 (size 4, align 0)
  lex.line int: 48-56 (size 8, align 8)
  lex.column int: 56-64 (size 8, align 8)
  lex.comment string: 64-80 (size 16, align 8)

* Reduce size of response struct

This reduces the size of the response struct by 8 bytes from:
  response.msg []byte: 0-24 (size 24, align 8)
  response.hijacked bool: 24-25 (size 1, align 1)
  padding: 25-32 (size 7, align 0)
  response.tsigStatus error: 32-48 (size 16, align 8)
  response.tsigTimersOnly bool: 48-49 (size 1, align 1)
  padding: 49-56 (size 7, align 0)
  response.tsigRequestMAC string: 56-72 (size 16, align 8)
  response.tsigSecret map[string]string: 72-80 (size 8, align 8)
  response.udp *net.UDPConn: 80-88 (size 8, align 8)
  response.tcp net.Conn: 88-104 (size 16, align 8)
  response.udpSession *github.com/tmthrgd/dns.SessionUDP: 104-112 (size 8, align 8)
  response.writer github.com/tmthrgd/dns.Writer: 112-128 (size 16, align 8)
  response.wg *sync.WaitGroup: 128-136 (size 8, align 8)
to:
  response.msg []byte: 0-24 (size 24, align 8)
  response.hijacked bool: 24-25 (size 1, align 1)
  response.tsigTimersOnly bool: 25-26 (size 1, align 1)
  padding: 26-32 (size 6, align 0)
  response.tsigStatus error: 32-48 (size 16, align 8)
  response.tsigRequestMAC string: 48-64 (size 16, align 8)
  response.tsigSecret map[string]string: 64-72 (size 8, align 8)
  response.udp *net.UDPConn: 72-80 (size 8, align 8)
  response.tcp net.Conn: 80-96 (size 16, align 8)
  response.udpSession *github.com/tmthrgd/dns.SessionUDP: 96-104 (size 8, align 8)
  response.writer github.com/tmthrgd/dns.Writer: 104-120 (size 16, align 8)
  response.wg *sync.WaitGroup: 120-128 (size 8, align 8)
2018-09-27 04:02:05 +09:30
Tom Thorogood ead9678cbc
Run go fmt on package (#759)
This is go fmt from go1.11 and so it picks up the new map formatting
heuristic.

See golang/go@542ea5ad91.
2018-09-27 03:06:02 +09:30
Tom Thorogood 60d113313c Move ServeMux into seperate file (#753)
This reduces the clutter in server.go.
2018-09-26 10:20:48 +01:00
Daniel Selifonov ab16005053 Bugfix for miekg/dns#748 (#749)
* Bugfix for miekg/dns#748

w.msg was being prematurely cleared prior to use by TsigVerify

* Modified patch after feedback from tmthrgd

Added a disposeBuffer method to the server that's passed a response. This wipes the reference to and frees the buffer used to store the message after TSIG validation has occured, not before. Since the pool is an attribute of the server (and the logic refers to a server UDPSize attribute), it made sense to make this a function of the server rather than a function of the response.

* Added TestServerRoundtripTsig to server_test.go

This test generates a TSIG signed query, and makes sure that server TSIG validation does not produce an error.

* Fixed data races introduced by TestServerRoundtripTsig

* Simplified error signalling in TestServerRoundtripTsig

* RunLocalUDPServerWithFinChan variadic closure argument added

This (clever hack suggested by tmthrgd) allows specifying field values (like TsigSecret) on Server instances at test time without making the race detector grouchy, but is backwards compatible with existing invocations of RunLocalUDPServerWithFinChan.
2018-09-26 09:19:35 +01:00
Tom Thorogood f195b71879 Replace unpackTxtString with identical unpackString (#751)
These two functions were identical (sans-variable names) before I
optimized unpackString in 5debfeec63.

This will improve the performance of it's only caller unpackTxt and is
covered by the test and benchmark added in 5debfeec63.
2018-09-26 09:14:19 +01:00
Tom Thorogood 94dab88533 Use correct build constraints for listen_*.go files (#750)
unix.SO_REUSEPORT isn't defined for solaris (or some other non-windows
platforms).

Fixes #747
2018-09-26 09:12:54 +01:00
Tom Thorogood 5debfeec63 Use strings.Builder in unpackString (#746)
* Add test case and benchmark for unpackString helper

* Use strings.Builder in unpackString
2018-09-23 11:21:14 +01:00
Miek Gieben f4db2ca6ed Release 1.0.10 2018-09-22 18:51:30 +01:00
tr3e 501e858f67 Fix issue #742 (#745)
* Fix error comparison in SetTA

* Add testcase TestParseTA()
2018-09-22 18:36:01 +01:00
chantra 833bf76c28 [tls] Carry TLS state within (possibly) response writer (#728)
* [tls] Carry TLS state within (possibly) response writer

This allows a server to make decision wether or not the link used to
connect to the DNS server is using TLS.
This can be used by the handler for instance to (but not limited to):
- log that the request was TLS vs TCP
- craft specific responsed knowing that the link is secured
- return custom answers based on client cert (if provided)
...

Fixes #711

* Address @tmthrgd comments:
- do not check whether w.tcp is nil
- create RR after setting txt value

* Address @miekg comments.

Attempt to make a TLS connection state specific test, it goes over
testing each individual server types (TLS, TCP, UDP) and validate that
tls.Connectionstate is only accessible when expected.

* ConnectionState() returns value instead of pointer

* * make ConnectionStater.ConnectionState() return a pointer again
* rename interface ConnectionState to ConnectionStater
* fix nits pointed by @tmthrgd

* @tmthrgd comment: Do not use concret type in `ConnectionState`
2018-09-22 18:34:55 +01:00
Miek Gieben 426ea785a9 Release 1.0.9 2018-09-15 22:43:45 +01:00
Tom Thorogood b0dc93d276
Make Shutdown wait for connections to terminate gracefully (#717)
* Make Shutdown wait for connections to terminate gracefully

* Add graceful shutdown test files from #713

* Tidy up graceful shutdown tests

* Call t.Error directly in checkInProgressQueriesAtShutdownServer

* Remove timeout arguments from RunLocal*ServerWithFinChan

* Merge defers together in (*Server).serve

This removes the defer from the UDP path, in favour of directly
calling (*sync.WaitGroup).Done after (*Serve).serveDNS has
returned.

* Replace checkInProgressQueriesAtShutdownServer implementation

This performs dialing, writing and reading as three seperate steps.

* Add sleep after writing shutdown test messages

* Avoid race condition when setting server timeouts

Server timeouts cannot be set after the server has started without
triggering the race detector. The timeout's are not strictly needed, so
remove them.

* Use a sync.Cond for testShutdownNotify

Using a chan erroneously triggered the race detector, using a sync.Cond
avoids that problem.

* Remove TestShutdownUDPWithContext

This doesn't really add anything.

* Move shutdown and conn into (*Server).init

* Only log ResponseWriter.WriteMsg error once

* Test that ShutdownContext waits for the reply

* Remove stray newline from diff

* Rename err to ctxErr in ShutdownContext

* Reword testShutdownNotify comment
2018-09-13 23:06:28 +09:30
Tom Thorogood e875a31a5c
Add SO_REUSEPORT support (#736)
* Use strings.TrimSuffix in ListenAndServe for TLS

This replaces the if/else statements with something simpler.

Interestingly, the first pull request I submitted to this library was
to fix the tcp6-tls case way back in 4744e915eb.

* Add SO_REUSEPORT implementation

Fixes #654

* Rename Reuseport field to ReusePort

* Rename supportsReuseport to match ReusePort

* Rename listenUDP and listenTCP file to listen_*.go
2018-09-10 20:12:54 +09:30
Tom Thorogood 8f0a42efa0 Fix TestServerStartStopRace calling t.Fatal on wrong goroutine (#739) 2018-09-09 20:47:16 +01:00
Tom Thorogood bfbb1715af Use ReadMsgUDP and WriteMsgUDP on windows (#738) 2018-09-08 17:33:21 +01:00
Tom Thorogood 7da8f0db5c Simplify (*Client).Dial handling of network type (#737)
* Simplify (*Client).Dial handling of network type

* Remove net.Dialer cast from (*Client).Dial
2018-09-08 17:27:21 +01:00
Tom Thorogood bf6da3a5bd Reduce UDP server memory usage (#735)
* Clear the response.msg field after unpacking

The allocated buffer cannot be freed by the garbage collector while the
response is alive, by clearing msg here, the GC can collect the buffer
sooner.

* Use a sync.Pool for UDP message buffers

* Return UDP message buffer to pool in all paths

* Move udpPool.New closure out of (*Server).init

The closure used to capture the *Server which would cause a reference
loop and prevent it from ever being released by the garbage collector.

This also gives the closure a more obvious name in memory profiles:
  github.com/miekg/dns.makeUDPBuffer.func1 rather than
  github.com/miekg/dns.(*Server).init.func1.
2018-09-08 17:15:17 +01:00
Tom Thorogood 3ce7efeace Fix Serve benchmark failures (#734)
* Fix Serve benchmark failures

At present, these benchmarks don't actually work or measure anything.
SetQuestion must have a fully qualified domain name (trailing dot) to
be valid. Because the question wasn't valid, the request never reached
the server and was rejected by the client.

With the error check added, the benchmarks started failing with:
--- FAIL: BenchmarkServe
    server_test.go:346: Exchange failed: dns: domain must be fully qualified

* Enable Serve6 benchmark

Currently this benchmark isn't run as it's not exported.

* Only enable BenchmarkServe6 when IPv6 is supported

The Serve6 benchmark has been disabled since 2014 (in 28d936c032)
because it doesn't play nice with Travis. We can just skip the benchmark
if it fails to bind to an IPv6 address.
2018-09-08 17:10:56 +01:00
Michael 9a34f54f7a Bump travis to 1.10/1.11 (#731) 2018-08-30 07:41:33 +01:00
Tom Thorogood 4bda1db839 Improve documentation of maxWorkersCount const (#726)
While I'm here, rename it to maxIdleWorkersCount so it's purpose is
clearer.

Updates #722
Updates #725
2018-08-16 17:10:20 +01:00
Alberto Bertogli 28216bf382 Add dnss to the users list (#723) 2018-08-16 17:06:07 +01:00
Tom Thorogood c9b812d1d9 Remove redundant parenthesis (#727)
* Remove redundant parenthesis

These were caught with:
    gofmt -r '(a) -> a' -w *.go

This commit only includes the changes where the formatting makes the
ordering of operations clear.

* Remove more redundant parenthesis

These were caught with:
    gofmt -r '(a) -> a' -w *.go

This commit includes the remaining changes where the formatting does not
make the ordering of operations as clear as the previous commit.
2018-08-16 17:05:27 +01:00
Tom Thorogood 208cd1e89e Simplify unlocking dance in *Serve functions (#719) 2018-08-10 22:50:48 +01:00
Olivier Poitrey 21d95e19e6 readme: add dnstrace to Users section (#721) 2018-08-10 20:17:47 +01:00
Tom Thorogood 4a8fde8d2a Add go1.10.x to Travis (#718)
tip now points to what will be go1.11 leaving go1.10 untested.
2018-08-04 08:48:20 +01:00
Tom Thorogood b559d43c31 Abstract shutdown checking into seperate function (#716) 2018-07-28 13:47:30 +01:00
Tom Thorogood 1e845a5b06 Use RFC 8032 functions added to x/crypto/ed25519 (#715)
This was added in golang/crypto@5ba7f63082 and can replace the
workaround from #458.
2018-07-25 13:01:44 +01:00
Lorenzo Fontana 8004f28488 Add testcases to validate consistency of packDataNsec (#714)
Signed-off-by: Lorenzo Fontana <lo@linux.com>
2018-07-23 22:44:09 +01:00
Jerry Jacobs 3e6e47bc11 README.md: Add github.com/xor-gate/sshfp (#706) 2018-07-01 19:37:35 +01:00
Miek Gieben e7c3f513a1
Remove compression from AFSDB (#704)
This type should not compress its rdata.

Fixes #521
2018-06-23 09:43:19 +01:00
Joel Sing ed07089f3b Correctly handle $GENERATE modifiers (#703)
* Add a ParseZone test for $GENERATE.

* Add a test for modToPrintf used by $GENERATE.

* Correctly handle $GENERATE modifiers.

As per http://www.zytrax.com/books/dns/ch8/generate.html, the width and type (aka base)
components of a modifier are optional. This means that ${2,0,d}, ${2,0} and ${2} are
valid modifiers, however only the first format was previously permitted. Use default
values for the width and/or type if they are unspecified in the modifier.
2018-06-23 09:12:44 +01:00
Miek Gieben df46691620
Add IsDuplicate function (#699)
The makes checking for duplicates more efficient, as you don't have to
use String() and re-parse elements from it anymore.

Fixes #679
2018-06-23 09:12:31 +01:00
Miek Gieben f90eb8fb45
tests: remove t.Log(f) when nothing is failing (#698)
* tests: remove t.Log(f) when nothing is failing

This clears up the travis output some more and adheres to the Unix
saying: no output is good news
2018-06-07 19:15:11 +01:00
Miek Gieben 4c681ac41f
dep ensure -update (#697)
* Some deletes

* some adds
2018-06-07 19:14:42 +01:00
Anton Korshikov c9cd01bc14 Update vendor libs (#690 and #655) (#694) 2018-06-06 18:35:07 +01:00
Andrew Tunnell-Jones 0a83f30697 Harden RSA public key unpacking (#693)
RFC 2537 (RSA/MD5) and RFC 3110 (RSA/SHA1) disallow leading zero octets.
RFC 5702 (RSA/SHA256 and RSA/SHA512) isn't specific but defers to these
earlier RFCs in other places.

There is an upper limit of 4096 bits for both the modulus and exponent.
The modulus must be at least 512 bits. No minimum is specified for the
exponent but a quick search suggests single byte exponents are viable.

Exponents larger than 32 bits are already disallowed. This commit adds
checks for the other requirements, general bounds checks, and defers
initialisation of the big num till the other checks have passed.
2018-06-05 07:18:10 +01:00
Miek Gieben 5a2b9fab83 Release 1.0.8 2018-06-04 22:06:13 +01:00
andrewtj da0e668c16 Fix unpacking RSA exponent and tighten exponent validation (#692)
* Add test from #688 demonstrating bug decoding RSA exponent

* Unpack RSA exponent in correct order

Fixes #688

* Don't unpack RSA keys with an exponent too large for the crypto package

* Update dnssec_test.go

Fix the one nit
2018-06-04 21:58:29 +01:00
Francois Tur d8bd04e7e1 Fix DialTimeout for direct call of Dial (#691)
* ensure dialTimeout is used at Dial time. Ensure dial functions setup the right timeout

* - on Dialing, ensure a dialTimeout for the Dialer only if it is just created, else keep going with parameters of the Dialer.
2018-06-04 21:36:08 +01:00
Tom Thorogood 8ccae88257 Require URLs for DOH addresses (#684)
* Require URLs for DOH addresses

* Move time.Now directly above http.Client.Do in DoH

* Remove https scheme check from DOH

Although the draft RFC explicitly requires that the scheme be https,
this was deemed undesirable, so remove it.
2018-05-29 15:39:02 +01:00
andrewtj 350cd086d1 Don't use padding in base32 helpers (#683)
The base32 variant NSEC3 uses doesn't have padding. This hasn't been a
problem in practice because SHA1 is the only current NSEC3 hash algorithm
and its output doesn't require padding.

No-pad support was introduced in Go 1.9 which is the oldest release this
package supports.
2018-05-21 20:47:51 +01:00
Miek Gieben 0947afec0a
Revert 6f3c0a126c (#682)
Previous behavior was correct. Checking with coredns:

current:

~~~
; <<>> DiG 9.10.3-P4-Debian <<>> +norec +noad +edns=1 +noednsneg soa miek.nl @localhost -p 1043
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: ?256, id: 35480
;; flags: qr; QUERY: 0, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 0
;; Query time: 0 msec
;; SERVER: ::1#1043(::1)
;; WHEN: Sat May 19 16:42:54 BST 2018
;; MSG SIZE  rcvd: 23
~~~

this pr:

~~~
; <<>> DiG 9.10.3-P4-Debian <<>> +norec +noad +edns=1 +noednsneg soa miek.nl @localhost -p 1043
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: BADVERS, id: 25912
;; flags: qr; QUERY: 0, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 0
;; Query time: 0 msec
;; SERVER: ::1#1043(::1)
;; WHEN: Sat May 19 16:51:28 BST 2018
;; MSG SIZE  rcvd: 23
~~~
2018-05-21 20:47:30 +01:00
Miek Gieben 0f8c7717de
doh: Fix mime type (#681)
Latest draft uses "application/dns-message".
2018-05-20 16:56:13 +01:00
Miek Gieben e57bf427e6 Release 1.0.7 2018-05-16 07:59:02 +00:00
Tom Thorogood 64746df23b WIP: DNS-over-HTTPS support for Client.Exchange API (#671)
* Add DNS-over-HTTPS support to (*Client).Exchange

* Ignore net/http goroutine leak from DoH

* Use existing Dialer and TLSConfig fields on Client for DOH

* Make DOH http.Client fully configurable

* Pipe context into exchangeDOH
2018-05-16 08:54:01 +01:00
Miek Gieben 3745b9737d
Small comment tweaks (#678)
Some post #668 comments nits.
2018-05-16 08:53:51 +01:00
Pierre Souchay 09649115c1 Fixed len computation when size just goes beyond 14 bits (#668)
* Fixed len computation when size just goes beyond 14 bits

* Added bouds checks around 14bits

* Len() always right including when around 14bits boudaries

* Avoid splitting into labels when not applicable

* Fixed comments

* Added comments in code

* Added new test cases

* Fixed computation of Len() for SRV and all kind of records

* Fixed Sign that was relying on non-copy for Unit tests

* Removed unused padding

* Fixed typo in PackBuffer() function

* Added comment about packBufferWithCompressionMap() for testing purposes
2018-05-16 07:20:13 +01:00
Uladzimir Trehubenka 621df0907e Make MaxTCPQueries configurable (#673) 2018-05-14 20:12:20 +01:00
Tom Thorogood 77d95a53d0 Handle empty NSEC3 salt in scanner (#677)
Fixes #676
2018-05-14 20:07:52 +01:00
chantra 1f2aa4c780 do not modify dns.Rcode when packing to wire format (#675)
* do not modify dns.Rcode when packing to wire format

When the message has an EDNS0 option in the additional section and
dns.Msg.Rcode is set to an extended rcode, dns.Msg.PackBuffer() modifies
dns.Msg.Rcode.
If you were to `Pack` the message and log it after, the Rcode would show
NOERROR.

Running the test before the change would error with:

```
=== RUN   TestPackNoSideEffect
--- FAIL: TestPackNoSideEffect (0.00s)
	msg_test.go:51: after pack: Rcode is expected to be BADVERS
```

after fixing dns.Msg.PackBuffer(), all tests are still passing.

Fixes #674

* address comments from PR#675
2018-05-13 08:36:02 +01:00
Miek Gieben a93f3e4f6b
copyHeader is redundant (#672)
copyHeader() is redundant, we allocate a header and then copy the
non-pointer elements into it; we don't need to do this, because if we
just asssign rr.Hdr to something else we get the same result.

Remove copyHeader() and the generation and use of it in ztypes.go.
2018-05-10 14:50:26 +01:00
1005 changed files with 14421 additions and 238721 deletions

32
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: "Code scanning - action"
on:
push:
branches: [master, ]
pull_request:
branches: [master]
schedule:
- cron: '0 23 * * 5'
jobs:
CodeQL-Build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 2
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

25
.github/workflows/go.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Go
on: [push, pull_request]
jobs:
build:
name: Build and Test
runs-on: ubuntu-latest
strategy:
matrix:
go: [ 1.19.x, 1.20.x ]
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: Check out code
uses: actions/checkout@v3
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...

View File

@ -1,20 +0,0 @@
language: go
sudo: false
go:
- 1.9.x
- tip
env:
- TESTS="-race -v -bench=. -coverprofile=coverage.txt -covermode=atomic"
- TESTS="-race -v ./..."
before_install:
# don't use the miekg/dns when testing forks
- mkdir -p $GOPATH/src/github.com/miekg
- ln -s $TRAVIS_BUILD_DIR $GOPATH/src/github.com/miekg/ || true
script:
- go test $TESTS
after_success:
- bash <(curl -s https://codecov.io/bash)

1
CODEOWNERS Normal file
View File

@ -0,0 +1 @@
* @miekg @tmthrgd

21
Gopkg.lock generated
View File

@ -1,21 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["ed25519","ed25519/internal/edwards25519"]
revision = "b080dc9a8c480b08e698fb1219160d598526310f"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["bpf","internal/iana","internal/socket","ipv4","ipv6"]
revision = "894f8ed5849b15b810ae41e9590a0d05395bba27"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "c4abc38abaeeeeb9be92455c9c02cae32841122b8982aaa067ef25bb8e86ff9d"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,26 +0,0 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
branch = "master"
name = "golang.org/x/crypto"

49
LICENSE
View File

@ -1,32 +1,29 @@
Extensions of the original work are copyright (c) 2011 Miek Gieben
BSD 3-Clause License
As this is fork of the official Go code the same license applies:
Copyright (c) 2009 The Go Authors. All rights reserved.
Copyright (c) 2009, The Go Authors. Extensions copyright (c) 2011, Miek Gieben.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,7 +1,7 @@
# Makefile for releasing.
#
# The release is controlled from version.go. The version found there is
# used to tag the git repo, we're not building any artifects so there is nothing
# used to tag the git repo, we're not building any artifacts so there is nothing
# to upload to github.
#
# * Up the version in version.go

View File

@ -7,10 +7,10 @@
> Less is more.
Complete and usable DNS library. All widely used Resource Records are supported, including the
DNSSEC types. It follows a lean and mean philosophy. If there is stuff you should know as a DNS
programmer there isn't a convenience function for it. Server side and client side programming is
supported, i.e. you can build servers and resolvers with it.
Complete and usable DNS library. All Resource Records are supported, including the DNSSEC types.
It follows a lean and mean philosophy. If there is stuff you should know as a DNS programmer there
isn't a convenience function for it. Server side and client side programming is supported, i.e. you
can build servers and resolvers with it.
We try to keep the "master" branch as sane as possible and at the bleeding edge of standards,
avoiding breaking changes wherever reasonable. We support the last two versions of Go.
@ -26,8 +26,8 @@ avoiding breaking changes wherever reasonable. We support the last two versions
A not-so-up-to-date-list-that-may-be-actually-current:
* https://github.com/coredns/coredns
* https://cloudflare.com
* https://github.com/abh/geodns
* https://github.com/baidu/bfe
* http://www.statdns.com/
* http://www.dnsinspect.com/
* https://github.com/chuangbo/jianbing-dictionary-dns
@ -41,12 +41,9 @@ A not-so-up-to-date-list-that-may-be-actually-current:
* https://github.com/StalkR/dns-reverse-proxy
* https://github.com/tianon/rawdns
* https://mesosphere.github.io/mesos-dns/
* https://pulse.turbobytes.com/
* https://play.google.com/store/apps/details?id=com.turbobytes.dig
* https://github.com/fcambus/statzone
* https://github.com/benschw/dns-clb-go
* https://github.com/corny/dnscheck for http://public-dns.info/
* https://namesmith.io
* https://github.com/corny/dnscheck for <http://public-dns.info/>
* https://github.com/miekg/unbound
* https://github.com/miekg/exdns
* https://dnslookup.org
@ -55,51 +52,69 @@ A not-so-up-to-date-list-that-may-be-actually-current:
* https://github.com/mehrdadrad/mylg
* https://github.com/bamarni/dockness
* https://github.com/fffaraz/microdns
* http://kelda.io
* https://github.com/ipdcode/hades (JD.COM)
* https://github.com/ipdcode/hades <https://jd.com>
* https://github.com/StackExchange/dnscontrol/
* https://www.dnsperf.com/
* https://dnssectest.net/
* https://dns.apebits.com
* https://github.com/oif/apex
* https://github.com/jedisct1/dnscrypt-proxy
* https://github.com/jedisct1/rpdns
* https://github.com/xor-gate/sshfp
* https://github.com/rs/dnstrace
* https://blitiri.com.ar/p/dnss ([github mirror](https://github.com/albertito/dnss))
* https://render.com
* https://github.com/peterzen/goresolver
* https://github.com/folbricht/routedns
* https://domainr.com/
* https://zonedb.org/
* https://router7.org/
* https://github.com/fortio/dnsping
* https://github.com/Luzilla/dnsbl_exporter
* https://github.com/bodgit/tsig
* https://github.com/v2fly/v2ray-core (test only)
* https://kuma.io/
* https://www.misaka.io/services/dns
* https://ping.sx/dig
* https://fleetdeck.io/
* https://github.com/markdingo/autoreverse
* https://github.com/slackhq/nebula
* https://github.com/dnschecktool/dow-proxy
* https://dnscheck.tools/
Send pull request if you want to be listed here.
# Features
* UDP/TCP queries, IPv4 and IPv6;
* RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE (for all record types) are supported;
* Fast:
* Reply speed around ~ 80K qps (faster hardware results in more qps);
* Parsing RRs ~ 100K RR/s, that's 5M records in about 50 seconds;
* Server side programming (mimicking the net/http package);
* Client side programming;
* DNSSEC: signing, validating and key generation for DSA, RSA, ECDSA and Ed25519;
* EDNS0, NSID, Cookies;
* AXFR/IXFR;
* TSIG, SIG(0);
* DNS over TLS: optional encrypted connection between client and server;
* DNS name compression;
* Depends only on the standard library.
* UDP/TCP queries, IPv4 and IPv6
* RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE (for all record types) are supported
* Fast
* Server side programming (mimicking the net/http package)
* Client side programming
* DNSSEC: signing, validating and key generation for DSA, RSA, ECDSA and Ed25519
* EDNS0, NSID, Cookies
* AXFR/IXFR
* TSIG, SIG(0)
* DNS over TLS (DoT): encrypted connection between client and server over TCP
* DNS name compression
Have fun!
Miek Gieben - 2010-2012 - <miek@miek.nl>
DNS Authors 2012-
# Building
Building is done with the `go` tool. If you have setup your GOPATH correctly, the following should
work:
This library uses Go modules and uses semantic versioning. Building is done with the `go` tool, so
the following should work:
go get github.com/miekg/dns
go build github.com/miekg/dns
## Examples
A short "how to use the API" is at the beginning of doc.go (this also will show
when you call `godoc github.com/miekg/dns`).
A short "how to use the API" is at the beginning of doc.go (this also will show when you call `godoc
github.com/miekg/dns`).
Example programs can be found in the `github.com/miekg/exdns` repository.
@ -123,6 +138,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
* 2915 - NAPTR record
* 2929 - DNS IANA Considerations
* 3110 - RSASHA1 DNS keys
* 3123 - APL record
* 3225 - DO bit (DNSSEC OK)
* 340{1,2,3} - NAPTR record
* 3445 - Limiting the scope of (DNS)KEY
@ -149,6 +165,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
* 6844 - CAA record
* 6891 - EDNS0 update
* 6895 - DNS IANA considerations
* 6944 - DNSSEC DNSKEY Algorithm Status
* 6975 - Algorithm Understanding in DNSSEC
* 7043 - EUI48/EUI64 records
* 7314 - DNS (EDNS) EXPIRE Option
@ -157,12 +174,16 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
* 7553 - URI record
* 7858 - DNS over TLS: Initiation and Performance Considerations
* 7871 - EDNS0 Client Subnet
* 7873 - Domain Name System (DNS) Cookies (draft-ietf-dnsop-cookies)
* 7873 - Domain Name System (DNS) Cookies
* 8080 - EdDSA for DNSSEC
* 8499 - DNS Terminology
* 8659 - DNS Certification Authority Authorization (CAA) Resource Record
* 8914 - Extended DNS Errors
* 8976 - Message Digest for DNS Zones (ZONEMD RR)
## Loosely based upon
## Loosely Based Upon
* `ldns`
* `NSD`
* `Net::DNS`
* `GRONG`
* ldns - <https://nlnetlabs.nl/projects/ldns/about/>
* NSD - <https://nlnetlabs.nl/projects/nsd/about/>
* Net::DNS - <http://www.net-dns.org/>
* GRONG - <https://github.com/bortzmeyer/grong>

61
acceptfunc.go Normal file
View File

@ -0,0 +1,61 @@
package dns
// MsgAcceptFunc is used early in the server code to accept or reject a message with RcodeFormatError.
// It returns a MsgAcceptAction to indicate what should happen with the message.
type MsgAcceptFunc func(dh Header) MsgAcceptAction
// DefaultMsgAcceptFunc checks the request and will reject if:
//
// * isn't a request (don't respond in that case)
//
// * opcode isn't OpcodeQuery or OpcodeNotify
//
// * Zero bit isn't zero
//
// * does not have exactly 1 question in the question section
//
// * has more than 1 RR in the Answer section
//
// * has more than 0 RRs in the Authority section
//
// * has more than 2 RRs in the Additional section
var DefaultMsgAcceptFunc MsgAcceptFunc = defaultMsgAcceptFunc
// MsgAcceptAction represents the action to be taken.
type MsgAcceptAction int
// Allowed returned values from a MsgAcceptFunc.
const (
MsgAccept MsgAcceptAction = iota // Accept the message
MsgReject // Reject the message with a RcodeFormatError
MsgIgnore // Ignore the error and send nothing back.
MsgRejectNotImplemented // Reject the message with a RcodeNotImplemented
)
func defaultMsgAcceptFunc(dh Header) MsgAcceptAction {
if isResponse := dh.Bits&_QR != 0; isResponse {
return MsgIgnore
}
// Don't allow dynamic updates, because then the sections can contain a whole bunch of RRs.
opcode := int(dh.Bits>>11) & 0xF
if opcode != OpcodeQuery && opcode != OpcodeNotify {
return MsgRejectNotImplemented
}
if dh.Qdcount != 1 {
return MsgReject
}
// NOTIFY requests can have a SOA in the ANSWER section. See RFC 1996 Section 3.7 and 3.11.
if dh.Ancount > 1 {
return MsgReject
}
// IXFR request could have one SOA RR in the NS section. See RFC 1995, section 3.
if dh.Nscount > 1 {
return MsgReject
}
if dh.Arcount > 2 {
return MsgReject
}
return MsgAccept
}

35
acceptfunc_test.go Normal file
View File

@ -0,0 +1,35 @@
package dns
import (
"testing"
)
func TestAcceptNotify(t *testing.T) {
HandleFunc("example.org.", handleNotify)
s, addrstr, _, err := RunLocalUDPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
defer s.Shutdown()
m := new(Msg)
m.SetNotify("example.org.")
// Set a SOA hint in the answer section, this is allowed according to RFC 1996.
soa, _ := NewRR("example.org. IN SOA sns.dns.icann.org. noc.dns.icann.org. 2018112827 7200 3600 1209600 3600")
m.Answer = []RR{soa}
c := new(Client)
resp, _, err := c.Exchange(m, addrstr)
if err != nil {
t.Errorf("failed to exchange: %v", err)
}
if resp.Rcode != RcodeSuccess {
t.Errorf("expected %s, got %s", RcodeToString[RcodeSuccess], RcodeToString[resp.Rcode])
}
}
func handleNotify(w ResponseWriter, req *Msg) {
m := new(Msg)
m.SetReply(req)
w.WriteMsg(m)
}

370
client.go
View File

@ -3,27 +3,50 @@ package dns
// A client implementation.
import (
"bytes"
"context"
"crypto/tls"
"encoding/binary"
"fmt"
"io"
"net"
"strings"
"time"
)
const dnsTimeout time.Duration = 2 * time.Second
const tcpIdleTimeout time.Duration = 8 * time.Second
const (
dnsTimeout time.Duration = 2 * time.Second
tcpIdleTimeout time.Duration = 8 * time.Second
)
func isPacketConn(c net.Conn) bool {
if _, ok := c.(net.PacketConn); !ok {
return false
}
if ua, ok := c.LocalAddr().(*net.UnixAddr); ok {
return ua.Net == "unixgram" || ua.Net == "unixpacket"
}
return true
}
// A Conn represents a connection to a DNS server.
type Conn struct {
net.Conn // a net.Conn holding the connection
UDPSize uint16 // minimum receive buffer for UDP messages
TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
tsigRequestMAC string
}
func (co *Conn) tsigProvider() TsigProvider {
if co.TsigProvider != nil {
return co.TsigProvider
}
// tsigSecretProvider will return ErrSecret if co.TsigSecret is nil.
return tsigSecretProvider(co.TsigSecret)
}
// A Client defines parameters for a DNS client.
type Client struct {
Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP)
@ -32,12 +55,13 @@ type Client struct {
Dialer *net.Dialer // a net.Dialer used to set local address, timeouts and more
// Timeout is a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout,
// WriteTimeout when non-zero. Can be overridden with net.Dialer.Timeout (see Client.ExchangeWithDialer and
// Client.Dialer) or context.Context.Deadline (see the deprecated ExchangeContext)
// Client.Dialer) or context.Context.Deadline (see ExchangeContext)
Timeout time.Duration
DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds, or net.Dialer.Timeout if expiring earlier - overridden by Timeout when that value is non-zero
ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
SingleInflight bool // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass
group singleflight
}
@ -78,43 +102,47 @@ func (c *Client) writeTimeout() time.Duration {
// Dial connects to the address on the named network.
func (c *Client) Dial(address string) (conn *Conn, err error) {
return c.DialContext(context.Background(), address)
}
// DialContext connects to the address on the named network, with a context.Context.
// For TLS over TCP (DoT) the context isn't used yet. This will be enabled when Go 1.18 is released.
func (c *Client) DialContext(ctx context.Context, address string) (conn *Conn, err error) {
// create a new dialer with the appropriate timeout
var d net.Dialer
if c.Dialer == nil {
d = net.Dialer{}
d = net.Dialer{Timeout: c.getTimeoutForRequest(c.dialTimeout())}
} else {
d = net.Dialer(*c.Dialer)
d = *c.Dialer
}
d.Timeout = c.getTimeoutForRequest(c.writeTimeout())
network := "udp"
useTLS := false
switch c.Net {
case "tcp-tls":
network = "tcp"
useTLS = true
case "tcp4-tls":
network = "tcp4"
useTLS = true
case "tcp6-tls":
network = "tcp6"
useTLS = true
default:
if c.Net != "" {
network = c.Net
}
network := c.Net
if network == "" {
network = "udp"
}
useTLS := strings.HasPrefix(network, "tcp") && strings.HasSuffix(network, "-tls")
conn = new(Conn)
if useTLS {
network = strings.TrimSuffix(network, "-tls")
// TODO(miekg): Enable after Go 1.18 is released, to be able to support two prev. releases.
/*
tlsDialer := tls.Dialer{
NetDialer: &d,
Config: c.TLSConfig,
}
conn.Conn, err = tlsDialer.DialContext(ctx, network, address)
*/
conn.Conn, err = tls.DialWithDialer(&d, network, address, c.TLSConfig)
} else {
conn.Conn, err = d.Dial(network, address)
conn.Conn, err = d.DialContext(ctx, network, address)
}
if err != nil {
return nil, err
}
conn.UDPSize = c.UDPSize
return conn, nil
}
@ -133,37 +161,55 @@ func (c *Client) Dial(address string) (conn *Conn, err error) {
// To specify a local address or a timeout, the caller has to set the `Client.Dialer`
// attribute appropriately
func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, err error) {
if !c.SingleInflight {
return c.exchange(m, address)
}
t := "nop"
if t1, ok := TypeToString[m.Question[0].Qtype]; ok {
t = t1
}
cl := "nop"
if cl1, ok := ClassToString[m.Question[0].Qclass]; ok {
cl = cl1
}
r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) {
return c.exchange(m, address)
})
if r != nil && shared {
r = r.Copy()
}
return r, rtt, err
}
func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
var co *Conn
co, err = c.Dial(a)
co, err := c.Dial(address)
if err != nil {
return nil, 0, err
}
defer co.Close()
return c.ExchangeWithConn(m, co)
}
// ExchangeWithConn has the same behavior as Exchange, just with a predetermined connection
// that will be used instead of creating a new one.
// Usage pattern with a *dns.Client:
//
// c := new(dns.Client)
// // connection management logic goes here
//
// conn := c.Dial(address)
// in, rtt, err := c.ExchangeWithConn(message, conn)
//
// This allows users of the library to implement their own connection management,
// as opposed to Exchange, which will always use new connections and incur the added overhead
// that entails when using "tcp" and especially "tcp-tls" clients.
//
// When the singleflight is set for this client the context is _not_ forwarded to the (shared) exchange, to
// prevent one cancellation from canceling all outstanding requests.
func (c *Client) ExchangeWithConn(m *Msg, conn *Conn) (r *Msg, rtt time.Duration, err error) {
return c.exchangeWithConnContext(context.Background(), m, conn)
}
func (c *Client) exchangeWithConnContext(ctx context.Context, m *Msg, conn *Conn) (r *Msg, rtt time.Duration, err error) {
if !c.SingleInflight {
return c.exchangeContext(ctx, m, conn)
}
q := m.Question[0]
key := fmt.Sprintf("%s:%d:%d", q.Name, q.Qtype, q.Qclass)
r, rtt, err, shared := c.group.Do(key, func() (*Msg, time.Duration, error) {
// When we're doing singleflight we don't want one context cancellation, cancel _all_ outstanding queries.
// Hence we ignore the context and use Background().
return c.exchangeContext(context.Background(), m, conn)
})
if r != nil && shared {
r = r.Copy()
}
return r, rtt, err
}
func (c *Client) exchangeContext(ctx context.Context, m *Msg, co *Conn) (r *Msg, rtt time.Duration, err error) {
opt := m.IsEdns0()
// If EDNS0 is used use that for size.
if opt != nil && opt.UDPSize() >= MinMsgSize {
@ -174,18 +220,41 @@ func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err erro
co.UDPSize = c.UDPSize
}
co.TsigSecret = c.TsigSecret
t := time.Now()
// write with the appropriate write timeout
co.SetWriteDeadline(t.Add(c.getTimeoutForRequest(c.writeTimeout())))
t := time.Now()
writeDeadline := t.Add(c.getTimeoutForRequest(c.writeTimeout()))
readDeadline := t.Add(c.getTimeoutForRequest(c.readTimeout()))
if deadline, ok := ctx.Deadline(); ok {
if deadline.Before(writeDeadline) {
writeDeadline = deadline
}
if deadline.Before(readDeadline) {
readDeadline = deadline
}
}
co.SetWriteDeadline(writeDeadline)
co.SetReadDeadline(readDeadline)
co.TsigSecret, co.TsigProvider = c.TsigSecret, c.TsigProvider
if err = co.WriteMsg(m); err != nil {
return nil, 0, err
}
co.SetReadDeadline(time.Now().Add(c.getTimeoutForRequest(c.readTimeout())))
r, err = co.ReadMsg()
if err == nil && r.Id != m.Id {
err = ErrId
if isPacketConn(co.Conn) {
for {
r, err = co.ReadMsg()
// Ignore replies with mismatched IDs because they might be
// responses to earlier queries that timed out.
if err != nil || r.Id == m.Id {
break
}
}
} else {
r, err = co.ReadMsg()
if err == nil && r.Id != m.Id {
err = ErrId
}
}
rtt = time.Since(t)
return r, rtt, err
@ -210,11 +279,8 @@ func (co *Conn) ReadMsg() (*Msg, error) {
return m, err
}
if t := m.IsTsig(); t != nil {
if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
return m, ErrSecret
}
// Need to work on the original message p, as that was used to calculate the tsig.
err = TsigVerify(p, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false)
err = TsigVerifyWithProvider(p, co.tsigProvider(), co.tsigRequestMAC, false)
}
return m, err
}
@ -229,24 +295,21 @@ func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
err error
)
switch t := co.Conn.(type) {
case *net.TCPConn, *tls.Conn:
r := t.(io.Reader)
// First two bytes specify the length of the entire message.
l, err := tcpMsgLen(r)
if err != nil {
return nil, err
}
p = make([]byte, l)
n, err = tcpRead(r, p)
default:
if isPacketConn(co.Conn) {
if co.UDPSize > MinMsgSize {
p = make([]byte, co.UDPSize)
} else {
p = make([]byte, MinMsgSize)
}
n, err = co.Read(p)
} else {
var length uint16
if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil {
return nil, err
}
p = make([]byte, length)
n, err = io.ReadFull(co.Conn, p)
}
if err != nil {
@ -266,78 +329,26 @@ func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
return p, err
}
// tcpMsgLen is a helper func to read first two bytes of stream as uint16 packet length.
func tcpMsgLen(t io.Reader) (int, error) {
p := []byte{0, 0}
n, err := t.Read(p)
if err != nil {
return 0, err
}
// As seen with my local router/switch, returns 1 byte on the above read,
// resulting a a ShortRead. Just write it out (instead of loop) and read the
// other byte.
if n == 1 {
n1, err := t.Read(p[1:])
if err != nil {
return 0, err
}
n += n1
}
if n != 2 {
return 0, ErrShortRead
}
l := binary.BigEndian.Uint16(p)
if l == 0 {
return 0, ErrShortRead
}
return int(l), nil
}
// tcpRead calls TCPConn.Read enough times to fill allocated buffer.
func tcpRead(t io.Reader, p []byte) (int, error) {
n, err := t.Read(p)
if err != nil {
return n, err
}
for n < len(p) {
j, err := t.Read(p[n:])
if err != nil {
return n, err
}
n += j
}
return n, err
}
// Read implements the net.Conn read method.
func (co *Conn) Read(p []byte) (n int, err error) {
if co.Conn == nil {
return 0, ErrConnEmpty
}
if len(p) < 2 {
if isPacketConn(co.Conn) {
// UDP connection
return co.Conn.Read(p)
}
var length uint16
if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil {
return 0, err
}
if int(length) > len(p) {
return 0, io.ErrShortBuffer
}
switch t := co.Conn.(type) {
case *net.TCPConn, *tls.Conn:
r := t.(io.Reader)
l, err := tcpMsgLen(r)
if err != nil {
return 0, err
}
if l > len(p) {
return int(l), io.ErrShortBuffer
}
return tcpRead(r, p[:l])
}
// UDP connection
n, err = co.Conn.Read(p)
if err != nil {
return n, err
}
return n, err
return io.ReadFull(co.Conn, p[:length])
}
// WriteMsg sends a message through the connection co.
@ -346,46 +357,32 @@ func (co *Conn) Read(p []byte) (n int, err error) {
func (co *Conn) WriteMsg(m *Msg) (err error) {
var out []byte
if t := m.IsTsig(); t != nil {
mac := ""
if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
return ErrSecret
}
out, mac, err = TsigGenerate(m, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false)
// Set for the next read, although only used in zone transfers
co.tsigRequestMAC = mac
// Set tsigRequestMAC for the next read, although only used in zone transfers.
out, co.tsigRequestMAC, err = TsigGenerateWithProvider(m, co.tsigProvider(), co.tsigRequestMAC, false)
} else {
out, err = m.Pack()
}
if err != nil {
return err
}
if _, err = co.Write(out); err != nil {
return err
}
return nil
_, err = co.Write(out)
return err
}
// Write implements the net.Conn Write method.
func (co *Conn) Write(p []byte) (n int, err error) {
switch t := co.Conn.(type) {
case *net.TCPConn, *tls.Conn:
w := t.(io.Writer)
lp := len(p)
if lp < 2 {
return 0, io.ErrShortBuffer
}
if lp > MaxMsgSize {
return 0, &Error{err: "message too large"}
}
l := make([]byte, 2, lp+2)
binary.BigEndian.PutUint16(l, uint16(lp))
p = append(l, p...)
n, err := io.Copy(w, bytes.NewReader(p))
return int(n), err
func (co *Conn) Write(p []byte) (int, error) {
if len(p) > MaxMsgSize {
return 0, &Error{err: "message too large"}
}
n, err = co.Conn.Write(p)
return n, err
if isPacketConn(co.Conn) {
return co.Conn.Write(p)
}
msg := make([]byte, 2+len(p))
binary.BigEndian.PutUint16(msg, uint16(len(p)))
copy(msg[2:], p)
return co.Conn.Write(msg)
}
// Return the appropriate timeout for a specific request
@ -421,20 +418,19 @@ func Dial(network, address string) (conn *Conn, err error) {
func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) {
client := Client{Net: "udp"}
r, _, err = client.ExchangeContext(ctx, m, a)
// ignorint rtt to leave the original ExchangeContext API unchanged, but
// ignoring rtt to leave the original ExchangeContext API unchanged, but
// this function will go away
return r, err
}
// ExchangeConn performs a synchronous query. It sends the message m via the connection
// c and waits for a reply. The connection c is not closed by ExchangeConn.
// This function is going away, but can easily be mimicked:
// Deprecated: This function is going away, but can easily be mimicked:
//
// co := &dns.Conn{Conn: c} // c is your net.Conn
// co.WriteMsg(m)
// in, _ := co.ReadMsg()
// co.Close()
//
func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) {
println("dns: ExchangeConn: this function is deprecated")
co := new(Conn)
@ -452,11 +448,7 @@ func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) {
// DialTimeout acts like Dial but takes a timeout.
func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) {
client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}}
conn, err = client.Dial(address)
if err != nil {
return nil, err
}
return conn, nil
return client.Dial(address)
}
// DialWithTLS connects to the address on the named network with TLS.
@ -465,12 +457,7 @@ func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, er
network += "-tls"
}
client := Client{Net: network, TLSConfig: tlsConfig}
conn, err = client.Dial(address)
if err != nil {
return nil, err
}
return conn, nil
return client.Dial(address)
}
// DialTimeoutWithTLS acts like DialWithTLS but takes a timeout.
@ -479,25 +466,18 @@ func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout
network += "-tls"
}
client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}, TLSConfig: tlsConfig}
conn, err = client.Dial(address)
if err != nil {
return nil, err
}
return conn, nil
return client.Dial(address)
}
// ExchangeContext acts like Exchange, but honors the deadline on the provided
// context, if present. If there is both a context deadline and a configured
// timeout on the client, the earliest of the two takes effect.
func (c *Client) ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
var timeout time.Duration
if deadline, ok := ctx.Deadline(); !ok {
timeout = 0
} else {
timeout = deadline.Sub(time.Now())
conn, err := c.DialContext(ctx, a)
if err != nil {
return nil, 0, err
}
// not passing the context to the underlying calls, as the API does not support
// context. For timeouts you should set up Client.Dialer and call Client.Exchange.
c.Dialer = &net.Dialer{Timeout: timeout}
return c.Exchange(m, a)
defer conn.Close()
return c.exchangeWithConnContext(ctx, m, conn)
}

View File

@ -3,20 +3,116 @@ package dns
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"path/filepath"
"strconv"
"strings"
"sync"
"testing"
"time"
)
func TestIsPacketConn(t *testing.T) {
// UDP
s, addrstr, _, err := RunLocalUDPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
defer s.Shutdown()
c, err := net.Dial("udp", addrstr)
if err != nil {
t.Fatalf("failed to dial: %v", err)
}
defer c.Close()
if !isPacketConn(c) {
t.Error("UDP connection should be a packet conn")
}
if !isPacketConn(struct{ *net.UDPConn }{c.(*net.UDPConn)}) {
t.Error("UDP connection (wrapped type) should be a packet conn")
}
// TCP
s, addrstr, _, err = RunLocalTCPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
defer s.Shutdown()
c, err = net.Dial("tcp", addrstr)
if err != nil {
t.Fatalf("failed to dial: %v", err)
}
defer c.Close()
if isPacketConn(c) {
t.Error("TCP connection should not be a packet conn")
}
if isPacketConn(struct{ *net.TCPConn }{c.(*net.TCPConn)}) {
t.Error("TCP connection (wrapped type) should not be a packet conn")
}
// Unix datagram
s, addrstr, _, err = RunLocalUnixGramServer(filepath.Join(t.TempDir(), "unixgram.sock"))
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
defer s.Shutdown()
c, err = net.Dial("unixgram", addrstr)
if err != nil {
t.Fatalf("failed to dial: %v", err)
}
defer c.Close()
if !isPacketConn(c) {
t.Error("Unix datagram connection should be a packet conn")
}
if !isPacketConn(struct{ *net.UnixConn }{c.(*net.UnixConn)}) {
t.Error("Unix datagram connection (wrapped type) should be a packet conn")
}
// Unix Seqpacket
shutChan, addrstr, err := RunLocalUnixSeqPacketServer(filepath.Join(t.TempDir(), "unixpacket.sock"))
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
defer func() {
shutChan <- &struct{}{}
}()
c, err = net.Dial("unixpacket", addrstr)
if err != nil {
t.Fatalf("failed to dial: %v", err)
}
defer c.Close()
if !isPacketConn(c) {
t.Error("Unix datagram connection should be a packet conn")
}
if !isPacketConn(struct{ *net.UnixConn }{c.(*net.UnixConn)}) {
t.Error("Unix datagram connection (wrapped type) should be a packet conn")
}
// Unix stream
s, addrstr, _, err = RunLocalUnixServer(filepath.Join(t.TempDir(), "unixstream.sock"))
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
defer s.Shutdown()
c, err = net.Dial("unix", addrstr)
if err != nil {
t.Fatalf("failed to dial: %v", err)
}
defer c.Close()
if isPacketConn(c) {
t.Error("Unix stream connection should not be a packet conn")
}
if isPacketConn(struct{ *net.UnixConn }{c.(*net.UnixConn)}) {
t.Error("Unix stream connection (wrapped type) should not be a packet conn")
}
}
func TestDialUDP(t *testing.T) {
HandleFunc("miek.nl.", HelloServer)
defer HandleRemove("miek.nl.")
s, addrstr, err := RunLocalUDPServer(":0")
s, addrstr, _, err := RunLocalUDPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
@ -39,7 +135,7 @@ func TestClientSync(t *testing.T) {
HandleFunc("miek.nl.", HelloServer)
defer HandleRemove("miek.nl.")
s, addrstr, err := RunLocalUDPServer(":0")
s, addrstr, _, err := RunLocalUDPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
@ -73,7 +169,7 @@ func TestClientLocalAddress(t *testing.T) {
HandleFunc("miek.nl.", HelloServerEchoAddrPort)
defer HandleRemove("miek.nl.")
s, addrstr, err := RunLocalUDPServer(":0")
s, addrstr, _, err := RunLocalUDPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
@ -93,7 +189,7 @@ func TestClientLocalAddress(t *testing.T) {
t.Errorf("failed to get an valid answer\n%v", r)
}
if len(r.Extra) != 1 {
t.Errorf("failed to get additional answers\n%v", r)
t.Fatalf("failed to get additional answers\n%v", r)
}
txt := r.Extra[0].(*TXT)
if txt == nil {
@ -117,7 +213,7 @@ func TestClientTLSSyncV4(t *testing.T) {
Certificates: []tls.Certificate{cert},
}
s, addrstr, err := RunLocalTLSServer(":0", &config)
s, addrstr, _, err := RunLocalTLSServer(":0", &config)
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
@ -163,11 +259,40 @@ func TestClientTLSSyncV4(t *testing.T) {
}
}
func isNetworkTimeout(err error) bool {
// TODO: when Go 1.14 support is dropped, do this: https://golang.org/doc/go1.15#net
var netError net.Error
return errors.As(err, &netError) && netError.Timeout()
}
func TestClientSyncBadID(t *testing.T) {
HandleFunc("miek.nl.", HelloServerBadID)
defer HandleRemove("miek.nl.")
s, addrstr, err := RunLocalUDPServer(":0")
s, addrstr, _, err := RunLocalUDPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
defer s.Shutdown()
m := new(Msg)
m.SetQuestion("miek.nl.", TypeSOA)
// Test with client.Exchange, the plain Exchange function is just a wrapper, so
// we don't need to test that separately.
c := &Client{
Timeout: 10 * time.Millisecond,
}
if _, _, err := c.Exchange(m, addrstr); err == nil || !isNetworkTimeout(err) {
t.Errorf("query did not time out")
}
}
func TestClientSyncBadThenGoodID(t *testing.T) {
HandleFunc("miek.nl.", HelloServerBadThenGoodID)
defer HandleRemove("miek.nl.")
s, addrstr, _, err := RunLocalUDPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
@ -177,11 +302,32 @@ func TestClientSyncBadID(t *testing.T) {
m.SetQuestion("miek.nl.", TypeSOA)
c := new(Client)
if _, _, err := c.Exchange(m, addrstr); err != ErrId {
t.Errorf("did not find a bad Id")
r, _, err := c.Exchange(m, addrstr)
if err != nil {
t.Errorf("failed to exchange: %v", err)
}
// And now with plain Exchange().
if _, err := Exchange(m, addrstr); err != ErrId {
if r.Id != m.Id {
t.Errorf("failed to get response with expected Id")
}
}
func TestClientSyncTCPBadID(t *testing.T) {
HandleFunc("miek.nl.", HelloServerBadID)
defer HandleRemove("miek.nl.")
s, addrstr, _, err := RunLocalTCPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
defer s.Shutdown()
m := new(Msg)
m.SetQuestion("miek.nl.", TypeSOA)
c := &Client{
Net: "tcp",
}
if _, _, err := c.Exchange(m, addrstr); err != ErrId {
t.Errorf("did not find a bad Id")
}
}
@ -190,7 +336,7 @@ func TestClientEDNS0(t *testing.T) {
HandleFunc("miek.nl.", HelloServer)
defer HandleRemove("miek.nl.")
s, addrstr, err := RunLocalUDPServer(":0")
s, addrstr, _, err := RunLocalUDPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
@ -237,7 +383,7 @@ func TestClientEDNS0Local(t *testing.T) {
HandleFunc("miek.nl.", handler)
defer HandleRemove("miek.nl.")
s, addrstr, err := RunLocalUDPServer(":0")
s, addrstr, _, err := RunLocalUDPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %s", err)
}
@ -287,7 +433,7 @@ func TestClientConn(t *testing.T) {
defer HandleRemove("miek.nl.")
// This uses TCP just to make it slightly different than TestClientSync
s, addrstr, err := RunLocalTCPServer(":0")
s, addrstr, _, err := RunLocalTCPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
@ -336,6 +482,24 @@ func TestClientConn(t *testing.T) {
}
}
func TestClientConnWriteSinglePacket(t *testing.T) {
c := &countingConn{}
conn := Conn{
Conn: c,
}
m := new(Msg)
m.SetQuestion("miek.nl.", TypeTXT)
err := conn.WriteMsg(m)
if err != nil {
t.Fatalf("failed to write: %v", err)
}
if c.writes != 1 {
t.Fatalf("incorrect number of Write calls")
}
}
func TestTruncatedMsg(t *testing.T) {
m := new(Msg)
m.SetQuestion("miek.nl.", TypeSRV)
@ -373,12 +537,12 @@ func TestTruncatedMsg(t *testing.T) {
m.Truncated = true
buf, err = m.Pack()
if err != nil {
t.Errorf("failed to pack truncated: %v", err)
t.Errorf("failed to pack truncated message: %v", err)
}
r = new(Msg)
if err = r.Unpack(buf); err != nil && err != ErrTruncated {
t.Errorf("unable to unpack truncated message: %v", err)
if err = r.Unpack(buf); err != nil {
t.Errorf("failed to unpack truncated message: %v", err)
}
if !r.Truncated {
t.Errorf("truncated message wasn't unpacked as truncated")
@ -407,9 +571,10 @@ func TestTruncatedMsg(t *testing.T) {
buf1 = buf[:len(buf)-off]
r = new(Msg)
if err = r.Unpack(buf1); err != nil && err != ErrTruncated {
t.Errorf("unable to unpack cutoff message: %v", err)
if err = r.Unpack(buf1); err == nil {
t.Error("cutoff message should have failed to unpack")
}
// r's header might be still usable.
if !r.Truncated {
t.Error("truncated cutoff message wasn't unpacked as truncated")
}
@ -438,8 +603,8 @@ func TestTruncatedMsg(t *testing.T) {
buf1 = buf[:len(buf)-off]
r = new(Msg)
if err = r.Unpack(buf1); err != nil && err != ErrTruncated {
t.Errorf("unable to unpack cutoff message: %v", err)
if err = r.Unpack(buf1); err == nil {
t.Error("cutoff message should have failed to unpack")
}
if !r.Truncated {
t.Error("truncated cutoff message wasn't unpacked as truncated")
@ -454,8 +619,8 @@ func TestTruncatedMsg(t *testing.T) {
r = new(Msg)
err = r.Unpack(buf1)
if err == nil || err == ErrTruncated {
t.Errorf("error should not be ErrTruncated from question cutoff unpack: %v", err)
if err == nil {
t.Errorf("error should be nil after question cutoff unpack: %v", err)
}
// Finally, if we only have the header, we don't return an error.
@ -484,49 +649,34 @@ func TestTimeout(t *testing.T) {
m := new(Msg)
m.SetQuestion("miek.nl.", TypeTXT)
// Use a channel + timeout to ensure we don't get stuck if the
// Client Timeout is not working properly
done := make(chan struct{}, 2)
runTest := func(name string, exchange func(m *Msg, addr string, timeout time.Duration) (*Msg, time.Duration, error)) {
t.Run(name, func(t *testing.T) {
start := time.Now()
timeout := time.Millisecond
allowable := timeout + (10 * time.Millisecond)
abortAfter := timeout + (100 * time.Millisecond)
timeout := time.Millisecond
allowable := timeout + 10*time.Millisecond
start := time.Now()
_, _, err := exchange(m, addrstr, timeout)
if err == nil {
t.Errorf("no timeout using Client.%s", name)
}
go func() {
length := time.Since(start)
if length > allowable {
t.Errorf("exchange took longer %v than specified Timeout %v", length, allowable)
}
})
}
runTest("Exchange", func(m *Msg, addr string, timeout time.Duration) (*Msg, time.Duration, error) {
c := &Client{Timeout: timeout}
_, _, err := c.Exchange(m, addrstr)
if err == nil {
t.Error("no timeout using Client.Exchange")
}
done <- struct{}{}
}()
go func() {
return c.Exchange(m, addr)
})
runTest("ExchangeContext", func(m *Msg, addr string, timeout time.Duration) (*Msg, time.Duration, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
c := &Client{}
_, _, err := c.ExchangeContext(ctx, m, addrstr)
if err == nil {
t.Error("no timeout using Client.ExchangeContext")
}
done <- struct{}{}
}()
// Wait for both the Exchange and ExchangeContext tests to be done.
for i := 0; i < 2; i++ {
select {
case <-done:
case <-time.After(abortAfter):
}
}
length := time.Since(start)
if length > allowable {
t.Errorf("exchange took longer %v than specified Timeout %v", length, allowable)
}
return new(Client).ExchangeContext(ctx, m, addrstr)
})
}
// Check that responses from deduplicated requests aren't shared between callers
@ -535,23 +685,20 @@ func TestConcurrentExchanges(t *testing.T) {
cases[0] = new(Msg)
cases[1] = new(Msg)
cases[1].Truncated = true
for _, m := range cases {
block := make(chan struct{})
waiting := make(chan struct{})
for _, m := range cases {
mm := m // redeclare m so as not to trip the race detector
handler := func(w ResponseWriter, req *Msg) {
r := m.Copy()
r := mm.Copy()
r.SetReply(req)
waiting <- struct{}{}
<-block
w.WriteMsg(r)
}
HandleFunc("miek.nl.", handler)
defer HandleRemove("miek.nl.")
s, addrstr, err := RunLocalUDPServer(":0")
s, addrstr, _, err := RunLocalUDPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %s", err)
}
@ -559,32 +706,58 @@ func TestConcurrentExchanges(t *testing.T) {
m := new(Msg)
m.SetQuestion("miek.nl.", TypeSRV)
c := &Client{
SingleInflight: true,
}
r := make([]*Msg, 2)
// Force this client to always return the same request,
// even though we're querying sequentially. Running the
// Exchange calls below concurrently can fail due to
// goroutine scheduling, but this simulates the same
// outcome.
c.group.dontDeleteForTesting = true
var wg sync.WaitGroup
wg.Add(len(r))
for i := 0; i < len(r); i++ {
go func(i int) {
defer wg.Done()
r[i], _, _ = c.Exchange(m.Copy(), addrstr)
if r[i] == nil {
t.Errorf("response %d is nil", i)
}
}(i)
r := make([]*Msg, 2)
for i := range r {
r[i], _, _ = c.Exchange(m.Copy(), addrstr)
if r[i] == nil {
t.Errorf("response %d is nil", i)
}
}
select {
case <-waiting:
case <-time.After(time.Second):
t.FailNow()
}
close(block)
wg.Wait()
if r[0] == r[1] {
t.Errorf("got same response, expected non-shared responses")
}
}
}
func TestExchangeWithConn(t *testing.T) {
HandleFunc("miek.nl.", HelloServer)
defer HandleRemove("miek.nl.")
s, addrstr, _, err := RunLocalUDPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
defer s.Shutdown()
m := new(Msg)
m.SetQuestion("miek.nl.", TypeSOA)
c := new(Client)
conn, err := c.Dial(addrstr)
if err != nil {
t.Fatalf("failed to dial: %v", err)
}
r, _, err := c.ExchangeWithConn(m, conn)
if err != nil {
t.Fatalf("failed to exchange: %v", err)
}
if r == nil {
t.Fatal("response is nil")
}
if r.Rcode != RcodeSuccess {
t.Errorf("failed to get an valid answer\n%v", r)
}
}

View File

@ -68,14 +68,10 @@ func ClientConfigFromReader(resolvconf io.Reader) (*ClientConfig, error) {
}
case "search": // set search path to given servers
c.Search = make([]string, len(f)-1)
for i := 0; i < len(c.Search); i++ {
c.Search[i] = f[i+1]
}
c.Search = append([]string(nil), f[1:]...)
case "options": // magic options
for i := 1; i < len(f); i++ {
s := f[i]
for _, s := range f[1:] {
switch {
case len(s) >= 6 && s[:6] == "ndots:":
n, _ := strconv.Atoi(s[6:])

View File

@ -1,188 +0,0 @@
//+build ignore
// compression_generate.go is meant to run with go generate. It will use
// go/{importer,types} to track down all the RR struct types. Then for each type
// it will look to see if there are (compressible) names, if so it will add that
// type to compressionLenHelperType and comressionLenSearchType which "fake" the
// compression so that Len() is fast.
package main
import (
"bytes"
"fmt"
"go/format"
"go/importer"
"go/types"
"log"
"os"
)
var packageHdr = `
// Code generated by "go run compress_generate.go"; DO NOT EDIT.
package dns
`
// getTypeStruct will take a type and the package scope, and return the
// (innermost) struct if the type is considered a RR type (currently defined as
// those structs beginning with a RR_Header, could be redefined as implementing
// the RR interface). The bool return value indicates if embedded structs were
// resolved.
func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
st, ok := t.Underlying().(*types.Struct)
if !ok {
return nil, false
}
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
return st, false
}
if st.Field(0).Anonymous() {
st, _ := getTypeStruct(st.Field(0).Type(), scope)
return st, true
}
return nil, false
}
func main() {
// Import and type-check the package
pkg, err := importer.Default().Import("github.com/miekg/dns")
fatalIfErr(err)
scope := pkg.Scope()
var domainTypes []string // Types that have a domain name in them (either compressible or not).
var cdomainTypes []string // Types that have a compressible domain name in them (subset of domainType)
Names:
for _, name := range scope.Names() {
o := scope.Lookup(name)
if o == nil || !o.Exported() {
continue
}
st, _ := getTypeStruct(o.Type(), scope)
if st == nil {
continue
}
if name == "PrivateRR" {
continue
}
if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" {
log.Fatalf("Constant Type%s does not exist.", o.Name())
}
for i := 1; i < st.NumFields(); i++ {
if _, ok := st.Field(i).Type().(*types.Slice); ok {
if st.Tag(i) == `dns:"domain-name"` {
domainTypes = append(domainTypes, o.Name())
continue Names
}
if st.Tag(i) == `dns:"cdomain-name"` {
cdomainTypes = append(cdomainTypes, o.Name())
domainTypes = append(domainTypes, o.Name())
continue Names
}
continue
}
switch {
case st.Tag(i) == `dns:"domain-name"`:
domainTypes = append(domainTypes, o.Name())
continue Names
case st.Tag(i) == `dns:"cdomain-name"`:
cdomainTypes = append(cdomainTypes, o.Name())
domainTypes = append(domainTypes, o.Name())
continue Names
}
}
}
b := &bytes.Buffer{}
b.WriteString(packageHdr)
// compressionLenHelperType - all types that have domain-name/cdomain-name can be used for compressing names
fmt.Fprint(b, "func compressionLenHelperType(c map[string]int, r RR) {\n")
fmt.Fprint(b, "switch x := r.(type) {\n")
for _, name := range domainTypes {
o := scope.Lookup(name)
st, _ := getTypeStruct(o.Type(), scope)
fmt.Fprintf(b, "case *%s:\n", name)
for i := 1; i < st.NumFields(); i++ {
out := func(s string) { fmt.Fprintf(b, "compressionLenHelper(c, x.%s)\n", st.Field(i).Name()) }
if _, ok := st.Field(i).Type().(*types.Slice); ok {
switch st.Tag(i) {
case `dns:"domain-name"`:
fallthrough
case `dns:"cdomain-name"`:
// For HIP we need to slice over the elements in this slice.
fmt.Fprintf(b, `for i := range x.%s {
compressionLenHelper(c, x.%s[i])
}
`, st.Field(i).Name(), st.Field(i).Name())
}
continue
}
switch {
case st.Tag(i) == `dns:"cdomain-name"`:
fallthrough
case st.Tag(i) == `dns:"domain-name"`:
out(st.Field(i).Name())
}
}
}
fmt.Fprintln(b, "}\n}\n\n")
// compressionLenSearchType - search cdomain-tags types for compressible names.
fmt.Fprint(b, "func compressionLenSearchType(c map[string]int, r RR) (int, bool) {\n")
fmt.Fprint(b, "switch x := r.(type) {\n")
for _, name := range cdomainTypes {
o := scope.Lookup(name)
st, _ := getTypeStruct(o.Type(), scope)
fmt.Fprintf(b, "case *%s:\n", name)
j := 1
for i := 1; i < st.NumFields(); i++ {
out := func(s string, j int) {
fmt.Fprintf(b, "k%d, ok%d := compressionLenSearch(c, x.%s)\n", j, j, st.Field(i).Name())
}
// There are no slice types with names that can be compressed.
switch {
case st.Tag(i) == `dns:"cdomain-name"`:
out(st.Field(i).Name(), j)
j++
}
}
k := "k1"
ok := "ok1"
for i := 2; i < j; i++ {
k += fmt.Sprintf(" + k%d", i)
ok += fmt.Sprintf(" && ok%d", i)
}
fmt.Fprintf(b, "return %s, %s\n", k, ok)
}
fmt.Fprintln(b, "}\nreturn 0, false\n}\n\n")
// gofmt
res, err := format.Source(b.Bytes())
if err != nil {
b.WriteTo(os.Stderr)
log.Fatal(err)
}
f, err := os.Create("zcompress.go")
fatalIfErr(err)
defer f.Close()
f.Write(res)
}
func fatalIfErr(err error) {
if err != nil {
log.Fatal(err)
}
}

View File

@ -4,6 +4,7 @@ import (
"errors"
"net"
"strconv"
"strings"
)
const hexDigit = "0123456789abcdef"
@ -104,7 +105,7 @@ func (dns *Msg) SetAxfr(z string) *Msg {
// SetTsig appends a TSIG RR to the message.
// This is only a skeleton TSIG RR that is added as the last RR in the
// additional section. The Tsig is calculated when the message is being send.
// additional section. The TSIG is calculated when the message is being send.
func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg {
t := new(TSIG)
t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0}
@ -145,10 +146,9 @@ func (dns *Msg) IsTsig() *TSIG {
// record in the additional section will do. It returns the OPT record
// found or nil.
func (dns *Msg) IsEdns0() *OPT {
// EDNS0 is at the end of the additional section, start there.
// We might want to change this to *only* look at the last two
// records. So we see TSIG and/or OPT - this a slightly bigger
// change though.
// RFC 6891, Section 6.1.1 allows the OPT record to appear
// anywhere in the additional record section, but it's usually at
// the end so start there.
for i := len(dns.Extra) - 1; i >= 0; i-- {
if dns.Extra[i].Header().Rrtype == TypeOPT {
return dns.Extra[i].(*OPT)
@ -157,17 +157,98 @@ func (dns *Msg) IsEdns0() *OPT {
return nil
}
// popEdns0 is like IsEdns0, but it removes the record from the message.
func (dns *Msg) popEdns0() *OPT {
// RFC 6891, Section 6.1.1 allows the OPT record to appear
// anywhere in the additional record section, but it's usually at
// the end so start there.
for i := len(dns.Extra) - 1; i >= 0; i-- {
if dns.Extra[i].Header().Rrtype == TypeOPT {
opt := dns.Extra[i].(*OPT)
dns.Extra = append(dns.Extra[:i], dns.Extra[i+1:]...)
return opt
}
}
return nil
}
// IsDomainName checks if s is a valid domain name, it returns the number of
// labels and true, when a domain name is valid. Note that non fully qualified
// domain name is considered valid, in this case the last label is counted in
// the number of labels. When false is returned the number of labels is not
// defined. Also note that this function is extremely liberal; almost any
// string is a valid domain name as the DNS is 8 bit protocol. It checks if each
// label fits in 63 characters, but there is no length check for the entire
// string s. I.e. a domain name longer than 255 characters is considered valid.
// label fits in 63 characters and that the entire name will fit into the 255
// octet wire format limit.
func IsDomainName(s string) (labels int, ok bool) {
_, labels, err := packDomainName(s, nil, 0, nil, false)
return labels, err == nil
// XXX: The logic in this function was copied from packDomainName and
// should be kept in sync with that function.
const lenmsg = 256
if len(s) == 0 { // Ok, for instance when dealing with update RR without any rdata.
return 0, false
}
s = Fqdn(s)
// Each dot ends a segment of the name. Except for escaped dots (\.), which
// are normal dots.
var (
off int
begin int
wasDot bool
)
for i := 0; i < len(s); i++ {
switch s[i] {
case '\\':
if off+1 > lenmsg {
return labels, false
}
// check for \DDD
if i+3 < len(s) && isDigit(s[i+1]) && isDigit(s[i+2]) && isDigit(s[i+3]) {
i += 3
begin += 3
} else {
i++
begin++
}
wasDot = false
case '.':
if i == 0 && len(s) > 1 {
// leading dots are not legal except for the root zone
return labels, false
}
if wasDot {
// two dots back to back is not legal
return labels, false
}
wasDot = true
labelLen := i - begin
if labelLen >= 1<<6 { // top two bits of length must be clear
return labels, false
}
// off can already (we're in a loop) be bigger than lenmsg
// this happens when a name isn't fully qualified
off += 1 + labelLen
if off > lenmsg {
return labels, false
}
labels++
begin = i + 1
default:
wasDot = false
}
}
return labels, true
}
// IsSubDomain checks if child is indeed a child of the parent. If child and parent
@ -181,7 +262,7 @@ func IsSubDomain(parent, child string) bool {
// The checking is performed on the binary payload.
func IsMsg(buf []byte) error {
// Header
if len(buf) < 12 {
if len(buf) < headerSize {
return errors.New("dns: bad message header")
}
// Header: Opcode
@ -191,11 +272,18 @@ func IsMsg(buf []byte) error {
// IsFqdn checks if a domain name is fully qualified.
func IsFqdn(s string) bool {
l := len(s)
if l == 0 {
s2 := strings.TrimSuffix(s, ".")
if s == s2 {
return false
}
return s[l-1] == '.'
i := strings.LastIndexFunc(s2, func(r rune) bool {
return r != '\\'
})
// Test whether we have an even number of escape sequences before
// the dot or none.
return (len(s2)-i)%2 != 0
}
// IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181.
@ -234,6 +322,12 @@ func Fqdn(s string) string {
return s + "."
}
// CanonicalName returns the domain name in canonical form. A name in canonical
// form is lowercase and fully qualified. See Section 6.2 in RFC 4034.
func CanonicalName(s string) string {
return strings.ToLower(Fqdn(s))
}
// Copied from the official Go code.
// ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
@ -244,19 +338,23 @@ func ReverseAddr(addr string) (arpa string, err error) {
if ip == nil {
return "", &Error{err: "unrecognized address: " + addr}
}
if ip.To4() != nil {
return strconv.Itoa(int(ip[15])) + "." + strconv.Itoa(int(ip[14])) + "." + strconv.Itoa(int(ip[13])) + "." +
strconv.Itoa(int(ip[12])) + ".in-addr.arpa.", nil
if v4 := ip.To4(); v4 != nil {
buf := make([]byte, 0, net.IPv4len*4+len("in-addr.arpa."))
// Add it, in reverse, to the buffer
for i := len(v4) - 1; i >= 0; i-- {
buf = strconv.AppendInt(buf, int64(v4[i]), 10)
buf = append(buf, '.')
}
// Append "in-addr.arpa." and return (buf already has the final .)
buf = append(buf, "in-addr.arpa."...)
return string(buf), nil
}
// Must be IPv6
buf := make([]byte, 0, len(ip)*4+len("ip6.arpa."))
buf := make([]byte, 0, net.IPv6len*4+len("ip6.arpa."))
// Add it, in reverse, to the buffer
for i := len(ip) - 1; i >= 0; i-- {
v := ip[i]
buf = append(buf, hexDigit[v&0xF])
buf = append(buf, '.')
buf = append(buf, hexDigit[v>>4])
buf = append(buf, '.')
buf = append(buf, hexDigit[v&0xF], '.', hexDigit[v>>4], '.')
}
// Append "ip6.arpa." and return (buf already has the final .)
buf = append(buf, "ip6.arpa."...)
@ -274,7 +372,7 @@ func (t Type) String() string {
// String returns the string representation for the class c.
func (c Class) String() string {
if s, ok := ClassToString[uint16(c)]; ok {
// Only emit mnemonics when they are unambiguous, specically ANY is in both.
// Only emit mnemonics when they are unambiguous, specially ANY is in both.
if _, ok := StringToType[s]; !ok {
return s
}

99
dns.go
View File

@ -1,6 +1,9 @@
package dns
import "strconv"
import (
"encoding/hex"
"strconv"
)
const (
year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits.
@ -34,10 +37,30 @@ type RR interface {
// copy returns a copy of the RR
copy() RR
// len returns the length (in octets) of the uncompressed RR in wire format.
len() int
// pack packs an RR into wire format.
pack([]byte, int, map[string]int, bool) (int, error)
// len returns the length (in octets) of the compressed or uncompressed RR in wire format.
//
// If compression is nil, the uncompressed size will be returned, otherwise the compressed
// size will be returned and domain names will be added to the map for future compression.
len(off int, compression map[string]struct{}) int
// pack packs the records RDATA into wire format. The header will
// already have been packed into msg.
pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error)
// unpack unpacks an RR from wire format.
//
// This will only be called on a new and empty RR type with only the header populated. It
// will only be called if the record's RDATA is non-empty.
unpack(msg []byte, off int) (off1 int, err error)
// parse parses an RR from zone file format.
//
// This will only be called on a new and empty RR type with only the header populated.
parse(c *zlexer, origin string) *ParseError
// isDuplicate returns whether the two RRs are duplicates.
isDuplicate(r2 RR) bool
}
// RR_Header is the header all DNS resource records share.
@ -55,16 +78,6 @@ func (h *RR_Header) Header() *RR_Header { return h }
// Just to implement the RR interface.
func (h *RR_Header) copy() RR { return nil }
func (h *RR_Header) copyHeader() *RR_Header {
r := new(RR_Header)
r.Name = h.Name
r.Rrtype = h.Rrtype
r.Class = h.Class
r.Ttl = h.Ttl
r.Rdlength = h.Rdlength
return r
}
func (h *RR_Header) String() string {
var s string
@ -80,28 +93,66 @@ func (h *RR_Header) String() string {
return s
}
func (h *RR_Header) len() int {
l := len(h.Name) + 1
func (h *RR_Header) len(off int, compression map[string]struct{}) int {
l := domainNameLen(h.Name, off, compression, true)
l += 10 // rrtype(2) + class(2) + ttl(4) + rdlength(2)
return l
}
func (h *RR_Header) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
// RR_Header has no RDATA to pack.
return off, nil
}
func (h *RR_Header) unpack(msg []byte, off int) (int, error) {
panic("dns: internal error: unpack should never be called on RR_Header")
}
func (h *RR_Header) parse(c *zlexer, origin string) *ParseError {
panic("dns: internal error: parse should never be called on RR_Header")
}
// ToRFC3597 converts a known RR to the unknown RR representation from RFC 3597.
func (rr *RFC3597) ToRFC3597(r RR) error {
buf := make([]byte, r.len()*2)
off, err := PackRR(r, buf, 0, nil, false)
buf := make([]byte, Len(r))
headerEnd, off, err := packRR(r, buf, 0, compressionMap{}, false)
if err != nil {
return err
}
buf = buf[:off]
if int(r.Header().Rdlength) > off {
return ErrBuf
*rr = RFC3597{Hdr: *r.Header()}
rr.Hdr.Rdlength = uint16(off - headerEnd)
if noRdata(rr.Hdr) {
return nil
}
rfc3597, _, err := unpackRFC3597(*r.Header(), buf, off-int(r.Header().Rdlength))
_, err = rr.unpack(buf, headerEnd)
return err
}
// fromRFC3597 converts an unknown RR representation from RFC 3597 to the known RR type.
func (rr *RFC3597) fromRFC3597(r RR) error {
hdr := r.Header()
*hdr = rr.Hdr
// Can't overflow uint16 as the length of Rdata is validated in (*RFC3597).parse.
// We can only get here when rr was constructed with that method.
hdr.Rdlength = uint16(hex.DecodedLen(len(rr.Rdata)))
if noRdata(*hdr) {
// Dynamic update.
return nil
}
// rr.pack requires an extra allocation and a copy so we just decode Rdata
// manually, it's simpler anyway.
msg, err := hex.DecodeString(rr.Rdata)
if err != nil {
return err
}
*rr = *rfc3597.(*RFC3597)
return nil
_, err = r.unpack(msg, 0)
return err
}

View File

@ -1,6 +1,7 @@
package dns
import (
"fmt"
"net"
"testing"
)
@ -63,6 +64,50 @@ func BenchmarkMsgLengthPack(b *testing.B) {
}
}
func BenchmarkMsgLengthMassive(b *testing.B) {
makeMsg := func(question string, ans, ns, e []RR) *Msg {
msg := new(Msg)
msg.SetQuestion(Fqdn(question), TypeANY)
msg.Answer = append(msg.Answer, ans...)
msg.Ns = append(msg.Ns, ns...)
msg.Extra = append(msg.Extra, e...)
msg.Compress = true
return msg
}
const name1 = "12345678901234567890123456789012345.12345678.123."
rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
answer := []RR{rrMx, rrMx}
for i := 0; i < 128; i++ {
rrA := testRR(fmt.Sprintf("example%03d.something%03delse.org. 2311 IN A 127.0.0.1", i/32, i%32))
answer = append(answer, rrA)
}
answer = append(answer, rrMx, rrMx)
msg := makeMsg(name1, answer, nil, nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
msg.Len()
}
}
func BenchmarkMsgLengthOnlyQuestion(b *testing.B) {
msg := new(Msg)
msg.SetQuestion(Fqdn("12345678901234567890123456789012345.12345678.123."), TypeANY)
msg.Compress = true
b.ResetTimer()
for i := 0; i < b.N; i++ {
msg.Len()
}
}
func BenchmarkMsgLengthEscapedName(b *testing.B) {
msg := new(Msg)
msg.SetQuestion(`\1\2\3\4\5\6\7\8\9\0\1\2\3\4\5\6\7\8\9\0\1\2\3\4\5\6\7\8\9\0\1\2\3\4\5.\1\2\3\4\5\6\7\8.\1\2\3.`, TypeANY)
b.ResetTimer()
for i := 0; i < b.N; i++ {
msg.Len()
}
}
func BenchmarkPackDomainName(b *testing.B) {
name1 := "12345678901234567890123456789012345.12345678.123."
buf := make([]byte, len(name1)+1)
@ -92,6 +137,36 @@ func BenchmarkUnpackDomainNameUnprintable(b *testing.B) {
}
}
func BenchmarkUnpackDomainNameLongest(b *testing.B) {
buf := make([]byte, len(longestDomain)+1)
n, err := PackDomainName(longestDomain, buf, 0, nil, false)
if err != nil {
b.Fatal(err)
}
if n != maxDomainNameWireOctets {
b.Fatalf("name wrong size in wire format, expected %d, got %d", maxDomainNameWireOctets, n)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, _ = UnpackDomainName(buf, 0)
}
}
func BenchmarkUnpackDomainNameLongestUnprintable(b *testing.B) {
buf := make([]byte, len(longestUnprintableDomain)+1)
n, err := PackDomainName(longestUnprintableDomain, buf, 0, nil, false)
if err != nil {
b.Fatal(err)
}
if n != maxDomainNameWireOctets {
b.Fatalf("name wrong size in wire format, expected %d, got %d", maxDomainNameWireOctets, n)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, _ = UnpackDomainName(buf, 0)
}
}
func BenchmarkCopy(b *testing.B) {
b.ReportAllocs()
m := new(Msg)
@ -112,7 +187,7 @@ func BenchmarkCopy(b *testing.B) {
func BenchmarkPackA(b *testing.B) {
a := &A{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, A: net.IPv4(127, 0, 0, 1)}
buf := make([]byte, a.len())
buf := make([]byte, Len(a))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
@ -123,7 +198,7 @@ func BenchmarkPackA(b *testing.B) {
func BenchmarkUnpackA(b *testing.B) {
a := &A{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, A: net.IPv4(127, 0, 0, 1)}
buf := make([]byte, a.len())
buf := make([]byte, Len(a))
PackRR(a, buf, 0, nil, false)
a = nil
b.ReportAllocs()
@ -136,7 +211,7 @@ func BenchmarkUnpackA(b *testing.B) {
func BenchmarkPackMX(b *testing.B) {
m := &MX{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, Mx: "mx.miek.nl."}
buf := make([]byte, m.len())
buf := make([]byte, Len(m))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
@ -147,7 +222,7 @@ func BenchmarkPackMX(b *testing.B) {
func BenchmarkUnpackMX(b *testing.B) {
m := &MX{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, Mx: "mx.miek.nl."}
buf := make([]byte, m.len())
buf := make([]byte, Len(m))
PackRR(m, buf, 0, nil, false)
m = nil
b.ReportAllocs()
@ -158,9 +233,9 @@ func BenchmarkUnpackMX(b *testing.B) {
}
func BenchmarkPackAAAAA(b *testing.B) {
aaaa := testRR(". IN A ::1")
aaaa := testRR(". IN AAAA ::1")
buf := make([]byte, aaaa.len())
buf := make([]byte, Len(aaaa))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
@ -169,9 +244,9 @@ func BenchmarkPackAAAAA(b *testing.B) {
}
func BenchmarkUnpackAAAA(b *testing.B) {
aaaa := testRR(". IN A ::1")
aaaa := testRR(". IN AAAA ::1")
buf := make([]byte, aaaa.len())
buf := make([]byte, Len(aaaa))
PackRR(aaaa, buf, 0, nil, false)
aaaa = nil
b.ReportAllocs()
@ -202,6 +277,45 @@ func BenchmarkPackMsg(b *testing.B) {
}
}
func BenchmarkPackMsgMassive(b *testing.B) {
makeMsg := func(question string, ans, ns, e []RR) *Msg {
msg := new(Msg)
msg.SetQuestion(Fqdn(question), TypeANY)
msg.Answer = append(msg.Answer, ans...)
msg.Ns = append(msg.Ns, ns...)
msg.Extra = append(msg.Extra, e...)
msg.Compress = true
return msg
}
const name1 = "12345678901234567890123456789012345.12345678.123."
rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
answer := []RR{rrMx, rrMx}
for i := 0; i < 128; i++ {
rrA := testRR(fmt.Sprintf("example%03d.something%03delse.org. 2311 IN A 127.0.0.1", i/32, i%32))
answer = append(answer, rrA)
}
answer = append(answer, rrMx, rrMx)
msg := makeMsg(name1, answer, nil, nil)
buf := make([]byte, 512)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = msg.PackBuffer(buf)
}
}
func BenchmarkPackMsgOnlyQuestion(b *testing.B) {
msg := new(Msg)
msg.SetQuestion(Fqdn("12345678901234567890123456789012345.12345678.123."), TypeANY)
msg.Compress = true
buf := make([]byte, 512)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = msg.PackBuffer(buf)
}
}
func BenchmarkUnpackMsg(b *testing.B) {
makeMsg := func(question string, ans, ns, e []RR) *Msg {
msg := new(Msg)
@ -228,3 +342,29 @@ func BenchmarkIdGeneration(b *testing.B) {
_ = id()
}
}
func BenchmarkReverseAddr(b *testing.B) {
b.Run("IP4", func(b *testing.B) {
for n := 0; n < b.N; n++ {
addr, err := ReverseAddr("192.0.2.1")
if err != nil {
b.Fatal(err)
}
if expect := "1.2.0.192.in-addr.arpa."; addr != expect {
b.Fatalf("invalid reverse address, expected %q, got %q", expect, addr)
}
}
})
b.Run("IP6", func(b *testing.B) {
for n := 0; n < b.N; n++ {
addr, err := ReverseAddr("2001:db8::68")
if err != nil {
b.Fatal(err)
}
if expect := "8.6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."; addr != expect {
b.Fatalf("invalid reverse address, expected %q, got %q", expect, addr)
}
}
})
}

View File

@ -10,8 +10,7 @@ import (
func TestPackUnpack(t *testing.T) {
out := new(Msg)
out.Answer = make([]RR, 1)
key := new(DNSKEY)
key = &DNSKEY{Flags: 257, Protocol: 3, Algorithm: RSASHA1}
key := &DNSKEY{Flags: 257, Protocol: 3, Algorithm: RSASHA1}
key.Hdr = RR_Header{Name: "miek.nl.", Rrtype: TypeDNSKEY, Class: ClassINET, Ttl: 3600}
key.PublicKey = "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ"
@ -25,8 +24,7 @@ func TestPackUnpack(t *testing.T) {
t.Error("failed to unpack msg with DNSKEY")
}
sig := new(RRSIG)
sig = &RRSIG{TypeCovered: TypeDNSKEY, Algorithm: RSASHA1, Labels: 2,
sig := &RRSIG{TypeCovered: TypeDNSKEY, Algorithm: RSASHA1, Labels: 2,
OrigTtl: 3600, Expiration: 4000, Inception: 4000, KeyTag: 34641, SignerName: "miek.nl.",
Signature: "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ"}
sig.Hdr = RR_Header{Name: "miek.nl.", Rrtype: TypeRRSIG, Class: ClassINET, Ttl: 3600}
@ -134,10 +132,10 @@ func TestPackNAPTR(t *testing.T) {
`apple.com. IN NAPTR 50 50 "se" "SIPS+D2T" "" _sips._tcp.apple.com.`,
} {
rr := testRR(n)
msg := make([]byte, rr.len())
msg := make([]byte, Len(rr))
if off, err := PackRR(rr, msg, 0, nil, false); err != nil {
t.Errorf("packing failed: %v", err)
t.Errorf("length %d, need more than %d", rr.len(), off)
t.Errorf("length %d, need more than %d", Len(rr), off)
}
}
}
@ -281,8 +279,8 @@ func TestTKEY(t *testing.T) {
t.Fatal("Unable to decode TKEY")
}
// Make sure we get back the same length
if rr.len() != len(tkeyBytes) {
t.Fatalf("Lengths don't match %d != %d", rr.len(), len(tkeyBytes))
if Len(rr) != len(tkeyBytes) {
t.Fatalf("Lengths don't match %d != %d", Len(rr), len(tkeyBytes))
}
// make space for it with some fudge room
msg := make([]byte, tkeyLen+1000)
@ -293,10 +291,10 @@ func TestTKEY(t *testing.T) {
if offset != len(tkeyBytes) {
t.Fatalf("mismatched TKEY RR size %d != %d", len(tkeyBytes), offset)
}
if bytes.Compare(tkeyBytes, msg[0:offset]) != 0 {
if !bytes.Equal(tkeyBytes, msg[0:offset]) {
t.Fatal("mismatched TKEY data after rewriting bytes")
}
t.Logf("got TKEY of: " + rr.String())
// Now add some bytes to this and make sure we can encode OtherData properly
tkey := rr.(*TKEY)
tkey.OtherData = "abcd"
@ -305,16 +303,14 @@ func TestTKEY(t *testing.T) {
if packErr != nil {
t.Fatal("unable to pack TKEY RR after modification", packErr)
}
if offset != (len(tkeyBytes) + 2) {
if offset != len(tkeyBytes)+2 {
t.Fatalf("mismatched TKEY RR size %d != %d", offset, len(tkeyBytes)+2)
}
t.Logf("modified to TKEY of: " + rr.String())
// Make sure we can parse our string output
tkey.Hdr.Class = ClassINET // https://github.com/miekg/dns/issues/577
newRR, newError := NewRR(tkey.String())
_, newError := NewRR(tkey.String())
if newError != nil {
t.Fatalf("unable to parse TKEY string: %s", newError)
}
t.Log("got reparsed TKEY of newRR: " + newRR.String())
}

268
dnssec.go
View File

@ -3,15 +3,14 @@ package dns
import (
"bytes"
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
_ "crypto/md5"
"crypto/rand"
"crypto/rsa"
_ "crypto/sha1"
_ "crypto/sha256"
_ "crypto/sha512"
_ "crypto/sha1" // need its init function
_ "crypto/sha256" // need its init function
_ "crypto/sha512" // need its init function
"encoding/asn1"
"encoding/binary"
"encoding/hex"
@ -19,8 +18,6 @@ import (
"sort"
"strings"
"time"
"golang.org/x/crypto/ed25519"
)
// DNSSEC encryption algorithm codes.
@ -67,10 +64,10 @@ var AlgorithmToString = map[uint8]string{
PRIVATEOID: "PRIVATEOID",
}
// StringToAlgorithm is the reverse of AlgorithmToString.
var StringToAlgorithm = reverseInt8(AlgorithmToString)
// AlgorithmToHash is a map of algorithm crypto hash IDs to crypto.Hash's.
// For newer algorithm that do their own hashing (i.e. ED25519) the returned value
// is 0, implying no (external) hashing should occur. The non-exported identityHash is then
// used.
var AlgorithmToHash = map[uint8]crypto.Hash{
RSAMD5: crypto.MD5, // Deprecated in RFC 6725
DSA: crypto.SHA1,
@ -80,7 +77,7 @@ var AlgorithmToHash = map[uint8]crypto.Hash{
ECDSAP256SHA256: crypto.SHA256,
ECDSAP384SHA384: crypto.SHA384,
RSASHA512: crypto.SHA512,
ED25519: crypto.Hash(0),
ED25519: 0,
}
// DNSSEC hashing algorithm codes.
@ -102,9 +99,6 @@ var HashToString = map[uint8]string{
SHA512: "SHA512",
}
// StringToHash is a map of names to hash IDs.
var StringToHash = reverseInt8(HashToString)
// DNSKEY flag values.
const (
SEP = 1
@ -146,12 +140,12 @@ func (k *DNSKEY) KeyTag() uint16 {
var keytag int
switch k.Algorithm {
case RSAMD5:
// Look at the bottom two bytes of the modules, which the last
// item in the pubkey. We could do this faster by looking directly
// at the base64 values. But I'm lazy.
// This algorithm has been deprecated, but keep this key-tag calculation.
// Look at the bottom two bytes of the modules, which the last item in the pubkey.
// See https://www.rfc-editor.org/errata/eid193 .
modulus, _ := fromBase64([]byte(k.PublicKey))
if len(modulus) > 1 {
x := binary.BigEndian.Uint16(modulus[len(modulus)-2:])
x := binary.BigEndian.Uint16(modulus[len(modulus)-3:])
keytag = int(x)
}
default:
@ -173,7 +167,7 @@ func (k *DNSKEY) KeyTag() uint16 {
keytag += int(v) << 8
}
}
keytag += (keytag >> 16) & 0xFFFF
keytag += keytag >> 16 & 0xFFFF
keytag &= 0xFFFF
}
return uint16(keytag)
@ -206,7 +200,7 @@ func (k *DNSKEY) ToDS(h uint8) *DS {
wire = wire[:n]
owner := make([]byte, 255)
off, err1 := PackDomainName(strings.ToLower(k.Hdr.Name), owner, 0, nil, false)
off, err1 := PackDomainName(CanonicalName(k.Hdr.Name), owner, 0, nil, false)
if err1 != nil {
return nil
}
@ -240,7 +234,7 @@ func (k *DNSKEY) ToDS(h uint8) *DS {
// ToCDNSKEY converts a DNSKEY record to a CDNSKEY record.
func (k *DNSKEY) ToCDNSKEY() *CDNSKEY {
c := &CDNSKEY{DNSKEY: *k}
c.Hdr = *k.Hdr.copyHeader()
c.Hdr = k.Hdr
c.Hdr.Rrtype = TypeCDNSKEY
return c
}
@ -248,7 +242,7 @@ func (k *DNSKEY) ToCDNSKEY() *CDNSKEY {
// ToCDS converts a DS record to a CDS record.
func (d *DS) ToCDS() *CDS {
c := &CDS{DS: *d}
c.Hdr = *d.Hdr.copyHeader()
c.Hdr = d.Hdr
c.Hdr.Rrtype = TypeCDS
return c
}
@ -268,16 +262,17 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
return ErrKey
}
h0 := rrset[0].Header()
rr.Hdr.Rrtype = TypeRRSIG
rr.Hdr.Name = rrset[0].Header().Name
rr.Hdr.Class = rrset[0].Header().Class
rr.Hdr.Name = h0.Name
rr.Hdr.Class = h0.Class
if rr.OrigTtl == 0 { // If set don't override
rr.OrigTtl = rrset[0].Header().Ttl
rr.OrigTtl = h0.Ttl
}
rr.TypeCovered = rrset[0].Header().Rrtype
rr.Labels = uint8(CountLabel(rrset[0].Header().Name))
rr.TypeCovered = h0.Rrtype
rr.Labels = uint8(CountLabel(h0.Name))
if strings.HasPrefix(rrset[0].Header().Name, "*") {
if strings.HasPrefix(h0.Name, "*") {
rr.Labels-- // wildcard, remove from label count
}
@ -290,7 +285,7 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
sigwire.Inception = rr.Inception
sigwire.KeyTag = rr.KeyTag
// For signing, lowercase this name
sigwire.SignerName = strings.ToLower(rr.SignerName)
sigwire.SignerName = CanonicalName(rr.SignerName)
// Create the desired binary blob
signdata := make([]byte, DefaultMsgSize)
@ -304,39 +299,27 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
return err
}
hash, ok := AlgorithmToHash[rr.Algorithm]
if !ok {
return ErrAlg
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
if err != nil {
return err
}
switch rr.Algorithm {
case ED25519:
// ed25519 signs the raw message and performs hashing internally.
// All other supported signature schemes operate over the pre-hashed
// message, and thus ed25519 must be handled separately here.
//
// The raw message is passed directly into sign and crypto.Hash(0) is
// used to signal to the crypto.Signer that the data has not been hashed.
signature, err := sign(k, append(signdata, wire...), crypto.Hash(0), rr.Algorithm)
if err != nil {
return err
}
rr.Signature = toBase64(signature)
case RSAMD5, DSA, DSANSEC3SHA1:
// See RFC 6944.
return ErrAlg
default:
h := hash.New()
h.Write(signdata)
h.Write(wire)
signature, err := sign(k, h.Sum(nil), hash, rr.Algorithm)
signature, err := sign(k, h.Sum(nil), cryptohash, rr.Algorithm)
if err != nil {
return err
}
rr.Signature = toBase64(signature)
return nil
}
return nil
}
func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte, error) {
@ -346,9 +329,8 @@ func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte,
}
switch alg {
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, ED25519:
return signature, nil
case ECDSAP256SHA256, ECDSAP384SHA384:
ecdsaSignature := &struct {
R, S *big.Int
@ -368,25 +350,16 @@ func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte,
signature := intToBytes(ecdsaSignature.R, intlen)
signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...)
return signature, nil
// There is no defined interface for what a DSA backed crypto.Signer returns
case DSA, DSANSEC3SHA1:
// t := divRoundUp(divRoundUp(p.PublicKey.Y.BitLen(), 8)-64, 8)
// signature := []byte{byte(t)}
// signature = append(signature, intToBytes(r1, 20)...)
// signature = append(signature, intToBytes(s1, 20)...)
// rr.Signature = signature
case ED25519:
return signature, nil
default:
return nil, ErrAlg
}
return nil, ErrAlg
}
// Verify validates an RRSet with the signature and key. This is only the
// cryptographic test, the signature validity period must be checked separately.
// This function copies the rdata of some RRs (to lowercase domain names) for the validation to work.
// It also checks that the Zone Key bit (RFC 4034 2.1.1) is set on the DNSKEY
// and that the Protocol field is set to 3 (RFC 4034 2.1.2).
func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
// First the easy checks
if !IsRRset(rrset) {
@ -401,20 +374,23 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
if rr.Algorithm != k.Algorithm {
return ErrKey
}
if strings.ToLower(rr.SignerName) != strings.ToLower(k.Hdr.Name) {
if !strings.EqualFold(rr.SignerName, k.Hdr.Name) {
return ErrKey
}
if k.Protocol != 3 {
return ErrKey
}
// RFC 4034 2.1.1 If bit 7 has value 0, then the DNSKEY record holds some
// other type of DNS public key and MUST NOT be used to verify RRSIGs that
// cover RRsets.
if k.Flags&ZONE == 0 {
return ErrKey
}
// IsRRset checked that we have at least one RR and that the RRs in
// the set have consistent type, class, and name. Also check that type and
// class matches the RRSIG record.
if rrset[0].Header().Class != rr.Hdr.Class {
return ErrRRset
}
if rrset[0].Header().Rrtype != rr.TypeCovered {
if h0 := rrset[0].Header(); h0.Class != rr.Hdr.Class || h0.Rrtype != rr.TypeCovered {
return ErrRRset
}
@ -428,7 +404,7 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
sigwire.Expiration = rr.Expiration
sigwire.Inception = rr.Inception
sigwire.KeyTag = rr.KeyTag
sigwire.SignerName = strings.ToLower(rr.SignerName)
sigwire.SignerName = CanonicalName(rr.SignerName)
// Create the desired binary blob
signeddata := make([]byte, DefaultMsgSize)
n, err := packSigWire(sigwire, signeddata)
@ -447,23 +423,22 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
// remove the domain name and assume its ours?
}
hash, ok := AlgorithmToHash[rr.Algorithm]
if !ok {
return ErrAlg
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
if err != nil {
return err
}
switch rr.Algorithm {
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, RSAMD5:
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
// TODO(mg): this can be done quicker, ie. cache the pubkey data somewhere??
pubkey := k.publicKeyRSA() // Get the key
if pubkey == nil {
return ErrKey
}
h := hash.New()
h.Write(signeddata)
h.Write(wire)
return rsa.VerifyPKCS1v15(pubkey, hash, h.Sum(nil), sigbuf)
return rsa.VerifyPKCS1v15(pubkey, cryptohash, h.Sum(nil), sigbuf)
case ECDSAP256SHA256, ECDSAP384SHA384:
pubkey := k.publicKeyECDSA()
@ -475,7 +450,6 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
r := new(big.Int).SetBytes(sigbuf[:len(sigbuf)/2])
s := new(big.Int).SetBytes(sigbuf[len(sigbuf)/2:])
h := hash.New()
h.Write(signeddata)
h.Write(wire)
if ecdsa.Verify(pubkey, h.Sum(nil), r, s) {
@ -512,12 +486,12 @@ func (rr *RRSIG) ValidityPeriod(t time.Time) bool {
}
modi := (int64(rr.Inception) - utc) / year68
mode := (int64(rr.Expiration) - utc) / year68
ti := int64(rr.Inception) + (modi * year68)
te := int64(rr.Expiration) + (mode * year68)
ti := int64(rr.Inception) + modi*year68
te := int64(rr.Expiration) + mode*year68
return ti <= utc && utc <= te
}
// Return the signatures base64 encodedig sigdata as a byte slice.
// Return the signatures base64 encoding sigdata as a byte slice.
func (rr *RRSIG) sigBuf() []byte {
sigbuf, err := fromBase64([]byte(rr.Signature))
if err != nil {
@ -533,6 +507,11 @@ func (k *DNSKEY) publicKeyRSA() *rsa.PublicKey {
return nil
}
if len(keybuf) < 1+1+64 {
// Exponent must be at least 1 byte and modulus at least 64
return nil
}
// RFC 2537/3110, section 2. RSA Public KEY Resource Records
// Length is in the 0th byte, unless its zero, then it
// it in bytes 1 and 2 and its a 16 bit number
@ -542,25 +521,35 @@ func (k *DNSKEY) publicKeyRSA() *rsa.PublicKey {
explen = uint16(keybuf[1])<<8 | uint16(keybuf[2])
keyoff = 3
}
pubkey := new(rsa.PublicKey)
pubkey.N = big.NewInt(0)
shift := uint64((explen - 1) * 8)
expo := uint64(0)
for i := int(explen - 1); i > 0; i-- {
expo += uint64(keybuf[keyoff+i]) << shift
shift -= 8
}
// Remainder
expo += uint64(keybuf[keyoff])
if expo > (2<<31)+1 {
// Larger expo than supported.
// println("dns: F5 primes (or larger) are not supported")
if explen > 4 || explen == 0 || keybuf[keyoff] == 0 {
// Exponent larger than supported by the crypto package,
// empty, or contains prohibited leading zero.
return nil
}
pubkey.E = int(expo)
pubkey.N.SetBytes(keybuf[keyoff+int(explen):])
modoff := keyoff + int(explen)
modlen := len(keybuf) - modoff
if modlen < 64 || modlen > 512 || keybuf[modoff] == 0 {
// Modulus is too small, large, or contains prohibited leading zero.
return nil
}
pubkey := new(rsa.PublicKey)
var expo uint64
// The exponent of length explen is between keyoff and modoff.
for _, v := range keybuf[keyoff:modoff] {
expo <<= 8
expo |= uint64(v)
}
if expo > 1<<31-1 {
// Larger exponent than supported by the crypto package.
return nil
}
pubkey.E = int(expo)
pubkey.N = new(big.Int).SetBytes(keybuf[modoff:])
return pubkey
}
@ -585,34 +574,8 @@ func (k *DNSKEY) publicKeyECDSA() *ecdsa.PublicKey {
return nil
}
}
pubkey.X = big.NewInt(0)
pubkey.X.SetBytes(keybuf[:len(keybuf)/2])
pubkey.Y = big.NewInt(0)
pubkey.Y.SetBytes(keybuf[len(keybuf)/2:])
return pubkey
}
func (k *DNSKEY) publicKeyDSA() *dsa.PublicKey {
keybuf, err := fromBase64([]byte(k.PublicKey))
if err != nil {
return nil
}
if len(keybuf) < 22 {
return nil
}
t, keybuf := int(keybuf[0]), keybuf[1:]
size := 64 + t*8
q, keybuf := keybuf[:20], keybuf[20:]
if len(keybuf) != 3*size {
return nil
}
p, keybuf := keybuf[:size], keybuf[size:]
g, y := keybuf[:size], keybuf[size:]
pubkey := new(dsa.PublicKey)
pubkey.Parameters.Q = big.NewInt(0).SetBytes(q)
pubkey.Parameters.P = big.NewInt(0).SetBytes(p)
pubkey.Parameters.G = big.NewInt(0).SetBytes(g)
pubkey.Y = big.NewInt(0).SetBytes(y)
pubkey.X = new(big.Int).SetBytes(keybuf[:len(keybuf)/2])
pubkey.Y = new(big.Int).SetBytes(keybuf[len(keybuf)/2:])
return pubkey
}
@ -642,15 +605,16 @@ func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) {
wires := make(wireSlice, len(rrset))
for i, r := range rrset {
r1 := r.copy()
r1.Header().Ttl = s.OrigTtl
labels := SplitDomainName(r1.Header().Name)
h := r1.Header()
h.Ttl = s.OrigTtl
labels := SplitDomainName(h.Name)
// 6.2. Canonical RR Form. (4) - wildcards
if len(labels) > int(s.Labels) {
// Wildcard
r1.Header().Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "."
h.Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "."
}
// RFC 4034: 6.2. Canonical RR Form. (2) - domain name to lowercase
r1.Header().Name = strings.ToLower(r1.Header().Name)
h.Name = CanonicalName(h.Name)
// 6.2. Canonical RR Form. (3) - domain rdata to lowercase.
// NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
// HINFO, MINFO, MX, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
@ -663,52 +627,52 @@ func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) {
// conversion.
switch x := r1.(type) {
case *NS:
x.Ns = strings.ToLower(x.Ns)
x.Ns = CanonicalName(x.Ns)
case *MD:
x.Md = strings.ToLower(x.Md)
x.Md = CanonicalName(x.Md)
case *MF:
x.Mf = strings.ToLower(x.Mf)
x.Mf = CanonicalName(x.Mf)
case *CNAME:
x.Target = strings.ToLower(x.Target)
x.Target = CanonicalName(x.Target)
case *SOA:
x.Ns = strings.ToLower(x.Ns)
x.Mbox = strings.ToLower(x.Mbox)
x.Ns = CanonicalName(x.Ns)
x.Mbox = CanonicalName(x.Mbox)
case *MB:
x.Mb = strings.ToLower(x.Mb)
x.Mb = CanonicalName(x.Mb)
case *MG:
x.Mg = strings.ToLower(x.Mg)
x.Mg = CanonicalName(x.Mg)
case *MR:
x.Mr = strings.ToLower(x.Mr)
x.Mr = CanonicalName(x.Mr)
case *PTR:
x.Ptr = strings.ToLower(x.Ptr)
x.Ptr = CanonicalName(x.Ptr)
case *MINFO:
x.Rmail = strings.ToLower(x.Rmail)
x.Email = strings.ToLower(x.Email)
x.Rmail = CanonicalName(x.Rmail)
x.Email = CanonicalName(x.Email)
case *MX:
x.Mx = strings.ToLower(x.Mx)
x.Mx = CanonicalName(x.Mx)
case *RP:
x.Mbox = strings.ToLower(x.Mbox)
x.Txt = strings.ToLower(x.Txt)
x.Mbox = CanonicalName(x.Mbox)
x.Txt = CanonicalName(x.Txt)
case *AFSDB:
x.Hostname = strings.ToLower(x.Hostname)
x.Hostname = CanonicalName(x.Hostname)
case *RT:
x.Host = strings.ToLower(x.Host)
x.Host = CanonicalName(x.Host)
case *SIG:
x.SignerName = strings.ToLower(x.SignerName)
x.SignerName = CanonicalName(x.SignerName)
case *PX:
x.Map822 = strings.ToLower(x.Map822)
x.Mapx400 = strings.ToLower(x.Mapx400)
x.Map822 = CanonicalName(x.Map822)
x.Mapx400 = CanonicalName(x.Mapx400)
case *NAPTR:
x.Replacement = strings.ToLower(x.Replacement)
x.Replacement = CanonicalName(x.Replacement)
case *KX:
x.Exchanger = strings.ToLower(x.Exchanger)
x.Exchanger = CanonicalName(x.Exchanger)
case *SRV:
x.Target = strings.ToLower(x.Target)
x.Target = CanonicalName(x.Target)
case *DNAME:
x.Target = strings.ToLower(x.Target)
x.Target = CanonicalName(x.Target)
}
// 6.2. Canonical RR Form. (5) - origTTL
wire := make([]byte, r1.len()+1) // +1 to be safe(r)
wire := make([]byte, Len(r1)+1) // +1 to be safe(r)
off, err1 := PackRR(r1, wire, 0, nil, false)
if err1 != nil {
return nil, err1

View File

@ -2,14 +2,12 @@ package dns
import (
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"math/big"
"golang.org/x/crypto/ed25519"
)
// Generate generates a DNSKEY of the given bit size.
@ -20,11 +18,7 @@ import (
// bits should be set to the size of the algorithm.
func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) {
switch k.Algorithm {
case DSA, DSANSEC3SHA1:
if bits != 1024 {
return nil, ErrKeySize
}
case RSAMD5, RSASHA1, RSASHA256, RSASHA1NSEC3SHA1:
case RSASHA1, RSASHA256, RSASHA1NSEC3SHA1:
if bits < 512 || bits > 4096 {
return nil, ErrKeySize
}
@ -44,23 +38,12 @@ func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) {
if bits != 256 {
return nil, ErrKeySize
}
default:
return nil, ErrAlg
}
switch k.Algorithm {
case DSA, DSANSEC3SHA1:
params := new(dsa.Parameters)
if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil {
return nil, err
}
priv := new(dsa.PrivateKey)
priv.PublicKey.Parameters = *params
err := dsa.GenerateKey(priv, rand.Reader)
if err != nil {
return nil, err
}
k.setPublicKeyDSA(params.Q, params.P, params.G, priv.PublicKey.Y)
return priv, nil
case RSAMD5, RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1:
case RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1:
priv, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, err
@ -120,16 +103,6 @@ func (k *DNSKEY) setPublicKeyECDSA(_X, _Y *big.Int) bool {
return true
}
// Set the public key for DSA
func (k *DNSKEY) setPublicKeyDSA(_Q, _P, _G, _Y *big.Int) bool {
if _Q == nil || _P == nil || _G == nil || _Y == nil {
return false
}
buf := dsaToBuf(_Q, _P, _G, _Y)
k.PublicKey = toBase64(buf)
return true
}
// Set the public key for Ed25519
func (k *DNSKEY) setPublicKeyED25519(_K ed25519.PublicKey) bool {
if _K == nil {
@ -164,15 +137,3 @@ func curveToBuf(_X, _Y *big.Int, intlen int) []byte {
buf = append(buf, intToBytes(_Y, intlen)...)
return buf
}
// Set the public key for X and Y for Curve. The two
// values are just concatenated.
func dsaToBuf(_Q, _P, _G, _Y *big.Int) []byte {
t := divRoundUp(divRoundUp(_G.BitLen(), 8)-64, 8)
buf := []byte{byte(t)}
buf = append(buf, intToBytes(_Q, 20)...)
buf = append(buf, intToBytes(_P, 64+t*8)...)
buf = append(buf, intToBytes(_G, 64+t*8)...)
buf = append(buf, intToBytes(_Y, 64+t*8)...)
return buf
}

View File

@ -1,17 +1,15 @@
package dns
import (
"bytes"
"bufio"
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"io"
"math/big"
"strconv"
"strings"
"golang.org/x/crypto/ed25519"
)
// NewPrivateKey returns a PrivateKey by parsing the string s.
@ -44,26 +42,7 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
return nil, ErrPrivKey
}
switch uint8(algo) {
case DSA:
priv, err := readPrivateKeyDSA(m)
if err != nil {
return nil, err
}
pub := k.publicKeyDSA()
if pub == nil {
return nil, ErrKey
}
priv.PublicKey = *pub
return priv, nil
case RSAMD5:
fallthrough
case RSASHA1:
fallthrough
case RSASHA1NSEC3SHA1:
fallthrough
case RSASHA256:
fallthrough
case RSASHA512:
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
priv, err := readPrivateKeyRSA(m)
if err != nil {
return nil, err
@ -74,11 +53,7 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
}
priv.PublicKey = *pub
return priv, nil
case ECCGOST:
return nil, ErrPrivKey
case ECDSAP256SHA256:
fallthrough
case ECDSAP384SHA384:
case ECDSAP256SHA256, ECDSAP384SHA384:
priv, err := readPrivateKeyECDSA(m)
if err != nil {
return nil, err
@ -92,7 +67,7 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
case ED25519:
return readPrivateKeyED25519(m)
default:
return nil, ErrPrivKey
return nil, ErrAlg
}
}
@ -109,21 +84,16 @@ func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) {
}
switch k {
case "modulus":
p.PublicKey.N = big.NewInt(0)
p.PublicKey.N.SetBytes(v1)
p.PublicKey.N = new(big.Int).SetBytes(v1)
case "publicexponent":
i := big.NewInt(0)
i.SetBytes(v1)
i := new(big.Int).SetBytes(v1)
p.PublicKey.E = int(i.Int64()) // int64 should be large enough
case "privateexponent":
p.D = big.NewInt(0)
p.D.SetBytes(v1)
p.D = new(big.Int).SetBytes(v1)
case "prime1":
p.Primes[0] = big.NewInt(0)
p.Primes[0].SetBytes(v1)
p.Primes[0] = new(big.Int).SetBytes(v1)
case "prime2":
p.Primes[1] = big.NewInt(0)
p.Primes[1].SetBytes(v1)
p.Primes[1] = new(big.Int).SetBytes(v1)
}
case "exponent1", "exponent2", "coefficient":
// not used in Go (yet)
@ -134,27 +104,9 @@ func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) {
return p, nil
}
func readPrivateKeyDSA(m map[string]string) (*dsa.PrivateKey, error) {
p := new(dsa.PrivateKey)
p.X = big.NewInt(0)
for k, v := range m {
switch k {
case "private_value(x)":
v1, err := fromBase64([]byte(v))
if err != nil {
return nil, err
}
p.X.SetBytes(v1)
case "created", "publish", "activate":
/* not used in Go (yet) */
}
}
return p, nil
}
func readPrivateKeyECDSA(m map[string]string) (*ecdsa.PrivateKey, error) {
p := new(ecdsa.PrivateKey)
p.D = big.NewInt(0)
p.D = new(big.Int)
// TODO: validate that the required flags are present
for k, v := range m {
switch k {
@ -181,22 +133,10 @@ func readPrivateKeyED25519(m map[string]string) (ed25519.PrivateKey, error) {
if err != nil {
return nil, err
}
if len(p1) != 32 {
if len(p1) != ed25519.SeedSize {
return nil, ErrPrivKey
}
// RFC 8080 and Golang's x/crypto/ed25519 differ as to how the
// private keys are represented. RFC 8080 specifies that private
// keys be stored solely as the seed value (p1 above) while the
// ed25519 package represents them as the seed value concatenated
// to the public key, which is derived from the seed value.
//
// ed25519.GenerateKey reads exactly 32 bytes from the passed in
// io.Reader and uses them as the seed. It also derives the
// public key and produces a compatible private key.
_, p, err = ed25519.GenerateKey(bytes.NewReader(p1))
if err != nil {
return nil, err
}
p = ed25519.NewKeyFromSeed(p1)
case "created", "publish", "activate":
/* not used in Go (yet) */
}
@ -207,23 +147,12 @@ func readPrivateKeyED25519(m map[string]string) (ed25519.PrivateKey, error) {
// parseKey reads a private key from r. It returns a map[string]string,
// with the key-value pairs, or an error when the file is not correct.
func parseKey(r io.Reader, file string) (map[string]string, error) {
s, cancel := scanInit(r)
m := make(map[string]string)
c := make(chan lex)
k := ""
defer func() {
cancel()
// zlexer can send up to two tokens, the next one and possibly 1 remainders.
// Do a non-blocking read.
_, ok := <-c
_, ok = <-c
if !ok {
// too bad
}
}()
// Start the lexer
go klexer(s, c)
for l := range c {
var k string
c := newKLexer(r)
for l, ok := c.Next(); ok; l, ok = c.Next() {
// It should alternate
switch l.value {
case zKey:
@ -232,41 +161,111 @@ func parseKey(r io.Reader, file string) (map[string]string, error) {
if k == "" {
return nil, &ParseError{file, "no private key seen", l}
}
//println("Setting", strings.ToLower(k), "to", l.token, "b")
m[strings.ToLower(k)] = l.token
k = ""
}
}
// Surface any read errors from r.
if err := c.Err(); err != nil {
return nil, &ParseError{file: file, err: err.Error()}
}
return m, nil
}
// klexer scans the sourcefile and returns tokens on the channel c.
func klexer(s *scan, c chan lex) {
var l lex
str := "" // Hold the current read text
commt := false
key := true
x, err := s.tokenText()
defer close(c)
for err == nil {
l.column = s.position.Column
l.line = s.position.Line
type klexer struct {
br io.ByteReader
readErr error
line int
column int
key bool
eol bool // end-of-line
}
func newKLexer(r io.Reader) *klexer {
br, ok := r.(io.ByteReader)
if !ok {
br = bufio.NewReaderSize(r, 1024)
}
return &klexer{
br: br,
line: 1,
key: true,
}
}
func (kl *klexer) Err() error {
if kl.readErr == io.EOF {
return nil
}
return kl.readErr
}
// readByte returns the next byte from the input
func (kl *klexer) readByte() (byte, bool) {
if kl.readErr != nil {
return 0, false
}
c, err := kl.br.ReadByte()
if err != nil {
kl.readErr = err
return 0, false
}
// delay the newline handling until the next token is delivered,
// fixes off-by-one errors when reporting a parse error.
if kl.eol {
kl.line++
kl.column = 0
kl.eol = false
}
if c == '\n' {
kl.eol = true
} else {
kl.column++
}
return c, true
}
func (kl *klexer) Next() (lex, bool) {
var (
l lex
str strings.Builder
commt bool
)
for x, ok := kl.readByte(); ok; x, ok = kl.readByte() {
l.line, l.column = kl.line, kl.column
switch x {
case ':':
if commt {
if commt || !kl.key {
break
}
l.token = str
if key {
l.value = zKey
c <- l
// Next token is a space, eat it
s.tokenText()
key = false
str = ""
} else {
l.value = zValue
}
kl.key = false
// Next token is a space, eat it
kl.readByte()
l.value = zKey
l.token = str.String()
return l, true
case ';':
commt = true
case '\n':
@ -274,24 +273,37 @@ func klexer(s *scan, c chan lex) {
// Reset a comment
commt = false
}
if kl.key && str.Len() == 0 {
// ignore empty lines
break
}
kl.key = true
l.value = zValue
l.token = str
c <- l
str = ""
commt = false
key = true
l.token = str.String()
return l, true
default:
if commt {
break
}
str += string(x)
str.WriteByte(x)
}
x, err = s.tokenText()
}
if len(str) > 0 {
if kl.readErr != nil && kl.readErr != io.EOF {
// Don't return any tokens after a read error occurs.
return lex{value: zEOF}, false
}
if str.Len() > 0 {
// Send remainder
l.token = str
l.value = zValue
c <- l
l.token = str.String()
return l, true
}
return lex{value: zEOF}, false
}

View File

@ -2,21 +2,21 @@ package dns
import (
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"math/big"
"strconv"
"golang.org/x/crypto/ed25519"
)
const format = "Private-key-format: v1.3\n"
var bigIntOne = big.NewInt(1)
// PrivateKeyString converts a PrivateKey to a string. This string has the same
// format as the private-key-file of BIND9 (Private-key-format: v1.3).
// It needs some info from the key (the algorithm), so its a method of the DNSKEY
// It supports rsa.PrivateKey, ecdsa.PrivateKey and dsa.PrivateKey
// It needs some info from the key (the algorithm), so its a method of the DNSKEY.
// It supports *rsa.PrivateKey, *ecdsa.PrivateKey and ed25519.PrivateKey.
func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string {
algorithm := strconv.Itoa(int(r.Algorithm))
algorithm += " (" + AlgorithmToString[r.Algorithm] + ")"
@ -31,12 +31,11 @@ func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string {
prime2 := toBase64(p.Primes[1].Bytes())
// Calculate Exponent1/2 and Coefficient as per: http://en.wikipedia.org/wiki/RSA#Using_the_Chinese_remainder_algorithm
// and from: http://code.google.com/p/go/issues/detail?id=987
one := big.NewInt(1)
p1 := big.NewInt(0).Sub(p.Primes[0], one)
q1 := big.NewInt(0).Sub(p.Primes[1], one)
exp1 := big.NewInt(0).Mod(p.D, p1)
exp2 := big.NewInt(0).Mod(p.D, q1)
coeff := big.NewInt(0).ModInverse(p.Primes[1], p.Primes[0])
p1 := new(big.Int).Sub(p.Primes[0], bigIntOne)
q1 := new(big.Int).Sub(p.Primes[1], bigIntOne)
exp1 := new(big.Int).Mod(p.D, p1)
exp2 := new(big.Int).Mod(p.D, q1)
coeff := new(big.Int).ModInverse(p.Primes[1], p.Primes[0])
exponent1 := toBase64(exp1.Bytes())
exponent2 := toBase64(exp2.Bytes())
@ -66,23 +65,8 @@ func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string {
"Algorithm: " + algorithm + "\n" +
"PrivateKey: " + private + "\n"
case *dsa.PrivateKey:
T := divRoundUp(divRoundUp(p.PublicKey.Parameters.G.BitLen(), 8)-64, 8)
prime := toBase64(intToBytes(p.PublicKey.Parameters.P, 64+T*8))
subprime := toBase64(intToBytes(p.PublicKey.Parameters.Q, 20))
base := toBase64(intToBytes(p.PublicKey.Parameters.G, 64+T*8))
priv := toBase64(intToBytes(p.X, 20))
pub := toBase64(intToBytes(p.PublicKey.Y, 64+T*8))
return format +
"Algorithm: " + algorithm + "\n" +
"Prime(p): " + prime + "\n" +
"Subprime(q): " + subprime + "\n" +
"Base(g): " + base + "\n" +
"Private_value(x): " + priv + "\n" +
"Public_value(y): " + pub + "\n"
case ed25519.PrivateKey:
private := toBase64(p[:32])
private := toBase64(p.Seed())
return format +
"Algorithm: " + algorithm + "\n" +
"PrivateKey: " + private + "\n"

View File

@ -3,27 +3,14 @@ package dns
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"reflect"
"strings"
"testing"
"time"
"golang.org/x/crypto/ed25519"
)
func getKey() *DNSKEY {
key := new(DNSKEY)
key.Hdr.Name = "miek.nl."
key.Hdr.Class = ClassINET
key.Hdr.Ttl = 14400
key.Flags = 256
key.Protocol = 3
key.Algorithm = RSASHA256
key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz"
return key
}
func getSoa() *SOA {
soa := new(SOA)
soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0}
@ -180,7 +167,7 @@ func Test65534(t *testing.T) {
key.Flags = 256
key.Protocol = 3
key.Algorithm = RSASHA256
privkey, _ := key.Generate(1024)
privkey, _ := key.Generate(512)
sig := new(RRSIG)
sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0}
@ -263,7 +250,7 @@ func TestKeyRSA(t *testing.T) {
key.Flags = 256
key.Protocol = 3
key.Algorithm = RSASHA256
priv, _ := key.Generate(2048)
priv, _ := key.Generate(512)
soa := new(SOA)
soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0}
@ -337,7 +324,7 @@ Activate: 20110302104537`
}
switch priv := p.(type) {
case *rsa.PrivateKey:
if 65537 != priv.PublicKey.E {
if priv.PublicKey.E != 65537 {
t.Error("exponenent should be 65537")
}
default:
@ -839,3 +826,45 @@ func TestInvalidRRSet(t *testing.T) {
t.Fatal("Verification did not return ErrRRset with inconsistent records")
}
}
// Issue #688 - RSA exponent unpacked in reverse
func TestRsaExponentUnpack(t *testing.T) {
zskRrDnskey, _ := NewRR("isc.org. 7200 IN DNSKEY 256 3 5 AwEAAcdkaRUlsRD4gcF63PpPJJ1E6kOIb3yn/UHptVsPEQtEbgJ2y20O eix4unpwoQkz+bIAd2rrOU/95wgV530x0/qqKwBLWoGkxdcnNcvVT4hl 3SOTZy1VjwkAfyayHPU8VisXqJGbB3KWevBZlb6AtrXzFu8AHuBeeAAe /fOgreCh")
kskRrDnskey, _ := NewRR("isc.org. 7200 IN DNSKEY 257 3 5 BEAAAAOhHQDBrhQbtphgq2wQUpEQ5t4DtUHxoMVFu2hWLDMvoOMRXjGr hhCeFvAZih7yJHf8ZGfW6hd38hXG/xylYCO6Krpbdojwx8YMXLA5/kA+ u50WIL8ZR1R6KTbsYVMf/Qx5RiNbPClw+vT+U8eXEJmO20jIS1ULgqy3 47cBB1zMnnz/4LJpA0da9CbKj3A254T515sNIMcwsB8/2+2E63/zZrQz Bkj0BrN/9Bexjpiks3jRhZatEsXn3dTy47R09Uix5WcJt+xzqZ7+ysyL KOOedS39Z7SDmsn2eA0FKtQpwA6LXeG2w+jxmw3oA8lVUgEf/rzeC/bB yBNsO70aEFTd")
kskRrRrsig, _ := NewRR("isc.org. 7200 IN RRSIG DNSKEY 5 2 7200 20180627230244 20180528230244 12892 isc.org. ebKBlhYi1hPGTdPg6zSwvprOIkoFMs+WIhMSjoYW6/K5CS9lDDFdK4cu TgXJRT3etrltTuJiFe2HRpp+7t5cKLy+CeJZVzqrCz200MoHiFuLI9yI DJQGaS5YYCiFbw5+jUGU6aUhZ7Y5/YufeqATkRZzdrKwgK+zri8LPw9T WLoVJPAOW7GR0dgxl9WKmO7Fzi9P8BZR3NuwLV7329X94j+4zyswaw7q e5vif0ybzFveODLsEi/E0a2rTXc4QzzyM0fSVxRkVQyQ7ifIPP4ohnnT d5qpPUbE8xxBzTdWR/TaKADC5aCFkppG9lVAq5CPfClii2949X5RYzy1 rxhuSA==")
zskRrRrsig, _ := NewRR("isc.org. 7200 IN RRSIG DNSKEY 5 2 7200 20180627230244 20180528230244 19923 isc.org. RgCfzUeq4RJPGoe9RRB6cWf6d/Du+tHK5SxI5QL1waA3O5qVtQKFkY1C dq/yyVjwzfjD9F62TObujOaktv8X80ZMcNPmgHbvK1xOqelMBWv5hxj3 xRe+QQObLZ5NPfHFsphQKXvwgO5Sjk8py2B2iCr3BHCZ8S38oIfuSrQx sn8=")
zsk, ksk := zskRrDnskey.(*DNSKEY), kskRrDnskey.(*DNSKEY)
zskSig, kskSig := zskRrRrsig.(*RRSIG), kskRrRrsig.(*RRSIG)
if e := zskSig.Verify(zsk, []RR{zsk, ksk}); e != nil {
t.Fatalf("cannot verify RRSIG with keytag [%d]. Cause [%s]", zsk.KeyTag(), e.Error())
}
if e := kskSig.Verify(ksk, []RR{zsk, ksk}); e != nil {
t.Fatalf("cannot verify RRSIG with keytag [%d]. Cause [%s]", ksk.KeyTag(), e.Error())
}
}
func TestParseKeyReadError(t *testing.T) {
m, err := parseKey(errReader{}, "")
if err == nil || !strings.Contains(err.Error(), errTestReadError.Error()) {
t.Errorf("expected error to contain %q, but got %v", errTestReadError, err)
}
if m != nil {
t.Errorf("expected a nil map, but got %v", m)
}
}
func TestRSAMD5KeyTag(t *testing.T) {
rr1, _ := NewRR("test. IN DNSKEY 257 3 1 AwEAAcntNdoMnY8pvyPcpDTAaiqHyAhf53XUBANq166won/fjBFvmuzhTuP5r4el/pV0tzEBL73zpoU48BqF66uiL+qRijXCySJiaBUvLNll5rpwuduAOoVpmwOmkC4fV6izHOAx/Uy8c+pYP0YR8+1P7GuTFxgnMmt9sUGtoe+la0X/ ;{id = 27461 (ksk), size = 1024b}")
rr2, _ := NewRR("test. IN DNSKEY 257 3 1 AwEAAf0bKO/m45ylk5BlSLmQHQRBLx1m/ZUXvyPFB387bJXxnTk6so3ub97L1RQ+8bOoiRh3Qm5EaYihjco7J8b/W5WbS3tVsE79nY584RfTKT2zcZ9AoFP2XLChXxPIf/6l0H9n6sH0aBjsG8vabEIp8e06INM3CXVPiMRPPeGNa0Ub ;{id = 27461 (ksk), size = 1024b}")
exp := uint16(27461)
if x := rr1.(*DNSKEY).KeyTag(); x != exp {
t.Errorf("expected %d, got %d, as keytag for rr1", exp, x)
}
if x := rr2.(*DNSKEY).KeyTag(); x != exp { // yes, same key tag
t.Errorf("expected %d, got %d, as keytag for rr2", exp, x)
}
}

View File

@ -30,10 +30,10 @@ func AddOrigin(s, origin string) string {
if dns.IsFqdn(s) {
return s // s is already a FQDN, no need to mess with it.
}
if len(origin) == 0 {
if origin == "" {
return s // Nothing to append.
}
if s == "@" || len(s) == 0 {
if s == "@" || s == "" {
return origin // Expand apex.
}
if origin == "." {
@ -50,7 +50,7 @@ func TrimDomainName(s, origin string) string {
// If the return value ends in a ".", the domain was not the suffix.
// origin can end in "." or not. Either way the results should be the same.
if len(s) == 0 {
if s == "" {
return "@"
}
// Someone is using TrimDomainName(s, ".") to remove a dot if it exists.

View File

@ -63,7 +63,7 @@ func TestTrimDomainName(t *testing.T) {
// Paranoid tests.
// These test shouldn't be needed but I was weary of off-by-one errors.
// In theory, these can't happen because there are no single-letter TLDs,
// but it is good to exercize the code this way.
// but it is good to exercise the code this way.
tests := []struct{ experiment, expected string }{
{"", "@"},
{".", "."},

224
doc.go
View File

@ -1,62 +1,61 @@
/*
Package dns implements a full featured interface to the Domain Name System.
Server- and client-side programming is supported.
The package allows complete control over what is sent out to the DNS. The package
API follows the less-is-more principle, by presenting a small, clean interface.
Both server- and client-side programming is supported. The package allows
complete control over what is sent out to the DNS. The API follows the
less-is-more principle, by presenting a small, clean interface.
The package dns supports (asynchronous) querying/replying, incoming/outgoing zone transfers,
It supports (asynchronous) querying/replying, incoming/outgoing zone transfers,
TSIG, EDNS0, dynamic updates, notifies and DNSSEC validation/signing.
Note that domain names MUST be fully qualified, before sending them, unqualified
Note that domain names MUST be fully qualified before sending them, unqualified
names in a message will result in a packing failure.
Resource records are native types. They are not stored in wire format.
Basic usage pattern for creating a new resource record:
Resource records are native types. They are not stored in wire format. Basic
usage pattern for creating a new resource record:
r := new(dns.MX)
r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX,
Class: dns.ClassINET, Ttl: 3600}
r.Preference = 10
r.Mx = "mx.miek.nl."
r := new(dns.MX)
r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 3600}
r.Preference = 10
r.Mx = "mx.miek.nl."
Or directly from a string:
mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.")
mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.")
Or when the default origin (.) and TTL (3600) and class (IN) suit you:
mx, err := dns.NewRR("miek.nl MX 10 mx.miek.nl")
mx, err := dns.NewRR("miek.nl MX 10 mx.miek.nl")
Or even:
mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek")
mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek")
In the DNS messages are exchanged, these messages contain resource
records (sets). Use pattern for creating a message:
In the DNS messages are exchanged, these messages contain resource records
(sets). Use pattern for creating a message:
m := new(dns.Msg)
m.SetQuestion("miek.nl.", dns.TypeMX)
m := new(dns.Msg)
m.SetQuestion("miek.nl.", dns.TypeMX)
Or when not certain if the domain name is fully qualified:
m.SetQuestion(dns.Fqdn("miek.nl"), dns.TypeMX)
The message m is now a message with the question section set to ask
the MX records for the miek.nl. zone.
The message m is now a message with the question section set to ask the MX
records for the miek.nl. zone.
The following is slightly more verbose, but more flexible:
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.RecursionDesired = true
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET}
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.RecursionDesired = true
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET}
After creating a message it can be sent.
Basic use pattern for synchronous querying the DNS at a
server configured on 127.0.0.1 and port 53:
After creating a message it can be sent. Basic use pattern for synchronous
querying the DNS at a server configured on 127.0.0.1 and port 53:
c := new(dns.Client)
in, rtt, err := c.Exchange(m1, "127.0.0.1:53")
c := new(dns.Client)
in, rtt, err := c.Exchange(m1, "127.0.0.1:53")
Suppressing multiple outstanding queries (with the same question, type and
class) is as easy as setting:
@ -73,7 +72,7 @@ and port to use for the connection:
Port: 12345,
Zone: "",
}
c.Dialer := &net.Dialer{
c.Dialer = &net.Dialer{
Timeout: 200 * time.Millisecond,
LocalAddr: &laddr,
}
@ -84,7 +83,7 @@ with:
in, err := dns.Exchange(m1, "127.0.0.1:53")
When this functions returns you will get dns message. A dns message consists
When this functions returns you will get DNS message. A DNS message consists
out of four sections.
The question section: in.Question, the answer section: in.Answer,
the authority section: in.Ns and the additional section: in.Extra.
@ -97,72 +96,70 @@ the Answer section:
// do something with t.Txt
}
Domain Name and TXT Character String Representations
# Domain Name and TXT Character String Representations
Both domain names and TXT character strings are converted to presentation
form both when unpacked and when converted to strings.
Both domain names and TXT character strings are converted to presentation form
both when unpacked and when converted to strings.
For TXT character strings, tabs, carriage returns and line feeds will be
converted to \t, \r and \n respectively. Back slashes and quotations marks
will be escaped. Bytes below 32 and above 127 will be converted to \DDD
form.
converted to \t, \r and \n respectively. Back slashes and quotations marks will
be escaped. Bytes below 32 and above 127 will be converted to \DDD form.
For domain names, in addition to the above rules brackets, periods,
spaces, semicolons and the at symbol are escaped.
For domain names, in addition to the above rules brackets, periods, spaces,
semicolons and the at symbol are escaped.
DNSSEC
# DNSSEC
DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It
uses public key cryptography to sign resource records. The
public keys are stored in DNSKEY records and the signatures in RRSIG records.
DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It uses
public key cryptography to sign resource records. The public keys are stored in
DNSKEY records and the signatures in RRSIG records.
Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK) bit
to a request.
Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK)
bit to a request.
m := new(dns.Msg)
m.SetEdns0(4096, true)
m := new(dns.Msg)
m.SetEdns0(4096, true)
Signature generation, signature verification and key generation are all supported.
DYNAMIC UPDATES
# DYNAMIC UPDATES
Dynamic updates reuses the DNS message format, but renames three of
the sections. Question is Zone, Answer is Prerequisite, Authority is
Update, only the Additional is not renamed. See RFC 2136 for the gory details.
Dynamic updates reuses the DNS message format, but renames three of the
sections. Question is Zone, Answer is Prerequisite, Authority is Update, only
the Additional is not renamed. See RFC 2136 for the gory details.
You can set a rather complex set of rules for the existence of absence of
certain resource records or names in a zone to specify if resource records
should be added or removed. The table from RFC 2136 supplemented with the Go
DNS function shows which functions exist to specify the prerequisites.
3.2.4 - Table Of Metavalues Used In Prerequisite Section
3.2.4 - Table Of Metavalues Used In Prerequisite Section
CLASS TYPE RDATA Meaning Function
--------------------------------------------------------------
ANY ANY empty Name is in use dns.NameUsed
ANY rrset empty RRset exists (value indep) dns.RRsetUsed
NONE ANY empty Name is not in use dns.NameNotUsed
NONE rrset empty RRset does not exist dns.RRsetNotUsed
zone rrset rr RRset exists (value dep) dns.Used
CLASS TYPE RDATA Meaning Function
--------------------------------------------------------------
ANY ANY empty Name is in use dns.NameUsed
ANY rrset empty RRset exists (value indep) dns.RRsetUsed
NONE ANY empty Name is not in use dns.NameNotUsed
NONE rrset empty RRset does not exist dns.RRsetNotUsed
zone rrset rr RRset exists (value dep) dns.Used
The prerequisite section can also be left empty.
If you have decided on the prerequisites you can tell what RRs should
be added or deleted. The next table shows the options you have and
what functions to call.
The prerequisite section can also be left empty. If you have decided on the
prerequisites you can tell what RRs should be added or deleted. The next table
shows the options you have and what functions to call.
3.4.2.6 - Table Of Metavalues Used In Update Section
3.4.2.6 - Table Of Metavalues Used In Update Section
CLASS TYPE RDATA Meaning Function
---------------------------------------------------------------
ANY ANY empty Delete all RRsets from name dns.RemoveName
ANY rrset empty Delete an RRset dns.RemoveRRset
NONE rrset rr Delete an RR from RRset dns.Remove
zone rrset rr Add to an RRset dns.Insert
CLASS TYPE RDATA Meaning Function
---------------------------------------------------------------
ANY ANY empty Delete all RRsets from name dns.RemoveName
ANY rrset empty Delete an RRset dns.RemoveRRset
NONE rrset rr Delete an RR from RRset dns.Remove
zone rrset rr Add to an RRset dns.Insert
TRANSACTION SIGNATURE
# TRANSACTION SIGNATURE
An TSIG or transaction signature adds a HMAC TSIG record to each message sent.
The supported algorithms include: HmacMD5, HmacSHA1, HmacSHA256 and HmacSHA512.
The supported algorithms include: HmacSHA1, HmacSHA256 and HmacSHA512.
Basic use pattern when querying with a TSIG name "axfr." (note that these key names
must be fully qualified - as they are domain names) and the base64 secret
@ -177,25 +174,49 @@ changes to the RRset after calling SetTsig() the signature will be incorrect.
c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
m := new(dns.Msg)
m.SetQuestion("miek.nl.", dns.TypeMX)
m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
m.SetTsig("axfr.", dns.HmacSHA256, 300, time.Now().Unix())
...
// When sending the TSIG RR is calculated and filled in before sending
When requesting an zone transfer (almost all TSIG usage is when requesting zone transfers), with
TSIG, this is the basic use pattern. In this example we request an AXFR for
miek.nl. with TSIG key named "axfr." and secret "so6ZGir4GPAqINNh9U5c3A=="
and using the server 176.58.119.54:
When requesting an zone transfer (almost all TSIG usage is when requesting zone
transfers), with TSIG, this is the basic use pattern. In this example we
request an AXFR for miek.nl. with TSIG key named "axfr." and secret
"so6ZGir4GPAqINNh9U5c3A==" and using the server 176.58.119.54:
t := new(dns.Transfer)
m := new(dns.Msg)
t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
m.SetAxfr("miek.nl.")
m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
m.SetTsig("axfr.", dns.HmacSHA256, 300, time.Now().Unix())
c, err := t.In(m, "176.58.119.54:53")
for r := range c { ... }
You can now read the records from the transfer as they come in. Each envelope is checked with TSIG.
If something is not correct an error is returned.
You can now read the records from the transfer as they come in. Each envelope
is checked with TSIG. If something is not correct an error is returned.
A custom TSIG implementation can be used. This requires additional code to
perform any session establishment and signature generation/verification. The
client must be configured with an implementation of the TsigProvider interface:
type Provider struct{}
func (*Provider) Generate(msg []byte, tsig *dns.TSIG) ([]byte, error) {
// Use tsig.Hdr.Name and tsig.Algorithm in your code to
// generate the MAC using msg as the payload.
}
func (*Provider) Verify(msg []byte, tsig *dns.TSIG) error {
// Use tsig.Hdr.Name and tsig.Algorithm in your code to verify
// that msg matches the value in tsig.MAC.
}
c := new(dns.Client)
c.TsigProvider = new(Provider)
m := new(dns.Msg)
m.SetQuestion("miek.nl.", dns.TypeMX)
m.SetTsig(keyname, dns.HmacSHA256, 300, time.Now().Unix())
...
// TSIG RR is calculated by calling your Generate method
Basic use pattern validating and replying to a message that has TSIG set.
@ -210,38 +231,38 @@ Basic use pattern validating and replying to a message that has TSIG set.
if r.IsTsig() != nil {
if w.TsigStatus() == nil {
// *Msg r has an TSIG record and it was validated
m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
m.SetTsig("axfr.", dns.HmacSHA256, 300, time.Now().Unix())
} else {
// *Msg r has an TSIG records and it was not valided
// *Msg r has an TSIG records and it was not validated
}
}
w.WriteMsg(m)
}
PRIVATE RRS
# PRIVATE RRS
RFC 6895 sets aside a range of type codes for private use. This range
is 65,280 - 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these
RFC 6895 sets aside a range of type codes for private use. This range is 65,280
- 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these
can be used, before requesting an official type code from IANA.
see http://miek.nl/2014/September/21/idn-and-private-rr-in-go-dns/ for more
See https://miek.nl/2014/september/21/idn-and-private-rr-in-go-dns/ for more
information.
EDNS0
# EDNS0
EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated
by RFC 6891. It defines an new RR type, the OPT RR, which is then completely
EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated by
RFC 6891. It defines a new RR type, the OPT RR, which is then completely
abused.
Basic use pattern for creating an (empty) OPT RR:
o := new(dns.OPT)
o.Hdr.Name = "." // MUST be the root zone, per definition.
o.Hdr.Rrtype = dns.TypeOPT
The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891)
interfaces. Currently only a few have been standardized: EDNS0_NSID
(RFC 5001) and EDNS0_SUBNET (draft-vandergaast-edns-client-subnet-02). Note
that these options may be combined in an OPT RR.
The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891) interfaces.
Currently only a few have been standardized: EDNS0_NSID (RFC 5001) and
EDNS0_SUBNET (RFC 7871). Note that these options may be combined in an OPT RR.
Basic use pattern for a server to check if (and which) options are set:
// o is a dns.OPT
@ -258,14 +279,13 @@ SIG(0)
From RFC 2931:
SIG(0) provides protection for DNS transactions and requests ....
... protection for glue records, DNS requests, protection for message headers
on requests and responses, and protection of the overall integrity of a response.
SIG(0) provides protection for DNS transactions and requests ....
... protection for glue records, DNS requests, protection for message headers
on requests and responses, and protection of the overall integrity of a response.
It works like TSIG, except that SIG(0) uses public key cryptography, instead of the shared
secret approach in TSIG.
Supported algorithms: DSA, ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256 and
RSASHA512.
It works like TSIG, except that SIG(0) uses public key cryptography, instead of
the shared secret approach in TSIG. Supported algorithms: ECDSAP256SHA256,
ECDSAP384SHA384, RSASHA1, RSASHA256 and RSASHA512.
Signing subsequent messages in multi-message sessions is not implemented.
*/

37
duplicate.go Normal file
View File

@ -0,0 +1,37 @@
package dns
//go:generate go run duplicate_generate.go
// IsDuplicate checks of r1 and r2 are duplicates of each other, excluding the TTL.
// So this means the header data is equal *and* the RDATA is the same. Returns true
// if so, otherwise false. It's a protocol violation to have identical RRs in a message.
func IsDuplicate(r1, r2 RR) bool {
// Check whether the record header is identical.
if !r1.Header().isDuplicate(r2.Header()) {
return false
}
// Check whether the RDATA is identical.
return r1.isDuplicate(r2)
}
func (r1 *RR_Header) isDuplicate(_r2 RR) bool {
r2, ok := _r2.(*RR_Header)
if !ok {
return false
}
if r1.Class != r2.Class {
return false
}
if r1.Rrtype != r2.Rrtype {
return false
}
if !isDuplicateName(r1.Name, r2.Name) {
return false
}
// ignore TTL
return true
}
// isDuplicateName checks if the domain names s1 and s2 are equal.
func isDuplicateName(s1, s2 string) bool { return equal(s1, s2) }

174
duplicate_generate.go Normal file
View File

@ -0,0 +1,174 @@
//go:build ignore
// +build ignore
// types_generate.go is meant to run with go generate. It will use
// go/{importer,types} to track down all the RR struct types. Then for each type
// it will generate conversion tables (TypeToRR and TypeToString) and banal
// methods (len, Header, copy) based on the struct tags. The generated source is
// written to ztypes.go, and is meant to be checked into git.
package main
import (
"bytes"
"fmt"
"go/format"
"go/types"
"log"
"os"
"golang.org/x/tools/go/packages"
)
var packageHdr = `
// Code generated by "go run duplicate_generate.go"; DO NOT EDIT.
package dns
`
func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
st, ok := t.Underlying().(*types.Struct)
if !ok {
return nil, false
}
if st.NumFields() == 0 {
return nil, false
}
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
return st, false
}
if st.Field(0).Anonymous() {
st, _ := getTypeStruct(st.Field(0).Type(), scope)
return st, true
}
return nil, false
}
// loadModule retrieves package description for a given module.
func loadModule(name string) (*types.Package, error) {
conf := packages.Config{Mode: packages.NeedTypes | packages.NeedTypesInfo}
pkgs, err := packages.Load(&conf, name)
if err != nil {
return nil, err
}
return pkgs[0].Types, nil
}
func main() {
// Import and type-check the package
pkg, err := loadModule("github.com/miekg/dns")
fatalIfErr(err)
scope := pkg.Scope()
// Collect actual types (*X)
var namedTypes []string
for _, name := range scope.Names() {
o := scope.Lookup(name)
if o == nil || !o.Exported() {
continue
}
if st, _ := getTypeStruct(o.Type(), scope); st == nil {
continue
}
if name == "PrivateRR" || name == "OPT" {
continue
}
namedTypes = append(namedTypes, o.Name())
}
b := &bytes.Buffer{}
b.WriteString(packageHdr)
// Generate the duplicate check for each type.
fmt.Fprint(b, "// isDuplicate() functions\n\n")
for _, name := range namedTypes {
o := scope.Lookup(name)
st, _ := getTypeStruct(o.Type(), scope)
fmt.Fprintf(b, "func (r1 *%s) isDuplicate(_r2 RR) bool {\n", name)
fmt.Fprintf(b, "r2, ok := _r2.(*%s)\n", name)
fmt.Fprint(b, "if !ok { return false }\n")
fmt.Fprint(b, "_ = r2\n")
for i := 1; i < st.NumFields(); i++ {
field := st.Field(i).Name()
o2 := func(s string) { fmt.Fprintf(b, s+"\n", field, field) }
o3 := func(s string) { fmt.Fprintf(b, s+"\n", field, field, field) }
// For some reason, a and aaaa don't pop up as *types.Slice here (mostly like because the are
// *indirectly* defined as a slice in the net package).
if _, ok := st.Field(i).Type().(*types.Slice); ok {
o2("if len(r1.%s) != len(r2.%s) {\nreturn false\n}")
if st.Tag(i) == `dns:"cdomain-name"` || st.Tag(i) == `dns:"domain-name"` {
o3(`for i := 0; i < len(r1.%s); i++ {
if !isDuplicateName(r1.%s[i], r2.%s[i]) {
return false
}
}`)
continue
}
if st.Tag(i) == `dns:"apl"` {
o3(`for i := 0; i < len(r1.%s); i++ {
if !r1.%s[i].equals(&r2.%s[i]) {
return false
}
}`)
continue
}
if st.Tag(i) == `dns:"pairs"` {
o2(`if !areSVCBPairArraysEqual(r1.%s, r2.%s) {
return false
}`)
continue
}
o3(`for i := 0; i < len(r1.%s); i++ {
if r1.%s[i] != r2.%s[i] {
return false
}
}`)
continue
}
switch st.Tag(i) {
case `dns:"-"`:
// ignored
case `dns:"a"`, `dns:"aaaa"`:
o2("if !r1.%s.Equal(r2.%s) {\nreturn false\n}")
case `dns:"cdomain-name"`, `dns:"domain-name"`:
o2("if !isDuplicateName(r1.%s, r2.%s) {\nreturn false\n}")
default:
o2("if r1.%s != r2.%s {\nreturn false\n}")
}
}
fmt.Fprintf(b, "return true\n}\n\n")
}
// gofmt
res, err := format.Source(b.Bytes())
if err != nil {
b.WriteTo(os.Stderr)
log.Fatal(err)
}
// write result
f, err := os.Create("zduplicate.go")
fatalIfErr(err)
defer f.Close()
f.Write(res)
}
func fatalIfErr(err error) {
if err != nil {
log.Fatal(err)
}
}

117
duplicate_test.go Normal file
View File

@ -0,0 +1,117 @@
package dns
import "testing"
func TestDuplicateA(t *testing.T) {
a1, _ := NewRR("www.example.org. 2700 IN A 127.0.0.1")
a2, _ := NewRR("www.example.org. IN A 127.0.0.1")
if !IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
}
a2, _ = NewRR("www.example.org. IN A 127.0.0.2")
if IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
}
}
func TestDuplicateTXT(t *testing.T) {
a1, _ := NewRR("www.example.org. IN TXT \"aa\"")
a2, _ := NewRR("www.example.org. IN TXT \"aa\"")
if !IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
}
a2, _ = NewRR("www.example.org. IN TXT \"aa\" \"bb\"")
if IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
}
a1, _ = NewRR("www.example.org. IN TXT \"aa\" \"bc\"")
if IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
}
}
func TestDuplicateSVCB(t *testing.T) {
a1, _ := NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1::3:3:3:3 key65300=\254\032\030\000\ \043,\;`)
a2, _ := NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1:0::3:3:3:3 key65300="\254\ \030\000 +\,;"`)
if !IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
}
a2, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1::3:3:3:3 key65300="\255\ \030\000 +\,;"`)
if IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
}
a1, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1::3:3:3:3`)
if IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
}
a2, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv4hint=1.1.1.1`)
if IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
}
a1, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv4hint=1.1.1.1,1.0.2.1`)
if IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
}
}
func TestDuplicateOwner(t *testing.T) {
a1, _ := NewRR("www.example.org. IN A 127.0.0.1")
a2, _ := NewRR("www.example.org. IN A 127.0.0.1")
if !IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
}
a2, _ = NewRR("WWw.exaMPle.org. IN A 127.0.0.2")
if IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
}
}
func TestDuplicateDomain(t *testing.T) {
a1, _ := NewRR("www.example.org. IN CNAME example.org.")
a2, _ := NewRR("www.example.org. IN CNAME example.org.")
if !IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
}
a2, _ = NewRR("www.example.org. IN CNAME exAMPLe.oRG.")
if !IsDuplicate(a1, a2) {
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
}
}
func TestDuplicateWrongRrtype(t *testing.T) {
// Test that IsDuplicate won't panic for a record that's lying about
// it's Rrtype.
r1 := &A{Hdr: RR_Header{Rrtype: TypeA}}
r2 := &AAAA{Hdr: RR_Header{Rrtype: TypeA}}
if IsDuplicate(r1, r2) {
t.Errorf("expected %s/%s not to be duplicates, but got true", r1.String(), r2.String())
}
r3 := &AAAA{Hdr: RR_Header{Rrtype: TypeA}}
r4 := &A{Hdr: RR_Header{Rrtype: TypeA}}
if IsDuplicate(r3, r4) {
t.Errorf("expected %s/%s not to be duplicates, but got true", r3.String(), r4.String())
}
r5 := &AAAA{Hdr: RR_Header{Rrtype: TypeA}}
r6 := &AAAA{Hdr: RR_Header{Rrtype: TypeA}}
if !IsDuplicate(r5, r6) {
t.Errorf("expected %s/%s to be duplicates, but got false", r5.String(), r6.String())
}
}

385
edns.go
View File

@ -14,6 +14,7 @@ const (
EDNS0LLQ = 0x1 // long lived queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01
EDNS0UL = 0x2 // update lease draft: http://files.dns-sd.org/draft-sekar-dns-ul.txt
EDNS0NSID = 0x3 // nsid (See RFC 5001)
EDNS0ESU = 0x4 // ENUM Source-URI draft: https://datatracker.ietf.org/doc/html/draft-kaplan-enum-source-uri-00
EDNS0DAU = 0x5 // DNSSEC Algorithm Understood
EDNS0DHU = 0x6 // DS Hash Understood
EDNS0N3U = 0x7 // NSEC3 Hash Understood
@ -22,11 +23,49 @@ const (
EDNS0COOKIE = 0xa // EDNS0 Cookie
EDNS0TCPKEEPALIVE = 0xb // EDNS0 tcp keep alive (See RFC 7828)
EDNS0PADDING = 0xc // EDNS0 padding (See RFC 7830)
EDNS0EDE = 0xf // EDNS0 extended DNS errors (See RFC 8914)
EDNS0LOCALSTART = 0xFDE9 // Beginning of range reserved for local/experimental use (See RFC 6891)
EDNS0LOCALEND = 0xFFFE // End of range reserved for local/experimental use (See RFC 6891)
_DO = 1 << 15 // DNSSEC OK
)
// makeDataOpt is used to unpack the EDNS0 option(s) from a message.
func makeDataOpt(code uint16) EDNS0 {
// All the EDNS0.* constants above need to be in this switch.
switch code {
case EDNS0LLQ:
return new(EDNS0_LLQ)
case EDNS0UL:
return new(EDNS0_UL)
case EDNS0NSID:
return new(EDNS0_NSID)
case EDNS0DAU:
return new(EDNS0_DAU)
case EDNS0DHU:
return new(EDNS0_DHU)
case EDNS0N3U:
return new(EDNS0_N3U)
case EDNS0SUBNET:
return new(EDNS0_SUBNET)
case EDNS0EXPIRE:
return new(EDNS0_EXPIRE)
case EDNS0COOKIE:
return new(EDNS0_COOKIE)
case EDNS0TCPKEEPALIVE:
return new(EDNS0_TCP_KEEPALIVE)
case EDNS0PADDING:
return new(EDNS0_PADDING)
case EDNS0EDE:
return new(EDNS0_EDE)
case EDNS0ESU:
return &EDNS0_ESU{Code: EDNS0ESU}
default:
e := new(EDNS0_LOCAL)
e.Code = code
return e
}
}
// OPT is the EDNS0 RR appended to messages to convey extra (meta) information.
// See RFC 6891.
type OPT struct {
@ -39,7 +78,10 @@ func (rr *OPT) String() string {
if rr.Do() {
s += "flags: do; "
} else {
s += "flags: ; "
s += "flags:; "
}
if rr.Hdr.Ttl&0x7FFF != 0 {
s += fmt.Sprintf("MBZ: 0x%04x, ", rr.Hdr.Ttl&0x7FFF)
}
s += "udp: " + strconv.Itoa(int(rr.UDPSize()))
@ -59,6 +101,10 @@ func (rr *OPT) String() string {
s += "\n; SUBNET: " + o.String()
case *EDNS0_COOKIE:
s += "\n; COOKIE: " + o.String()
case *EDNS0_EXPIRE:
s += "\n; EXPIRE: " + o.String()
case *EDNS0_TCP_KEEPALIVE:
s += "\n; KEEPALIVE: " + o.String()
case *EDNS0_UL:
s += "\n; UPDATE LEASE: " + o.String()
case *EDNS0_LLQ:
@ -73,41 +119,53 @@ func (rr *OPT) String() string {
s += "\n; LOCAL OPT: " + o.String()
case *EDNS0_PADDING:
s += "\n; PADDING: " + o.String()
case *EDNS0_EDE:
s += "\n; EDE: " + o.String()
case *EDNS0_ESU:
s += "\n; ESU: " + o.String()
}
}
return s
}
func (rr *OPT) len() int {
l := rr.Hdr.len()
for i := 0; i < len(rr.Option); i++ {
func (rr *OPT) len(off int, compression map[string]struct{}) int {
l := rr.Hdr.len(off, compression)
for _, o := range rr.Option {
l += 4 // Account for 2-byte option code and 2-byte option length.
lo, _ := rr.Option[i].pack()
lo, _ := o.pack()
l += len(lo)
}
return l
}
func (*OPT) parse(c *zlexer, origin string) *ParseError {
return &ParseError{err: "OPT records do not have a presentation format"}
}
func (rr *OPT) isDuplicate(r2 RR) bool { return false }
// return the old value -> delete SetVersion?
// Version returns the EDNS version used. Only zero is defined.
func (rr *OPT) Version() uint8 {
return uint8((rr.Hdr.Ttl & 0x00FF0000) >> 16)
return uint8(rr.Hdr.Ttl & 0x00FF0000 >> 16)
}
// SetVersion sets the version of EDNS. This is usually zero.
func (rr *OPT) SetVersion(v uint8) {
rr.Hdr.Ttl = rr.Hdr.Ttl&0xFF00FFFF | (uint32(v) << 16)
rr.Hdr.Ttl = rr.Hdr.Ttl&0xFF00FFFF | uint32(v)<<16
}
// ExtendedRcode returns the EDNS extended RCODE field (the upper 8 bits of the TTL).
func (rr *OPT) ExtendedRcode() int {
return int((rr.Hdr.Ttl & 0xFF000000) >> 24)
return int(rr.Hdr.Ttl&0xFF000000>>24) << 4
}
// SetExtendedRcode sets the EDNS extended RCODE field.
func (rr *OPT) SetExtendedRcode(v uint8) {
rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | (uint32(v) << 24)
//
// If the RCODE is not an extended RCODE, will reset the extended RCODE field to 0.
func (rr *OPT) SetExtendedRcode(v uint16) {
rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | uint32(v>>4)<<24
}
// UDPSize returns the UDP buffer size.
@ -140,6 +198,16 @@ func (rr *OPT) SetDo(do ...bool) {
}
}
// Z returns the Z part of the OPT RR as a uint16 with only the 15 least significant bits used.
func (rr *OPT) Z() uint16 {
return uint16(rr.Hdr.Ttl & 0x7FFF)
}
// SetZ sets the Z part of the OPT RR, note only the 15 least significant bits of z are used.
func (rr *OPT) SetZ(z uint16) {
rr.Hdr.Ttl = rr.Hdr.Ttl&^0x7FFF | uint32(z&0x7FFF)
}
// EDNS0 defines an EDNS0 Option. An OPT RR can have multiple options appended to it.
type EDNS0 interface {
// Option returns the option code for the option.
@ -151,6 +219,8 @@ type EDNS0 interface {
unpack([]byte) error
// String returns the string representation of the option.
String() string
// copy returns a deep-copy of the option.
copy() EDNS0
}
// EDNS0_NSID option is used to retrieve a nameserver
@ -181,7 +251,8 @@ func (e *EDNS0_NSID) pack() ([]byte, error) {
// Option implements the EDNS0 interface.
func (e *EDNS0_NSID) Option() uint16 { return EDNS0NSID } // Option returns the option code.
func (e *EDNS0_NSID) unpack(b []byte) error { e.Nsid = hex.EncodeToString(b); return nil }
func (e *EDNS0_NSID) String() string { return string(e.Nsid) }
func (e *EDNS0_NSID) String() string { return e.Nsid }
func (e *EDNS0_NSID) copy() EDNS0 { return &EDNS0_NSID{e.Code, e.Nsid} }
// EDNS0_SUBNET is the subnet option that is used to give the remote nameserver
// an idea of where the client lives. See RFC 7871. It can then give back a different
@ -192,7 +263,7 @@ func (e *EDNS0_NSID) String() string { return string(e.Nsid) }
// o.Hdr.Name = "."
// o.Hdr.Rrtype = dns.TypeOPT
// e := new(dns.EDNS0_SUBNET)
// e.Code = dns.EDNS0SUBNET
// e.Code = dns.EDNS0SUBNET // by default this is filled in through unpacking OPT packets (unpackDataOpt)
// e.Family = 1 // 1 for IPv4 source address, 2 for IPv6
// e.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6
// e.SourceScope = 0
@ -271,22 +342,16 @@ func (e *EDNS0_SUBNET) unpack(b []byte) error {
if e.SourceNetmask > net.IPv4len*8 || e.SourceScope > net.IPv4len*8 {
return errors.New("dns: bad netmask")
}
addr := make([]byte, net.IPv4len)
for i := 0; i < net.IPv4len && 4+i < len(b); i++ {
addr[i] = b[4+i]
}
e.Address = net.IPv4(addr[0], addr[1], addr[2], addr[3])
addr := make(net.IP, net.IPv4len)
copy(addr, b[4:])
e.Address = addr.To16()
case 2:
if e.SourceNetmask > net.IPv6len*8 || e.SourceScope > net.IPv6len*8 {
return errors.New("dns: bad netmask")
}
addr := make([]byte, net.IPv6len)
for i := 0; i < net.IPv6len && 4+i < len(b); i++ {
addr[i] = b[4+i]
}
e.Address = net.IP{addr[0], addr[1], addr[2], addr[3], addr[4],
addr[5], addr[6], addr[7], addr[8], addr[9], addr[10],
addr[11], addr[12], addr[13], addr[14], addr[15]}
addr := make(net.IP, net.IPv6len)
copy(addr, b[4:])
e.Address = addr
default:
return errors.New("dns: bad address family")
}
@ -305,6 +370,16 @@ func (e *EDNS0_SUBNET) String() (s string) {
return
}
func (e *EDNS0_SUBNET) copy() EDNS0 {
return &EDNS0_SUBNET{
e.Code,
e.Family,
e.SourceNetmask,
e.SourceScope,
e.Address,
}
}
// The EDNS0_COOKIE option is used to add a DNS Cookie to a message.
//
// o := new(dns.OPT)
@ -340,11 +415,12 @@ func (e *EDNS0_COOKIE) pack() ([]byte, error) {
func (e *EDNS0_COOKIE) Option() uint16 { return EDNS0COOKIE }
func (e *EDNS0_COOKIE) unpack(b []byte) error { e.Cookie = hex.EncodeToString(b); return nil }
func (e *EDNS0_COOKIE) String() string { return e.Cookie }
func (e *EDNS0_COOKIE) copy() EDNS0 { return &EDNS0_COOKIE{e.Code, e.Cookie} }
// The EDNS0_UL (Update Lease) (draft RFC) option is used to tell the server to set
// an expiration on an update RR. This is helpful for clients that cannot clean
// up after themselves. This is a draft RFC and more information can be found at
// http://files.dns-sd.org/draft-sekar-dns-ul.txt
// https://tools.ietf.org/html/draft-sekar-dns-ul-02
//
// o := new(dns.OPT)
// o.Hdr.Name = "."
@ -354,23 +430,36 @@ func (e *EDNS0_COOKIE) String() string { return e.Cookie }
// e.Lease = 120 // in seconds
// o.Option = append(o.Option, e)
type EDNS0_UL struct {
Code uint16 // Always EDNS0UL
Lease uint32
Code uint16 // Always EDNS0UL
Lease uint32
KeyLease uint32
}
// Option implements the EDNS0 interface.
func (e *EDNS0_UL) Option() uint16 { return EDNS0UL }
func (e *EDNS0_UL) String() string { return strconv.FormatUint(uint64(e.Lease), 10) }
func (e *EDNS0_UL) String() string { return fmt.Sprintf("%d %d", e.Lease, e.KeyLease) }
func (e *EDNS0_UL) copy() EDNS0 { return &EDNS0_UL{e.Code, e.Lease, e.KeyLease} }
// Copied: http://golang.org/src/pkg/net/dnsmsg.go
func (e *EDNS0_UL) pack() ([]byte, error) {
b := make([]byte, 4)
var b []byte
if e.KeyLease == 0 {
b = make([]byte, 4)
} else {
b = make([]byte, 8)
binary.BigEndian.PutUint32(b[4:], e.KeyLease)
}
binary.BigEndian.PutUint32(b, e.Lease)
return b, nil
}
func (e *EDNS0_UL) unpack(b []byte) error {
if len(b) < 4 {
switch len(b) {
case 4:
e.KeyLease = 0
case 8:
e.KeyLease = binary.BigEndian.Uint32(b[4:])
default:
return ErrBuf
}
e.Lease = binary.BigEndian.Uint32(b)
@ -415,12 +504,15 @@ func (e *EDNS0_LLQ) unpack(b []byte) error {
func (e *EDNS0_LLQ) String() string {
s := strconv.FormatUint(uint64(e.Version), 10) + " " + strconv.FormatUint(uint64(e.Opcode), 10) +
" " + strconv.FormatUint(uint64(e.Error), 10) + " " + strconv.FormatUint(uint64(e.Id), 10) +
" " + strconv.FormatUint(uint64(e.Error), 10) + " " + strconv.FormatUint(e.Id, 10) +
" " + strconv.FormatUint(uint64(e.LeaseLife), 10)
return s
}
func (e *EDNS0_LLQ) copy() EDNS0 {
return &EDNS0_LLQ{e.Code, e.Version, e.Opcode, e.Error, e.Id, e.LeaseLife}
}
// EDNS0_DUA implements the EDNS0 "DNSSEC Algorithm Understood" option. See RFC 6975.
// EDNS0_DAU implements the EDNS0 "DNSSEC Algorithm Understood" option. See RFC 6975.
type EDNS0_DAU struct {
Code uint16 // Always EDNS0DAU
AlgCode []uint8
@ -433,15 +525,16 @@ func (e *EDNS0_DAU) unpack(b []byte) error { e.AlgCode = b; return nil }
func (e *EDNS0_DAU) String() string {
s := ""
for i := 0; i < len(e.AlgCode); i++ {
if a, ok := AlgorithmToString[e.AlgCode[i]]; ok {
for _, alg := range e.AlgCode {
if a, ok := AlgorithmToString[alg]; ok {
s += " " + a
} else {
s += " " + strconv.Itoa(int(e.AlgCode[i]))
s += " " + strconv.Itoa(int(alg))
}
}
return s
}
func (e *EDNS0_DAU) copy() EDNS0 { return &EDNS0_DAU{e.Code, e.AlgCode} }
// EDNS0_DHU implements the EDNS0 "DS Hash Understood" option. See RFC 6975.
type EDNS0_DHU struct {
@ -456,15 +549,16 @@ func (e *EDNS0_DHU) unpack(b []byte) error { e.AlgCode = b; return nil }
func (e *EDNS0_DHU) String() string {
s := ""
for i := 0; i < len(e.AlgCode); i++ {
if a, ok := HashToString[e.AlgCode[i]]; ok {
for _, alg := range e.AlgCode {
if a, ok := HashToString[alg]; ok {
s += " " + a
} else {
s += " " + strconv.Itoa(int(e.AlgCode[i]))
s += " " + strconv.Itoa(int(alg))
}
}
return s
}
func (e *EDNS0_DHU) copy() EDNS0 { return &EDNS0_DHU{e.Code, e.AlgCode} }
// EDNS0_N3U implements the EDNS0 "NSEC3 Hash Understood" option. See RFC 6975.
type EDNS0_N3U struct {
@ -480,43 +574,58 @@ func (e *EDNS0_N3U) unpack(b []byte) error { e.AlgCode = b; return nil }
func (e *EDNS0_N3U) String() string {
// Re-use the hash map
s := ""
for i := 0; i < len(e.AlgCode); i++ {
if a, ok := HashToString[e.AlgCode[i]]; ok {
for _, alg := range e.AlgCode {
if a, ok := HashToString[alg]; ok {
s += " " + a
} else {
s += " " + strconv.Itoa(int(e.AlgCode[i]))
s += " " + strconv.Itoa(int(alg))
}
}
return s
}
func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} }
// EDNS0_EXPIRE implementes the EDNS0 option as described in RFC 7314.
// EDNS0_EXPIRE implements the EDNS0 option as described in RFC 7314.
type EDNS0_EXPIRE struct {
Code uint16 // Always EDNS0EXPIRE
Expire uint32
Empty bool // Empty is used to signal an empty Expire option in a backwards compatible way, it's not used on the wire.
}
// Option implements the EDNS0 interface.
func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE }
func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) }
func (e *EDNS0_EXPIRE) copy() EDNS0 { return &EDNS0_EXPIRE{e.Code, e.Expire, e.Empty} }
func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
if e.Empty {
return []byte{}, nil
}
b := make([]byte, 4)
b[0] = byte(e.Expire >> 24)
b[1] = byte(e.Expire >> 16)
b[2] = byte(e.Expire >> 8)
b[3] = byte(e.Expire)
binary.BigEndian.PutUint32(b, e.Expire)
return b, nil
}
func (e *EDNS0_EXPIRE) unpack(b []byte) error {
if len(b) == 0 {
// zero-length EXPIRE query, see RFC 7314 Section 2
e.Empty = true
return nil
}
if len(b) < 4 {
return ErrBuf
}
e.Expire = binary.BigEndian.Uint32(b)
e.Empty = false
return nil
}
func (e *EDNS0_EXPIRE) String() (s string) {
if e.Empty {
return ""
}
return strconv.FormatUint(uint64(e.Expire), 10)
}
// The EDNS0_LOCAL option is used for local/experimental purposes. The option
// code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND]
// (RFC6891), although any unassigned code can actually be used. The content of
@ -540,6 +649,11 @@ func (e *EDNS0_LOCAL) Option() uint16 { return e.Code }
func (e *EDNS0_LOCAL) String() string {
return strconv.FormatInt(int64(e.Code), 10) + ":0x" + hex.EncodeToString(e.Data)
}
func (e *EDNS0_LOCAL) copy() EDNS0 {
b := make([]byte, len(e.Data))
copy(b, e.Data)
return &EDNS0_LOCAL{e.Code, b}
}
func (e *EDNS0_LOCAL) pack() ([]byte, error) {
b := make([]byte, len(e.Data))
@ -562,57 +676,53 @@ func (e *EDNS0_LOCAL) unpack(b []byte) error {
// EDNS0_TCP_KEEPALIVE is an EDNS0 option that instructs the server to keep
// the TCP connection alive. See RFC 7828.
type EDNS0_TCP_KEEPALIVE struct {
Code uint16 // Always EDNSTCPKEEPALIVE
Length uint16 // the value 0 if the TIMEOUT is omitted, the value 2 if it is present;
Timeout uint16 // an idle timeout value for the TCP connection, specified in units of 100 milliseconds, encoded in network byte order.
Code uint16 // Always EDNSTCPKEEPALIVE
// Timeout is an idle timeout value for the TCP connection, specified in
// units of 100 milliseconds, encoded in network byte order. If set to 0,
// pack will return a nil slice.
Timeout uint16
// Length is the option's length.
// Deprecated: this field is deprecated and is always equal to 0.
Length uint16
}
// Option implements the EDNS0 interface.
func (e *EDNS0_TCP_KEEPALIVE) Option() uint16 { return EDNS0TCPKEEPALIVE }
func (e *EDNS0_TCP_KEEPALIVE) pack() ([]byte, error) {
if e.Timeout != 0 && e.Length != 2 {
return nil, errors.New("dns: timeout specified but length is not 2")
if e.Timeout > 0 {
b := make([]byte, 2)
binary.BigEndian.PutUint16(b, e.Timeout)
return b, nil
}
if e.Timeout == 0 && e.Length != 0 {
return nil, errors.New("dns: timeout not specified but length is not 0")
}
b := make([]byte, 4+e.Length)
binary.BigEndian.PutUint16(b[0:], e.Code)
binary.BigEndian.PutUint16(b[2:], e.Length)
if e.Length == 2 {
binary.BigEndian.PutUint16(b[4:], e.Timeout)
}
return b, nil
return nil, nil
}
func (e *EDNS0_TCP_KEEPALIVE) unpack(b []byte) error {
if len(b) < 4 {
return ErrBuf
}
e.Length = binary.BigEndian.Uint16(b[2:4])
if e.Length != 0 && e.Length != 2 {
return errors.New("dns: length mismatch, want 0/2 but got " + strconv.FormatUint(uint64(e.Length), 10))
}
if e.Length == 2 {
if len(b) < 6 {
return ErrBuf
}
e.Timeout = binary.BigEndian.Uint16(b[4:6])
switch len(b) {
case 0:
case 2:
e.Timeout = binary.BigEndian.Uint16(b)
default:
return fmt.Errorf("dns: length mismatch, want 0/2 but got %d", len(b))
}
return nil
}
func (e *EDNS0_TCP_KEEPALIVE) String() (s string) {
s = "use tcp keep-alive"
if e.Length == 0 {
func (e *EDNS0_TCP_KEEPALIVE) String() string {
s := "use tcp keep-alive"
if e.Timeout == 0 {
s += ", timeout omitted"
} else {
s += fmt.Sprintf(", timeout %dms", e.Timeout*100)
}
return
return s
}
func (e *EDNS0_TCP_KEEPALIVE) copy() EDNS0 { return &EDNS0_TCP_KEEPALIVE{e.Code, e.Timeout, e.Length} }
// EDNS0_PADDING option is used to add padding to a request/response. The default
// value of padding SHOULD be 0x0 but other values MAY be used, for instance if
// compression is applied before encryption which may break signatures.
@ -625,3 +735,122 @@ func (e *EDNS0_PADDING) Option() uint16 { return EDNS0PADDING }
func (e *EDNS0_PADDING) pack() ([]byte, error) { return e.Padding, nil }
func (e *EDNS0_PADDING) unpack(b []byte) error { e.Padding = b; return nil }
func (e *EDNS0_PADDING) String() string { return fmt.Sprintf("%0X", e.Padding) }
func (e *EDNS0_PADDING) copy() EDNS0 {
b := make([]byte, len(e.Padding))
copy(b, e.Padding)
return &EDNS0_PADDING{b}
}
// Extended DNS Error Codes (RFC 8914).
const (
ExtendedErrorCodeOther uint16 = iota
ExtendedErrorCodeUnsupportedDNSKEYAlgorithm
ExtendedErrorCodeUnsupportedDSDigestType
ExtendedErrorCodeStaleAnswer
ExtendedErrorCodeForgedAnswer
ExtendedErrorCodeDNSSECIndeterminate
ExtendedErrorCodeDNSBogus
ExtendedErrorCodeSignatureExpired
ExtendedErrorCodeSignatureNotYetValid
ExtendedErrorCodeDNSKEYMissing
ExtendedErrorCodeRRSIGsMissing
ExtendedErrorCodeNoZoneKeyBitSet
ExtendedErrorCodeNSECMissing
ExtendedErrorCodeCachedError
ExtendedErrorCodeNotReady
ExtendedErrorCodeBlocked
ExtendedErrorCodeCensored
ExtendedErrorCodeFiltered
ExtendedErrorCodeProhibited
ExtendedErrorCodeStaleNXDOMAINAnswer
ExtendedErrorCodeNotAuthoritative
ExtendedErrorCodeNotSupported
ExtendedErrorCodeNoReachableAuthority
ExtendedErrorCodeNetworkError
ExtendedErrorCodeInvalidData
)
// ExtendedErrorCodeToString maps extended error info codes to a human readable
// description.
var ExtendedErrorCodeToString = map[uint16]string{
ExtendedErrorCodeOther: "Other",
ExtendedErrorCodeUnsupportedDNSKEYAlgorithm: "Unsupported DNSKEY Algorithm",
ExtendedErrorCodeUnsupportedDSDigestType: "Unsupported DS Digest Type",
ExtendedErrorCodeStaleAnswer: "Stale Answer",
ExtendedErrorCodeForgedAnswer: "Forged Answer",
ExtendedErrorCodeDNSSECIndeterminate: "DNSSEC Indeterminate",
ExtendedErrorCodeDNSBogus: "DNSSEC Bogus",
ExtendedErrorCodeSignatureExpired: "Signature Expired",
ExtendedErrorCodeSignatureNotYetValid: "Signature Not Yet Valid",
ExtendedErrorCodeDNSKEYMissing: "DNSKEY Missing",
ExtendedErrorCodeRRSIGsMissing: "RRSIGs Missing",
ExtendedErrorCodeNoZoneKeyBitSet: "No Zone Key Bit Set",
ExtendedErrorCodeNSECMissing: "NSEC Missing",
ExtendedErrorCodeCachedError: "Cached Error",
ExtendedErrorCodeNotReady: "Not Ready",
ExtendedErrorCodeBlocked: "Blocked",
ExtendedErrorCodeCensored: "Censored",
ExtendedErrorCodeFiltered: "Filtered",
ExtendedErrorCodeProhibited: "Prohibited",
ExtendedErrorCodeStaleNXDOMAINAnswer: "Stale NXDOMAIN Answer",
ExtendedErrorCodeNotAuthoritative: "Not Authoritative",
ExtendedErrorCodeNotSupported: "Not Supported",
ExtendedErrorCodeNoReachableAuthority: "No Reachable Authority",
ExtendedErrorCodeNetworkError: "Network Error",
ExtendedErrorCodeInvalidData: "Invalid Data",
}
// StringToExtendedErrorCode is a map from human readable descriptions to
// extended error info codes.
var StringToExtendedErrorCode = reverseInt16(ExtendedErrorCodeToString)
// EDNS0_EDE option is used to return additional information about the cause of
// DNS errors.
type EDNS0_EDE struct {
InfoCode uint16
ExtraText string
}
// Option implements the EDNS0 interface.
func (e *EDNS0_EDE) Option() uint16 { return EDNS0EDE }
func (e *EDNS0_EDE) copy() EDNS0 { return &EDNS0_EDE{e.InfoCode, e.ExtraText} }
func (e *EDNS0_EDE) String() string {
info := strconv.FormatUint(uint64(e.InfoCode), 10)
if s, ok := ExtendedErrorCodeToString[e.InfoCode]; ok {
info += fmt.Sprintf(" (%s)", s)
}
return fmt.Sprintf("%s: (%s)", info, e.ExtraText)
}
func (e *EDNS0_EDE) pack() ([]byte, error) {
b := make([]byte, 2+len(e.ExtraText))
binary.BigEndian.PutUint16(b[0:], e.InfoCode)
copy(b[2:], []byte(e.ExtraText))
return b, nil
}
func (e *EDNS0_EDE) unpack(b []byte) error {
if len(b) < 2 {
return ErrBuf
}
e.InfoCode = binary.BigEndian.Uint16(b[0:])
e.ExtraText = string(b[2:])
return nil
}
// The EDNS0_ESU option for ENUM Source-URI Extension
type EDNS0_ESU struct {
Code uint16
Uri string
}
// Option implements the EDNS0 interface.
func (e *EDNS0_ESU) Option() uint16 { return EDNS0ESU }
func (e *EDNS0_ESU) String() string { return e.Uri }
func (e *EDNS0_ESU) copy() EDNS0 { return &EDNS0_ESU{e.Code, e.Uri} }
func (e *EDNS0_ESU) pack() ([]byte, error) { return []byte(e.Uri), nil }
func (e *EDNS0_ESU) unpack(b []byte) error {
e.Uri = string(b)
return nil
}

View File

@ -1,6 +1,10 @@
package dns
import "testing"
import (
"bytes"
"net"
"testing"
)
func TestOPTTtl(t *testing.T) {
e := &OPT{}
@ -62,7 +66,214 @@ func TestOPTTtl(t *testing.T) {
}
e.SetExtendedRcode(42)
if e.ExtendedRcode() != 42 {
t.Errorf("set 42, expected %d, got %d", 42, e.ExtendedRcode())
// ExtendedRcode has the last 4 bits set to 0.
if e.ExtendedRcode() != 42&0xFFFFFFF0 {
t.Errorf("set 42, expected %d, got %d", 42&0xFFFFFFF0, e.ExtendedRcode())
}
// This will reset the 8 upper bits of the extended rcode
e.SetExtendedRcode(RcodeNotAuth)
if e.ExtendedRcode() != 0 {
t.Errorf("Setting a non-extended rcode is expected to set extended rcode to 0, got: %d", e.ExtendedRcode())
}
}
func TestEDNS0_SUBNETUnpack(t *testing.T) {
for _, ip := range []net.IP{
net.IPv4(0xde, 0xad, 0xbe, 0xef),
net.ParseIP("192.0.2.1"),
net.ParseIP("2001:db8::68"),
} {
var s1 EDNS0_SUBNET
s1.Address = ip
if ip.To4() == nil {
s1.Family = 2
s1.SourceNetmask = net.IPv6len * 8
} else {
s1.Family = 1
s1.SourceNetmask = net.IPv4len * 8
}
b, err := s1.pack()
if err != nil {
t.Fatalf("failed to pack: %v", err)
}
var s2 EDNS0_SUBNET
if err := s2.unpack(b); err != nil {
t.Fatalf("failed to unpack: %v", err)
}
if !ip.Equal(s2.Address) {
t.Errorf("address different after unpacking; expected %s, got %s", ip, s2.Address)
}
}
}
func TestEDNS0_UL(t *testing.T) {
cases := []struct {
l uint32
kl uint32
}{
{0x01234567, 0},
{0x76543210, 0xFEDCBA98},
}
for _, c := range cases {
expect := EDNS0_UL{EDNS0UL, c.l, c.kl}
b, err := expect.pack()
if err != nil {
t.Fatalf("failed to pack: %v", err)
}
actual := EDNS0_UL{EDNS0UL, ^uint32(0), ^uint32(0)}
if err := actual.unpack(b); err != nil {
t.Fatalf("failed to unpack: %v", err)
}
if expect != actual {
t.Errorf("unpacked option is different; expected %v, got %v", expect, actual)
}
}
}
func TestZ(t *testing.T) {
e := &OPT{}
e.Hdr.Name = "."
e.Hdr.Rrtype = TypeOPT
e.SetVersion(8)
e.SetDo()
if e.Z() != 0 {
t.Errorf("expected Z of 0, got %d", e.Z())
}
e.SetZ(5)
if e.Z() != 5 {
t.Errorf("expected Z of 5, got %d", e.Z())
}
e.SetZ(0xFFFF)
if e.Z() != 0x7FFF {
t.Errorf("expected Z of 0x7FFFF, got %d", e.Z())
}
if e.Version() != 8 {
t.Errorf("expected version to still be 8, got %d", e.Version())
}
if !e.Do() {
t.Error("expected DO to be set")
}
}
func TestEDNS0_ESU(t *testing.T) {
p := []byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x29, 0x04,
0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00,
0x04, 0x00, 0x24, 0x73, 0x69, 0x70, 0x3A, 0x2B,
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x40, 0x74, 0x65, 0x73, 0x74, 0x2E, 0x63,
0x6F, 0x6D, 0x3B, 0x75, 0x73, 0x65, 0x72, 0x3D,
0x63, 0x67, 0x72, 0x61, 0x74, 0x65, 0x73,
}
m := new(Msg)
if err := m.Unpack(p); err != nil {
t.Fatalf("failed to unpack: %v", err)
}
opt := m.IsEdns0()
if opt == nil {
t.Fatalf("expected edns0 option")
}
if len(opt.Option) != 1 {
t.Fatalf("expected only one option: %v", opt.Option)
}
edns0 := opt.Option[0]
esu, ok := edns0.(*EDNS0_ESU)
if !ok {
t.Fatalf("expected option of type EDNS0_ESU, got %t", edns0)
}
expect := "sip:+123456789@test.com;user=cgrates"
if esu.Uri != expect {
t.Errorf("unpacked option is different; expected %v, got %v", expect, esu.Uri)
}
}
func TestEDNS0_TCP_KEEPALIVE_unpack(t *testing.T) {
cases := []struct {
name string
b []byte
expected uint16
expectedErr bool
}{
{
name: "empty",
b: []byte{},
expected: 0,
},
{
name: "timeout 1",
b: []byte{0, 1},
expected: 1,
},
{
name: "invalid",
b: []byte{0, 1, 3},
expectedErr: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
e := &EDNS0_TCP_KEEPALIVE{}
err := e.unpack(tc.b)
if err != nil && !tc.expectedErr {
t.Error("failed to unpack, expected no error")
}
if err == nil && tc.expectedErr {
t.Error("unpacked, but expected an error")
}
if e.Timeout != tc.expected {
t.Errorf("invalid timeout, actual: %d, expected: %d", e.Timeout, tc.expected)
}
})
}
}
func TestEDNS0_TCP_KEEPALIVE_pack(t *testing.T) {
cases := []struct {
name string
edns *EDNS0_TCP_KEEPALIVE
expected []byte
}{
{
name: "empty",
edns: &EDNS0_TCP_KEEPALIVE{
Code: EDNS0TCPKEEPALIVE,
Timeout: 0,
},
expected: nil,
},
{
name: "timeout 1",
edns: &EDNS0_TCP_KEEPALIVE{
Code: EDNS0TCPKEEPALIVE,
Timeout: 1,
},
expected: []byte{0, 1},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
b, err := tc.edns.pack()
if err != nil {
t.Error("expected no error")
}
if tc.expected == nil && b != nil {
t.Errorf("invalid result, expected nil")
}
res := bytes.Compare(b, tc.expected)
if res != 0 {
t.Errorf("invalid result, expected: %v, actual: %v", tc.expected, b)
}
})
}
}

View File

@ -16,7 +16,7 @@ func ExampleMX() {
m := new(dns.Msg)
m.SetQuestion("miek.nl.", dns.TypeMX)
m.RecursionDesired = true
r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port)
r, _, err := c.Exchange(m, net.JoinHostPort(config.Servers[0], config.Port))
if err != nil {
return
}
@ -39,7 +39,7 @@ func ExampleDS() {
zone := "miek.nl"
m.SetQuestion(dns.Fqdn(zone), dns.TypeDNSKEY)
m.SetEdns0(4096, true)
r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port)
r, _, err := c.Exchange(m, net.JoinHostPort(config.Servers[0], config.Port))
if err != nil {
return
}
@ -64,6 +64,7 @@ type APAIR struct {
func NewAPAIR() dns.PrivateRdata { return new(APAIR) }
func (rd *APAIR) String() string { return rd.addr[0].String() + " " + rd.addr[1].String() }
func (rd *APAIR) Parse(txt []string) error {
if len(txt) != 2 {
return errors.New("two addresses required for APAIR")
@ -121,21 +122,23 @@ func (rd *APAIR) Len() int {
func ExamplePrivateHandle() {
dns.PrivateHandle("APAIR", TypeAPAIR, NewAPAIR)
defer dns.PrivateHandleRemove(TypeAPAIR)
var oldId = dns.Id
dns.Id = func() uint16 { return 3 }
defer func() { dns.Id = oldId }()
rr, err := dns.NewRR("miek.nl. APAIR (1.2.3.4 1.2.3.5)")
if err != nil {
log.Fatal("could not parse APAIR record: ", err)
}
fmt.Println(rr)
// Output: miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5
fmt.Println(rr) // see first line of Output below
m := new(dns.Msg)
m.Id = 12345
m.SetQuestion("miek.nl.", TypeAPAIR)
m.Answer = append(m.Answer, rr)
fmt.Println(m)
// ;; opcode: QUERY, status: NOERROR, id: 12345
// Output: miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5
// ;; opcode: QUERY, status: NOERROR, id: 3
// ;; flags: rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
//
// ;; QUESTION SECTION:

View File

@ -20,7 +20,7 @@ func Field(r RR, i int) string {
return ""
}
d := reflect.ValueOf(r).Elem().Field(i)
switch k := d.Kind(); k {
switch d.Kind() {
case reflect.String:
return d.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@ -31,6 +31,9 @@ func Field(r RR, i int) string {
switch reflect.ValueOf(r).Elem().Type().Field(i).Tag {
case `dns:"a"`:
// TODO(miek): Hmm store this as 16 bytes
if d.Len() < net.IPv4len {
return ""
}
if d.Len() < net.IPv6len {
return net.IPv4(byte(d.Index(0).Uint()),
byte(d.Index(1).Uint()),
@ -42,6 +45,9 @@ func Field(r RR, i int) string {
byte(d.Index(14).Uint()),
byte(d.Index(15).Uint())).String()
case `dns:"aaaa"`:
if d.Len() < net.IPv6len {
return ""
}
return net.IP{
byte(d.Index(0).Uint()),
byte(d.Index(1).Uint()),

16
format_test.go Normal file
View File

@ -0,0 +1,16 @@
package dns
import (
"testing"
)
func TestFieldEmptyAOrAAAAData(t *testing.T) {
res := Field(new(A), 1)
if res != "" {
t.Errorf("expected empty string but got %v", res)
}
res = Field(new(AAAA), 1)
if res != "" {
t.Errorf("expected empty string but got %v", res)
}
}

12
fuzz.go
View File

@ -1,7 +1,10 @@
//go:build fuzz
// +build fuzz
package dns
import "strings"
func Fuzz(data []byte) int {
msg := new(Msg)
@ -16,7 +19,14 @@ func Fuzz(data []byte) int {
}
func FuzzNewRR(data []byte) int {
if _, err := NewRR(string(data)); err != nil {
str := string(data)
// Do not fuzz lines that include the $INCLUDE keyword and hint the fuzzer
// at avoiding them.
// See GH#1025 for context.
if strings.Contains(strings.ToUpper(str), "$INCLUDE") {
return -1
}
if _, err := NewRR(str); err != nil {
return 0
}
return 1

269
fuzz_test.go Normal file
View File

@ -0,0 +1,269 @@
package dns
import (
"net"
"testing"
)
// TestPackDataOpt tests generated using fuzz.go and with a message pack
// containing the following bytes:
// "0000\x00\x00000000\x00\x00/00000" +
// "0\x00\v\x00#\b00000000\x00\x00)000" +
// "000\x00\x1c00\x00\x0000\x00\x01000\x00\x00\x00\b" +
// "\x00\v\x00\x02\x0000000000"
// That bytes sequence created the overflow error.
func TestPackDataOpt(t *testing.T) {
type args struct {
option []EDNS0
msg []byte
off int
}
tests := []struct {
name string
args args
want int
wantErr bool
wantErrMsg string
}{
{
name: "overflow",
args: args{
option: []EDNS0{
&EDNS0_LOCAL{Code: 0x3030, Data: []uint8{}},
&EDNS0_LOCAL{Code: 0x3030, Data: []uint8{0x30}},
&EDNS0_LOCAL{Code: 0x3030, Data: []uint8{}},
&EDNS0_SUBNET{
Code: 0x0, Family: 0x2,
SourceNetmask: 0x0, SourceScope: 0x30,
Address: net.IP{0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
},
msg: []byte{
0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x2,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x0b, 0x00,
0x23, 0x08, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x00, 0x00, 0x29, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00,
0x00, 0x30, 0x30, 0x00, 0x01, 0x30, 0x00, 0x00,
0x00,
},
off: 54,
},
wantErr: true,
wantErrMsg: "dns: overflow packing opt",
want: 57,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := packDataOpt(tt.args.option, tt.args.msg, tt.args.off)
if (err != nil) != tt.wantErr {
t.Errorf("packDataOpt() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil && tt.wantErrMsg != err.Error() {
t.Errorf("packDataOpt() error msg = %v, wantErrMsg %v", err.Error(), tt.wantErrMsg)
return
}
if got != tt.want {
t.Errorf("packDataOpt() = %v, want %v", got, tt.want)
}
})
}
}
// TestCrashNSEC tests generated using fuzz.go and with a message pack
// containing the following bytes:
// "0000\x00\x00000000\x00\x00/00000" +
// "0\x00\v\x00#\b00000\x00\x00\x00\x00\x00\x1a000" +
// "000\x00\x00\x00\x00\x1a000000\x00\x00\x00\x00\x1a0" +
// "00000\x00\v00\a0000000\x00"
// That byte sequence, when Unpack() and subsequent Pack() created a
// panic: runtime error: slice bounds out of range
// which was attributed to the fact that NSEC RR length computation was different (and smaller)
// then when within packDataNsec.
func TestCrashNSEC(t *testing.T) {
compression := make(map[string]struct{})
nsec := &NSEC{
Hdr: RR_Header{
Name: ".",
Rrtype: 0x2f,
Class: 0x3030,
Ttl: 0x30303030,
Rdlength: 0xb,
},
NextDomain: ".",
TypeBitMap: []uint16{
0x2302, 0x2303, 0x230a, 0x230b,
0x2312, 0x2313, 0x231a, 0x231b,
0x2322, 0x2323,
},
}
expectedLength := 19
l := nsec.len(0, compression)
if l != expectedLength {
t.Fatalf("expected length of %d, got %d", expectedLength, l)
}
}
// TestCrashNSEC3 tests generated using fuzz.go and with a message pack
// containing the following bytes:
// "0000\x00\x00000000\x00\x00200000" +
// "0\x00\v0000\x00\x00#\x0300\x00\x00\x00\x1a000" +
// "000\x00\v00\x0200\x00\x03000\x00"
// That byte sequence, when Unpack() and subsequent Pack() created a
// panic: runtime error: slice bounds out of range
// which was attributed to the fact that NSEC3 RR length computation was
// different (and smaller) then within NSEC3.pack (which relies on
// packDataNsec).
func TestCrashNSEC3(t *testing.T) {
compression := make(map[string]struct{})
nsec3 := &NSEC3{
Hdr: RR_Header{
Name: ".",
Rrtype: 0x32,
Class: 0x3030,
Ttl: 0x30303030,
Rdlength: 0xb,
},
Hash: 0x30,
Flags: 0x30,
Iterations: 0x3030,
SaltLength: 0x0,
Salt: "",
HashLength: 0x0,
NextDomain: ".",
TypeBitMap: []uint16{
0x2302, 0x2303, 0x230a, 0x230b,
},
}
expectedLength := 24
l := nsec3.len(0, compression)
if l != expectedLength {
t.Fatalf("expected length of %d, got %d", expectedLength, l)
}
}
// TestNewRRCommentLengthCrasherString test inputs to NewRR that generated crashes.
func TestNewRRCommentLengthCrasherString(t *testing.T) {
tests := []struct {
name string
in string
err string
}{
{
"HINFO1", " HINFO ;;;;;;;;;;;;;" +
";;;;;;;;\x00\x19;;;;;;;;;;" +
";\u007f;;;;;;;;;;;;;;;;;;" +
";;}mP_Qq_3sJ_1_84X_5" +
"45iW_3K4p8J8_v9_LT3_" +
"6_0l_3D4VT3xq6N_3K__" +
"_U_xX2m;;;;;;(;;;;;;" +
";;;;;;;;;;;;;;;\x1d;;;;" +
";;;;;;-0x804dBDe8ba " +
"\t \t\tr HINFO \" \t\t\tve" +
"k1xH11e__P6_dk1_51bo" +
"g8gJK1V_O_v84_Bw4_1_" +
"72jQ3_0J3V_S5iYn4h5X" +
"R_2n___51J nN_ \t\tm " +
"aa_XO4_5\t \t\t \t\tg6b" +
"p_KI_1_YWc_K8c2b___A" +
"e_Y1m__4Y_R_avy6t08x" +
"b5Cp9_7uS_yLa\t\t\t d " +
"EKe1Q83vS___ a \t\t " +
"\tmP_Qq_3sJ_1_84X_545" +
"iW_3K4p8J8_v9_LT3_6_" +
"0l_3D4VT3xq6N_3K___U" +
"_xX2\"\" \t \t_fL Ogl5" +
"_09i_9__3O7C__QMAG2U" +
"35IO8RRU6aJ9_6_57_6_" +
"b05BMoX5I__4833_____" +
"yfD_2_OPs__sqzM_pqQi" +
"_\t\t \tN__GuY4_Trath_0" +
"yy___cAK_a__0J0q5 L_" +
"p63Fzdva_Lb_29V7_R__" +
"Go_H2_8m_4__FJM5B_Y5" +
"Slw_ghp_55l_X2_Pnt6Y" +
"_Wd_hM7jRZ_\t\t \tm \t" +
" \t\ta md rK \x00 7_\"sr " +
"- sg o -0x804dBDe8b" +
"a \t \t\tN_W6J3PBS_W__C" +
"yJu__k6F_jY0INI_LC27" +
"7x14b_1b___Y8f_K_3y_" +
"0055yaP_LKu_72g_T_32" +
"iBk1Zm_o 9i1P44_S0_" +
"_4AXUpo2__H55tL_g78_" +
"8V_8l0yg6bp_KI_1_YWc" +
"_K8c2b \t \tmaa_XO4_5" +
"rg6bp_KI_1_YWc_K8c2b" +
" _C20w i_4 \t\t u_k d" +
" rKsg09099 \"\"2335779" +
"05047986112651e025 \t" +
" \t\tN_W6J3PBS_W__CyJu" +
"__k6F_jY0INI_LC277x1" +
"4b_1b___Y8f_K_3y_005" +
"5yaP_LKu_72g_T_32iBk" +
"1Zm_o 9i1P44_S0__4A" +
"XUpo2__H55tL_g78_8V_" +
"8l0y_9K9_C__6af__wj_" +
"UbSYy_ge29S_s_Qe259q" +
"_kGod \t\t\t\t :0xb1AF1F" +
"b71D2ACeaB3FEce2ssg " +
"o dr-0x804dBDe8ba \t " +
"\t\t$ Y5 _BzOc6S_Lk0K" +
"y43j1TzV__9367tbX56_" +
"6B3__q6_v8_4_0_t_2q_" +
"nJ2gV3j9_tkOrx_H__a}" +
"mT 0g6bp_KI_1_YWc_K8" +
"c2b\t_ a\t \t54KM8f9_63" +
"zJ2Q_c1_C_Zf4ICF4m0q" +
"_RVm_3Zh4vr7yI_H2 a" +
" m 0yq__TiqA_FQBv_SS" +
"_Hm_8T8__M8F2_53TTo_" +
"k_o2__u_W6Vr__524q9l" +
"9CQsC_kOU___g_94 \"" +
" ~a_j_16_6iUSu_96V1W" +
"5r01j____gn157__8_LO" +
"0y_08Jr6OR__WF8__JK_" +
"N_wx_k_CGB_SjJ9R74i_" +
"7_1t_6 m NULLNULLNUL" +
"L \t \t\t\t drK\t\x00 7_\"\" 5" +
"_5_y732S43__D_8U9FX2" +
"27_k\t\tg6bp_KI_1_YWc_" +
"K8c2b_J_wx8yw1CMw27j" +
"___f_a8uw_ Er9gB_L2 " +
"\t\t \t\t\tm aa_XO4_5 Y_" +
" I_T7762_zlMi_n8_FjH" +
"vy62p__M4S_8__r092af" +
"P_T_vhp6__SA_jVF13c5" +
"2__8J48K__S4YcjoY91X" +
"_iNf06 am aa_XO4_5\t" +
" d _ am_SYY4G__2h4QL" +
"iUIDd \t\t \tXXp__KFjR" +
"V__JU3o\"\" d \t_Iks_ " +
"aa_XO4_5<g6bp_KI_1_Y" +
"Wc_K8c2b _BzOc6S_Lk0" +
"Ky43j1TzV__9367tbX56" +
"_6B3__q6_v8_4_0_t_2q" +
"_nJ2gV3j9_tkOrx_H__ " +
"a\t_Iks_ \\ ma 0_58_r1" +
"y8jib_FaV_C_e \t \td\"\"" +
" ^Dy_0 \t\t \t ;;;;;;;" +
";;;;;;;;;;;",
`dns: bad HINFO Fields: "comment length insufficient for parsing" at line: 1:1951`,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, err := NewRR(tc.in)
if err == nil {
t.Errorf("Expecting error for crasher line %s", tc.in)
}
if tc.err != err.Error() {
t.Errorf("Expecting error %s, got %s", tc.err, err.Error())
}
})
}
}

View File

@ -2,8 +2,8 @@ package dns
import (
"bytes"
"errors"
"fmt"
"io"
"strconv"
"strings"
)
@ -18,142 +18,230 @@ import (
// * rhs (rdata)
// But we are lazy here, only the range is parsed *all* occurrences
// of $ after that are interpreted.
// Any error are returned as a string value, the empty string signals
// "no error".
func generate(l lex, c chan lex, t chan *Token, o string) string {
step := 1
if i := strings.IndexAny(l.token, "/"); i != -1 {
if i+1 == len(l.token) {
return "bad step in $GENERATE range"
func (zp *ZoneParser) generate(l lex) (RR, bool) {
token := l.token
step := int64(1)
if i := strings.IndexByte(token, '/'); i >= 0 {
if i+1 == len(token) {
return zp.setParseError("bad step in $GENERATE range", l)
}
if s, err := strconv.Atoi(l.token[i+1:]); err == nil {
if s < 0 {
return "bad step in $GENERATE range"
}
step = s
} else {
return "bad step in $GENERATE range"
s, err := strconv.ParseInt(token[i+1:], 10, 64)
if err != nil || s <= 0 {
return zp.setParseError("bad step in $GENERATE range", l)
}
l.token = l.token[:i]
step = s
token = token[:i]
}
sx := strings.SplitN(l.token, "-", 2)
sx := strings.SplitN(token, "-", 2)
if len(sx) != 2 {
return "bad start-stop in $GENERATE range"
}
start, err := strconv.Atoi(sx[0])
if err != nil {
return "bad start in $GENERATE range"
}
end, err := strconv.Atoi(sx[1])
if err != nil {
return "bad stop in $GENERATE range"
}
if end < 0 || start < 0 || end < start {
return "bad range in $GENERATE range"
return zp.setParseError("bad start-stop in $GENERATE range", l)
}
start, err := strconv.ParseInt(sx[0], 10, 64)
if err != nil {
return zp.setParseError("bad start in $GENERATE range", l)
}
end, err := strconv.ParseInt(sx[1], 10, 64)
if err != nil {
return zp.setParseError("bad stop in $GENERATE range", l)
}
if end < 0 || start < 0 || end < start || (end-start)/step > 65535 {
return zp.setParseError("bad range in $GENERATE range", l)
}
// _BLANK
l, ok := zp.c.Next()
if !ok || l.value != zBlank {
return zp.setParseError("garbage after $GENERATE range", l)
}
<-c // _BLANK
// Create a complete new string, which we then parse again.
s := ""
BuildRR:
l = <-c
if l.value != zNewline && l.value != zEOF {
s += l.token
goto BuildRR
}
for i := start; i <= end; i += step {
var (
escape bool
dom bytes.Buffer
mod string
err error
offset int
)
var s string
for l, ok := zp.c.Next(); ok; l, ok = zp.c.Next() {
if l.err {
return zp.setParseError("bad data in $GENERATE directive", l)
}
if l.value == zNewline {
break
}
for j := 0; j < len(s); j++ { // No 'range' because we need to jump around
switch s[j] {
case '\\':
if escape {
dom.WriteByte('\\')
escape = false
continue
}
escape = true
case '$':
mod = "%d"
offset = 0
if escape {
dom.WriteByte('$')
escape = false
continue
}
escape = false
if j+1 >= len(s) { // End of the string
dom.WriteString(fmt.Sprintf(mod, i+offset))
continue
} else {
if s[j+1] == '$' {
dom.WriteByte('$')
j++
continue
}
}
// Search for { and }
if s[j+1] == '{' { // Modifier block
sep := strings.Index(s[j+2:], "}")
if sep == -1 {
return "bad modifier in $GENERATE"
}
mod, offset, err = modToPrintf(s[j+2 : j+2+sep])
if err != nil {
return err.Error()
}
j += 2 + sep // Jump to it
}
dom.WriteString(fmt.Sprintf(mod, i+offset))
default:
if escape { // Pretty useless here
escape = false
continue
}
dom.WriteByte(s[j])
}
}
// Re-parse the RR and send it on the current channel t
rx, err := NewRR("$ORIGIN " + o + "\n" + dom.String())
if err != nil {
return err.Error()
}
t <- &Token{RR: rx}
// Its more efficient to first built the rrlist and then parse it in
// one go! But is this a problem?
s += l.token
}
r := &generateReader{
s: s,
cur: start,
start: start,
end: end,
step: step,
file: zp.file,
lex: &l,
}
zp.sub = NewZoneParser(r, zp.origin, zp.file)
zp.sub.includeDepth, zp.sub.includeAllowed = zp.includeDepth, zp.includeAllowed
zp.sub.generateDisallowed = true
zp.sub.SetDefaultTTL(defaultTtl)
return zp.subNext()
}
type generateReader struct {
s string
si int
cur int64
start int64
end int64
step int64
mod bytes.Buffer
escape bool
eof bool
file string
lex *lex
}
func (r *generateReader) parseError(msg string, end int) *ParseError {
r.eof = true // Make errors sticky.
l := *r.lex
l.token = r.s[r.si-1 : end]
l.column += r.si // l.column starts one zBLANK before r.s
return &ParseError{r.file, msg, l}
}
func (r *generateReader) Read(p []byte) (int, error) {
// NewZLexer, through NewZoneParser, should use ReadByte and
// not end up here.
panic("not implemented")
}
func (r *generateReader) ReadByte() (byte, error) {
if r.eof {
return 0, io.EOF
}
if r.mod.Len() > 0 {
return r.mod.ReadByte()
}
if r.si >= len(r.s) {
r.si = 0
r.cur += r.step
r.eof = r.cur > r.end || r.cur < 0
return '\n', nil
}
si := r.si
r.si++
switch r.s[si] {
case '\\':
if r.escape {
r.escape = false
return '\\', nil
}
r.escape = true
return r.ReadByte()
case '$':
if r.escape {
r.escape = false
return '$', nil
}
mod := "%d"
if si >= len(r.s)-1 {
// End of the string
fmt.Fprintf(&r.mod, mod, r.cur)
return r.mod.ReadByte()
}
if r.s[si+1] == '$' {
r.si++
return '$', nil
}
var offset int64
// Search for { and }
if r.s[si+1] == '{' {
// Modifier block
sep := strings.Index(r.s[si+2:], "}")
if sep < 0 {
return 0, r.parseError("bad modifier in $GENERATE", len(r.s))
}
var errMsg string
mod, offset, errMsg = modToPrintf(r.s[si+2 : si+2+sep])
if errMsg != "" {
return 0, r.parseError(errMsg, si+3+sep)
}
if r.start+offset < 0 || r.end+offset > 1<<31-1 {
return 0, r.parseError("bad offset in $GENERATE", si+3+sep)
}
r.si += 2 + sep // Jump to it
}
fmt.Fprintf(&r.mod, mod, r.cur+offset)
return r.mod.ReadByte()
default:
if r.escape { // Pretty useless here
r.escape = false
return r.ReadByte()
}
return r.s[si], nil
}
return ""
}
// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
func modToPrintf(s string) (string, int, error) {
xs := strings.SplitN(s, ",", 3)
if len(xs) != 3 {
return "", 0, errors.New("bad modifier in $GENERATE")
func modToPrintf(s string) (string, int64, string) {
// Modifier is { offset [ ,width [ ,base ] ] } - provide default
// values for optional width and type, if necessary.
var offStr, widthStr, base string
switch xs := strings.Split(s, ","); len(xs) {
case 1:
offStr, widthStr, base = xs[0], "0", "d"
case 2:
offStr, widthStr, base = xs[0], xs[1], "d"
case 3:
offStr, widthStr, base = xs[0], xs[1], xs[2]
default:
return "", 0, "bad modifier in $GENERATE"
}
// xs[0] is offset, xs[1] is width, xs[2] is base
if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" {
return "", 0, errors.New("bad base in $GENERATE")
switch base {
case "o", "d", "x", "X":
default:
return "", 0, "bad base in $GENERATE"
}
offset, err := strconv.Atoi(xs[0])
if err != nil || offset > 255 {
return "", 0, errors.New("bad offset in $GENERATE")
offset, err := strconv.ParseInt(offStr, 10, 64)
if err != nil {
return "", 0, "bad offset in $GENERATE"
}
width, err := strconv.Atoi(xs[1])
if err != nil || width > 255 {
return "", offset, errors.New("bad width in $GENERATE")
width, err := strconv.ParseInt(widthStr, 10, 64)
if err != nil || width < 0 || width > 255 {
return "", 0, "bad width in $GENERATE"
}
switch {
case width < 0:
return "", offset, errors.New("bad width in $GENERATE")
case width == 0:
return "%" + xs[1] + xs[2], offset, nil
if width == 0 {
return "%" + base, offset, ""
}
return "%0" + xs[1] + xs[2], offset, nil
return "%0" + widthStr + base, offset, ""
}

235
generate_test.go Normal file
View File

@ -0,0 +1,235 @@
package dns
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
func TestGenerateRangeGuard(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "dns")
if err != nil {
t.Fatalf("could not create tmpdir for test: %v", err)
}
defer os.RemoveAll(tmpdir)
for i := 0; i <= 1; i++ {
path := filepath.Join(tmpdir, fmt.Sprintf("%04d.conf", i))
data := []byte(fmt.Sprintf("dhcp-%04d A 10.0.0.%d", i, i))
if err := ioutil.WriteFile(path, data, 0644); err != nil {
t.Fatalf("could not create tmpfile for test: %v", err)
}
}
var tests = [...]struct {
zone string
fail bool
}{
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-1 dhcp-${0,4,d} A 10.0.0.$
`, false},
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-1 dhcp-${0,0,x} A 10.0.0.$
`, false},
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 128-129 dhcp-${-128,4,d} A 10.0.0.$
`, false},
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 128-129 dhcp-${-129,4,d} A 10.0.0.$
`, true},
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-2 dhcp-${2147483647,4,d} A 10.0.0.$
`, true},
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-1 dhcp-${2147483646,4,d} A 10.0.0.$
`, false},
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-1/step dhcp-${0,4,d} A 10.0.0.$
`, true},
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-1/ dhcp-${0,4,d} A 10.0.0.$
`, true},
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-10/2 dhcp-${0,4,d} A 10.0.0.$
`, false},
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-1/0 dhcp-${0,4,d} A 10.0.0.$
`, true},
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-1 $$INCLUDE ` + tmpdir + string(filepath.Separator) + `${0,4,d}.conf
`, false},
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-1 dhcp-${0,4,d} A 10.0.0.$
$GENERATE 0-2 dhcp-${0,4,d} A 10.1.0.$
`, false},
}
for i := range tests {
z := NewZoneParser(strings.NewReader(tests[i].zone), "test.", "test")
z.SetIncludeAllowed(true)
for _, ok := z.Next(); ok; _, ok = z.Next() {
}
err := z.Err()
if err != nil && !tests[i].fail {
t.Errorf("expected \n\n%s\nto be parsed, but got %v", tests[i].zone, err)
} else if err == nil && tests[i].fail {
t.Errorf("expected \n\n%s\nto fail, but got no error", tests[i].zone)
}
}
}
func TestGenerateIncludeDepth(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "dns")
if err != nil {
t.Fatalf("could not create tmpfile for test: %v", err)
}
defer os.Remove(tmpfile.Name())
zone := `@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-1 $$INCLUDE ` + tmpfile.Name() + `
`
if _, err := tmpfile.WriteString(zone); err != nil {
t.Fatalf("could not write to tmpfile for test: %v", err)
}
if err := tmpfile.Close(); err != nil {
t.Fatalf("could not close tmpfile for test: %v", err)
}
zp := NewZoneParser(strings.NewReader(zone), ".", tmpfile.Name())
zp.SetIncludeAllowed(true)
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
}
const expected = "too deeply nested $INCLUDE"
if err := zp.Err(); err == nil || !strings.Contains(err.Error(), expected) {
t.Errorf("expected error to include %q, got %v", expected, err)
}
}
func TestGenerateIncludeDisallowed(t *testing.T) {
const zone = `@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-1 $$INCLUDE test.conf
`
zp := NewZoneParser(strings.NewReader(zone), ".", "")
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
}
const expected = "$INCLUDE directive not allowed"
if err := zp.Err(); err == nil || !strings.Contains(err.Error(), expected) {
t.Errorf("expected error to include %q, got %v", expected, err)
}
}
func TestGenerateSurfacesErrors(t *testing.T) {
const zone = `@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-1 dhcp-${0,4,dd} A 10.0.0.$
`
zp := NewZoneParser(strings.NewReader(zone), ".", "test")
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
}
const expected = `test: dns: bad base in $GENERATE: "${0,4,dd}" at line: 2:20`
if err := zp.Err(); err == nil || err.Error() != expected {
t.Errorf("expected specific error, wanted %q, got %v", expected, err)
}
}
func TestGenerateSurfacesLexerErrors(t *testing.T) {
const zone = `@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 0-1 dhcp-${0,4,d} A 10.0.0.$ )
`
zp := NewZoneParser(strings.NewReader(zone), ".", "test")
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
}
const expected = `test: dns: bad data in $GENERATE directive: "extra closing brace" at line: 2:40`
if err := zp.Err(); err == nil || err.Error() != expected {
t.Errorf("expected specific error, wanted %q, got %v", expected, err)
}
}
func TestGenerateModToPrintf(t *testing.T) {
tests := []struct {
mod string
wantFmt string
wantOffset int64
wantErr bool
}{
{"0,0,d", "%d", 0, false},
{"0,0", "%d", 0, false},
{"0", "%d", 0, false},
{"3,2,d", "%02d", 3, false},
{"3,2", "%02d", 3, false},
{"3", "%d", 3, false},
{"0,0,o", "%o", 0, false},
{"0,0,x", "%x", 0, false},
{"0,0,X", "%X", 0, false},
{"0,0,z", "", 0, true},
{"0,0,0,d", "", 0, true},
{"-100,0,d", "%d", -100, false},
}
for _, test := range tests {
gotFmt, gotOffset, errMsg := modToPrintf(test.mod)
switch {
case errMsg != "" && !test.wantErr:
t.Errorf("modToPrintf(%q) - expected empty-error, but got %v", test.mod, errMsg)
case errMsg == "" && test.wantErr:
t.Errorf("modToPrintf(%q) - expected error, but got empty-error", test.mod)
case gotFmt != test.wantFmt:
t.Errorf("modToPrintf(%q) - expected format %q, but got %q", test.mod, test.wantFmt, gotFmt)
case gotOffset != test.wantOffset:
t.Errorf("modToPrintf(%q) - expected offset %d, but got %d", test.mod, test.wantOffset, gotOffset)
}
}
}
func BenchmarkGenerate(b *testing.B) {
const zone = `@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
$GENERATE 32-158 dhcp-${-32,4,d} A 10.0.0.$
`
for n := 0; n < b.N; n++ {
zp := NewZoneParser(strings.NewReader(zone), ".", "")
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
}
if err := zp.Err(); err != nil {
b.Fatal(err)
}
}
}
func TestCrasherString(t *testing.T) {
tests := []struct {
in string
err string
}{
{"$GENERATE 0-300103\"$$GENERATE 2-2", "bad range in $GENERATE"},
{"$GENERATE 0-5414137360", "bad range in $GENERATE"},
{"$GENERATE 11522-3668518066406258", "bad range in $GENERATE"},
{"$GENERATE 0-200\"(;00000000000000\n$$GENERATE 0-0", "dns: garbage after $GENERATE range: \"\\\"\" at line: 1:16"},
{"$GENERATE 6-2048 $$GENERATE 6-036160 $$$$ORIGIN \\$", `dns: nested $GENERATE directive not allowed: "6-036160" at line: 1:19`},
}
for _, tc := range tests {
t.Run(tc.in, func(t *testing.T) {
_, err := NewRR(tc.in)
if err == nil {
t.Errorf("Expecting error for crasher line %s", tc.in)
}
if !strings.Contains(err.Error(), tc.err) {
t.Errorf("Expecting error %s, got %s", tc.err, err.Error())
}
})
}
}

10
go.mod Normal file
View File

@ -0,0 +1,10 @@
module github.com/miekg/dns
go 1.14
require (
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2
)

33
go.sum Normal file
View File

@ -0,0 +1,33 @@
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

31
hash.go Normal file
View File

@ -0,0 +1,31 @@
package dns
import (
"bytes"
"crypto"
"hash"
)
// identityHash will not hash, it only buffers the data written into it and returns it as-is.
type identityHash struct {
b *bytes.Buffer
}
// Implement the hash.Hash interface.
func (i identityHash) Write(b []byte) (int, error) { return i.b.Write(b) }
func (i identityHash) Size() int { return i.b.Len() }
func (i identityHash) BlockSize() int { return 1024 }
func (i identityHash) Reset() { i.b.Reset() }
func (i identityHash) Sum(b []byte) []byte { return append(b, i.b.Bytes()...) }
func hashFromAlgorithm(alg uint8) (hash.Hash, crypto.Hash, error) {
hashnumber, ok := AlgorithmToHash[alg]
if !ok {
return nil, 0, ErrAlg
}
if hashnumber == 0 {
return identityHash{b: &bytes.Buffer{}}, hashnumber, nil
}
return hashnumber.New(), hashnumber, nil
}

View File

@ -7,24 +7,6 @@ import (
"testing"
)
func TestTCPRtt(t *testing.T) {
m := new(Msg)
m.RecursionDesired = true
m.SetQuestion("example.org.", TypeA)
c := &Client{}
for _, proto := range []string{"udp", "tcp"} {
c.Net = proto
_, rtt, err := c.Exchange(m, "8.8.4.4:53")
if err != nil {
t.Fatal(err)
}
if rtt == 0 {
t.Fatalf("expecting non zero rtt %s, got zero", c.Net)
}
}
}
func TestNSEC3MissingSalt(t *testing.T) {
rr := testRR("ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. NSEC3 1 1 12 aabbccdd K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H")
m := new(Msg)

View File

@ -10,13 +10,13 @@ package dns
// escaped dots (\.) for instance.
// s must be a syntactically valid domain name, see IsDomainName.
func SplitDomainName(s string) (labels []string) {
if len(s) == 0 {
if s == "" {
return nil
}
fqdnEnd := 0 // offset of the final '.' or the length of the name
idx := Split(s)
begin := 0
if s[len(s)-1] == '.' {
if IsFqdn(s) {
fqdnEnd = len(s) - 1
} else {
fqdnEnd = len(s)
@ -28,16 +28,13 @@ func SplitDomainName(s string) (labels []string) {
case 1:
// no-op
default:
end := 0
for i := 1; i < len(idx); i++ {
end = idx[i]
for _, end := range idx[1:] {
labels = append(labels, s[begin:end-1])
begin = end
}
}
labels = append(labels, s[begin:fqdnEnd])
return labels
return append(labels, s[begin:fqdnEnd])
}
// CompareDomainName compares the names s1 and s2 and
@ -86,7 +83,7 @@ func CompareDomainName(s1, s2 string) (n int) {
return
}
// CountLabel counts the the number of labels in the string s.
// CountLabel counts the number of labels in the string s.
// s must be a syntactically valid domain name.
func CountLabel(s string) (labels int) {
if s == "." {
@ -125,24 +122,27 @@ func Split(s string) []int {
}
// NextLabel returns the index of the start of the next label in the
// string s starting at offset.
// string s starting at offset. A negative offset will cause a panic.
// The bool end is true when the end of the string has been reached.
// Also see PrevLabel.
func NextLabel(s string, offset int) (i int, end bool) {
quote := false
if s == "" {
return 0, true
}
for i = offset; i < len(s)-1; i++ {
switch s[i] {
case '\\':
quote = !quote
default:
quote = false
case '.':
if quote {
quote = !quote
continue
}
return i + 1, false
if s[i] != '.' {
continue
}
j := i - 1
for j >= 0 && s[j] == '\\' {
j--
}
if (j-i)%2 == 0 {
continue
}
return i + 1, false
}
return i + 1, true
}
@ -152,17 +152,38 @@ func NextLabel(s string, offset int) (i int, end bool) {
// The bool start is true when the start of the string has been overshot.
// Also see NextLabel.
func PrevLabel(s string, n int) (i int, start bool) {
if s == "" {
return 0, true
}
if n == 0 {
return len(s), false
}
lab := Split(s)
if lab == nil {
return 0, true
l := len(s) - 1
if s[l] == '.' {
l--
}
if n > len(lab) {
return 0, true
for ; l >= 0 && n > 0; l-- {
if s[l] != '.' {
continue
}
j := l - 1
for j >= 0 && s[j] == '\\' {
j--
}
if (j-l)%2 == 0 {
continue
}
n--
if n == 0 {
return l + 1, false
}
}
return lab[len(lab)-n], false
return 0, n > 1
}
// equal compares a and b while ignoring case. It returns true when equal otherwise false.
@ -178,10 +199,10 @@ func equal(a, b string) bool {
ai := a[i]
bi := b[i]
if ai >= 'A' && ai <= 'Z' {
ai |= ('a' - 'A')
ai |= 'a' - 'A'
}
if bi >= 'A' && bi <= 'Z' {
bi |= ('a' - 'A')
bi |= 'a' - 'A'
}
if ai != bi {
return false

View File

@ -40,16 +40,17 @@ func TestCompareDomainName(t *testing.T) {
func TestSplit(t *testing.T) {
splitter := map[string]int{
"www.miek.nl.": 3,
"www.miek.nl": 3,
"www..miek.nl": 4,
`www\.miek.nl.`: 2,
`www\\.miek.nl.`: 3,
".": 0,
"nl.": 1,
"nl": 1,
"com.": 1,
".com.": 2,
"www.miek.nl.": 3,
"www.miek.nl": 3,
"www..miek.nl": 4,
`www\.miek.nl.`: 2,
`www\\.miek.nl.`: 3,
`www\\\.miek.nl.`: 2,
".": 0,
"nl.": 1,
"nl": 1,
"com.": 1,
".com.": 2,
}
for s, i := range splitter {
if x := len(Split(s)); x != i {
@ -79,12 +80,32 @@ func TestSplit2(t *testing.T) {
}
}
func TestNextLabel(t *testing.T) {
type next struct {
string
int
}
nexts := map[next]int{
{"", 1}: 0,
{"www.miek.nl.", 0}: 4,
{"www.miek.nl.", 4}: 9,
{"www.miek.nl.", 9}: 12,
}
for s, i := range nexts {
x, ok := NextLabel(s.string, s.int)
if i != x {
t.Errorf("label should be %d, got %d, %t: next %d, %s", i, x, ok, s.int, s.string)
}
}
}
func TestPrevLabel(t *testing.T) {
type prev struct {
string
int
}
prever := map[prev]int{
{"", 1}: 0,
{"www.miek.nl.", 0}: 12,
{"www.miek.nl.", 1}: 9,
{"www.miek.nl.", 2}: 4,
@ -102,7 +123,7 @@ func TestPrevLabel(t *testing.T) {
for s, i := range prever {
x, ok := PrevLabel(s.string, s.int)
if i != x {
t.Errorf("label should be %d, got %d, %t: preving %d, %s", i, x, ok, s.int, s.string)
t.Errorf("label should be %d, got %d, %t: previous %d, %s", i, x, ok, s.int, s.string)
}
}
}
@ -155,13 +176,18 @@ func TestIsDomainName(t *testing.T) {
lab int
}
names := map[string]*ret{
"..": {false, 1},
"@.": {true, 1},
"www.example.com": {true, 3},
"www.e%ample.com": {true, 3},
"www.example.com.": {true, 3},
"mi\\k.nl.": {true, 2},
"mi\\k.nl": {true, 2},
".": {true, 1},
"..": {false, 0},
"double-dot..test": {false, 1},
".leading-dot.test": {false, 0},
"@.": {true, 1},
"www.example.com": {true, 3},
"www.e%ample.com": {true, 3},
"www.example.com.": {true, 3},
"mi\\k.nl.": {true, 2},
"mi\\k.nl": {true, 2},
longestDomain: {true, 4},
longestUnprintableDomain: {true, 4},
}
for d, ok := range names {
l, k := IsDomainName(d)
@ -172,6 +198,58 @@ func TestIsDomainName(t *testing.T) {
}
}
func TestIsFqdnEscaped(t *testing.T) {
for s, expect := range map[string]bool{
".": true,
"\\.": false,
"\\\\.": true,
"\\\\\\.": false,
"\\\\\\\\.": true,
"a.": true,
"a\\.": false,
"a\\\\.": true,
"a\\\\\\.": false,
"ab.": true,
"ab\\.": false,
"ab\\\\.": true,
"ab\\\\\\.": false,
"..": true,
".\\.": false,
".\\\\.": true,
".\\\\\\.": false,
"example.org.": true,
"example.org\\.": false,
"example.org\\\\.": true,
"example.org\\\\\\.": false,
"example\\.org.": true,
"example\\\\.org.": true,
"example\\\\\\.org.": true,
"\\example.org.": true,
"\\\\example.org.": true,
"\\\\\\example.org.": true,
} {
if got := IsFqdn(s); got != expect {
t.Errorf("IsFqdn(%q) = %t, expected %t", s, got, expect)
}
}
}
func TestCanonicalName(t *testing.T) {
for s, expect := range map[string]string{
"": ".",
".": ".",
"tld": "tld.",
"tld.": "tld.",
"example.test": "example.test.",
"Lower.CASE.test.": "lower.case.test.",
"*.Test": "*.test.",
} {
if got := CanonicalName(s); got != expect {
t.Errorf("CanonicalName(%q) = %q, expected %q", s, got, expect)
}
}
}
func BenchmarkSplitLabels(b *testing.B) {
for i := 0; i < b.N; i++ {
Split("www.example.com.")
@ -199,3 +277,63 @@ func BenchmarkIsSubDomain(b *testing.B) {
IsSubDomain("miek.nl.", "aa.example.com.")
}
}
func BenchmarkNextLabelSimple(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
NextLabel("www.example.com", 0)
NextLabel("www.example.com", 5)
NextLabel("www.example.com", 12)
}
}
func BenchmarkPrevLabelSimple(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
PrevLabel("www.example.com", 0)
PrevLabel("www.example.com", 5)
PrevLabel("www.example.com", 12)
}
}
func BenchmarkNextLabelComplex(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
NextLabel(`www\.example.com`, 0)
NextLabel(`www\\.example.com`, 0)
NextLabel(`www\\\.example.com`, 0)
}
}
func BenchmarkPrevLabelComplex(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
PrevLabel(`www\.example.com`, 10)
PrevLabel(`www\\.example.com`, 10)
PrevLabel(`www\\\.example.com`, 10)
}
}
func BenchmarkNextLabelMixed(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
NextLabel("www.example.com", 0)
NextLabel(`www\.example.com`, 0)
NextLabel("www.example.com", 5)
NextLabel(`www\\.example.com`, 0)
NextLabel("www.example.com", 12)
NextLabel(`www\\\.example.com`, 0)
}
}
func BenchmarkPrevLabelMixed(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
PrevLabel("www.example.com", 0)
PrevLabel(`www\.example.com`, 10)
PrevLabel("www.example.com", 5)
PrevLabel(`www\\.example.com`, 10)
PrevLabel("www.example.com", 12)
PrevLabel(`www\\\.example.com`, 10)
}
}

View File

@ -4,12 +4,13 @@ import (
"encoding/hex"
"fmt"
"net"
"strings"
"testing"
)
func TestCompressLength(t *testing.T) {
m := new(Msg)
m.SetQuestion("miek.nl", TypeMX)
m.SetQuestion("miek.nl.", TypeMX)
ul := m.Len()
m.Compress = true
if ul != m.Len() {
@ -52,6 +53,7 @@ func TestMsgCompressLength(t *testing.T) {
func TestMsgLength(t *testing.T) {
makeMsg := func(question string, ans, ns, e []RR) *Msg {
msg := new(Msg)
msg.Compress = true
msg.SetQuestion(Fqdn(question), TypeANY)
msg.Answer = append(msg.Answer, ans...)
msg.Ns = append(msg.Ns, ns...)
@ -79,14 +81,99 @@ func TestMsgLength(t *testing.T) {
}
}
func TestCompressionLenSearchInsert(t *testing.T) {
c := make(map[string]struct{})
compressionLenSearch(c, "example.com", 12)
if _, ok := c["example.com"]; !ok {
t.Errorf("bad example.com")
}
if _, ok := c["com"]; !ok {
t.Errorf("bad com")
}
// Test boundaries
c = make(map[string]struct{})
// foo label starts at 16379
// com label starts at 16384
compressionLenSearch(c, "foo.com", 16379)
if _, ok := c["foo.com"]; !ok {
t.Errorf("bad foo.com")
}
// com label is accessible
if _, ok := c["com"]; !ok {
t.Errorf("bad com")
}
c = make(map[string]struct{})
// foo label starts at 16379
// com label starts at 16385 => outside range
compressionLenSearch(c, "foo.com", 16380)
if _, ok := c["foo.com"]; !ok {
t.Errorf("bad foo.com")
}
// com label is NOT accessible
if _, ok := c["com"]; ok {
t.Errorf("bad com")
}
c = make(map[string]struct{})
compressionLenSearch(c, "example.com", 16375)
if _, ok := c["example.com"]; !ok {
t.Errorf("bad example.com")
}
// com starts AFTER 16384
if _, ok := c["com"]; !ok {
t.Errorf("bad com")
}
c = make(map[string]struct{})
compressionLenSearch(c, "example.com", 16376)
if _, ok := c["example.com"]; !ok {
t.Errorf("bad example.com")
}
// com starts AFTER 16384
if _, ok := c["com"]; ok {
t.Errorf("bad com")
}
}
func TestCompressionLenSearch(t *testing.T) {
c := make(map[string]struct{})
compressed, ok := compressionLenSearch(c, "a.b.org.", maxCompressionOffset)
if compressed != 0 || ok {
t.Errorf("Failed: compressed:=%d, ok:=%v", compressed, ok)
}
c["org."] = struct{}{}
compressed, ok = compressionLenSearch(c, "a.b.org.", maxCompressionOffset)
if compressed != 4 || !ok {
t.Errorf("Failed: compressed:=%d, ok:=%v", compressed, ok)
}
c["b.org."] = struct{}{}
compressed, ok = compressionLenSearch(c, "a.b.org.", maxCompressionOffset)
if compressed != 2 || !ok {
t.Errorf("Failed: compressed:=%d, ok:=%v", compressed, ok)
}
// Not found long compression
c["x.b.org."] = struct{}{}
compressed, ok = compressionLenSearch(c, "a.b.org.", maxCompressionOffset)
if compressed != 2 || !ok {
t.Errorf("Failed: compressed:=%d, ok:=%v", compressed, ok)
}
// Found long compression
c["a.b.org."] = struct{}{}
compressed, ok = compressionLenSearch(c, "a.b.org.", maxCompressionOffset)
if compressed != 0 || !ok {
t.Errorf("Failed: compressed:=%d, ok:=%v", compressed, ok)
}
}
func TestMsgLength2(t *testing.T) {
// Serialized replies
var testMessages = []string{
// google.com. IN A?
"064e81800001000b0004000506676f6f676c6503636f6d0000010001c00c00010001000000050004adc22986c00c00010001000000050004adc22987c00c00010001000000050004adc22988c00c00010001000000050004adc22989c00c00010001000000050004adc2298ec00c00010001000000050004adc22980c00c00010001000000050004adc22981c00c00010001000000050004adc22982c00c00010001000000050004adc22983c00c00010001000000050004adc22984c00c00010001000000050004adc22985c00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc0d800010001000000050004d8ef200ac0ea00010001000000050004d8ef220ac0fc00010001000000050004d8ef240ac10e00010001000000050004d8ef260a0000290500000000050000",
// amazon.com. IN A? (reply has no EDNS0 record)
// TODO(miek): this one is off-by-one, need to find out why
//"6de1818000010004000a000806616d617a6f6e03636f6d0000010001c00c000100010000000500044815c2d4c00c000100010000000500044815d7e8c00c00010001000000050004b02062a6c00c00010001000000050004cdfbf236c00c000200010000000500140570646e733408756c747261646e73036f726700c00c000200010000000500150570646e733508756c747261646e7304696e666f00c00c000200010000000500160570646e733608756c747261646e7302636f02756b00c00c00020001000000050014036e7331037033310664796e656374036e657400c00c00020001000000050006036e7332c0cfc00c00020001000000050006036e7333c0cfc00c00020001000000050006036e7334c0cfc00c000200010000000500110570646e733108756c747261646e73c0dac00c000200010000000500080570646e7332c127c00c000200010000000500080570646e7333c06ec0cb00010001000000050004d04e461fc0eb00010001000000050004cc0dfa1fc0fd00010001000000050004d04e471fc10f00010001000000050004cc0dfb1fc12100010001000000050004cc4a6c01c121001c000100000005001020010502f3ff00000000000000000001c13e00010001000000050004cc4a6d01c13e001c0001000000050010261000a1101400000000000000000001",
"6de1818000010004000a000806616d617a6f6e03636f6d0000010001c00c000100010000000500044815c2d4c00c000100010000000500044815d7e8c00c00010001000000050004b02062a6c00c00010001000000050004cdfbf236c00c000200010000000500140570646e733408756c747261646e73036f726700c00c000200010000000500150570646e733508756c747261646e7304696e666f00c00c000200010000000500160570646e733608756c747261646e7302636f02756b00c00c00020001000000050014036e7331037033310664796e656374036e657400c00c00020001000000050006036e7332c0cfc00c00020001000000050006036e7333c0cfc00c00020001000000050006036e7334c0cfc00c000200010000000500110570646e733108756c747261646e73c0dac00c000200010000000500080570646e7332c127c00c000200010000000500080570646e7333c06ec0cb00010001000000050004d04e461fc0eb00010001000000050004cc0dfa1fc0fd00010001000000050004d04e471fc10f00010001000000050004cc0dfb1fc12100010001000000050004cc4a6c01c121001c000100000005001020010502f3ff00000000000000000001c13e00010001000000050004cc4a6d01c13e001c0001000000050010261000a1101400000000000000000001",
// yahoo.com. IN A?
"fc2d81800001000300070008057961686f6f03636f6d0000010001c00c00010001000000050004628afd6dc00c00010001000000050004628bb718c00c00010001000000050004cebe242dc00c00020001000000050006036e7336c00cc00c00020001000000050006036e7338c00cc00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc00c00020001000000050006036e7335c00cc07b0001000100000005000444b48310c08d00010001000000050004448eff10c09f00010001000000050004cb54dd35c0b100010001000000050004628a0b9dc0c30001000100000005000477a0f77cc05700010001000000050004ca2bdfaac06900010001000000050004caa568160000290500000000050000",
// microsoft.com. IN A?
@ -111,10 +198,10 @@ func TestMsgLength2(t *testing.T) {
lenUnComp := m.Len()
b, _ = m.Pack()
pacUnComp := len(b)
if pacComp+1 != lenComp {
if pacComp != lenComp {
t.Errorf("msg.Len(compressed)=%d actual=%d for test %d", lenComp, pacComp, i)
}
if pacUnComp+1 != lenUnComp {
if pacUnComp != lenUnComp {
t.Errorf("msg.Len(uncompressed)=%d actual=%d for test %d", lenUnComp, pacUnComp, i)
}
}
@ -159,7 +246,7 @@ func TestMsgCompressLengthLargeRecords(t *testing.T) {
msg.SetQuestion("my.service.acme.", TypeSRV)
j := 1
for i := 0; i < 250; i++ {
target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", j, i)
target := fmt.Sprintf("host-redis-1-%d.test.acme.com.node.dc1.consul.", i)
msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: 1, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", j, i)})
}
@ -172,3 +259,259 @@ func TestMsgCompressLengthLargeRecords(t *testing.T) {
t.Fatalf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d", msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
}
}
func compressionMapsEqual(a map[string]struct{}, b map[string]int) bool {
if len(a) != len(b) {
return false
}
for k := range a {
if _, ok := b[k]; !ok {
return false
}
}
return true
}
func compressionMapsDifference(a map[string]struct{}, b map[string]int) string {
var s strings.Builder
var c int
fmt.Fprintf(&s, "length compression map (%d):", len(a))
for k := range b {
if _, ok := a[k]; !ok {
if c > 0 {
s.WriteString(",")
}
fmt.Fprintf(&s, " missing %q", k)
c++
}
}
c = 0
fmt.Fprintf(&s, "\npack compression map (%d):", len(b))
for k := range a {
if _, ok := b[k]; !ok {
if c > 0 {
s.WriteString(",")
}
fmt.Fprintf(&s, " missing %q", k)
c++
}
}
return s.String()
}
func TestCompareCompressionMapsForANY(t *testing.T) {
msg := new(Msg)
msg.Compress = true
msg.SetQuestion("a.service.acme.", TypeANY)
// Be sure to have more than 14bits
for i := 0; i < 2000; i++ {
target := fmt.Sprintf("host.app-%d.x%d.test.acme.", i%250, i)
msg.Answer = append(msg.Answer, &AAAA{Hdr: RR_Header{Name: target, Rrtype: TypeAAAA, Class: ClassINET, Ttl: 0x3c}, AAAA: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, byte(i / 255), byte(i % 255)}})
msg.Answer = append(msg.Answer, &A{Hdr: RR_Header{Name: target, Rrtype: TypeA, Class: ClassINET, Ttl: 0x3c}, A: net.IP{127, 0, byte(i / 255), byte(i % 255)}})
if msg.Len() > 16384 {
break
}
}
for labelSize := 0; labelSize < 63; labelSize++ {
msg.SetQuestion(fmt.Sprintf("a%s.service.acme.", strings.Repeat("x", labelSize)), TypeANY)
compressionFake := make(map[string]struct{})
lenFake := msgLenWithCompressionMap(msg, compressionFake)
compressionReal := make(map[string]int)
buf, err := msg.packBufferWithCompressionMap(nil, compressionMap{ext: compressionReal}, true)
if err != nil {
t.Fatal(err)
}
if lenFake != len(buf) {
t.Fatalf("padding= %d ; Predicted len := %d != real:= %d", labelSize, lenFake, len(buf))
}
if !compressionMapsEqual(compressionFake, compressionReal) {
t.Fatalf("padding= %d ; Fake Compression Map != Real Compression Map\n%s", labelSize, compressionMapsDifference(compressionFake, compressionReal))
}
}
}
func TestCompareCompressionMapsForSRV(t *testing.T) {
msg := new(Msg)
msg.Compress = true
msg.SetQuestion("a.service.acme.", TypeSRV)
// Be sure to have more than 14bits
for i := 0; i < 2000; i++ {
target := fmt.Sprintf("host.app-%d.x%d.test.acme.", i%250, i)
msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: ClassINET, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
msg.Extra = append(msg.Extra, &A{Hdr: RR_Header{Name: target, Rrtype: TypeA, Class: ClassINET, Ttl: 0x3c}, A: net.IP{127, 0, byte(i / 255), byte(i % 255)}})
if msg.Len() > 16384 {
break
}
}
for labelSize := 0; labelSize < 63; labelSize++ {
msg.SetQuestion(fmt.Sprintf("a%s.service.acme.", strings.Repeat("x", labelSize)), TypeAAAA)
compressionFake := make(map[string]struct{})
lenFake := msgLenWithCompressionMap(msg, compressionFake)
compressionReal := make(map[string]int)
buf, err := msg.packBufferWithCompressionMap(nil, compressionMap{ext: compressionReal}, true)
if err != nil {
t.Fatal(err)
}
if lenFake != len(buf) {
t.Fatalf("padding= %d ; Predicted len := %d != real:= %d", labelSize, lenFake, len(buf))
}
if !compressionMapsEqual(compressionFake, compressionReal) {
t.Fatalf("padding= %d ; Fake Compression Map != Real Compression Map\n%s", labelSize, compressionMapsDifference(compressionFake, compressionReal))
}
}
}
func TestMsgCompressLengthLargeRecordsWithPaddingPermutation(t *testing.T) {
msg := new(Msg)
msg.Compress = true
msg.SetQuestion("my.service.acme.", TypeSRV)
for i := 0; i < 250; i++ {
target := fmt.Sprintf("host-redis-x-%d.test.acme.com.node.dc1.consul.", i)
msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: ClassINET, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.x.%d.", i)})
}
for labelSize := 1; labelSize < 63; labelSize++ {
msg.SetQuestion(fmt.Sprintf("my.%s.service.acme.", strings.Repeat("x", labelSize)), TypeSRV)
predicted := msg.Len()
buf, err := msg.Pack()
if err != nil {
t.Error(err)
}
if predicted != len(buf) {
t.Fatalf("padding= %d ; predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d", labelSize, msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
}
}
}
func TestMsgCompressLengthLargeRecordsAllValues(t *testing.T) {
// we want to cross the 14 (16384) bit boundary here, so we build it up to just below and go slightly over.
msg := new(Msg)
msg.Compress = true
msg.SetQuestion("redis.service.consul.", TypeSRV)
for i := 0; i < 170; i++ {
target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", i/256, i%256)
msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: ClassINET, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)})
}
// msg.Len() == 15458
// msg.Len() == 16470 at 180
for i := 170; i < 181; i++ {
target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", i/256, i%256)
msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: ClassINET, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)})
predicted := msg.Len()
buf, err := msg.Pack()
if err != nil {
t.Error(err)
}
if predicted != len(buf) {
t.Fatalf("predicted compressed length is wrong for %d records: predicted %s (len=%d) %d, actual %d", i, msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
}
}
}
func TestMsgCompressionMultipleQuestions(t *testing.T) {
msg := new(Msg)
msg.Compress = true
msg.SetQuestion("www.example.org.", TypeA)
msg.Question = append(msg.Question, Question{"other.example.org.", TypeA, ClassINET})
predicted := msg.Len()
buf, err := msg.Pack()
if err != nil {
t.Error(err)
}
if predicted != len(buf) {
t.Fatalf("predicted compressed length is wrong: predicted %d, actual %d", predicted, len(buf))
}
}
func TestMsgCompressMultipleCompressedNames(t *testing.T) {
msg := new(Msg)
msg.Compress = true
msg.SetQuestion("www.example.com.", TypeSRV)
msg.Answer = append(msg.Answer, &MINFO{
Hdr: RR_Header{Name: "www.example.com.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c},
Rmail: "mail.example.org.",
Email: "mail.example.org.",
})
msg.Answer = append(msg.Answer, &SOA{
Hdr: RR_Header{Name: "www.example.com.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c},
Ns: "ns.example.net.",
Mbox: "mail.example.net.",
})
predicted := msg.Len()
buf, err := msg.Pack()
if err != nil {
t.Error(err)
}
if predicted != len(buf) {
t.Fatalf("predicted compressed length is wrong: predicted %d, actual %d", predicted, len(buf))
}
}
func TestMsgCompressLengthEscapingMatch(t *testing.T) {
// Although slightly non-optimal, "example.org." and "ex\\097mple.org."
// are not considered equal in the compression map, even though \097 is
// a valid escaping of a. This test ensures that the Len code and the
// Pack code don't disagree on this.
msg := new(Msg)
msg.Compress = true
msg.SetQuestion("www.example.org.", TypeA)
msg.Answer = append(msg.Answer, &NS{Hdr: RR_Header{Name: "ex\\097mple.org.", Rrtype: TypeNS, Class: ClassINET}, Ns: "ns.example.org."})
predicted := msg.Len()
buf, err := msg.Pack()
if err != nil {
t.Error(err)
}
if predicted != len(buf) {
t.Fatalf("predicted compressed length is wrong: predicted %d, actual %d", predicted, len(buf))
}
}
func TestMsgLengthEscaped(t *testing.T) {
msg := new(Msg)
msg.SetQuestion(`\000\001\002.\003\004\005\006\007\008\009.\a\b\c.`, TypeA)
predicted := msg.Len()
buf, err := msg.Pack()
if err != nil {
t.Error(err)
}
if predicted != len(buf) {
t.Fatalf("predicted compressed length is wrong: predicted %d, actual %d", predicted, len(buf))
}
}
func TestMsgCompressLengthEscaped(t *testing.T) {
msg := new(Msg)
msg.Compress = true
msg.SetQuestion("www.example.org.", TypeA)
msg.Answer = append(msg.Answer, &NS{Hdr: RR_Header{Name: `\000\001\002.example.org.`, Rrtype: TypeNS, Class: ClassINET}, Ns: `ns.\e\x\a\m\p\l\e.org.`})
msg.Answer = append(msg.Answer, &NS{Hdr: RR_Header{Name: `www.\e\x\a\m\p\l\e.org.`, Rrtype: TypeNS, Class: ClassINET}, Ns: "ns.example.org."})
predicted := msg.Len()
buf, err := msg.Pack()
if err != nil {
t.Error(err)
}
if predicted != len(buf) {
t.Fatalf("predicted compressed length is wrong: predicted %d, actual %d", predicted, len(buf))
}
}

24
listen_no_reuseport.go Normal file
View File

@ -0,0 +1,24 @@
//go:build !go1.11 || (!aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd)
// +build !go1.11 !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd
package dns
import "net"
const supportsReusePort = false
func listenTCP(network, addr string, reuseport bool) (net.Listener, error) {
if reuseport {
// TODO(tmthrgd): return an error?
}
return net.Listen(network, addr)
}
func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) {
if reuseport {
// TODO(tmthrgd): return an error?
}
return net.ListenPacket(network, addr)
}

45
listen_reuseport.go Normal file
View File

@ -0,0 +1,45 @@
//go:build go1.11 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd)
// +build go1.11
// +build aix darwin dragonfly freebsd linux netbsd openbsd
package dns
import (
"context"
"net"
"syscall"
"golang.org/x/sys/unix"
)
const supportsReusePort = true
func reuseportControl(network, address string, c syscall.RawConn) error {
var opErr error
err := c.Control(func(fd uintptr) {
opErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
})
if err != nil {
return err
}
return opErr
}
func listenTCP(network, addr string, reuseport bool) (net.Listener, error) {
var lc net.ListenConfig
if reuseport {
lc.Control = reuseportControl
}
return lc.Listen(context.Background(), network, addr)
}
func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) {
var lc net.ListenConfig
if reuseport {
lc.Control = reuseportControl
}
return lc.ListenPacket(context.Background(), network, addr)
}

842
msg.go

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
//+build ignore
//go:build ignore
// +build ignore
// msg_generate.go is meant to run with go generate. It will use
// go/{importer,types} to track down all the RR struct types. Then for each type
@ -10,11 +11,12 @@ import (
"bytes"
"fmt"
"go/format"
"go/importer"
"go/types"
"log"
"os"
"strings"
"golang.org/x/tools/go/packages"
)
var packageHdr = `
@ -34,6 +36,9 @@ func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
if !ok {
return nil, false
}
if st.NumFields() == 0 {
return nil, false
}
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
return st, false
}
@ -44,9 +49,19 @@ func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
return nil, false
}
// loadModule retrieves package description for a given module.
func loadModule(name string) (*types.Package, error) {
conf := packages.Config{Mode: packages.NeedTypes | packages.NeedTypesInfo}
pkgs, err := packages.Load(&conf, name)
if err != nil {
return nil, err
}
return pkgs[0].Types, nil
}
func main() {
// Import and type-check the package
pkg, err := importer.Default().Import("github.com/miekg/dns")
pkg, err := loadModule("github.com/miekg/dns")
fatalIfErr(err)
scope := pkg.Scope()
@ -80,13 +95,7 @@ func main() {
o := scope.Lookup(name)
st, _ := getTypeStruct(o.Type(), scope)
fmt.Fprintf(b, "func (rr *%s) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) {\n", name)
fmt.Fprint(b, `off, err := rr.Hdr.pack(msg, off, compression, compress)
if err != nil {
return off, err
}
headerEnd := off
`)
fmt.Fprintf(b, "func (rr *%s) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {\n", name)
for i := 1; i < st.NumFields(); i++ {
o := func(s string) {
fmt.Fprintf(b, s, st.Field(i).Name())
@ -105,8 +114,12 @@ return off, err
o("off, err = packDataOpt(rr.%s, msg, off)\n")
case `dns:"nsec"`:
o("off, err = packDataNsec(rr.%s, msg, off)\n")
case `dns:"pairs"`:
o("off, err = packDataSVCB(rr.%s, msg, off)\n")
case `dns:"domain-name"`:
o("off, err = packDataDomainNames(rr.%s, msg, off, compression, compress)\n")
o("off, err = packDataDomainNames(rr.%s, msg, off, compression, false)\n")
case `dns:"apl"`:
o("off, err = packDataApl(rr.%s, msg, off)\n")
default:
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
}
@ -116,9 +129,9 @@ return off, err
switch {
case st.Tag(i) == `dns:"-"`: // ignored
case st.Tag(i) == `dns:"cdomain-name"`:
o("off, err = PackDomainName(rr.%s, msg, off, compression, compress)\n")
o("off, err = packDomainName(rr.%s, msg, off, compression, compress)\n")
case st.Tag(i) == `dns:"domain-name"`:
o("off, err = PackDomainName(rr.%s, msg, off, compression, false)\n")
o("off, err = packDomainName(rr.%s, msg, off, compression, false)\n")
case st.Tag(i) == `dns:"a"`:
o("off, err = packDataA(rr.%s, msg, off)\n")
case st.Tag(i) == `dns:"aaaa"`:
@ -154,7 +167,8 @@ if rr.%s != "-" {
fallthrough
case st.Tag(i) == `dns:"hex"`:
o("off, err = packStringHex(rr.%s, msg, off)\n")
case st.Tag(i) == `dns:"any"`:
o("off, err = packStringAny(rr.%s, msg, off)\n")
case st.Tag(i) == `dns:"octet"`:
o("off, err = packStringOctet(rr.%s, msg, off)\n")
case st.Tag(i) == "":
@ -176,8 +190,6 @@ if rr.%s != "-" {
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
}
}
// We have packed everything, only now we know the rdlength of this RR
fmt.Fprintln(b, "rr.Header().Rdlength = uint16(off-headerEnd)")
fmt.Fprintln(b, "return off, nil }\n")
}
@ -186,14 +198,8 @@ if rr.%s != "-" {
o := scope.Lookup(name)
st, _ := getTypeStruct(o.Type(), scope)
fmt.Fprintf(b, "func unpack%s(h RR_Header, msg []byte, off int) (RR, int, error) {\n", name)
fmt.Fprintf(b, "rr := new(%s)\n", name)
fmt.Fprint(b, "rr.Hdr = h\n")
fmt.Fprint(b, `if noRdata(h) {
return rr, off, nil
}
var err error
rdStart := off
fmt.Fprintf(b, "func (rr *%s) unpack(msg []byte, off int) (off1 int, err error) {\n", name)
fmt.Fprint(b, `rdStart := off
_ = rdStart
`)
@ -201,7 +207,7 @@ _ = rdStart
o := func(s string) {
fmt.Fprintf(b, s, st.Field(i).Name())
fmt.Fprint(b, `if err != nil {
return rr, off, err
return off, err
}
`)
}
@ -221,7 +227,7 @@ return rr, off, err
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
}
fmt.Fprint(b, `if err != nil {
return rr, off, err
return off, err
}
`)
continue
@ -236,8 +242,12 @@ return rr, off, err
o("rr.%s, off, err = unpackDataOpt(msg, off)\n")
case `dns:"nsec"`:
o("rr.%s, off, err = unpackDataNsec(msg, off)\n")
case `dns:"pairs"`:
o("rr.%s, off, err = unpackDataSVCB(msg, off)\n")
case `dns:"domain-name"`:
o("rr.%s, off, err = unpackDataDomainNames(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
case `dns:"apl"`:
o("rr.%s, off, err = unpackDataApl(msg, off)\n")
default:
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
}
@ -264,6 +274,8 @@ return rr, off, err
o("rr.%s, off, err = unpackStringBase64(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
case `dns:"hex"`:
o("rr.%s, off, err = unpackStringHex(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
case `dns:"any"`:
o("rr.%s, off, err = unpackStringAny(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
case `dns:"octet"`:
o("rr.%s, off, err = unpackStringOctet(msg, off)\n")
case "":
@ -287,22 +299,13 @@ return rr, off, err
// If we've hit len(msg) we return without error.
if i < st.NumFields()-1 {
fmt.Fprintf(b, `if off == len(msg) {
return rr, off, nil
return off, nil
}
`)
}
}
fmt.Fprintf(b, "return rr, off, err }\n\n")
fmt.Fprintf(b, "return off, nil }\n\n")
}
// Generate typeToUnpack map
fmt.Fprintln(b, "var typeToUnpack = map[uint16]func(RR_Header, []byte, int) (RR, int, error){")
for _, name := range namedTypes {
if name == "RFC3597" {
continue
}
fmt.Fprintf(b, "Type%s: unpack%s,\n", name, name)
}
fmt.Fprintln(b, "}\n")
// gofmt
res, err := format.Source(b.Bytes())

View File

@ -6,7 +6,8 @@ import (
"encoding/binary"
"encoding/hex"
"net"
"strconv"
"sort"
"strings"
)
// helper functions called from the generated zmsg.go
@ -25,12 +26,13 @@ func unpackDataA(msg []byte, off int) (net.IP, int, error) {
}
func packDataA(a net.IP, msg []byte, off int) (int, error) {
// It must be a slice of 4, even if it is 16, we encode only the first 4
if off+net.IPv4len > len(msg) {
return len(msg), &Error{err: "overflow packing a"}
}
switch len(a) {
case net.IPv4len, net.IPv6len:
// It must be a slice of 4, even if it is 16, we encode only the first 4
if off+net.IPv4len > len(msg) {
return len(msg), &Error{err: "overflow packing a"}
}
copy(msg[off:], a.To4())
off += net.IPv4len
case 0:
@ -51,12 +53,12 @@ func unpackDataAAAA(msg []byte, off int) (net.IP, int, error) {
}
func packDataAAAA(aaaa net.IP, msg []byte, off int) (int, error) {
if off+net.IPv6len > len(msg) {
return len(msg), &Error{err: "overflow packing aaaa"}
}
switch len(aaaa) {
case net.IPv6len:
if off+net.IPv6len > len(msg) {
return len(msg), &Error{err: "overflow packing aaaa"}
}
copy(msg[off:], aaaa)
off += net.IPv6len
case 0:
@ -99,14 +101,14 @@ func unpackHeader(msg []byte, off int) (rr RR_Header, off1 int, truncmsg []byte,
return hdr, off, msg, err
}
// pack packs an RR header, returning the offset to the end of the header.
// packHeader packs an RR header, returning the offset to the end of the header.
// See PackDomainName for documentation about the compression.
func (hdr RR_Header) pack(msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) {
func (hdr RR_Header) packHeader(msg []byte, off int, compression compressionMap, compress bool) (int, error) {
if off == len(msg) {
return off, nil
}
off, err = PackDomainName(hdr.Name, msg, off, compression, compress)
off, err := packDomainName(hdr.Name, msg, off, compression, compress)
if err != nil {
return len(msg), err
}
@ -122,7 +124,7 @@ func (hdr RR_Header) pack(msg []byte, off int, compression map[string]int, compr
if err != nil {
return len(msg), err
}
off, err = packUint16(hdr.Rdlength, msg, off)
off, err = packUint16(0, msg, off) // The RDLENGTH field will be set later in packRR.
if err != nil {
return len(msg), err
}
@ -141,20 +143,24 @@ func truncateMsgFromRdlength(msg []byte, off int, rdlength uint16) (truncmsg []b
return msg[:lenrd], nil
}
var base32HexNoPadEncoding = base32.HexEncoding.WithPadding(base32.NoPadding)
func fromBase32(s []byte) (buf []byte, err error) {
for i, b := range s {
if b >= 'a' && b <= 'z' {
s[i] = b - 32
}
}
buflen := base32.HexEncoding.DecodedLen(len(s))
buflen := base32HexNoPadEncoding.DecodedLen(len(s))
buf = make([]byte, buflen)
n, err := base32.HexEncoding.Decode(buf, s)
n, err := base32HexNoPadEncoding.Decode(buf, s)
buf = buf[:n]
return
}
func toBase32(b []byte) string { return base32.HexEncoding.EncodeToString(b) }
func toBase32(b []byte) string {
return base32HexNoPadEncoding.EncodeToString(b)
}
func fromBase64(s []byte) (buf []byte, err error) {
buflen := base64.StdEncoding.DecodedLen(len(s))
@ -173,14 +179,14 @@ func unpackUint8(msg []byte, off int) (i uint8, off1 int, err error) {
if off+1 > len(msg) {
return 0, len(msg), &Error{err: "overflow unpacking uint8"}
}
return uint8(msg[off]), off + 1, nil
return msg[off], off + 1, nil
}
func packUint8(i uint8, msg []byte, off int) (off1 int, err error) {
if off+1 > len(msg) {
return len(msg), &Error{err: "overflow packing uint8"}
}
msg[off] = byte(i)
msg[off] = i
return off + 1, nil
}
@ -219,8 +225,8 @@ func unpackUint48(msg []byte, off int) (i uint64, off1 int, err error) {
return 0, len(msg), &Error{err: "overflow unpacking uint64 as uint48"}
}
// Used in TSIG where the last 48 bits are occupied, so for now, assume a uint48 (6 bytes)
i = (uint64(uint64(msg[off])<<40 | uint64(msg[off+1])<<32 | uint64(msg[off+2])<<24 | uint64(msg[off+3])<<16 |
uint64(msg[off+4])<<8 | uint64(msg[off+5])))
i = uint64(msg[off])<<40 | uint64(msg[off+1])<<32 | uint64(msg[off+2])<<24 | uint64(msg[off+3])<<16 |
uint64(msg[off+4])<<8 | uint64(msg[off+5])
off += 6
return i, off, nil
}
@ -260,32 +266,36 @@ func unpackString(msg []byte, off int) (string, int, error) {
return "", off, &Error{err: "overflow unpacking txt"}
}
l := int(msg[off])
if off+l+1 > len(msg) {
off++
if off+l > len(msg) {
return "", off, &Error{err: "overflow unpacking txt"}
}
s := make([]byte, 0, l)
for _, b := range msg[off+1 : off+1+l] {
switch b {
case '"', '\\':
s = append(s, '\\', b)
default:
if b < 32 || b > 127 { // unprintable
var buf [3]byte
bufs := strconv.AppendInt(buf[:0], int64(b), 10)
s = append(s, '\\')
for i := 0; i < 3-len(bufs); i++ {
s = append(s, '0')
}
for _, r := range bufs {
s = append(s, r)
}
} else {
s = append(s, b)
var s strings.Builder
consumed := 0
for i, b := range msg[off : off+l] {
switch {
case b == '"' || b == '\\':
if consumed == 0 {
s.Grow(l * 2)
}
s.Write(msg[off+consumed : off+i])
s.WriteByte('\\')
s.WriteByte(b)
consumed = i + 1
case b < ' ' || b > '~': // unprintable
if consumed == 0 {
s.Grow(l * 2)
}
s.Write(msg[off+consumed : off+i])
s.WriteString(escapeByte(b))
consumed = i + 1
}
}
off += 1 + l
return string(s), off, nil
if consumed == 0 { // no escaping needed
return string(msg[off : off+l]), off + l, nil
}
s.Write(msg[off+consumed : off+l])
return s.String(), off + l, nil
}
func packString(s string, msg []byte, off int) (int, error) {
@ -359,7 +369,7 @@ func packStringHex(s string, msg []byte, off int) (int, error) {
if err != nil {
return len(msg), err
}
if off+(len(h)) > len(msg) {
if off+len(h) > len(msg) {
return len(msg), &Error{err: "overflow packing hex"}
}
copy(msg[off:off+len(h)], h)
@ -367,6 +377,22 @@ func packStringHex(s string, msg []byte, off int) (int, error) {
return off, nil
}
func unpackStringAny(msg []byte, off, end int) (string, int, error) {
if end > len(msg) {
return "", len(msg), &Error{err: "overflow unpacking anything"}
}
return string(msg[off:end]), end, nil
}
func packStringAny(s string, msg []byte, off int) (int, error) {
if off+len(s) > len(msg) {
return len(msg), &Error{err: "overflow packing anything"}
}
copy(msg[off:off+len(s)], s)
off += len(s)
return off, nil
}
func unpackStringTxt(msg []byte, off int) ([]string, int, error) {
txt, off, err := unpackTxt(msg, off)
if err != nil {
@ -387,7 +413,7 @@ func packStringTxt(s []string, msg []byte, off int) (int, error) {
func unpackDataOpt(msg []byte, off int) ([]EDNS0, int, error) {
var edns []EDNS0
Option:
code := uint16(0)
var code uint16
if off+4 > len(msg) {
return nil, len(msg), &Error{err: "overflow unpacking opt"}
}
@ -398,79 +424,12 @@ Option:
if off+int(optlen) > len(msg) {
return nil, len(msg), &Error{err: "overflow unpacking opt"}
}
switch code {
case EDNS0NSID:
e := new(EDNS0_NSID)
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
return nil, len(msg), err
}
edns = append(edns, e)
off += int(optlen)
case EDNS0SUBNET:
e := new(EDNS0_SUBNET)
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
return nil, len(msg), err
}
edns = append(edns, e)
off += int(optlen)
case EDNS0COOKIE:
e := new(EDNS0_COOKIE)
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
return nil, len(msg), err
}
edns = append(edns, e)
off += int(optlen)
case EDNS0UL:
e := new(EDNS0_UL)
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
return nil, len(msg), err
}
edns = append(edns, e)
off += int(optlen)
case EDNS0LLQ:
e := new(EDNS0_LLQ)
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
return nil, len(msg), err
}
edns = append(edns, e)
off += int(optlen)
case EDNS0DAU:
e := new(EDNS0_DAU)
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
return nil, len(msg), err
}
edns = append(edns, e)
off += int(optlen)
case EDNS0DHU:
e := new(EDNS0_DHU)
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
return nil, len(msg), err
}
edns = append(edns, e)
off += int(optlen)
case EDNS0N3U:
e := new(EDNS0_N3U)
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
return nil, len(msg), err
}
edns = append(edns, e)
off += int(optlen)
case EDNS0PADDING:
e := new(EDNS0_PADDING)
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
return nil, len(msg), err
}
edns = append(edns, e)
off += int(optlen)
default:
e := new(EDNS0_LOCAL)
e.Code = code
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
return nil, len(msg), err
}
edns = append(edns, e)
off += int(optlen)
e := makeDataOpt(code)
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
return nil, len(msg), err
}
edns = append(edns, e)
off += int(optlen)
if off < len(msg) {
goto Option
@ -482,16 +441,14 @@ Option:
func packDataOpt(options []EDNS0, msg []byte, off int) (int, error) {
for _, el := range options {
b, err := el.pack()
if err != nil || off+3 > len(msg) {
if err != nil || off+4 > len(msg) {
return len(msg), &Error{err: "overflow packing opt"}
}
binary.BigEndian.PutUint16(msg[off:], el.Option()) // Option code
binary.BigEndian.PutUint16(msg[off+2:], uint16(len(b))) // Length
off += 4
if off+len(b) > len(msg) {
copy(msg[off:], b)
off = len(msg)
continue
return len(msg), &Error{err: "overflow packing opt"}
}
// Actual data
copy(msg[off:off+len(b)], b)
@ -519,7 +476,7 @@ func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
length, window, lastwindow := 0, 0, -1
for off < len(msg) {
if off+2 > len(msg) {
return nsec, len(msg), &Error{err: "overflow unpacking nsecx"}
return nsec, len(msg), &Error{err: "overflow unpacking NSEC(3)"}
}
window = int(msg[off])
length = int(msg[off+1])
@ -527,22 +484,21 @@ func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
if window <= lastwindow {
// RFC 4034: Blocks are present in the NSEC RR RDATA in
// increasing numerical order.
return nsec, len(msg), &Error{err: "out of order NSEC block"}
return nsec, len(msg), &Error{err: "out of order NSEC(3) block in type bitmap"}
}
if length == 0 {
// RFC 4034: Blocks with no types present MUST NOT be included.
return nsec, len(msg), &Error{err: "empty NSEC block"}
return nsec, len(msg), &Error{err: "empty NSEC(3) block in type bitmap"}
}
if length > 32 {
return nsec, len(msg), &Error{err: "NSEC block too long"}
return nsec, len(msg), &Error{err: "NSEC(3) block too long in type bitmap"}
}
if off+length > len(msg) {
return nsec, len(msg), &Error{err: "overflowing NSEC block"}
return nsec, len(msg), &Error{err: "overflowing NSEC(3) block in type bitmap"}
}
// Walk the bytes in the window and extract the type bits
for j := 0; j < length; j++ {
b := msg[off+j]
for j, b := range msg[off : off+length] {
// Check the bits one by one, and set the type
if b&0x80 == 0x80 {
nsec = append(nsec, uint16(window*256+j*8+0))
@ -575,13 +531,45 @@ func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
return nsec, off, nil
}
// typeBitMapLen is a helper function which computes the "maximum" length of
// a the NSEC Type BitMap field.
func typeBitMapLen(bitmap []uint16) int {
var l int
var lastwindow, lastlength uint16
for _, t := range bitmap {
window := t / 256
length := (t-window*256)/8 + 1
if window > lastwindow && lastlength != 0 { // New window, jump to the new offset
l += int(lastlength) + 2
lastlength = 0
}
if window < lastwindow || length < lastlength {
// packDataNsec would return Error{err: "nsec bits out of order"} here, but
// when computing the length, we want do be liberal.
continue
}
lastwindow, lastlength = window, length
}
l += int(lastlength) + 2
return l
}
func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) {
if len(bitmap) == 0 {
return off, nil
}
if off > len(msg) {
return off, &Error{err: "overflow packing nsec"}
}
toZero := msg[off:]
if maxLen := typeBitMapLen(bitmap); maxLen < len(toZero) {
toZero = toZero[:maxLen]
}
for i := range toZero {
toZero[i] = 0
}
var lastwindow, lastlength uint16
for j := 0; j < len(bitmap); j++ {
t := bitmap[j]
for _, t := range bitmap {
window := t / 256
length := (t-window*256)/8 + 1
if window > lastwindow && lastlength != 0 { // New window, jump to the new offset
@ -599,13 +587,72 @@ func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) {
// Setting the octets length
msg[off+1] = byte(length)
// Setting the bit value for the type in the right octet
msg[off+1+int(length)] |= byte(1 << (7 - (t % 8)))
msg[off+1+int(length)] |= byte(1 << (7 - t%8))
lastwindow, lastlength = window, length
}
off += int(lastlength) + 2
return off, nil
}
func unpackDataSVCB(msg []byte, off int) ([]SVCBKeyValue, int, error) {
var xs []SVCBKeyValue
var code uint16
var length uint16
var err error
for off < len(msg) {
code, off, err = unpackUint16(msg, off)
if err != nil {
return nil, len(msg), &Error{err: "overflow unpacking SVCB"}
}
length, off, err = unpackUint16(msg, off)
if err != nil || off+int(length) > len(msg) {
return nil, len(msg), &Error{err: "overflow unpacking SVCB"}
}
e := makeSVCBKeyValue(SVCBKey(code))
if e == nil {
return nil, len(msg), &Error{err: "bad SVCB key"}
}
if err := e.unpack(msg[off : off+int(length)]); err != nil {
return nil, len(msg), err
}
if len(xs) > 0 && e.Key() <= xs[len(xs)-1].Key() {
return nil, len(msg), &Error{err: "SVCB keys not in strictly increasing order"}
}
xs = append(xs, e)
off += int(length)
}
return xs, off, nil
}
func packDataSVCB(pairs []SVCBKeyValue, msg []byte, off int) (int, error) {
pairs = append([]SVCBKeyValue(nil), pairs...)
sort.Slice(pairs, func(i, j int) bool {
return pairs[i].Key() < pairs[j].Key()
})
prev := svcb_RESERVED
for _, el := range pairs {
if el.Key() == prev {
return len(msg), &Error{err: "repeated SVCB keys are not allowed"}
}
prev = el.Key()
packed, err := el.pack()
if err != nil {
return len(msg), err
}
off, err = packUint16(uint16(el.Key()), msg, off)
if err != nil {
return len(msg), &Error{err: "overflow packing SVCB"}
}
off, err = packUint16(uint16(len(packed)), msg, off)
if err != nil || off+len(packed) > len(msg) {
return len(msg), &Error{err: "overflow packing SVCB"}
}
copy(msg[off:off+len(packed)], packed)
off += len(packed)
}
return off, nil
}
func unpackDataDomainNames(msg []byte, off, end int) ([]string, int, error) {
var (
servers []string
@ -625,13 +672,141 @@ func unpackDataDomainNames(msg []byte, off, end int) ([]string, int, error) {
return servers, off, nil
}
func packDataDomainNames(names []string, msg []byte, off int, compression map[string]int, compress bool) (int, error) {
func packDataDomainNames(names []string, msg []byte, off int, compression compressionMap, compress bool) (int, error) {
var err error
for j := 0; j < len(names); j++ {
off, err = PackDomainName(names[j], msg, off, compression, false && compress)
for _, name := range names {
off, err = packDomainName(name, msg, off, compression, compress)
if err != nil {
return len(msg), err
}
}
return off, nil
}
func packDataApl(data []APLPrefix, msg []byte, off int) (int, error) {
var err error
for i := range data {
off, err = packDataAplPrefix(&data[i], msg, off)
if err != nil {
return len(msg), err
}
}
return off, nil
}
func packDataAplPrefix(p *APLPrefix, msg []byte, off int) (int, error) {
if len(p.Network.IP) != len(p.Network.Mask) {
return len(msg), &Error{err: "address and mask lengths don't match"}
}
var err error
prefix, _ := p.Network.Mask.Size()
addr := p.Network.IP.Mask(p.Network.Mask)[:(prefix+7)/8]
switch len(p.Network.IP) {
case net.IPv4len:
off, err = packUint16(1, msg, off)
case net.IPv6len:
off, err = packUint16(2, msg, off)
default:
err = &Error{err: "unrecognized address family"}
}
if err != nil {
return len(msg), err
}
off, err = packUint8(uint8(prefix), msg, off)
if err != nil {
return len(msg), err
}
var n uint8
if p.Negation {
n = 0x80
}
// trim trailing zero bytes as specified in RFC3123 Sections 4.1 and 4.2.
i := len(addr) - 1
for ; i >= 0 && addr[i] == 0; i-- {
}
addr = addr[:i+1]
adflen := uint8(len(addr)) & 0x7f
off, err = packUint8(n|adflen, msg, off)
if err != nil {
return len(msg), err
}
if off+len(addr) > len(msg) {
return len(msg), &Error{err: "overflow packing APL prefix"}
}
off += copy(msg[off:], addr)
return off, nil
}
func unpackDataApl(msg []byte, off int) ([]APLPrefix, int, error) {
var result []APLPrefix
for off < len(msg) {
prefix, end, err := unpackDataAplPrefix(msg, off)
if err != nil {
return nil, len(msg), err
}
off = end
result = append(result, prefix)
}
return result, off, nil
}
func unpackDataAplPrefix(msg []byte, off int) (APLPrefix, int, error) {
family, off, err := unpackUint16(msg, off)
if err != nil {
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"}
}
prefix, off, err := unpackUint8(msg, off)
if err != nil {
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"}
}
nlen, off, err := unpackUint8(msg, off)
if err != nil {
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"}
}
var ip []byte
switch family {
case 1:
ip = make([]byte, net.IPv4len)
case 2:
ip = make([]byte, net.IPv6len)
default:
return APLPrefix{}, len(msg), &Error{err: "unrecognized APL address family"}
}
if int(prefix) > 8*len(ip) {
return APLPrefix{}, len(msg), &Error{err: "APL prefix too long"}
}
afdlen := int(nlen & 0x7f)
if afdlen > len(ip) {
return APLPrefix{}, len(msg), &Error{err: "APL length too long"}
}
if off+afdlen > len(msg) {
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL address"}
}
// Address MUST NOT contain trailing zero bytes per RFC3123 Sections 4.1 and 4.2.
off += copy(ip, msg[off:off+afdlen])
if afdlen > 0 {
last := ip[afdlen-1]
if last == 0 {
return APLPrefix{}, len(msg), &Error{err: "extra APL address bits"}
}
}
ipnet := net.IPNet{
IP: ip,
Mask: net.CIDRMask(int(prefix), 8*len(ip)),
}
return APLPrefix{
Negation: (nlen & 0x80) != 0,
Network: ipnet,
}, off, nil
}

593
msg_helpers_test.go Normal file
View File

@ -0,0 +1,593 @@
package dns
import (
"bytes"
"net"
"testing"
)
// TestPacketDataNsec tests generated using fuzz.go and with a message pack
// containing the following bytes: 0000\x00\x00000000\x00\x002000000\x0060000\x00\x130000000000000000000"
// That bytes sequence created the overflow error and further permutations of that sequence were able to trigger
// the other code paths.
func TestPackDataNsec(t *testing.T) {
type args struct {
bitmap []uint16
msg []byte
off int
}
tests := []struct {
name string
args args
wantOff int
wantBytes []byte
wantErr bool
wantErrMsg string
}{
{
name: "overflow",
args: args{
bitmap: []uint16{
8962, 8963, 8970, 8971, 8978, 8979,
8986, 8987, 8994, 8995, 9002, 9003,
9010, 9011, 9018, 9019, 9026, 9027,
9034, 9035, 9042, 9043, 9050, 9051,
9058, 9059, 9066,
},
msg: []byte{
48, 48, 48, 48, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 50,
48, 48, 48, 48, 48, 48,
0, 54, 48, 48, 48, 48,
0, 19, 48, 48,
},
off: 48,
},
wantErr: true,
wantErrMsg: "dns: overflow packing nsec",
wantOff: 48,
},
{
name: "disordered nsec bits",
args: args{
bitmap: []uint16{
8962,
1,
},
msg: []byte{
48, 48, 48, 48, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 50, 48, 48, 48, 48, 48, 48, 0, 54, 48,
48, 48, 48, 0, 19, 48, 48, 48, 48, 48, 48, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 50, 48, 48,
48, 48, 48, 48, 0, 54, 48, 48, 48, 48, 0, 19,
48, 48, 48, 48, 48, 48, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 50, 48, 48, 48, 48, 48, 48, 0,
54, 48, 48, 48, 48, 0, 19, 48, 48, 48, 48, 48,
48, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 50,
48, 48, 48, 48, 48, 48, 0, 54, 48, 48, 48, 48,
0, 19, 48, 48, 48, 48, 48, 48, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 50, 48, 48, 48, 48, 48,
48, 0, 54, 48, 48, 48, 48, 0, 19, 48, 48,
},
off: 0,
},
wantErr: true,
wantErrMsg: "dns: nsec bits out of order",
wantOff: 155,
},
{
name: "simple message with only one window",
args: args{
bitmap: []uint16{
1,
},
msg: []byte{
48, 48, 48, 48, 0, 0,
0, 1, 0, 0, 0, 0,
0, 0, 50, 48, 48, 48,
48, 48, 48, 0, 54, 48,
48, 48, 48, 0, 19, 48, 48,
},
off: 0,
},
wantErr: false,
wantOff: 3,
wantBytes: []byte{0, 1, 64},
},
{
name: "multiple types",
args: args{
bitmap: []uint16{
TypeNS, TypeSOA, TypeRRSIG, TypeDNSKEY, TypeNSEC3PARAM,
},
msg: []byte{
48, 48, 48, 48, 0, 0,
0, 1, 0, 0, 0, 0,
0, 0, 50, 48, 48, 48,
48, 48, 48, 0, 54, 48,
48, 48, 48, 0, 19, 48, 48,
},
off: 0,
},
wantErr: false,
wantOff: 9,
wantBytes: []byte{0, 7, 34, 0, 0, 0, 0, 2, 144},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotOff, err := packDataNsec(tt.args.bitmap, tt.args.msg, tt.args.off)
if (err != nil) != tt.wantErr {
t.Errorf("packDataNsec() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil && tt.wantErrMsg != err.Error() {
t.Errorf("packDataNsec() error msg = %v, wantErrMsg %v", err.Error(), tt.wantErrMsg)
return
}
if gotOff != tt.wantOff {
t.Errorf("packDataNsec() = %v, want off %v", gotOff, tt.wantOff)
}
if err == nil && tt.args.off < len(tt.args.msg) && gotOff < len(tt.args.msg) {
if want, got := tt.wantBytes, tt.args.msg[tt.args.off:gotOff]; !bytes.Equal(got, want) {
t.Errorf("packDataNsec() = %v, want bytes %v", got, want)
}
}
})
}
}
func TestPackDataNsecDirtyBuffer(t *testing.T) {
zeroBuf := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0}
dirtyBuf := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}
off1, _ := packDataNsec([]uint16{TypeNS, TypeSOA, TypeRRSIG}, zeroBuf, 0)
off2, _ := packDataNsec([]uint16{TypeNS, TypeSOA, TypeRRSIG}, dirtyBuf, 0)
if off1 != off2 {
t.Errorf("off1 %v != off2 %v", off1, off2)
}
if !bytes.Equal(zeroBuf[:off1], dirtyBuf[:off2]) {
t.Errorf("dirty buffer differs from zero buffer: %v, %v", zeroBuf[:off1], dirtyBuf[:off2])
}
}
func BenchmarkPackDataNsec(b *testing.B) {
benches := []struct {
name string
types []uint16
}{
{"empty", nil},
{"typical", []uint16{TypeNS, TypeSOA, TypeRRSIG, TypeDNSKEY, TypeNSEC3PARAM}},
{"multiple_windows", []uint16{1, 300, 350, 10000, 20000}},
}
for _, bb := range benches {
b.Run(bb.name, func(b *testing.B) {
buf := make([]byte, 100)
for n := 0; n < b.N; n++ {
packDataNsec(bb.types, buf, 0)
}
})
}
}
func TestUnpackString(t *testing.T) {
msg := []byte("\x00abcdef\x0f\\\"ghi\x04mmm\x7f")
msg[0] = byte(len(msg) - 1)
got, _, err := unpackString(msg, 0)
if err != nil {
t.Fatal(err)
}
if want := `abcdef\015\\\"ghi\004mmm\127`; want != got {
t.Errorf("expected %q, got %q", want, got)
}
}
func BenchmarkUnpackString(b *testing.B) {
b.Run("Escaped", func(b *testing.B) {
msg := []byte("\x00abcdef\x0f\\\"ghi\x04mmm")
msg[0] = byte(len(msg) - 1)
for n := 0; n < b.N; n++ {
got, _, err := unpackString(msg, 0)
if err != nil {
b.Fatal(err)
}
if want := `abcdef\015\\\"ghi\004mmm`; want != got {
b.Errorf("expected %q, got %q", want, got)
}
}
})
b.Run("Unescaped", func(b *testing.B) {
msg := []byte("\x00large.example.com")
msg[0] = byte(len(msg) - 1)
for n := 0; n < b.N; n++ {
got, _, err := unpackString(msg, 0)
if err != nil {
b.Fatal(err)
}
if want := "large.example.com"; want != got {
b.Errorf("expected %q, got %q", want, got)
}
}
})
}
func TestPackDataAplPrefix(t *testing.T) {
tests := []struct {
name string
negation bool
ip net.IP
mask net.IPMask
expect []byte
}{
{
"1:192.0.2.0/24",
false,
net.ParseIP("192.0.2.0").To4(),
net.CIDRMask(24, 32),
[]byte{0x00, 0x01, 0x18, 0x03, 192, 0, 2},
},
{
"2:2001:db8:cafe::0/48",
false,
net.ParseIP("2001:db8:cafe::"),
net.CIDRMask(48, 128),
[]byte{0x00, 0x02, 0x30, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe},
},
{
"with trailing zero bytes 2:2001:db8:cafe::0/64",
false,
net.ParseIP("2001:db8:cafe::"),
net.CIDRMask(64, 128),
[]byte{0x00, 0x02, 0x40, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe},
},
{
"no non-zero bytes 2::/16",
false,
net.ParseIP("::"),
net.CIDRMask(16, 128),
[]byte{0x00, 0x02, 0x10, 0x00},
},
{
"!2:2001:db8::/32",
true,
net.ParseIP("2001:db8::"),
net.CIDRMask(32, 128),
[]byte{0x00, 0x02, 0x20, 0x84, 0x20, 0x01, 0x0d, 0xb8},
},
{
"normalize 1:198.51.103.255/22",
false,
net.ParseIP("198.51.103.255").To4(),
net.CIDRMask(22, 32),
[]byte{0x00, 0x01, 0x16, 0x03, 198, 51, 100}, // 1:198.51.100.0/22
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ap := &APLPrefix{
Negation: tt.negation,
Network: net.IPNet{IP: tt.ip, Mask: tt.mask},
}
out := make([]byte, 16)
off, err := packDataAplPrefix(ap, out, 0)
if err != nil {
t.Fatalf("expected no error, got %q", err)
}
if !bytes.Equal(tt.expect, out[:off]) {
t.Fatalf("expected output %02x, got %02x", tt.expect, out[:off])
}
// Make sure the packed bytes would be accepted by its own unpack
_, _, err = unpackDataAplPrefix(out, 0)
if err != nil {
t.Fatalf("expected no error, got %q", err)
}
})
}
}
func TestPackDataAplPrefix_Failures(t *testing.T) {
tests := []struct {
name string
ip net.IP
mask net.IPMask
}{
{
"family mismatch",
net.ParseIP("2001:db8::"),
net.CIDRMask(24, 32),
},
{
"unrecognized family",
[]byte{0x42},
[]byte{0xff},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ap := &APLPrefix{Network: net.IPNet{IP: tt.ip, Mask: tt.mask}}
msg := make([]byte, 16)
off, err := packDataAplPrefix(ap, msg, 0)
if err == nil {
t.Fatal("expected error, got none")
}
if off != len(msg) {
t.Fatalf("expected %d, got %d", len(msg), off)
}
})
}
}
func TestPackDataAplPrefix_BufferBounds(t *testing.T) {
ap := &APLPrefix{
Negation: false,
Network: net.IPNet{
IP: net.ParseIP("2001:db8::"),
Mask: net.CIDRMask(32, 128),
},
}
wire := []byte{0x00, 0x02, 0x20, 0x04, 0x20, 0x01, 0x0d, 0xb8}
t.Run("small", func(t *testing.T) {
msg := make([]byte, len(wire))
_, err := packDataAplPrefix(ap, msg, 1) // offset
if err == nil {
t.Fatal("expected error, got none")
}
})
t.Run("exact fit", func(t *testing.T) {
msg := make([]byte, len(wire))
off, err := packDataAplPrefix(ap, msg, 0)
if err != nil {
t.Fatalf("expected no error, got %q", err)
}
if !bytes.Equal(wire, msg[:off]) {
t.Fatalf("expected %02x, got %02x", wire, msg[:off])
}
})
}
func TestPackDataApl(t *testing.T) {
in := []APLPrefix{
{
Negation: true,
Network: net.IPNet{
IP: net.ParseIP("198.51.0.0").To4(),
Mask: net.CIDRMask(16, 32),
},
},
{
Negation: false,
Network: net.IPNet{
IP: net.ParseIP("2001:db8:beef::"),
Mask: net.CIDRMask(48, 128),
},
},
}
expect := []byte{
// 1:192.51.0.0/16
0x00, 0x01, 0x10, 0x82, 0xc6, 0x33,
// 2:2001:db8:beef::0/48
0x00, 0x02, 0x30, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xbe, 0xef,
}
msg := make([]byte, 32)
off, err := packDataApl(in, msg, 0)
if err != nil {
t.Fatalf("expected no error, got %q", err)
}
if !bytes.Equal(expect, msg[:off]) {
t.Fatalf("expected %02x, got %02x", expect, msg[:off])
}
}
func TestUnpackDataAplPrefix(t *testing.T) {
tests := []struct {
name string
wire []byte
negation bool
ip net.IP
mask net.IPMask
}{
{
"1:192.0.2.0/24",
[]byte{0x00, 0x01, 0x18, 0x03, 192, 0, 2},
false,
net.ParseIP("192.0.2.0").To4(),
net.CIDRMask(24, 32),
},
{
"2:2001:db8::/32",
[]byte{0x00, 0x02, 0x20, 0x04, 0x20, 0x01, 0x0d, 0xb8},
false,
net.ParseIP("2001:db8::"),
net.CIDRMask(32, 128),
},
{
"!2:2001:db8:8000::/33",
[]byte{0x00, 0x02, 0x21, 0x85, 0x20, 0x01, 0x0d, 0xb8, 0x80},
true,
net.ParseIP("2001:db8:8000::"),
net.CIDRMask(33, 128),
},
{
"1:0.0.0.0/0",
[]byte{0x00, 0x01, 0x00, 0x00},
false,
net.ParseIP("0.0.0.0").To4(),
net.CIDRMask(0, 32),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, off, err := unpackDataAplPrefix(tt.wire, 0)
if err != nil {
t.Fatalf("expected no error, got %q", err)
}
if off != len(tt.wire) {
t.Fatalf("expected offset %d, got %d", len(tt.wire), off)
}
if got.Negation != tt.negation {
t.Errorf("expected negation %v, got %v", tt.negation, got.Negation)
}
if !bytes.Equal(got.Network.IP, tt.ip) {
t.Errorf("expected IP %02x, got %02x", tt.ip, got.Network.IP)
}
if !bytes.Equal(got.Network.Mask, tt.mask) {
t.Errorf("expected mask %02x, got %02x", tt.mask, got.Network.Mask)
}
})
}
}
func TestUnpackDataAplPrefix_Errors(t *testing.T) {
tests := []struct {
name string
wire []byte
want string
}{
{
"incomplete header",
[]byte{0x00, 0x01, 0x18},
"dns: overflow unpacking APL prefix",
},
{
"unrecognized family",
[]byte{0x00, 0x03, 0x00, 0x00},
"dns: unrecognized APL address family",
},
{
"prefix too large for family IPv4",
[]byte{0x00, 0x01, 0x21, 0x04, 192, 0, 2, 0},
"dns: APL prefix too long",
},
{
"prefix too large for family IPv6",
[]byte{0x00, 0x02, 0x81, 0x85, 0x20, 0x01, 0x0d, 0xb8, 0x80},
"dns: APL prefix too long",
},
{
"afdlen too long for address family IPv4",
[]byte{0x00, 0x01, 22, 0x05, 192, 0, 2, 0, 0},
"dns: APL length too long",
},
{
"overflow unpacking APL address",
[]byte{0x00, 0x01, 0x10, 0x02, 192},
"dns: overflow unpacking APL address",
},
{
"address included trailing zeros",
[]byte{0x00, 0x01, 0x10, 0x04, 192, 0, 2, 0},
"dns: extra APL address bits",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, _, err := unpackDataAplPrefix(tt.wire, 0)
if err == nil {
t.Fatal("expected error, got none")
}
if err.Error() != tt.want {
t.Errorf("expected %s, got %s", tt.want, err.Error())
}
})
}
}
func TestUnpackDataApl(t *testing.T) {
wire := []byte{
// 2:2001:db8:cafe:4200:0/56
0x00, 0x02, 0x38, 0x07, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe, 0x42,
// 1:192.0.2.0/24
0x00, 0x01, 0x18, 0x03, 192, 0, 2,
// !1:192.0.2.128/25
0x00, 0x01, 0x19, 0x84, 192, 0, 2, 128,
// 1:10.0.0.0/24
0x00, 0x01, 0x18, 0x01, 0x0a,
// !1:10.0.0.1/32
0x00, 0x01, 0x20, 0x84, 0x0a, 0, 0, 1,
// !1:0.0.0.0/0
0x00, 0x01, 0x00, 0x80,
// 2::0/0
0x00, 0x02, 0x00, 0x00,
}
expect := []APLPrefix{
{
Negation: false,
Network: net.IPNet{
IP: net.ParseIP("2001:db8:cafe:4200::"),
Mask: net.CIDRMask(56, 128),
},
},
{
Negation: false,
Network: net.IPNet{
IP: net.ParseIP("192.0.2.0").To4(),
Mask: net.CIDRMask(24, 32),
},
},
{
Negation: true,
Network: net.IPNet{
IP: net.ParseIP("192.0.2.128").To4(),
Mask: net.CIDRMask(25, 32),
},
},
{
Negation: false,
Network: net.IPNet{
IP: net.ParseIP("10.0.0.0").To4(),
Mask: net.CIDRMask(24, 32),
},
},
{
Negation: true,
Network: net.IPNet{
IP: net.ParseIP("10.0.0.1").To4(),
Mask: net.CIDRMask(32, 32),
},
},
{
Negation: true,
Network: net.IPNet{
IP: net.ParseIP("0.0.0.0").To4(),
Mask: net.CIDRMask(0, 32),
},
},
{
Negation: false,
Network: net.IPNet{
IP: net.ParseIP("::").To16(),
Mask: net.CIDRMask(0, 128),
},
},
}
got, off, err := unpackDataApl(wire, 0)
if err != nil {
t.Fatalf("expected no error, got %q", err)
}
if off != len(wire) {
t.Fatalf("expected offset %d, got %d", len(wire), off)
}
if len(got) != len(expect) {
t.Fatalf("expected %d prefixes, got %d", len(expect), len(got))
}
for i, exp := range expect {
if got[i].Negation != exp.Negation {
t.Errorf("[%d] expected negation %v, got %v", i, exp.Negation, got[i].Negation)
}
if !bytes.Equal(got[i].Network.IP, exp.Network.IP) {
t.Errorf("[%d] expected IP %02x, got %02x", i, exp.Network.IP, got[i].Network.IP)
}
if !bytes.Equal(got[i].Network.Mask, exp.Network.Mask) {
t.Errorf("[%d] expected mask %02x, got %02x", i, exp.Network.Mask, got[i].Network.Mask)
}
}
}

View File

@ -8,14 +8,12 @@ import (
"testing"
)
const (
maxPrintableLabel = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789x"
tooLongLabel = maxPrintableLabel + "x"
)
const maxPrintableLabel = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789x"
var (
longDomain = maxPrintableLabel[:53] + strings.TrimSuffix(
strings.Join([]string{".", ".", ".", ".", "."}, maxPrintableLabel[:49]), ".")
reChar = regexp.MustCompile(`.`)
i = -1
maxUnprintableLabel = reChar.ReplaceAllStringFunc(maxPrintableLabel, func(ch string) string {
@ -24,8 +22,105 @@ var (
}
return fmt.Sprintf("\\%03d", i)
})
// These are the longest possible domain names in presentation format.
longestDomain = maxPrintableLabel[:61] + strings.Join([]string{".", ".", ".", "."}, maxPrintableLabel)
longestUnprintableDomain = maxUnprintableLabel[:61*4] + strings.Join([]string{".", ".", ".", "."}, maxUnprintableLabel)
)
func TestPackNoSideEffect(t *testing.T) {
m := new(Msg)
m.SetQuestion(Fqdn("example.com."), TypeNS)
a := new(Msg)
o := &OPT{
Hdr: RR_Header{
Name: ".",
Rrtype: TypeOPT,
},
}
o.SetUDPSize(DefaultMsgSize)
a.Extra = append(a.Extra, o)
a.SetRcode(m, RcodeBadVers)
a.Pack()
if a.Rcode != RcodeBadVers {
t.Errorf("after pack: Rcode is expected to be BADVERS")
}
}
func TestPackExtendedBadCookie(t *testing.T) {
m := new(Msg)
m.SetQuestion(Fqdn("example.com."), TypeNS)
a := new(Msg)
a.SetReply(m)
o := &OPT{
Hdr: RR_Header{
Name: ".",
Rrtype: TypeOPT,
},
}
o.SetUDPSize(DefaultMsgSize)
a.Extra = append(a.Extra, o)
a.SetRcode(m, RcodeBadCookie)
edns0 := a.IsEdns0()
if edns0 == nil {
t.Fatal("Expected OPT RR")
}
// SetExtendedRcode is only called as part of `Pack()`, hence at this stage,
// the OPT RR is not set yet.
if edns0.ExtendedRcode() == RcodeBadCookie&0xFFFFFFF0 {
t.Errorf("ExtendedRcode is expected to not be BADCOOKIE before Pack")
}
a.Pack()
edns0 = a.IsEdns0()
if edns0 == nil {
t.Fatal("Expected OPT RR")
}
if edns0.ExtendedRcode() != RcodeBadCookie&0xFFFFFFF0 {
t.Errorf("ExtendedRcode is expected to be BADCOOKIE after Pack")
}
}
func TestUnPackExtendedRcode(t *testing.T) {
m := new(Msg)
m.SetQuestion(Fqdn("example.com."), TypeNS)
a := new(Msg)
a.SetReply(m)
o := &OPT{
Hdr: RR_Header{
Name: ".",
Rrtype: TypeOPT,
},
}
o.SetUDPSize(DefaultMsgSize)
a.Extra = append(a.Extra, o)
a.SetRcode(m, RcodeBadCookie)
packed, err := a.Pack()
if err != nil {
t.Fatalf("Could not unpack %v", a)
}
unpacked := new(Msg)
if err := unpacked.Unpack(packed); err != nil {
t.Fatalf("Failed to unpack message")
}
if unpacked.Rcode != RcodeBadCookie {
t.Fatalf("Rcode should be matching RcodeBadCookie (%d), got (%d)", RcodeBadCookie, unpacked.Rcode)
}
}
func TestUnpackDomainName(t *testing.T) {
var cases = []struct {
label string
@ -38,19 +133,19 @@ func TestUnpackDomainName(t *testing.T) {
".",
""},
{"long label",
string(63) + maxPrintableLabel + "\x00",
"?" + maxPrintableLabel + "\x00",
maxPrintableLabel + ".",
""},
{"unprintable label",
string(63) + regexp.MustCompile(`\\[0-9]+`).ReplaceAllStringFunc(maxUnprintableLabel,
"?" + regexp.MustCompile(`\\[0-9]+`).ReplaceAllStringFunc(maxUnprintableLabel,
func(escape string) string {
n, _ := strconv.ParseInt(escape[1:], 10, 8)
return string(n)
return string(rune(n))
}) + "\x00",
maxUnprintableLabel + ".",
""},
{"long domain",
string(53) + strings.Replace(longDomain, ".", string(49), -1) + "\x00",
"5" + strings.Replace(longDomain, ".", "1", -1) + "\x00",
longDomain + ".",
""},
{"compression pointer",
@ -58,10 +153,9 @@ func TestUnpackDomainName(t *testing.T) {
"\x03foo" + "\x05\x03com\x00" + "\x07example" + "\xC0\x05",
"foo.\\003com\\000.example.com.",
""},
{"too long domain",
string(54) + "x" + strings.Replace(longDomain, ".", string(49), -1) + "\x00",
"x" + longDomain + ".",
"6" + "x" + strings.Replace(longDomain, ".", "1", -1) + "\x00",
"",
ErrLongDomain.Error()},
{"too long by pointer",
// a matryoshka doll name to get over 255 octets after expansion via internal pointers
@ -103,10 +197,12 @@ func TestUnpackDomainName(t *testing.T) {
""},
{"truncated name", "\x07example\x03", "", "dns: buffer size too small"},
{"non-absolute name", "\x07example\x03com", "", "dns: buffer size too small"},
{"compression pointer cycle",
{"compression pointer cycle (too many)", "\xC0\x00", "", "dns: too many compression pointers"},
{"compression pointer cycle (too long)",
"\x03foo" + "\x03bar" + "\x07example" + "\xC0\x04",
"",
"dns: too many compression pointers"},
ErrLongDomain.Error()},
{"forward compression pointer", "\x02\xC0\xFF\xC0\x01", "", ErrBuf.Error()},
{"reserved compression pointer 0b10", "\x07example\x80", "", "dns: bad rdata"},
{"reserved compression pointer 0b01", "\x07example\x40", "", "dns: bad rdata"},
}
@ -122,3 +218,109 @@ func TestUnpackDomainName(t *testing.T) {
}
}
}
func TestPackDomainNameCompressionMap(t *testing.T) {
expected := map[string]struct{}{
`www\.this.is.\131an.example.org.`: {},
`is.\131an.example.org.`: {},
`\131an.example.org.`: {},
`example.org.`: {},
`org.`: {},
}
msg := make([]byte, 256)
for _, compress := range []bool{true, false} {
compression := make(map[string]int)
_, err := PackDomainName(`www\.this.is.\131an.example.org.`, msg, 0, compression, compress)
if err != nil {
t.Fatalf("PackDomainName failed: %v", err)
}
if !compressionMapsEqual(expected, compression) {
t.Errorf("expected compression maps to be equal\n%s", compressionMapsDifference(expected, compression))
}
}
}
func TestPackDomainNameNSECTypeBitmap(t *testing.T) {
ownername := "some-very-long-ownername.com."
msg := &Msg{
Compress: true,
Answer: []RR{
&NS{
Hdr: RR_Header{
Name: ownername,
Rrtype: TypeNS,
Class: ClassINET,
},
Ns: "ns1.server.com.",
},
&NSEC{
Hdr: RR_Header{
Name: ownername,
Rrtype: TypeNSEC,
Class: ClassINET,
},
NextDomain: "a.com.",
TypeBitMap: []uint16{TypeNS, TypeNSEC},
},
},
}
// Pack msg and then unpack into msg2
buf, err := msg.Pack()
if err != nil {
t.Fatalf("msg.Pack failed: %v", err)
}
var msg2 Msg
if err := msg2.Unpack(buf); err != nil {
t.Fatalf("msg2.Unpack failed: %v", err)
}
if !IsDuplicate(msg.Answer[1], msg2.Answer[1]) {
t.Error("message differs after packing and unpacking")
// Print NSEC RR for both cases
t.Logf("expected: %v", msg.Answer[1])
t.Logf("got: %v", msg2.Answer[1])
}
}
func TestPackUnpackManyCompressionPointers(t *testing.T) {
m := new(Msg)
m.Compress = true
m.SetQuestion("example.org.", TypeNS)
for domain := "a."; len(domain) < maxDomainNameWireOctets; domain += "a." {
m.Answer = append(m.Answer, &NS{Hdr: RR_Header{Name: domain, Rrtype: TypeNS, Class: ClassINET}, Ns: "example.org."})
b, err := m.Pack()
if err != nil {
t.Fatalf("Pack failed for %q and %d records with: %v", domain, len(m.Answer), err)
}
var m2 Msg
if err := m2.Unpack(b); err != nil {
t.Fatalf("Unpack failed for %q and %d records with: %v", domain, len(m.Answer), err)
}
}
}
func TestLenDynamicA(t *testing.T) {
for _, rr := range []RR{
testRR("example.org. A"),
testRR("example.org. AAAA"),
testRR("example.org. L32"),
} {
msg := make([]byte, Len(rr))
off, err := PackRR(rr, msg, 0, nil, false)
if err != nil {
t.Fatalf("PackRR failed for %T: %v", rr, err)
}
if off != len(msg) {
t.Errorf("Len(rr) wrong for %T: Len(rr) = %d, PackRR(rr) = %d", rr, len(msg), off)
}
}
}

117
msg_truncate.go Normal file
View File

@ -0,0 +1,117 @@
package dns
// Truncate ensures the reply message will fit into the requested buffer
// size by removing records that exceed the requested size.
//
// It will first check if the reply fits without compression and then with
// compression. If it won't fit with compression, Truncate then walks the
// record adding as many records as possible without exceeding the
// requested buffer size.
//
// If the message fits within the requested size without compression,
// Truncate will set the message's Compress attribute to false. It is
// the caller's responsibility to set it back to true if they wish to
// compress the payload regardless of size.
//
// The TC bit will be set if any records were excluded from the message.
// If the TC bit is already set on the message it will be retained.
// TC indicates that the client should retry over TCP.
//
// According to RFC 2181, the TC bit should only be set if not all of the
// "required" RRs can be included in the response. Unfortunately, we have
// no way of knowing which RRs are required so we set the TC bit if any RR
// had to be omitted from the response.
//
// The appropriate buffer size can be retrieved from the requests OPT
// record, if present, and is transport specific otherwise. dns.MinMsgSize
// should be used for UDP requests without an OPT record, and
// dns.MaxMsgSize for TCP requests without an OPT record.
func (dns *Msg) Truncate(size int) {
if dns.IsTsig() != nil {
// To simplify this implementation, we don't perform
// truncation on responses with a TSIG record.
return
}
// RFC 6891 mandates that the payload size in an OPT record
// less than 512 (MinMsgSize) bytes must be treated as equal to 512 bytes.
//
// For ease of use, we impose that restriction here.
if size < MinMsgSize {
size = MinMsgSize
}
l := msgLenWithCompressionMap(dns, nil) // uncompressed length
if l <= size {
// Don't waste effort compressing this message.
dns.Compress = false
return
}
dns.Compress = true
edns0 := dns.popEdns0()
if edns0 != nil {
// Account for the OPT record that gets added at the end,
// by subtracting that length from our budget.
//
// The EDNS(0) OPT record must have the root domain and
// it's length is thus unaffected by compression.
size -= Len(edns0)
}
compression := make(map[string]struct{})
l = headerSize
for _, r := range dns.Question {
l += r.len(l, compression)
}
var numAnswer int
if l < size {
l, numAnswer = truncateLoop(dns.Answer, size, l, compression)
}
var numNS int
if l < size {
l, numNS = truncateLoop(dns.Ns, size, l, compression)
}
var numExtra int
if l < size {
_, numExtra = truncateLoop(dns.Extra, size, l, compression)
}
// See the function documentation for when we set this.
dns.Truncated = dns.Truncated || len(dns.Answer) > numAnswer ||
len(dns.Ns) > numNS || len(dns.Extra) > numExtra
dns.Answer = dns.Answer[:numAnswer]
dns.Ns = dns.Ns[:numNS]
dns.Extra = dns.Extra[:numExtra]
if edns0 != nil {
// Add the OPT record back onto the additional section.
dns.Extra = append(dns.Extra, edns0)
}
}
func truncateLoop(rrs []RR, size, l int, compression map[string]struct{}) (int, int) {
for i, r := range rrs {
if r == nil {
continue
}
l += r.len(l, compression)
if l > size {
// Return size, rather than l prior to this record,
// to prevent any further records being added.
return size, i
}
if l == size {
return l, i + 1
}
}
return l, len(rrs)
}

187
msg_truncate_test.go Normal file
View File

@ -0,0 +1,187 @@
package dns
import (
"fmt"
"testing"
)
func TestRequestTruncateAnswer(t *testing.T) {
m := new(Msg)
m.SetQuestion("large.example.com.", TypeSRV)
reply := new(Msg)
reply.SetReply(m)
for i := 1; i < 200; i++ {
reply.Answer = append(reply.Answer, testRR(
fmt.Sprintf("large.example.com. 10 IN SRV 0 0 80 10-0-0-%d.default.pod.k8s.example.com.", i)))
}
reply.Truncate(MinMsgSize)
if want, got := MinMsgSize, reply.Len(); want < got {
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
}
if !reply.Truncated {
t.Errorf("truncated bit should be set")
}
}
func TestRequestTruncateExtra(t *testing.T) {
m := new(Msg)
m.SetQuestion("large.example.com.", TypeSRV)
reply := new(Msg)
reply.SetReply(m)
for i := 1; i < 200; i++ {
reply.Extra = append(reply.Extra, testRR(
fmt.Sprintf("large.example.com. 10 IN SRV 0 0 80 10-0-0-%d.default.pod.k8s.example.com.", i)))
}
reply.Truncate(MinMsgSize)
if want, got := MinMsgSize, reply.Len(); want < got {
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
}
if !reply.Truncated {
t.Errorf("truncated bit should be set")
}
}
func TestRequestTruncateExtraEdns0(t *testing.T) {
const size = 4096
m := new(Msg)
m.SetQuestion("large.example.com.", TypeSRV)
m.SetEdns0(size, true)
reply := new(Msg)
reply.SetReply(m)
for i := 1; i < 200; i++ {
reply.Extra = append(reply.Extra, testRR(
fmt.Sprintf("large.example.com. 10 IN SRV 0 0 80 10-0-0-%d.default.pod.k8s.example.com.", i)))
}
reply.SetEdns0(size, true)
reply.Truncate(size)
if want, got := size, reply.Len(); want < got {
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
}
if !reply.Truncated {
t.Errorf("truncated bit should be set")
}
opt := reply.Extra[len(reply.Extra)-1]
if opt.Header().Rrtype != TypeOPT {
t.Errorf("expected last RR to be OPT")
}
}
func TestRequestTruncateExtraRegression(t *testing.T) {
const size = 2048
m := new(Msg)
m.SetQuestion("large.example.com.", TypeSRV)
m.SetEdns0(size, true)
reply := new(Msg)
reply.SetReply(m)
for i := 1; i < 33; i++ {
reply.Answer = append(reply.Answer, testRR(
fmt.Sprintf("large.example.com. 10 IN SRV 0 0 80 10-0-0-%d.default.pod.k8s.example.com.", i)))
}
for i := 1; i < 33; i++ {
reply.Extra = append(reply.Extra, testRR(
fmt.Sprintf("10-0-0-%d.default.pod.k8s.example.com. 10 IN A 10.0.0.%d", i, i)))
}
reply.SetEdns0(size, true)
reply.Truncate(size)
if want, got := size, reply.Len(); want < got {
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
}
if !reply.Truncated {
t.Errorf("truncated bit should be set")
}
opt := reply.Extra[len(reply.Extra)-1]
if opt.Header().Rrtype != TypeOPT {
t.Errorf("expected last RR to be OPT")
}
}
func TestTruncation(t *testing.T) {
reply := new(Msg)
for i := 0; i < 61; i++ {
reply.Answer = append(reply.Answer, testRR(fmt.Sprintf("http.service.tcp.srv.k8s.example.org. 5 IN SRV 0 0 80 10-144-230-%d.default.pod.k8s.example.org.", i)))
}
for i := 0; i < 5; i++ {
reply.Extra = append(reply.Extra, testRR(fmt.Sprintf("ip-10-10-52-5%d.subdomain.example.org. 5 IN A 10.10.52.5%d", i, i)))
}
for i := 0; i < 5; i++ {
reply.Ns = append(reply.Ns, testRR(fmt.Sprintf("srv.subdomain.example.org. 5 IN NS ip-10-10-33-6%d.subdomain.example.org.", i)))
}
for bufsize := 1024; bufsize <= 4096; bufsize += 12 {
m := new(Msg)
m.SetQuestion("http.service.tcp.srv.k8s.example.org.", TypeSRV)
m.SetEdns0(uint16(bufsize), true)
copy := reply.Copy()
copy.SetReply(m)
copy.Truncate(bufsize)
if want, got := bufsize, copy.Len(); want < got {
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
}
}
}
func TestRequestTruncateAnswerExact(t *testing.T) {
const size = 867 // Bit fiddly, but this hits the rl == size break clause in Truncate, 52 RRs should remain.
m := new(Msg)
m.SetQuestion("large.example.com.", TypeSRV)
m.SetEdns0(size, false)
reply := new(Msg)
reply.SetReply(m)
for i := 1; i < 200; i++ {
reply.Answer = append(reply.Answer, testRR(fmt.Sprintf("large.example.com. 10 IN A 127.0.0.%d", i)))
}
reply.Truncate(size)
if want, got := size, reply.Len(); want < got {
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
}
if expected := 52; len(reply.Answer) != expected {
t.Errorf("wrong number of answers; expected %d, got %d", expected, len(reply.Answer))
}
}
func BenchmarkMsgTruncate(b *testing.B) {
const size = 2048
m := new(Msg)
m.SetQuestion("example.com.", TypeA)
m.SetEdns0(size, true)
reply := new(Msg)
reply.SetReply(m)
for i := 1; i < 33; i++ {
reply.Answer = append(reply.Answer, testRR(
fmt.Sprintf("large.example.com. 10 IN SRV 0 0 80 10-0-0-%d.default.pod.k8s.example.com.", i)))
}
for i := 1; i < 33; i++ {
reply.Extra = append(reply.Extra, testRR(
fmt.Sprintf("10-0-0-%d.default.pod.k8s.example.com. 10 IN A 10.0.0.%d", i, i)))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
copy := reply.Copy()
b.StartTimer()
copy.Truncate(size)
}
}

View File

@ -2,53 +2,48 @@ package dns
import (
"crypto/sha1"
"hash"
"encoding/hex"
"strings"
)
type saltWireFmt struct {
Salt string `dns:"size-hex"`
}
// HashName hashes a string (label) according to RFC 5155. It returns the hashed string in uppercase.
func HashName(label string, ha uint8, iter uint16, salt string) string {
saltwire := new(saltWireFmt)
saltwire.Salt = salt
wire := make([]byte, DefaultMsgSize)
n, err := packSaltWire(saltwire, wire)
if ha != SHA1 {
return ""
}
wireSalt := make([]byte, hex.DecodedLen(len(salt)))
n, err := packStringHex(salt, wireSalt, 0)
if err != nil {
return ""
}
wire = wire[:n]
wireSalt = wireSalt[:n]
name := make([]byte, 255)
off, err := PackDomainName(strings.ToLower(label), name, 0, nil, false)
if err != nil {
return ""
}
name = name[:off]
var s hash.Hash
switch ha {
case SHA1:
s = sha1.New()
default:
return ""
}
s := sha1.New()
// k = 0
s.Write(name)
s.Write(wire)
s.Write(wireSalt)
nsec3 := s.Sum(nil)
// k > 0
for k := uint16(0); k < iter; k++ {
s.Reset()
s.Write(nsec3)
s.Write(wire)
s.Write(wireSalt)
nsec3 = s.Sum(nsec3[:0])
}
return toBase32(nsec3)
}
// Cover returns true if a name is covered by the NSEC3 record
// Cover returns true if a name is covered by the NSEC3 record.
func (rr *NSEC3) Cover(name string) bool {
nameHash := HashName(name, rr.Hash, rr.Iterations, rr.Salt)
owner := strings.ToUpper(rr.Hdr.Name)
@ -63,8 +58,10 @@ func (rr *NSEC3) Cover(name string) bool {
}
nextHash := rr.NextDomain
if ownerHash == nextHash { // empty interval
return false
// if empty interval found, try cover wildcard hashes so nameHash shouldn't match with ownerHash
if ownerHash == nextHash && nameHash != ownerHash { // empty interval
return true
}
if ownerHash > nextHash { // end of zone
if nameHash > ownerHash { // covered since there is nothing after ownerHash
@ -96,11 +93,3 @@ func (rr *NSEC3) Match(name string) bool {
}
return false
}
func packSaltWire(sw *saltWireFmt, msg []byte) (int, error) {
off, err := packStringHex(sw.Salt, msg, 0)
if err != nil {
return off, err
}
return off, nil
}

View File

@ -1,6 +1,9 @@
package dns
import "testing"
import (
"strconv"
"testing"
)
func TestPackNsec3(t *testing.T) {
nsec3 := HashName("dnsex.nl.", SHA1, 0, "DEAD")
@ -112,6 +115,18 @@ func TestNsec3(t *testing.T) {
name: "asd.com.",
covers: false,
},
{ // empty interval wildcard
rr: &NSEC3{
Hdr: RR_Header{Name: "2n1tb3vairuobl6rkdvii42n9tfmialp.com."},
Hash: 1,
Flags: 1,
Iterations: 5,
Salt: "F10E9F7EA83FC8F3",
NextDomain: "2N1TB3VAIRUOBL6RKDVII42N9TFMIALP",
},
name: "*.asd.com.",
covers: true,
},
{ // name hash is before owner hash, not covered
rr: &NSEC3{
Hdr: RR_Header{Name: "3V62ULR0NRE83V0RJA2VJGTLIF9V6RAB.com."},
@ -131,3 +146,25 @@ func TestNsec3(t *testing.T) {
}
}
}
func TestNsec3EmptySalt(t *testing.T) {
rr, _ := NewRR("CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 86400 IN NSEC3 1 1 0 - CK0Q1GIN43N1ARRC9OSM6QPQR81H5M9A NS SOA RRSIG DNSKEY NSEC3PARAM")
if !rr.(*NSEC3).Match("com.") {
t.Fatalf("expected record to match com. label")
}
}
func BenchmarkHashName(b *testing.B) {
for _, iter := range []uint16{
150, 2500, 5000, 10000, ^uint16(0),
} {
b.Run(strconv.Itoa(int(iter)), func(b *testing.B) {
for n := 0; n < b.N; n++ {
if HashName("some.example.org.", SHA1, iter, "deadbeef") == "" {
b.Fatalf("HashName failed")
}
}
})
}
}

View File

@ -245,8 +245,7 @@ func testTXTRRQuick(t *testing.T) {
rrbytes := make([]byte, 0, len(owner)+2+2+4+2+len(rdata))
rrbytes = append(rrbytes, owner...)
rrbytes = append(rrbytes, typeAndClass...)
rrbytes = append(rrbytes, byte(len(rdata)>>8))
rrbytes = append(rrbytes, byte(len(rdata)))
rrbytes = append(rrbytes, byte(len(rdata)>>8), byte(len(rdata)))
rrbytes = append(rrbytes, rdata...)
rr, _, err := UnpackRR(rrbytes, 0)
if err != nil {
@ -267,7 +266,7 @@ func testTXTRRQuick(t *testing.T) {
return false
}
if len(rdata) == 0 {
// string'ing won't produce any data to parse
// stringifying won't produce any data to parse
return true
}
rrString := rr.String()
@ -341,7 +340,7 @@ func TestParseDirectiveMisc(t *testing.T) {
func TestNSEC(t *testing.T) {
nsectests := map[string]string{
"nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F": "nl.\t3600\tIN\tNSEC3PARAM\t1 0 5 30923C44C6CBBB8F",
"nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F": "nl.\t3600\tIN\tNSEC3PARAM\t1 0 5 30923C44C6CBBB8F",
"p2209hipbpnm681knjnu0m1febshlv4e.nl. IN NSEC3 1 1 5 30923C44C6CBBB8F P90DG1KE8QEAN0B01613LHQDG0SOJ0TA NS SOA TXT RRSIG DNSKEY NSEC3PARAM": "p2209hipbpnm681knjnu0m1febshlv4e.nl.\t3600\tIN\tNSEC3\t1 1 5 30923C44C6CBBB8F P90DG1KE8QEAN0B01613LHQDG0SOJ0TA NS SOA TXT RRSIG DNSKEY NSEC3PARAM",
"localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC",
"localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC TYPE65534": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC TYPE65534",
@ -358,12 +357,29 @@ func TestNSEC(t *testing.T) {
t.Errorf("`%s' should be equal to\n`%s', but is `%s'", i, o, rr.String())
}
}
rr, err := NewRR("nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F")
if err != nil {
t.Fatal("failed to parse RR: ", err)
}
if nsec3param, ok := rr.(*NSEC3PARAM); ok {
if nsec3param.SaltLength != 8 {
t.Fatalf("nsec3param saltlen %d != 8", nsec3param.SaltLength)
}
} else {
t.Fatal("not nsec3 param: ", err)
}
}
func TestParseLOC(t *testing.T) {
lt := map[string]string{
"SW1A2AA.find.me.uk. LOC 51 30 12.748 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 30 12.748 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m",
"SW1A2AA.find.me.uk. LOC 51 0 0.0 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 00 0.000 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m",
"SW1A2AA.find.me.uk. LOC 51 0 0.0 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 00 0.000 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m",
"SW1A2AA.find.me.uk. LOC 51 30 12.748 N 00 07 39.611 W 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 30 12.748 N 00 07 39.611 W 0m 1m 10000m 10m",
// Exercise boundary cases
"SW1A2AA.find.me.uk. LOC 90 0 0.0 N 180 0 0.0 W 42849672.95 90000000.00m 90000000.00m 90000000.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t90 00 0.000 N 180 00 0.000 W 42849672.95m 90000000m 90000000m 90000000m",
"SW1A2AA.find.me.uk. LOC 89 59 59.999 N 179 59 59.999 W -100000 90000000.00m 90000000.00m 90000000m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t89 59 59.999 N 179 59 59.999 W -100000m 90000000m 90000000m 90000000m",
// use float64 to have enough precision.
"example.com. LOC 42 21 43.952 N 71 5 6.344 W -24m 1m 200m 10m": "example.com.\t3600\tIN\tLOC\t42 21 43.952 N 71 05 6.344 W -24m 1m 200m 10m",
}
for i, o := range lt {
rr, err := NewRR(i)
@ -375,6 +391,90 @@ func TestParseLOC(t *testing.T) {
t.Errorf("`%s' should be equal to\n`%s', but is `%s'", i, o, rr.String())
}
}
// Invalid cases (out of range values)
lt = map[string]string{ // Pair of (invalid) RDATA and the bad field name
// One of the subfields is out of range.
"91 0 0.0 N 00 07 39.611 W 0m": "Latitude",
"89 60 0.0 N 00 07 39.611 W 0m": "Latitude",
"89 00 60.0 N 00 07 39.611 W 0m": "Latitude",
"1 00 -1 N 00 07 39.611 W 0m": "Latitude",
"0 0 0.0 N 181 00 0.0 W 0m": "Longitude",
"0 0 0.0 N 179 60 0.0 W 0m": "Longitude",
"0 0 0.0 N 179 00 60.0 W 0m": "Longitude",
"0 0 0.0 N 1 00 -1 W 0m": "Longitude",
// Each subfield is valid, but resulting latitude would be out of range.
"90 01 00.0 N 00 07 39.611 W 0m": "Latitude",
"0 0 0.0 N 180 01 0.0 W 0m": "Longitude",
}
for rdata, field := range lt {
_, err := NewRR(fmt.Sprintf("example.com. LOC %s", rdata))
if err == nil || !strings.Contains(err.Error(), field) {
t.Errorf("expected error to contain %q, but got %v", field, err)
}
}
}
// this tests a subroutine for the LOC RR parser. It's complicated enough to test separately.
func TestStringToCm(t *testing.T) {
tests := []struct {
// Test description: the input token and the expected return values from stringToCm.
token string
e uint8
m uint8
ok bool
}{
{"100", 4, 1, true},
{"0100", 4, 1, true}, // leading 0 (allowed)
{"100.99", 4, 1, true},
{"90000000", 9, 9, true},
{"90000000.00", 9, 9, true},
{"0", 0, 0, true},
{"0.00", 0, 0, true},
{"0.01", 0, 1, true},
{".01", 0, 1, true}, // empty 'meter' part (allowed)
{"0.1", 1, 1, true},
// out of range (too large)
{"90000001", 0, 0, false},
{"90000000.01", 0, 0, false},
// more than 2 digits in 'cmeter' part
{"0.000", 0, 0, false},
{"0.001", 0, 0, false},
{"0.999", 0, 0, false},
// with plus or minus sign (disallowed)
{"-100", 0, 0, false},
{"+100", 0, 0, false},
{"0.-10", 0, 0, false},
{"0.+10", 0, 0, false},
{"0a.00", 0, 0, false}, // invalid string for 'meter' part
{".1x", 0, 0, false}, // invalid string for 'cmeter' part
{".", 0, 0, false}, // empty 'cmeter' part (disallowed)
{"1.", 0, 0, false}, // ditto
{"m", 0, 0, false}, // only the "m" suffix
}
for _, tc := range tests {
tc := tc
t.Run(tc.token, func(t *testing.T) {
// In all cases the expected result is the same with or without the 'm' suffix.
// So we test both cases using the same test code.
for _, sfx := range []string{"", "m"} {
token := tc.token + sfx
e, m, ok := stringToCm(token)
if ok != tc.ok {
t.Fatal("unexpected validation result")
}
if m != tc.m {
t.Fatalf("Expected %d, got %d", tc.m, m)
}
if e != tc.e {
t.Fatalf("Expected %d, got %d", tc.e, e)
}
}
})
}
}
func TestParseDS(t *testing.T) {
@ -398,16 +498,16 @@ func TestQuotes(t *testing.T) {
`t.example.com. IN TXT "a bc"`: "t.example.com.\t3600\tIN\tTXT\t\"a bc\"",
`t.example.com. IN TXT "a
bc"`: "t.example.com.\t3600\tIN\tTXT\t\"a\\010 bc\"",
`t.example.com. IN TXT ""`: "t.example.com.\t3600\tIN\tTXT\t\"\"",
`t.example.com. IN TXT "a"`: "t.example.com.\t3600\tIN\tTXT\t\"a\"",
`t.example.com. IN TXT "aa"`: "t.example.com.\t3600\tIN\tTXT\t\"aa\"",
`t.example.com. IN TXT "aaa" ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
`t.example.com. IN TXT "abc" "DEF"`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"",
`t.example.com. IN TXT "abc" ( "DEF" )`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"",
`t.example.com. IN TXT aaa ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
`t.example.com. IN TXT aaa aaa;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"",
`t.example.com. IN TXT aaa aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"",
`t.example.com. IN TXT aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
`t.example.com. IN TXT ""`: "t.example.com.\t3600\tIN\tTXT\t\"\"",
`t.example.com. IN TXT "a"`: "t.example.com.\t3600\tIN\tTXT\t\"a\"",
`t.example.com. IN TXT "aa"`: "t.example.com.\t3600\tIN\tTXT\t\"aa\"",
`t.example.com. IN TXT "aaa" ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
`t.example.com. IN TXT "abc" "DEF"`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"",
`t.example.com. IN TXT "abc" ( "DEF" )`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"",
`t.example.com. IN TXT aaa ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
`t.example.com. IN TXT aaa aaa;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"",
`t.example.com. IN TXT aaa aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"",
`t.example.com. IN TXT aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
"cid.urn.arpa. NAPTR 100 50 \"s\" \"z3950+I2L+I2C\" \"\" _z3950._tcp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"z3950+I2L+I2C\" \"\" _z3950._tcp.gatech.edu.",
"cid.urn.arpa. NAPTR 100 50 \"s\" \"rcds+I2C\" \"\" _rcds._udp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"rcds+I2C\" \"\" _rcds._udp.gatech.edu.",
"cid.urn.arpa. NAPTR 100 50 \"s\" \"http+I2L+I2C+I2R\" \"\" _http._tcp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"http+I2L+I2C+I2R\" \"\" _http._tcp.gatech.edu.",
@ -432,7 +532,7 @@ func TestParseClass(t *testing.T) {
"t.example.com. CH A 127.0.0.1": "t.example.com. 3600 CH A 127.0.0.1",
// ClassANY can not occur in zone files
// "t.example.com. ANY A 127.0.0.1": "t.example.com. 3600 ANY A 127.0.0.1",
"t.example.com. NONE A 127.0.0.1": "t.example.com. 3600 NONE A 127.0.0.1",
"t.example.com. NONE A 127.0.0.1": "t.example.com. 3600 NONE A 127.0.0.1",
"t.example.com. CLASS255 A 127.0.0.1": "t.example.com. 3600 CLASS255 A 127.0.0.1",
}
for i, o := range tests {
@ -534,21 +634,25 @@ $TTL 314
example.com. DNAME 10 ; TTL=314 after second $TTL
`
reCaseFromComment := regexp.MustCompile(`TTL=(\d+)\s+(.*)`)
records := ParseZone(strings.NewReader(zone), "", "")
z := NewZoneParser(strings.NewReader(zone), "", "")
var i int
for record := range records {
for rr, ok := z.Next(); ok; rr, ok = z.Next() {
i++
if record.Error != nil {
t.Error(record.Error)
expected := reCaseFromComment.FindStringSubmatch(z.Comment())
if len(expected) != 3 {
t.Errorf("regexp didn't match for record %d", i)
continue
}
expected := reCaseFromComment.FindStringSubmatch(record.Comment)
expectedTTL, _ := strconv.ParseUint(expected[1], 10, 32)
ttl := record.RR.Header().Ttl
ttl := rr.Header().Ttl
if ttl != uint32(expectedTTL) {
t.Errorf("%s: expected TTL %d, got %d", expected[2], expectedTTL, ttl)
}
}
if err := z.Err(); err != nil {
t.Error(err)
}
if i != 10 {
t.Errorf("expected %d records, got %d", 5, i)
}
@ -587,16 +691,12 @@ func TestRelativeNameErrors(t *testing.T) {
},
}
for _, errorCase := range badZones {
entries := ParseZone(strings.NewReader(errorCase.zoneContents), "", "")
for entry := range entries {
if entry.Error == nil {
t.Errorf("%s: expected error, got nil", errorCase.label)
continue
}
err := entry.Error.err
if err != errorCase.expectedErr {
t.Errorf("%s: expected error `%s`, got `%s`", errorCase.label, errorCase.expectedErr, err)
}
z := NewZoneParser(strings.NewReader(errorCase.zoneContents), "", "")
z.Next()
if err := z.Err(); err == nil {
t.Errorf("%s: expected error, got nil", errorCase.label)
} else if !strings.Contains(err.Error(), errorCase.expectedErr) {
t.Errorf("%s: expected error `%s`, got `%s`", errorCase.label, errorCase.expectedErr, err)
}
}
}
@ -703,9 +803,13 @@ func TestRfc1982(t *testing.T) {
}
func TestEmpty(t *testing.T) {
for range ParseZone(strings.NewReader(""), "", "") {
z := NewZoneParser(strings.NewReader(""), "", "")
for _, ok := z.Next(); ok; _, ok = z.Next() {
t.Errorf("should be empty")
}
if err := z.Err(); err != nil {
t.Error("got an error when it shouldn't")
}
}
func TestLowercaseTokens(t *testing.T) {
@ -847,20 +951,25 @@ func TestPX(t *testing.T) {
func TestComment(t *testing.T) {
// Comments we must see
comments := map[string]bool{"; this is comment 1": true,
"; this is comment 4": true, "; this is comment 6": true,
"; this is comment 7": true, "; this is comment 8": true}
comments := map[string]bool{
"; this is comment 1": true,
"; this is comment 2": true,
"; this is comment 4": true,
"; this is comment 6": true,
"; this is comment 7": true,
"; this is comment 8": true,
}
zone := `
foo. IN A 10.0.0.1 ; this is comment 1
foo. IN A (
10.0.0.2 ; this is comment2
10.0.0.2 ; this is comment 2
)
; this is comment3
; this is comment 3
foo. IN A 10.0.0.3
foo. IN A ( 10.0.0.4 ); this is comment 4
foo. IN A 10.0.0.5
; this is comment5
; this is comment 5
foo. IN A 10.0.0.6
@ -868,15 +977,117 @@ foo. IN DNSKEY 256 3 5 AwEAAb+8l ; this is comment 6
foo. IN NSEC miek.nl. TXT RRSIG NSEC; this is comment 7
foo. IN TXT "THIS IS TEXT MAN"; this is comment 8
`
for x := range ParseZone(strings.NewReader(zone), ".", "") {
if x.Error == nil {
if x.Comment != "" {
if _, ok := comments[x.Comment]; !ok {
t.Errorf("wrong comment %s", x.Comment)
}
z := NewZoneParser(strings.NewReader(zone), ".", "")
for _, ok := z.Next(); ok; _, ok = z.Next() {
if z.Comment() != "" {
if _, okC := comments[z.Comment()]; !okC {
t.Errorf("wrong comment %q", z.Comment())
}
}
}
if err := z.Err(); err != nil {
t.Error("got an error when it shouldn't")
}
}
func TestZoneParserComments(t *testing.T) {
for i, test := range []struct {
zone string
comments []string
}{
{
`name. IN SOA a6.nstld.com. hostmaster.nic.name. (
203362132 ; serial
5m ; refresh (5 minutes)
5m ; retry (5 minutes)
2w ; expire (2 weeks)
300 ; minimum (5 minutes)
) ; y
. 3600000 IN NS ONE.MY-ROOTS.NET. ; x`,
[]string{"; serial ; refresh (5 minutes) ; retry (5 minutes) ; expire (2 weeks) ; minimum (5 minutes) ; y", "; x"},
},
{
`name. IN SOA a6.nstld.com. hostmaster.nic.name. (
203362132 ; serial
5m ; refresh (5 minutes)
5m ; retry (5 minutes)
2w ; expire (2 weeks)
300 ; minimum (5 minutes)
) ; y
. 3600000 IN NS ONE.MY-ROOTS.NET.`,
[]string{"; serial ; refresh (5 minutes) ; retry (5 minutes) ; expire (2 weeks) ; minimum (5 minutes) ; y", ""},
},
{
`name. IN SOA a6.nstld.com. hostmaster.nic.name. (
203362132 ; serial
5m ; refresh (5 minutes)
5m ; retry (5 minutes)
2w ; expire (2 weeks)
300 ; minimum (5 minutes)
)
. 3600000 IN NS ONE.MY-ROOTS.NET.`,
[]string{"; serial ; refresh (5 minutes) ; retry (5 minutes) ; expire (2 weeks) ; minimum (5 minutes)", ""},
},
{
`name. IN SOA a6.nstld.com. hostmaster.nic.name. (
203362132 ; serial
5m ; refresh (5 minutes)
5m ; retry (5 minutes)
2w ; expire (2 weeks)
300 ; minimum (5 minutes)
)
. 3600000 IN NS ONE.MY-ROOTS.NET. ; x`,
[]string{"; serial ; refresh (5 minutes) ; retry (5 minutes) ; expire (2 weeks) ; minimum (5 minutes)", "; x"},
},
{
`name. IN SOA a6.nstld.com. hostmaster.nic.name. (
203362132 ; serial
5m ; refresh (5 minutes)
5m ; retry (5 minutes)
2w ; expire (2 weeks)
300 ; minimum (5 minutes)
)`,
[]string{"; serial ; refresh (5 minutes) ; retry (5 minutes) ; expire (2 weeks) ; minimum (5 minutes)"},
},
{
`. 3600000 IN NS ONE.MY-ROOTS.NET. ; x`,
[]string{"; x"},
},
{
`. 3600000 IN NS ONE.MY-ROOTS.NET.`,
[]string{""},
},
{
`. 3600000 IN NS ONE.MY-ROOTS.NET. ;;x`,
[]string{";;x"},
},
} {
r := strings.NewReader(test.zone)
var j int
z := NewZoneParser(r, "", "")
for rr, ok := z.Next(); ok; rr, ok = z.Next() {
if j >= len(test.comments) {
t.Fatalf("too many records for zone %d at %d record, expected %d", i, j+1, len(test.comments))
}
if z.Comment() != test.comments[j] {
t.Errorf("invalid comment for record %d:%d %v", i, j, rr)
t.Logf("expected %q", test.comments[j])
t.Logf("got %q", z.Comment())
}
j++
}
if err := z.Err(); err != nil {
t.Fatal(err)
}
if j != len(test.comments) {
t.Errorf("too few records for zone %d, got %d, expected %d", i, j, len(test.comments))
}
}
}
func TestEUIxx(t *testing.T) {
@ -928,8 +1139,8 @@ func TestTXT(t *testing.T) {
if rr.String() != `_raop._tcp.local. 60 IN TXT "single value"` {
t.Error("bad representation of TXT record:", rr.String())
}
if rr.len() != 28+1+12 {
t.Error("bad size of serialized record:", rr.len())
if Len(rr) != 28+1+12 {
t.Error("bad size of serialized record:", Len(rr))
}
}
@ -948,8 +1159,8 @@ func TestTXT(t *testing.T) {
if rr.String() != `_raop._tcp.local. 60 IN TXT "a=1" "b=2" "c=3" "d=4"` {
t.Error("bad representation of TXT multi value record:", rr.String())
}
if rr.len() != 28+1+3+1+3+1+3+1+3 {
t.Error("bad size of serialized multi value record:", rr.len())
if Len(rr) != 28+1+3+1+3+1+3+1+3 {
t.Error("bad size of serialized multi value record:", Len(rr))
}
}
@ -968,8 +1179,8 @@ func TestTXT(t *testing.T) {
if rr.String() != `_raop._tcp.local. 60 IN TXT ""` {
t.Error("bad representation of empty-string TXT record:", rr.String())
}
if rr.len() != 28+1 {
t.Error("bad size of serialized record:", rr.len())
if Len(rr) != 28+1 {
t.Error("bad size of serialized record:", Len(rr))
}
}
@ -998,8 +1209,8 @@ func TestTypeXXXX(t *testing.T) {
t.Errorf("this should not work, for TYPE655341")
}
_, err = NewRR("example.com IN TYPE1 \\# 4 0a000001")
if err == nil {
t.Errorf("this should not work")
if err != nil {
t.Errorf("failed to parse TYPE1 RR: %v", err)
}
}
@ -1102,9 +1313,8 @@ func TestNewPrivateKey(t *testing.T) {
algorithms := []algorithm{
{ECDSAP256SHA256, 256},
{ECDSAP384SHA384, 384},
{RSASHA1, 1024},
{RSASHA256, 2048},
{DSA, 1024},
{RSASHA1, 512},
{RSASHA256, 512},
{ED25519, 256},
}
@ -1305,10 +1515,10 @@ func TestParseSSHFP(t *testing.T) {
func TestParseHINFO(t *testing.T) {
dt := map[string]string{
"example.net. HINFO A B": "example.net. 3600 IN HINFO \"A\" \"B\"",
"example.net. HINFO A B": "example.net. 3600 IN HINFO \"A\" \"B\"",
"example.net. HINFO \"A\" \"B\"": "example.net. 3600 IN HINFO \"A\" \"B\"",
"example.net. HINFO A B C D E F": "example.net. 3600 IN HINFO \"A\" \"B C D E F\"",
"example.net. HINFO AB": "example.net. 3600 IN HINFO \"AB\" \"\"",
"example.net. HINFO AB": "example.net. 3600 IN HINFO \"AB\" \"\"",
// "example.net. HINFO PC-Intel-700mhz \"Redhat Linux 7.1\"": "example.net. 3600 IN HINFO \"PC-Intel-700mhz\" \"Redhat Linux 7.1\"",
// This one is recommended in Pro Bind book http://www.zytrax.com/books/dns/ch8/hinfo.html
// but effectively, even Bind would replace it to correctly formed text when you AXFR
@ -1328,9 +1538,9 @@ func TestParseHINFO(t *testing.T) {
func TestParseCAA(t *testing.T) {
lt := map[string]string{
"example.net. CAA 0 issue \"symantec.com\"": "example.net.\t3600\tIN\tCAA\t0 issue \"symantec.com\"",
"example.net. CAA 0 issue \"symantec.com\"": "example.net.\t3600\tIN\tCAA\t0 issue \"symantec.com\"",
"example.net. CAA 0 issuewild \"symantec.com; stuff\"": "example.net.\t3600\tIN\tCAA\t0 issuewild \"symantec.com; stuff\"",
"example.net. CAA 128 tbs \"critical\"": "example.net.\t3600\tIN\tCAA\t128 tbs \"critical\"",
"example.net. CAA 128 tbs \"critical\"": "example.net.\t3600\tIN\tCAA\t128 tbs \"critical\"",
"example.net. CAA 2 auth \"0>09\\006\\010+\\006\\001\\004\\001\\214y\\002\\003\\001\\006\\009`\\134H\\001e\\003\\004\\002\\001\\004 y\\209\\012\\221r\\220\\156Q\\218\\150\\150{\\166\\245:\\231\\182%\\157:\\133\\179}\\1923r\\238\\151\\255\\128q\\145\\002\\001\\000\"": "example.net.\t3600\tIN\tCAA\t2 auth \"0>09\\006\\010+\\006\\001\\004\\001\\214y\\002\\003\\001\\006\\009`\\134H\\001e\\003\\004\\002\\001\\004 y\\209\\012\\221r\\220\\156Q\\218\\150\\150{\\166\\245:\\231\\182%\\157:\\133\\179}\\1923r\\238\\151\\255\\128q\\145\\002\\001\\000\"",
"example.net. TYPE257 0 issue \"symantec.com\"": "example.net.\t3600\tIN\tCAA\t0 issue \"symantec.com\"",
}
@ -1424,6 +1634,91 @@ func TestParseCSYNC(t *testing.T) {
}
}
func TestParseSVCB(t *testing.T) {
svcbs := map[string]string{
`example.com. 3600 IN SVCB 0 cloudflare.com.`: `example.com. 3600 IN SVCB 0 cloudflare.com.`,
`example.com. 3600 IN SVCB 65000 cloudflare.com. alpn=h2 ipv4hint=3.4.3.2`: `example.com. 3600 IN SVCB 65000 cloudflare.com. alpn="h2" ipv4hint="3.4.3.2"`,
`example.com. 3600 IN SVCB 65000 cloudflare.com. key65000=4\ 3 key65001="\" " key65002 key65003= key65004="" key65005== key65006==\"\" key65007=\254 key65008=\032`: `example.com. 3600 IN SVCB 65000 cloudflare.com. key65000="4\ 3" key65001="\"\ " key65002="" key65003="" key65004="" key65005="=" key65006="=\"\"" key65007="\254" key65008="\ "`,
// Explained in svcb.go "In AliasMode, records SHOULD NOT include any SvcParams,"
`example.com. 3600 IN SVCB 0 no-default-alpn`: `example.com. 3600 IN SVCB 0 no-default-alpn.`,
// From the specification
`example.com. HTTPS 0 foo.example.com.`: `example.com. 3600 IN HTTPS 0 foo.example.com.`,
`example.com. SVCB 1 .`: `example.com. 3600 IN SVCB 1 .`,
`example.com. SVCB 16 foo.example.com. port=53`: `example.com. 3600 IN SVCB 16 foo.example.com. port="53"`,
`example.com. SVCB 1 foo.example.com. key667=hello`: `example.com. 3600 IN SVCB 1 foo.example.com. key667="hello"`,
`example.com. SVCB 1 foo.example.com. key667="hello\210qoo"`: `example.com. 3600 IN SVCB 1 foo.example.com. key667="hello\210qoo"`,
`example.com. SVCB 1 foo.example.com. ipv6hint="2001:db8::1,2001:db8::53:1"`: `example.com. 3600 IN SVCB 1 foo.example.com. ipv6hint="2001:db8::1,2001:db8::53:1"`,
`example.com. SVCB 1 example.com. ipv6hint="2001:db8::198.51.100.100"`: `example.com. 3600 IN SVCB 1 example.com. ipv6hint="2001:db8::c633:6464"`,
`example.com. SVCB 16 foo.example.org. alpn=h2,h3-19 mandatory=ipv4hint,alpn ipv4hint=192.0.2.1`: `example.com. 3600 IN SVCB 16 foo.example.org. alpn="h2,h3-19" mandatory="ipv4hint,alpn" ipv4hint="192.0.2.1"`,
`example.com. SVCB 16 foo.example.org. alpn="f\\\\oo\\,bar,h2"`: `example.com. 3600 IN SVCB 16 foo.example.org. alpn="f\\\092oo\\\044bar,h2"`,
`example.com. SVCB 16 foo.example.org. alpn=f\\\092oo\092,bar,h2`: `example.com. 3600 IN SVCB 16 foo.example.org. alpn="f\\\092oo\\\044bar,h2"`,
// From draft-ietf-add-ddr-06
`_dns.example.net. SVCB 1 example.net. alpn=h2 dohpath=/dns-query{?dns}`: `_dns.example.net. 3600 IN SVCB 1 example.net. alpn="h2" dohpath="/dns-query{?dns}"`,
`_dns.example.net. SVCB 1 example.net. alpn=h2 dohpath=/dns\045query{\?dns}`: `_dns.example.net. 3600 IN SVCB 1 example.net. alpn="h2" dohpath="/dns-query{?dns}"`,
}
for s, o := range svcbs {
rr, err := NewRR(s)
if err != nil {
t.Error("failed to parse RR: ", err)
continue
}
if rr.String() != o {
t.Errorf("`%s' should be equal to\n`%s', but is `%s'", s, o, rr.String())
}
}
}
func TestParseBadSVCB(t *testing.T) {
header := `example.com. 3600 IN HTTPS `
evils := []string{
`65536 . no-default-alpn`, // bad priority
`1 ..`, // bad domain
`1 . no-default-alpn=1`, // value illegal
`1 . key`, // invalid key
`1 . key=`, // invalid key
`1 . =`, // invalid key
`1 . ==`, // invalid key
`1 . =a`, // invalid key
`1 . ""`, // invalid key
`1 . ""=`, // invalid key
`1 . "a"`, // invalid key
`1 . "a"=`, // invalid key
`1 . key1=`, // we know that key
`1 . key65535`, // key reserved
`1 . key065534`, // key can't be padded
`1 . key65534="f`, // unterminated value
`1 . key65534="`, // unterminated value
`1 . key65534=\2`, // invalid numeric escape
`1 . key65534=\24`, // invalid numeric escape
`1 . key65534=\256`, // invalid numeric escape
`1 . key65534=\`, // invalid numeric escape
`1 . key65534=""alpn`, // zQuote ending needs whitespace
`1 . key65534="a"alpn`, // zQuote ending needs whitespace
`1 . ipv6hint=1.1.1.1`, // not ipv6
`1 . ipv6hint=1:1:1:1`, // not ipv6
`1 . ipv6hint=a`, // not ipv6
`1 . ipv6hint=`, // empty ipv6
`1 . ipv4hint=1.1.1.1.1`, // not ipv4
`1 . ipv4hint=::fc`, // not ipv4
`1 . ipv4hint=..11`, // not ipv4
`1 . ipv4hint=a`, // not ipv4
`1 . ipv4hint=`, // empty ipv4
`1 . port=`, // empty port
`1 . echconfig=YUd`, // bad base64
`1 . alpn=h\`, // unterminated escape
`1 . alpn=h2\\.h3`, // comma-separated list with bad character
`1 . alpn=h2,,h3`, // empty protocol identifier
`1 . alpn=h3,`, // final protocol identifier empty
}
for _, o := range evils {
_, err := NewRR(header + o)
if err == nil {
t.Error("failed to reject invalid RR: ", header+o)
continue
}
}
}
func TestParseBadNAPTR(t *testing.T) {
// Should look like: mplus.ims.vodafone.com. 3600 IN NAPTR 10 100 "S" "SIP+D2U" "" _sip._udp.mplus.ims.vodafone.com.
naptr := `mplus.ims.vodafone.com. 3600 IN NAPTR 10 100 S SIP+D2U _sip._udp.mplus.ims.vodafone.com.`
@ -1463,3 +1758,260 @@ func TestBad(t *testing.T) {
}
}
}
func TestNULLRecord(t *testing.T) {
// packet captured from iodine
packet := `8116840000010001000000000569627a6c700474657374046d69656b026e6c00000a0001c00c000a0001000000000005497f000001`
data, _ := hex.DecodeString(packet)
msg := new(Msg)
err := msg.Unpack(data)
if err != nil {
t.Fatalf("Failed to unpack NULL record")
}
if _, ok := msg.Answer[0].(*NULL); !ok {
t.Fatalf("Expected packet to contain NULL record")
}
}
func TestParseAPL(t *testing.T) {
tests := []struct {
name string
in string
expect string
}{
{
"v4",
". APL 1:192.0.2.0/24",
".\t3600\tIN\tAPL\t1:192.0.2.0/24",
},
{
"v6",
". APL 2:2001:db8::/32",
".\t3600\tIN\tAPL\t2:2001:db8::/32",
},
{
"null v6",
". APL 2:::/0",
".\t3600\tIN\tAPL\t2:::/0",
},
{
"null v4",
". APL 1:0.0.0.0/0",
".\t3600\tIN\tAPL\t1:0.0.0.0/0",
},
{
"full v6",
". APL 2:::/0",
".\t3600\tIN\tAPL\t2:::/0",
},
{
"full v4",
". APL 1:192.0.2.1/32",
".\t3600\tIN\tAPL\t1:192.0.2.1/32",
},
{
"full v6",
". APL 2:2001:0db8:d2b4:b6ba:50db:49cc:a8d1:5bb1/128",
".\t3600\tIN\tAPL\t2:2001:db8:d2b4:b6ba:50db:49cc:a8d1:5bb1/128",
},
{
"v4in6",
". APL 2:::ffff:192.0.2.0/120",
".\t3600\tIN\tAPL\t2:::ffff:192.0.2.0/120",
},
{
"v4in6 v6 syntax",
". APL 2:::ffff:c000:0200/120",
".\t3600\tIN\tAPL\t2:::ffff:192.0.2.0/120",
},
{
"negate",
". APL !1:192.0.2.0/24",
".\t3600\tIN\tAPL\t!1:192.0.2.0/24",
},
{
"multiple",
". APL 1:192.0.2.0/24 !1:192.0.2.1/32 2:2001:db8::/32 !2:2001:db8:1::0/48",
".\t3600\tIN\tAPL\t1:192.0.2.0/24 !1:192.0.2.1/32 2:2001:db8::/32 !2:2001:db8:1::/48",
},
{
"no address",
". APL",
".\t3600\tIN\tAPL\t",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
rr, err := NewRR(tc.in)
if err != nil {
t.Fatalf("failed to parse RR: %s", err)
}
got := rr.String()
if got != tc.expect {
t.Errorf("expected %q, got %q", tc.expect, got)
}
})
}
}
func TestParseAPLErrors(t *testing.T) {
tests := []struct {
name string
in string
}{
{
"unexpected",
`. APL ""`,
},
{
"unrecognized family",
". APL 3:0.0.0.0/0",
},
{
"malformed family",
". APL foo:0.0.0.0/0",
},
{
"malformed address",
". APL 1:192.0.2/16",
},
{
"extra bits",
". APL 2:2001:db8::/0",
},
{
"address mismatch v2",
". APL 1:2001:db8::/64",
},
{
"address mismatch v6",
". APL 2:192.0.2.1/32",
},
{
"no prefix",
". APL 1:192.0.2.1",
},
{
"no family",
". APL 0.0.0.0/0",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, err := NewRR(tc.in)
if err == nil {
t.Fatal("expected error, got none")
}
})
}
}
func TestUnpackRRWithHeaderInvalidLengths(t *testing.T) {
rr, err := NewRR("test.example.org. 300 IN SSHFP 1 2 BC6533CDC95A79078A39A56EA7635984ED655318ADA9B6159E30723665DA95BB")
if err != nil {
t.Fatalf("failed to parse SSHFP record: %v", err)
}
buf := make([]byte, Len(rr))
headerEnd, end, err := packRR(rr, buf, 0, compressionMap{}, false)
if err != nil {
t.Fatalf("failed to pack A record: %v", err)
}
rr.Header().Rdlength = uint16(end - headerEnd)
for _, off := range []int{
-1,
end + 1,
1<<16 - 1,
} {
_, _, err := UnpackRRWithHeader(*rr.Header(), buf, off)
if de, ok := err.(*Error); !ok || de.err != "bad off" {
t.Errorf("UnpackRRWithHeader with bad offset (%d) returned wrong or no error: %v", off, err)
}
}
for _, rdlength := range []uint16{
uint16(end - headerEnd + 1),
uint16(end),
1<<16 - 1,
} {
rr.Header().Rdlength = rdlength
_, _, err := UnpackRRWithHeader(*rr.Header(), buf, headerEnd)
if de, ok := err.(*Error); !ok || de.err != "bad rdlength" {
t.Errorf("UnpackRRWithHeader with bad rdlength (%d) returned wrong or no error: %v", rdlength, err)
}
}
}
func TestParseZONEMD(t *testing.T) {
// Uses examples from https://tools.ietf.org/html/rfc8976
dt := map[string]string{
// Simple Zone
`example. 86400 IN ZONEMD 2018031900 1 1 (
c68090d90a7aed71
6bc459f9340e3d7c
1370d4d24b7e2fc3
a1ddc0b9a87153b9
a9713b3c9ae5cc27
777f98b8e730044c )
`: "example.\t86400\tIN\tZONEMD\t2018031900 1 1 c68090d90a7aed716bc459f9340e3d7c1370d4d24b7e2fc3a1ddc0b9a87153b9a9713b3c9ae5cc27777f98b8e730044c",
// Complex Zone
`example. 86400 IN ZONEMD 2018031900 1 1 (
a3b69bad980a3504
e1cffcb0fd6397f9
3848071c93151f55
2ae2f6b1711d4bd2
d8b39808226d7b9d
b71e34b72077f8fe )
`: "example.\t86400\tIN\tZONEMD\t2018031900 1 1 a3b69bad980a3504e1cffcb0fd6397f93848071c93151f552ae2f6b1711d4bd2d8b39808226d7b9db71e34b72077f8fe",
// Multiple Digests Zone
`example. 86400 IN ZONEMD 2018031900 1 1 (
62e6cf51b02e54b9
b5f967d547ce4313
6792901f9f88e637
493daaf401c92c27
9dd10f0edb1c56f8
080211f8480ee306 )
`: "example.\t86400\tIN\tZONEMD\t2018031900 1 1 62e6cf51b02e54b9b5f967d547ce43136792901f9f88e637493daaf401c92c279dd10f0edb1c56f8080211f8480ee306",
`example. 86400 IN ZONEMD 2018031900 1 2 (
08cfa1115c7b948c
4163a901270395ea
226a930cd2cbcf2f
a9a5e6eb85f37c8a
4e114d884e66f176
eab121cb02db7d65
2e0cc4827e7a3204
f166b47e5613fd27 )
`: "example.\t86400\tIN\tZONEMD\t2018031900 1 2 08cfa1115c7b948c4163a901270395ea226a930cd2cbcf2fa9a5e6eb85f37c8a4e114d884e66f176eab121cb02db7d652e0cc4827e7a3204f166b47e5613fd27",
`example. 86400 IN ZONEMD 2018031900 1 240 (
e2d523f654b9422a
96c5a8f44607bbee )
`: "example. 86400 IN ZONEMD 2018031900 1 240 e2d523f654b9422a96c5a8f44607bbee",
`example. 86400 IN ZONEMD 2018031900 241 1 (
e1846540e33a9e41
89792d18d5d131f6
05fc283e )
`: "example. 86400 IN ZONEMD 2018031900 241 1 e1846540e33a9e4189792d18d5d131f605fc283e",
// URI.ARPA zone
`uri.arpa. 3600 IN ZONEMD 2018100702 1 1 (
0dbc3c4dbfd75777c12ca19c337854b1577799901307c482e9d91d5d15
cd934d16319d98e30c4201cf25a1d5a0254960 )`: "uri.arpa.\t3600\tIN\tZONEMD\t2018100702 1 1 0dbc3c4dbfd75777c12ca19c337854b1577799901307c482e9d91d5d15cd934d16319d98e30c4201cf25a1d5a0254960",
// ROOT-SERVERS.NET Zone
`root-servers.net. 3600000 IN ZONEMD 2018091100 1 1 (
f1ca0ccd91bd5573d9f431c00ee0101b2545c97602be0a97
8a3b11dbfc1c776d5b3e86ae3d973d6b5349ba7f04340f79 )
`: "root-servers.net.\t3600000\tIN\tZONEMD\t2018091100 1 1 f1ca0ccd91bd5573d9f431c00ee0101b2545c97602be0a978a3b11dbfc1c776d5b3e86ae3d973d6b5349ba7f04340f79",
}
for i, o := range dt {
rr, err := NewRR(i)
if err != nil {
t.Error("failed to parse RR: ", err)
continue
}
if rr.String() != o {
t.Errorf("`%s' should be equal to\n`%s', but is `%s'", i, o, rr.String())
}
}
}

View File

@ -1,24 +1,20 @@
package dns
import (
"fmt"
"strings"
)
import "strings"
// PrivateRdata is an interface used for implementing "Private Use" RR types, see
// RFC 6895. This allows one to experiment with new RR types, without requesting an
// official type code. Also see dns.PrivateHandle and dns.PrivateHandleRemove.
type PrivateRdata interface {
// String returns the text presentaton of the Rdata of the Private RR.
// String returns the text presentation of the Rdata of the Private RR.
String() string
// Parse parses the Rdata of the private RR.
Parse([]string) error
// Pack is used when packing a private RR into a buffer.
Pack([]byte) (int, error)
// Unpack is used when unpacking a private RR from a buffer.
// TODO(miek): diff. signature than Pack, see edns0.go for instance.
Unpack([]byte) (int, error)
// Copy copies the Rdata.
// Copy copies the Rdata into the PrivateRdata argument.
Copy(PrivateRdata) error
// Len returns the length in octets of the Rdata.
Len() int
@ -29,21 +25,8 @@ type PrivateRdata interface {
type PrivateRR struct {
Hdr RR_Header
Data PrivateRdata
}
func mkPrivateRR(rrtype uint16) *PrivateRR {
// Panics if RR is not an instance of PrivateRR.
rrfunc, ok := TypeToRR[rrtype]
if !ok {
panic(fmt.Sprintf("dns: invalid operation with Private RR type %d", rrtype))
}
anyrr := rrfunc()
switch rr := anyrr.(type) {
case *PrivateRR:
return rr
}
panic(fmt.Sprintf("dns: RR is not a PrivateRR, TypeToRR[%d] generator returned %T", rrtype, anyrr))
generator func() PrivateRdata // for copy
}
// Header return the RR header of r.
@ -52,98 +35,79 @@ func (r *PrivateRR) Header() *RR_Header { return &r.Hdr }
func (r *PrivateRR) String() string { return r.Hdr.String() + r.Data.String() }
// Private len and copy parts to satisfy RR interface.
func (r *PrivateRR) len() int { return r.Hdr.len() + r.Data.Len() }
func (r *PrivateRR) len(off int, compression map[string]struct{}) int {
l := r.Hdr.len(off, compression)
l += r.Data.Len()
return l
}
func (r *PrivateRR) copy() RR {
// make new RR like this:
rr := mkPrivateRR(r.Hdr.Rrtype)
newh := r.Hdr.copyHeader()
rr.Hdr = *newh
rr := &PrivateRR{r.Hdr, r.generator(), r.generator}
err := r.Data.Copy(rr.Data)
if err != nil {
panic("dns: got value that could not be used to copy Private rdata")
if err := r.Data.Copy(rr.Data); err != nil {
panic("dns: got value that could not be used to copy Private rdata: " + err.Error())
}
return rr
}
func (r *PrivateRR) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) {
off, err := r.Hdr.pack(msg, off, compression, compress)
if err != nil {
return off, err
}
headerEnd := off
func (r *PrivateRR) pack(msg []byte, off int, compression compressionMap, compress bool) (int, error) {
n, err := r.Data.Pack(msg[off:])
if err != nil {
return len(msg), err
}
off += n
r.Header().Rdlength = uint16(off - headerEnd)
return off, nil
}
func (r *PrivateRR) unpack(msg []byte, off int) (int, error) {
off1, err := r.Data.Unpack(msg[off:])
off += off1
return off, err
}
func (r *PrivateRR) parse(c *zlexer, origin string) *ParseError {
var l lex
text := make([]string, 0, 2) // could be 0..N elements, median is probably 1
Fetch:
for {
// TODO(miek): we could also be returning _QUOTE, this might or might not
// be an issue (basically parsing TXT becomes hard)
switch l, _ = c.Next(); l.value {
case zNewline, zEOF:
break Fetch
case zString:
text = append(text, l.token)
}
}
err := r.Data.Parse(text)
if err != nil {
return &ParseError{"", err.Error(), l}
}
return nil
}
func (r *PrivateRR) isDuplicate(r2 RR) bool { return false }
// PrivateHandle registers a private resource record type. It requires
// string and numeric representation of private RR type and generator function as argument.
func PrivateHandle(rtypestr string, rtype uint16, generator func() PrivateRdata) {
rtypestr = strings.ToUpper(rtypestr)
TypeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator()} }
TypeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator(), generator} }
TypeToString[rtype] = rtypestr
StringToType[rtypestr] = rtype
typeToUnpack[rtype] = func(h RR_Header, msg []byte, off int) (RR, int, error) {
if noRdata(h) {
return &h, off, nil
}
var err error
rr := mkPrivateRR(h.Rrtype)
rr.Hdr = h
off1, err := rr.Data.Unpack(msg[off:])
off += off1
if err != nil {
return rr, off, err
}
return rr, off, err
}
setPrivateRR := func(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) {
rr := mkPrivateRR(h.Rrtype)
rr.Hdr = h
var l lex
text := make([]string, 0, 2) // could be 0..N elements, median is probably 1
Fetch:
for {
// TODO(miek): we could also be returning _QUOTE, this might or might not
// be an issue (basically parsing TXT becomes hard)
switch l = <-c; l.value {
case zNewline, zEOF:
break Fetch
case zString:
text = append(text, l.token)
}
}
err := rr.Data.Parse(text)
if err != nil {
return nil, &ParseError{f, err.Error(), l}, ""
}
return rr, nil, ""
}
typeToparserFunc[rtype] = parserFunc{setPrivateRR, true}
}
// PrivateHandleRemove removes defenitions required to support private RR type.
// PrivateHandleRemove removes definitions required to support private RR type.
func PrivateHandleRemove(rtype uint16) {
rtypestr, ok := TypeToString[rtype]
if ok {
delete(TypeToRR, rtype)
delete(TypeToString, rtype)
delete(typeToparserFunc, rtype)
delete(StringToType, rtypestr)
delete(typeToUnpack, rtype)
}
return
}

View File

@ -158,9 +158,11 @@ func TestPrivateZoneParser(t *testing.T) {
defer dns.PrivateHandleRemove(TypeVERSION)
r := strings.NewReader(smallzone)
for x := range dns.ParseZone(r, ".", "") {
if err := x.Error; err != nil {
t.Fatal(err)
}
z := dns.NewZoneParser(r, ".", "")
for _, ok := z.Next(); ok; _, ok = z.Next() {
}
if err := z.Err(); err != nil {
t.Fatal(err)
}
}

View File

@ -1,49 +0,0 @@
package dns
import "encoding/binary"
// rawSetRdlength sets the rdlength in the header of
// the RR. The offset 'off' must be positioned at the
// start of the header of the RR, 'end' must be the
// end of the RR.
func rawSetRdlength(msg []byte, off, end int) bool {
l := len(msg)
Loop:
for {
if off+1 > l {
return false
}
c := int(msg[off])
off++
switch c & 0xC0 {
case 0x00:
if c == 0x00 {
// End of the domainname
break Loop
}
if off+c > l {
return false
}
off += c
case 0xC0:
// pointer, next byte included, ends domainname
off++
break Loop
}
}
// The domainname has been seen, we at the start of the fixed part in the header.
// Type is 2 bytes, class is 2 bytes, ttl 4 and then 2 bytes for the length.
off += 2 + 2 + 4
if off+2 > l {
return false
}
//off+1 is the end of the header, 'end' is the end of the rr
//so 'end' - 'off+2' is the length of the rdata
rdatalen := end - (off + 2)
if rdatalen > 0xFFFF {
return false
}
binary.BigEndian.PutUint16(msg[off:], uint16(rdatalen))
return true
}

View File

@ -1,19 +0,0 @@
package dns
import "testing"
const LinodeAddr = "176.58.119.54:53"
func TestClientRemote(t *testing.T) {
m := new(Msg)
m.SetQuestion("go.dns.miek.nl.", TypeTXT)
c := new(Client)
r, _, err := c.Exchange(m, LinodeAddr)
if err != nil {
t.Errorf("failed to exchange: %v", err)
}
if r != nil && r.Rcode != RcodeSuccess {
t.Errorf("failed to get an valid answer\n%v", r)
}
}

View File

@ -12,6 +12,20 @@ var StringToOpcode = reverseInt(OpcodeToString)
// StringToRcode is a map of rcodes to strings.
var StringToRcode = reverseInt(RcodeToString)
func init() {
// Preserve previous NOTIMP typo, see github.com/miekg/dns/issues/733.
StringToRcode["NOTIMPL"] = RcodeNotImplemented
}
// StringToAlgorithm is the reverse of AlgorithmToString.
var StringToAlgorithm = reverseInt8(AlgorithmToString)
// StringToHash is a map of names to hash IDs.
var StringToHash = reverseInt8(HashToString)
// StringToCertType is the reverseof CertTypeToString.
var StringToCertType = reverseInt16(CertTypeToString)
// Reverse a map
func reverseInt8(m map[uint8]string) map[string]uint8 {
n := make(map[string]uint8, len(m))

View File

@ -1,7 +1,11 @@
package dns
// testRR returns the RR from string s. The error is thrown away.
// testRR is a helper that wraps a call to NewRR and panics if the error is non-nil.
func testRR(s string) RR {
r, _ := NewRR(s)
r, err := NewRR(s)
if err != nil {
panic(err)
}
return r
}

View File

@ -5,6 +5,7 @@ package dns
// rrs.
// m is used to store the RRs temporary. If it is nil a new map will be allocated.
func Dedup(rrs []RR, m map[string]RR) []RR {
if m == nil {
m = make(map[string]RR)
}
@ -14,10 +15,11 @@ func Dedup(rrs []RR, m map[string]RR) []RR {
for _, r := range rrs {
key := normalizedString(r)
keys = append(keys, &key)
if _, ok := m[key]; ok {
if mr, ok := m[key]; ok {
// Shortest TTL wins.
if m[key].Header().Ttl > r.Header().Ttl {
m[key].Header().Ttl = r.Header().Ttl
rh, mrh := r.Header(), mr.Header()
if mrh.Ttl > rh.Ttl {
mrh.Ttl = rh.Ttl
}
continue
}

1145
scan.go

File diff suppressed because it is too large Load Diff

2041
scan_rr.go

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,60 @@
package dns
import (
"io"
"io/ioutil"
"net"
"os"
"strings"
"testing"
)
func TestParseZoneInclude(t *testing.T) {
func TestZoneParserGenerate(t *testing.T) {
zone := "$ORIGIN example.org.\n$GENERATE 10-12 foo${2,3,d} IN A 127.0.0.$"
wantRRs := []RR{
&A{Hdr: RR_Header{Name: "foo012.example.org."}, A: net.ParseIP("127.0.0.10")},
&A{Hdr: RR_Header{Name: "foo013.example.org."}, A: net.ParseIP("127.0.0.11")},
&A{Hdr: RR_Header{Name: "foo014.example.org."}, A: net.ParseIP("127.0.0.12")},
}
wantIdx := 0
z := NewZoneParser(strings.NewReader(zone), "", "")
for rr, ok := z.Next(); ok; rr, ok = z.Next() {
if wantIdx >= len(wantRRs) {
t.Fatalf("expected %d RRs, but got more", len(wantRRs))
}
if got, want := rr.Header().Name, wantRRs[wantIdx].Header().Name; got != want {
t.Fatalf("expected name %s, but got %s", want, got)
}
a, okA := rr.(*A)
if !okA {
t.Fatalf("expected *A RR, but got %T", rr)
}
if got, want := a.A, wantRRs[wantIdx].(*A).A; !got.Equal(want) {
t.Fatalf("expected A with IP %v, but got %v", got, want)
}
wantIdx++
}
if err := z.Err(); err != nil {
t.Fatalf("expected no error, but got %s", err)
}
if wantIdx != len(wantRRs) {
t.Errorf("too few records, expected %d, got %d", len(wantRRs), wantIdx)
}
}
func TestZoneParserInclude(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "dns")
if err != nil {
t.Fatalf("could not create tmpfile for test: %s", err)
}
defer os.Remove(tmpfile.Name())
if _, err := tmpfile.WriteString("foo\tIN\tA\t127.0.0.1"); err != nil {
t.Fatalf("unable to write content to tmpfile %q: %s", tmpfile.Name(), err)
@ -21,28 +63,285 @@ func TestParseZoneInclude(t *testing.T) {
t.Fatalf("could not close tmpfile %q: %s", tmpfile.Name(), err)
}
zone := "$ORIGIN example.org.\n$INCLUDE " + tmpfile.Name()
zone := "$ORIGIN example.org.\n$INCLUDE " + tmpfile.Name() + "\nbar\tIN\tA\t127.0.0.2"
tok := ParseZone(strings.NewReader(zone), "", "")
for x := range tok {
if x.Error != nil {
t.Fatalf("expected no error, but got %s", x.Error)
}
if x.RR.Header().Name != "foo.example.org." {
t.Fatalf("expected %s, but got %s", "foo.example.org.", x.RR.Header().Name)
var got int
z := NewZoneParser(strings.NewReader(zone), "", "")
z.SetIncludeAllowed(true)
for rr, ok := z.Next(); ok; _, ok = z.Next() {
switch rr.Header().Name {
case "foo.example.org.", "bar.example.org.":
default:
t.Fatalf("expected foo.example.org. or bar.example.org., but got %s", rr.Header().Name)
}
got++
}
if err := z.Err(); err != nil {
t.Fatalf("expected no error, but got %s", err)
}
if expected := 2; got != expected {
t.Errorf("failed to parse zone after include, expected %d records, got %d", expected, got)
}
os.Remove(tmpfile.Name())
tok = ParseZone(strings.NewReader(zone), "", "")
for x := range tok {
if x.Error == nil {
t.Fatalf("expected first token to contain an error but it didn't")
z = NewZoneParser(strings.NewReader(zone), "", "")
z.SetIncludeAllowed(true)
z.Next()
if err := z.Err(); err == nil ||
!strings.Contains(err.Error(), "failed to open") ||
!strings.Contains(err.Error(), tmpfile.Name()) ||
!strings.Contains(err.Error(), "no such file or directory") {
t.Fatalf(`expected error to contain: "failed to open", %q and "no such file or directory" but got: %s`,
tmpfile.Name(), err)
}
}
func TestZoneParserIncludeDisallowed(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "dns")
if err != nil {
t.Fatalf("could not create tmpfile for test: %s", err)
}
defer os.Remove(tmpfile.Name())
if _, err := tmpfile.WriteString("foo\tIN\tA\t127.0.0.1"); err != nil {
t.Fatalf("unable to write content to tmpfile %q: %s", tmpfile.Name(), err)
}
if err := tmpfile.Close(); err != nil {
t.Fatalf("could not close tmpfile %q: %s", tmpfile.Name(), err)
}
zp := NewZoneParser(strings.NewReader("$INCLUDE "+tmpfile.Name()), "example.org.", "")
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
}
const expect = "$INCLUDE directive not allowed"
if err := zp.Err(); err == nil || !strings.Contains(err.Error(), expect) {
t.Errorf("expected error to contain %q, got %v", expect, err)
}
}
func TestZoneParserAddressAAAA(t *testing.T) {
tests := []struct {
record string
want *AAAA
}{
{
record: "1.example.org. 600 IN AAAA ::1",
want: &AAAA{Hdr: RR_Header{Name: "1.example.org."}, AAAA: net.IPv6loopback},
},
{
record: "2.example.org. 600 IN AAAA ::FFFF:127.0.0.1",
want: &AAAA{Hdr: RR_Header{Name: "2.example.org."}, AAAA: net.ParseIP("::FFFF:127.0.0.1")},
},
}
for _, tc := range tests {
got, err := NewRR(tc.record)
if err != nil {
t.Fatalf("expected no error, but got %s", err)
}
if !strings.Contains(x.Error.Error(), "failed to open") ||
!strings.Contains(x.Error.Error(), tmpfile.Name()) {
t.Fatalf(`expected error to contain: "failed to open" and %q but got: %s`, tmpfile.Name(), x.Error)
aaaa, ok := got.(*AAAA)
if !ok {
t.Fatalf("expected *AAAA RR, but got %T", got)
}
if !aaaa.AAAA.Equal(tc.want.AAAA) {
t.Fatalf("expected AAAA with IP %v, but got %v", tc.want.AAAA, aaaa.AAAA)
}
}
}
func TestZoneParserAddressBad(t *testing.T) {
records := []string{
"1.bad.example.org. 600 IN A ::1",
"2.bad.example.org. 600 IN A ::FFFF:127.0.0.1",
"3.bad.example.org. 600 IN AAAA 127.0.0.1",
}
for _, record := range records {
const expect = "bad A"
if got, err := NewRR(record); err == nil || !strings.Contains(err.Error(), expect) {
t.Errorf("NewRR(%v) = %v, want err to contain %q", record, got, expect)
}
}
}
func TestParseTA(t *testing.T) {
rr, err := NewRR(` Ta 0 0 0`)
if err != nil {
t.Fatalf("expected no error, but got %s", err)
}
if rr == nil {
t.Fatal(`expected a normal RR, but got nil`)
}
}
var errTestReadError = &Error{"test error"}
type errReader struct{}
func (errReader) Read(p []byte) (int, error) { return 0, errTestReadError }
func TestParseZoneReadError(t *testing.T) {
rr, err := ReadRR(errReader{}, "")
if err == nil || !strings.Contains(err.Error(), errTestReadError.Error()) {
t.Errorf("expected error to contain %q, but got %v", errTestReadError, err)
}
if rr != nil {
t.Errorf("expected a nil RR, but got %v", rr)
}
}
func TestUnexpectedNewline(t *testing.T) {
zone := `
example.com. 60 PX
1000 TXT 1K
`
zp := NewZoneParser(strings.NewReader(zone), "example.com.", "")
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
}
const expect = `dns: unexpected newline: "\n" at line: 2:18`
if err := zp.Err(); err == nil || err.Error() != expect {
t.Errorf("expected error to contain %q, got %v", expect, err)
}
// Test that newlines inside braces still work.
zone = `
example.com. 60 PX (
1000 TXT 1K )
`
zp = NewZoneParser(strings.NewReader(zone), "example.com.", "")
var count int
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
count++
}
if count != 1 {
t.Errorf("expected 1 record, got %d", count)
}
if err := zp.Err(); err != nil {
t.Errorf("unexpected error: %v", err)
}
}
func TestParseRFC3597InvalidLength(t *testing.T) {
// We need to space separate the 00s otherwise it will exceed the maximum token size
// of the zone lexer.
_, err := NewRR("example. 3600 CLASS1 TYPE1 \\# 65536 " + strings.Repeat("00 ", 65536))
if err == nil {
t.Error("should not have parsed excessively long RFC3579 record")
}
}
func TestParseKnownRRAsRFC3597(t *testing.T) {
t.Run("with RDATA", func(t *testing.T) {
// This was found by oss-fuzz.
_, err := NewRR("example. 3600 tYpe44 \\# 03 75 0100")
if err != nil {
t.Errorf("failed to parse RFC3579 format: %v", err)
}
rr, err := NewRR("example. 3600 CLASS1 TYPE1 \\# 4 7f000001")
if err != nil {
t.Fatalf("failed to parse RFC3579 format: %v", err)
}
if rr.Header().Rrtype != TypeA {
t.Errorf("expected TypeA (1) Rrtype, but got %v", rr.Header().Rrtype)
}
a, ok := rr.(*A)
if !ok {
t.Fatalf("expected *A RR, but got %T", rr)
}
localhost := net.IPv4(127, 0, 0, 1)
if !a.A.Equal(localhost) {
t.Errorf("expected A with IP %v, but got %v", localhost, a.A)
}
})
t.Run("without RDATA", func(t *testing.T) {
rr, err := NewRR("example. 3600 CLASS1 TYPE1 \\# 0")
if err != nil {
t.Fatalf("failed to parse RFC3579 format: %v", err)
}
if rr.Header().Rrtype != TypeA {
t.Errorf("expected TypeA (1) Rrtype, but got %v", rr.Header().Rrtype)
}
a, ok := rr.(*A)
if !ok {
t.Fatalf("expected *A RR, but got %T", rr)
}
if len(a.A) != 0 {
t.Errorf("expected A with empty IP, but got %v", a.A)
}
})
}
func BenchmarkNewRR(b *testing.B) {
const name1 = "12345678901234567890123456789012345.12345678.123."
const s = name1 + " 3600 IN MX 10 " + name1
for n := 0; n < b.N; n++ {
_, err := NewRR(s)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkReadRR(b *testing.B) {
const name1 = "12345678901234567890123456789012345.12345678.123."
const s = name1 + " 3600 IN MX 10 " + name1 + "\n"
for n := 0; n < b.N; n++ {
r := struct{ io.Reader }{strings.NewReader(s)}
// r is now only an io.Reader and won't benefit from the
// io.ByteReader special-case in zlexer.Next.
_, err := ReadRR(r, "")
if err != nil {
b.Fatal(err)
}
}
}
const benchZone = `
foo. IN A 10.0.0.1 ; this is comment 1
foo. IN A (
10.0.0.2 ; this is comment 2
)
; this is comment 3
foo. IN A 10.0.0.3
foo. IN A ( 10.0.0.4 ); this is comment 4
foo. IN A 10.0.0.5
; this is comment 5
foo. IN A 10.0.0.6
foo. IN DNSKEY 256 3 5 AwEAAb+8l ; this is comment 6
foo. IN NSEC miek.nl. TXT RRSIG NSEC; this is comment 7
foo. IN TXT "THIS IS TEXT MAN"; this is comment 8
`
func BenchmarkZoneParser(b *testing.B) {
for n := 0; n < b.N; n++ {
zp := NewZoneParser(strings.NewReader(benchZone), "example.org.", "")
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
}
if err := zp.Err(); err != nil {
b.Fatal(err)
}
}
}

View File

@ -1,56 +0,0 @@
package dns
// Implement a simple scanner, return a byte stream from an io reader.
import (
"bufio"
"context"
"io"
"text/scanner"
)
type scan struct {
src *bufio.Reader
position scanner.Position
eof bool // Have we just seen a eof
ctx context.Context
}
func scanInit(r io.Reader) (*scan, context.CancelFunc) {
s := new(scan)
s.src = bufio.NewReader(r)
s.position.Line = 1
ctx, cancel := context.WithCancel(context.Background())
s.ctx = ctx
return s, cancel
}
// tokenText returns the next byte from the input
func (s *scan) tokenText() (byte, error) {
c, err := s.src.ReadByte()
if err != nil {
return c, err
}
select {
case <-s.ctx.Done():
return c, context.Canceled
default:
break
}
// delay the newline handling until the next token is delivered,
// fixes off-by-one errors when reporting a parse error.
if s.eof == true {
s.position.Line++
s.position.Column = 0
s.eof = false
}
if c == '\n' {
s.eof = true
return c, nil
}
s.position.Column++
return c, nil
}

122
serve_mux.go Normal file
View File

@ -0,0 +1,122 @@
package dns
import (
"sync"
)
// ServeMux is an DNS request multiplexer. It matches the zone name of
// each incoming request against a list of registered patterns add calls
// the handler for the pattern that most closely matches the zone name.
//
// ServeMux is DNSSEC aware, meaning that queries for the DS record are
// redirected to the parent zone (if that is also registered), otherwise
// the child gets the query.
//
// ServeMux is also safe for concurrent access from multiple goroutines.
//
// The zero ServeMux is empty and ready for use.
type ServeMux struct {
z map[string]Handler
m sync.RWMutex
}
// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux {
return new(ServeMux)
}
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = NewServeMux()
func (mux *ServeMux) match(q string, t uint16) Handler {
mux.m.RLock()
defer mux.m.RUnlock()
if mux.z == nil {
return nil
}
q = CanonicalName(q)
var handler Handler
for off, end := 0, false; !end; off, end = NextLabel(q, off) {
if h, ok := mux.z[q[off:]]; ok {
if t != TypeDS {
return h
}
// Continue for DS to see if we have a parent too, if so delegate to the parent
handler = h
}
}
// Wildcard match, if we have found nothing try the root zone as a last resort.
if h, ok := mux.z["."]; ok {
return h
}
return handler
}
// Handle adds a handler to the ServeMux for pattern.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
if pattern == "" {
panic("dns: invalid pattern " + pattern)
}
mux.m.Lock()
if mux.z == nil {
mux.z = make(map[string]Handler)
}
mux.z[CanonicalName(pattern)] = handler
mux.m.Unlock()
}
// HandleFunc adds a handler function to the ServeMux for pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) {
mux.Handle(pattern, HandlerFunc(handler))
}
// HandleRemove deregisters the handler specific for pattern from the ServeMux.
func (mux *ServeMux) HandleRemove(pattern string) {
if pattern == "" {
panic("dns: invalid pattern " + pattern)
}
mux.m.Lock()
delete(mux.z, CanonicalName(pattern))
mux.m.Unlock()
}
// ServeDNS dispatches the request to the handler whose pattern most
// closely matches the request message.
//
// ServeDNS is DNSSEC aware, meaning that queries for the DS record
// are redirected to the parent zone (if that is also registered),
// otherwise the child gets the query.
//
// If no handler is found, or there is no question, a standard REFUSED
// message is returned
func (mux *ServeMux) ServeDNS(w ResponseWriter, req *Msg) {
var h Handler
if len(req.Question) >= 1 { // allow more than one question
h = mux.match(req.Question[0].Name, req.Question[0].Qtype)
}
if h != nil {
h.ServeDNS(w, req)
} else {
handleRefused(w, req)
}
}
// Handle registers the handler with the given pattern
// in the DefaultServeMux. The documentation for
// ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
// HandleRemove deregisters the handle with the given pattern
// in the DefaultServeMux.
func HandleRemove(pattern string) { DefaultServeMux.HandleRemove(pattern) }
// HandleFunc registers the handler function with the given pattern
// in the DefaultServeMux.
func HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) {
DefaultServeMux.HandleFunc(pattern, handler)
}

72
serve_mux_test.go Normal file
View File

@ -0,0 +1,72 @@
package dns
import "testing"
func TestDotAsCatchAllWildcard(t *testing.T) {
mux := NewServeMux()
mux.Handle(".", HandlerFunc(HelloServer))
mux.Handle("example.com.", HandlerFunc(AnotherHelloServer))
handler := mux.match("www.miek.nl.", TypeTXT)
if handler == nil {
t.Error("wildcard match failed")
}
handler = mux.match("www.example.com.", TypeTXT)
if handler == nil {
t.Error("example.com match failed")
}
handler = mux.match("a.www.example.com.", TypeTXT)
if handler == nil {
t.Error("a.www.example.com match failed")
}
handler = mux.match("boe.", TypeTXT)
if handler == nil {
t.Error("boe. match failed")
}
}
func TestCaseFolding(t *testing.T) {
mux := NewServeMux()
mux.Handle("_udp.example.com.", HandlerFunc(HelloServer))
handler := mux.match("_dns._udp.example.com.", TypeSRV)
if handler == nil {
t.Error("case sensitive characters folded")
}
handler = mux.match("_DNS._UDP.EXAMPLE.COM.", TypeSRV)
if handler == nil {
t.Error("case insensitive characters not folded")
}
}
func TestRootServer(t *testing.T) {
mux := NewServeMux()
mux.Handle(".", HandlerFunc(HelloServer))
handler := mux.match(".", TypeNS)
if handler == nil {
t.Error("root match failed")
}
}
func BenchmarkMuxMatch(b *testing.B) {
mux := NewServeMux()
mux.Handle("_udp.example.com.", HandlerFunc(HelloServer))
bench := func(q string) func(*testing.B) {
return func(b *testing.B) {
for n := 0; n < b.N; n++ {
handler := mux.match(q, TypeSRV)
if handler == nil {
b.Fatal("couldn't find match")
}
}
}
}
b.Run("lowercase", bench("_dns._udp.example.com."))
b.Run("uppercase", bench("_DNS._UDP.EXAMPLE.COM."))
}

788
server.go

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

98
sig0.go
View File

@ -2,8 +2,8 @@ package dns
import (
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"encoding/binary"
"math/big"
@ -18,18 +18,14 @@ func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) {
if k == nil {
return nil, ErrPrivKey
}
if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 {
if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 {
return nil, ErrKey
}
rr.Header().Rrtype = TypeSIG
rr.Header().Class = ClassANY
rr.Header().Ttl = 0
rr.Header().Name = "."
rr.OrigTtl = 0
rr.TypeCovered = 0
rr.Labels = 0
buf := make([]byte, m.Len()+rr.len())
rr.Hdr = RR_Header{Name: ".", Rrtype: TypeSIG, Class: ClassANY, Ttl: 0}
rr.OrigTtl, rr.TypeCovered, rr.Labels = 0, 0, 0
buf := make([]byte, m.Len()+Len(rr))
mbuf, err := m.PackBuffer(buf)
if err != nil {
return nil, err
@ -43,18 +39,17 @@ func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) {
}
buf = buf[:off:cap(buf)]
hash, ok := AlgorithmToHash[rr.Algorithm]
if !ok {
return nil, ErrAlg
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
if err != nil {
return nil, err
}
hasher := hash.New()
// Write SIG rdata
hasher.Write(buf[len(mbuf)+1+2+2+4+2:])
h.Write(buf[len(mbuf)+1+2+2+4+2:])
// Write message
hasher.Write(buf[:len(mbuf)])
h.Write(buf[:len(mbuf)])
signature, err := sign(k, hasher.Sum(nil), hash, rr.Algorithm)
signature, err := sign(k, h.Sum(nil), cryptohash, rr.Algorithm)
if err != nil {
return nil, err
}
@ -83,32 +78,21 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
if k == nil {
return ErrKey
}
if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 {
if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 {
return ErrKey
}
var hash crypto.Hash
switch rr.Algorithm {
case DSA, RSASHA1:
hash = crypto.SHA1
case RSASHA256, ECDSAP256SHA256:
hash = crypto.SHA256
case ECDSAP384SHA384:
hash = crypto.SHA384
case RSASHA512:
hash = crypto.SHA512
default:
return ErrAlg
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
if err != nil {
return err
}
hasher := hash.New()
buflen := len(buf)
qdc := binary.BigEndian.Uint16(buf[4:])
anc := binary.BigEndian.Uint16(buf[6:])
auc := binary.BigEndian.Uint16(buf[8:])
adc := binary.BigEndian.Uint16(buf[10:])
offset := 12
var err error
offset := headerSize
for i := uint16(0); i < qdc && offset < buflen; i++ {
_, offset, err = UnpackDomainName(buf, offset)
if err != nil {
@ -127,8 +111,7 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
if offset+1 >= buflen {
continue
}
var rdlen uint16
rdlen = binary.BigEndian.Uint16(buf[offset:])
rdlen := binary.BigEndian.Uint16(buf[offset:])
offset += 2
offset += int(rdlen)
}
@ -168,47 +151,40 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
}
// If key has come from the DNS name compression might
// have mangled the case of the name
if strings.ToLower(signername) != strings.ToLower(k.Header().Name) {
if !strings.EqualFold(signername, k.Header().Name) {
return &Error{err: "signer name doesn't match key name"}
}
sigend := offset
hasher.Write(buf[sigstart:sigend])
hasher.Write(buf[:10])
hasher.Write([]byte{
h.Write(buf[sigstart:sigend])
h.Write(buf[:10])
h.Write([]byte{
byte((adc - 1) << 8),
byte(adc - 1),
})
hasher.Write(buf[12:bodyend])
h.Write(buf[12:bodyend])
hashed := hasher.Sum(nil)
hashed := h.Sum(nil)
sig := buf[sigend:]
switch k.Algorithm {
case DSA:
pk := k.publicKeyDSA()
sig = sig[1:]
r := big.NewInt(0)
r.SetBytes(sig[:len(sig)/2])
s := big.NewInt(0)
s.SetBytes(sig[len(sig)/2:])
case RSASHA1, RSASHA256, RSASHA512:
pk := k.publicKeyRSA()
if pk != nil {
if dsa.Verify(pk, hashed, r, s) {
return rsa.VerifyPKCS1v15(pk, cryptohash, hashed, sig)
}
case ECDSAP256SHA256, ECDSAP384SHA384:
pk := k.publicKeyECDSA()
r := new(big.Int).SetBytes(sig[:len(sig)/2])
s := new(big.Int).SetBytes(sig[len(sig)/2:])
if pk != nil {
if ecdsa.Verify(pk, hashed, r, s) {
return nil
}
return ErrSig
}
case RSASHA1, RSASHA256, RSASHA512:
pk := k.publicKeyRSA()
case ED25519:
pk := k.publicKeyED25519()
if pk != nil {
return rsa.VerifyPKCS1v15(pk, hash, hashed, sig)
}
case ECDSAP256SHA256, ECDSAP384SHA384:
pk := k.publicKeyECDSA()
r := big.NewInt(0)
r.SetBytes(sig[:len(sig)/2])
s := big.NewInt(0)
s.SetBytes(sig[len(sig)/2:])
if pk != nil {
if ecdsa.Verify(pk, hashed, r, s) {
if ed25519.Verify(pk, hashed, sig) {
return nil
}
return ErrSig

View File

@ -12,23 +12,25 @@ func TestSIG0(t *testing.T) {
}
m := new(Msg)
m.SetQuestion("example.org.", TypeSOA)
for _, alg := range []uint8{ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256, RSASHA512} {
for _, alg := range []uint8{ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256, RSASHA512, ED25519} {
algstr := AlgorithmToString[alg]
keyrr := new(KEY)
keyrr.Hdr.Name = algstr + "."
keyrr.Hdr.Rrtype = TypeKEY
keyrr.Hdr.Class = ClassINET
keyrr.Algorithm = alg
keysize := 1024
keysize := 512
switch alg {
case ECDSAP256SHA256:
case ECDSAP256SHA256, ED25519:
keysize = 256
case ECDSAP384SHA384:
keysize = 384
case RSASHA512:
keysize = 1024
}
pk, err := keyrr.Generate(keysize)
if err != nil {
t.Errorf("failed to generate key for “%s”: %v", algstr, err)
t.Errorf("failed to generate key for %q: %v", algstr, err)
continue
}
now := uint32(time.Now().Unix())
@ -43,16 +45,16 @@ func TestSIG0(t *testing.T) {
sigrr.SignerName = keyrr.Hdr.Name
mb, err := sigrr.Sign(pk.(crypto.Signer), m)
if err != nil {
t.Errorf("failed to sign message using “%s”: %v", algstr, err)
t.Errorf("failed to sign message using %q: %v", algstr, err)
continue
}
m := new(Msg)
if err := m.Unpack(mb); err != nil {
t.Errorf("failed to unpack message signed using “%s”: %v", algstr, err)
t.Errorf("failed to unpack message signed using %q: %v", algstr, err)
continue
}
if len(m.Extra) != 1 {
t.Errorf("missing SIG for message signed using “%s”", algstr)
t.Errorf("missing SIG for message signed using %q", algstr)
continue
}
var sigrrwire *SIG
@ -69,20 +71,20 @@ func TestSIG0(t *testing.T) {
id = "sigrrwire"
}
if err := rr.Verify(keyrr, mb); err != nil {
t.Errorf("failed to verify “%s” signed SIG(%s): %v", algstr, id, err)
t.Errorf("failed to verify %q signed SIG(%s): %v", algstr, id, err)
continue
}
}
mb[13]++
if err := sigrr.Verify(keyrr, mb); err == nil {
t.Errorf("verify succeeded on an altered message using “%s”", algstr)
t.Errorf("verify succeeded on an altered message using %q", algstr)
continue
}
sigrr.Expiration = 2
sigrr.Inception = 1
mb, _ = sigrr.Sign(pk.(crypto.Signer), m)
if err := sigrr.Verify(keyrr, mb); err == nil {
t.Errorf("verify succeeded on an expired message using “%s”", algstr)
t.Errorf("verify succeeded on an expired message using %q", algstr)
continue
}
}

View File

@ -23,6 +23,8 @@ type call struct {
type singleflight struct {
sync.Mutex // protects m
m map[string]*call // lazily initialized
dontDeleteForTesting bool // this is only to be used by TestConcurrentExchanges
}
// Do executes and returns the results of the given function, making
@ -49,9 +51,11 @@ func (g *singleflight) Do(key string, fn func() (*Msg, time.Duration, error)) (v
c.val, c.rtt, c.err = fn()
c.wg.Done()
g.Lock()
delete(g.m, key)
g.Unlock()
if !g.dontDeleteForTesting {
g.Lock()
delete(g.m, key)
g.Unlock()
}
return c.val, c.rtt, c.err, c.dups > 0
}

View File

@ -14,10 +14,7 @@ func (r *SMIMEA) Sign(usage, selector, matchingType int, cert *x509.Certificate)
r.MatchingType = uint8(matchingType)
r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert)
if err != nil {
return err
}
return nil
return err
}
// Verify verifies a SMIMEA record against an SSL certificate. If it is OK

935
svcb.go Normal file
View File

@ -0,0 +1,935 @@
package dns
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"net"
"sort"
"strconv"
"strings"
)
// SVCBKey is the type of the keys used in the SVCB RR.
type SVCBKey uint16
// Keys defined in draft-ietf-dnsop-svcb-https-08 Section 14.3.2.
const (
SVCB_MANDATORY SVCBKey = iota
SVCB_ALPN
SVCB_NO_DEFAULT_ALPN
SVCB_PORT
SVCB_IPV4HINT
SVCB_ECHCONFIG
SVCB_IPV6HINT
SVCB_DOHPATH // draft-ietf-add-svcb-dns-02 Section 9
svcb_RESERVED SVCBKey = 65535
)
var svcbKeyToStringMap = map[SVCBKey]string{
SVCB_MANDATORY: "mandatory",
SVCB_ALPN: "alpn",
SVCB_NO_DEFAULT_ALPN: "no-default-alpn",
SVCB_PORT: "port",
SVCB_IPV4HINT: "ipv4hint",
SVCB_ECHCONFIG: "ech",
SVCB_IPV6HINT: "ipv6hint",
SVCB_DOHPATH: "dohpath",
}
var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap)
func reverseSVCBKeyMap(m map[SVCBKey]string) map[string]SVCBKey {
n := make(map[string]SVCBKey, len(m))
for u, s := range m {
n[s] = u
}
return n
}
// String takes the numerical code of an SVCB key and returns its name.
// Returns an empty string for reserved keys.
// Accepts unassigned keys as well as experimental/private keys.
func (key SVCBKey) String() string {
if x := svcbKeyToStringMap[key]; x != "" {
return x
}
if key == svcb_RESERVED {
return ""
}
return "key" + strconv.FormatUint(uint64(key), 10)
}
// svcbStringToKey returns the numerical code of an SVCB key.
// Returns svcb_RESERVED for reserved/invalid keys.
// Accepts unassigned keys as well as experimental/private keys.
func svcbStringToKey(s string) SVCBKey {
if strings.HasPrefix(s, "key") {
a, err := strconv.ParseUint(s[3:], 10, 16)
// no leading zeros
// key shouldn't be registered
if err != nil || a == 65535 || s[3] == '0' || svcbKeyToStringMap[SVCBKey(a)] != "" {
return svcb_RESERVED
}
return SVCBKey(a)
}
if key, ok := svcbStringToKeyMap[s]; ok {
return key
}
return svcb_RESERVED
}
func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
l, _ := c.Next()
i, e := strconv.ParseUint(l.token, 10, 16)
if e != nil || l.err {
return &ParseError{l.token, "bad SVCB priority", l}
}
rr.Priority = uint16(i)
c.Next() // zBlank
l, _ = c.Next() // zString
rr.Target = l.token
name, nameOk := toAbsoluteName(l.token, o)
if l.err || !nameOk {
return &ParseError{l.token, "bad SVCB Target", l}
}
rr.Target = name
// Values (if any)
l, _ = c.Next()
var xs []SVCBKeyValue
// Helps require whitespace between pairs.
// Prevents key1000="a"key1001=...
canHaveNextKey := true
for l.value != zNewline && l.value != zEOF {
switch l.value {
case zString:
if !canHaveNextKey {
// The key we can now read was probably meant to be
// a part of the last value.
return &ParseError{l.token, "bad SVCB value quotation", l}
}
// In key=value pairs, value does not have to be quoted unless value
// contains whitespace. And keys don't need to have values.
// Similarly, keys with an equality signs after them don't need values.
// l.token includes at least up to the first equality sign.
idx := strings.IndexByte(l.token, '=')
var key, value string
if idx < 0 {
// Key with no value and no equality sign
key = l.token
} else if idx == 0 {
return &ParseError{l.token, "bad SVCB key", l}
} else {
key, value = l.token[:idx], l.token[idx+1:]
if value == "" {
// We have a key and an equality sign. Maybe we have nothing
// after "=" or we have a double quote.
l, _ = c.Next()
if l.value == zQuote {
// Only needed when value ends with double quotes.
// Any value starting with zQuote ends with it.
canHaveNextKey = false
l, _ = c.Next()
switch l.value {
case zString:
// We have a value in double quotes.
value = l.token
l, _ = c.Next()
if l.value != zQuote {
return &ParseError{l.token, "SVCB unterminated value", l}
}
case zQuote:
// There's nothing in double quotes.
default:
return &ParseError{l.token, "bad SVCB value", l}
}
}
}
}
kv := makeSVCBKeyValue(svcbStringToKey(key))
if kv == nil {
return &ParseError{l.token, "bad SVCB key", l}
}
if err := kv.parse(value); err != nil {
return &ParseError{l.token, err.Error(), l}
}
xs = append(xs, kv)
case zQuote:
return &ParseError{l.token, "SVCB key can't contain double quotes", l}
case zBlank:
canHaveNextKey = true
default:
return &ParseError{l.token, "bad SVCB values", l}
}
l, _ = c.Next()
}
// "In AliasMode, records SHOULD NOT include any SvcParams, and recipients MUST
// ignore any SvcParams that are present."
// However, we don't check rr.Priority == 0 && len(xs) > 0 here
// It is the responsibility of the user of the library to check this.
// This is to encourage the fixing of the source of this error.
rr.Value = xs
return nil
}
// makeSVCBKeyValue returns an SVCBKeyValue struct with the key or nil for reserved keys.
func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
switch key {
case SVCB_MANDATORY:
return new(SVCBMandatory)
case SVCB_ALPN:
return new(SVCBAlpn)
case SVCB_NO_DEFAULT_ALPN:
return new(SVCBNoDefaultAlpn)
case SVCB_PORT:
return new(SVCBPort)
case SVCB_IPV4HINT:
return new(SVCBIPv4Hint)
case SVCB_ECHCONFIG:
return new(SVCBECHConfig)
case SVCB_IPV6HINT:
return new(SVCBIPv6Hint)
case SVCB_DOHPATH:
return new(SVCBDoHPath)
case svcb_RESERVED:
return nil
default:
e := new(SVCBLocal)
e.KeyCode = key
return e
}
}
// SVCB RR. See RFC xxxx (https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-08).
//
// NOTE: The HTTPS/SVCB RFCs are in the draft stage.
// The API, including constants and types related to SVCBKeyValues, may
// change in future versions in accordance with the latest drafts.
type SVCB struct {
Hdr RR_Header
Priority uint16 // If zero, Value must be empty or discarded by the user of this library
Target string `dns:"domain-name"`
Value []SVCBKeyValue `dns:"pairs"`
}
// HTTPS RR. Everything valid for SVCB applies to HTTPS as well.
// Except that the HTTPS record is intended for use with the HTTP and HTTPS protocols.
//
// NOTE: The HTTPS/SVCB RFCs are in the draft stage.
// The API, including constants and types related to SVCBKeyValues, may
// change in future versions in accordance with the latest drafts.
type HTTPS struct {
SVCB
}
func (rr *HTTPS) String() string {
return rr.SVCB.String()
}
func (rr *HTTPS) parse(c *zlexer, o string) *ParseError {
return rr.SVCB.parse(c, o)
}
// SVCBKeyValue defines a key=value pair for the SVCB RR type.
// An SVCB RR can have multiple SVCBKeyValues appended to it.
type SVCBKeyValue interface {
Key() SVCBKey // Key returns the numerical key code.
pack() ([]byte, error) // pack returns the encoded value.
unpack([]byte) error // unpack sets the value.
String() string // String returns the string representation of the value.
parse(string) error // parse sets the value to the given string representation of the value.
copy() SVCBKeyValue // copy returns a deep-copy of the pair.
len() int // len returns the length of value in the wire format.
}
// SVCBMandatory pair adds to required keys that must be interpreted for the RR
// to be functional. If ignored, the whole RRSet must be ignored.
// "port" and "no-default-alpn" are mandatory by default if present,
// so they shouldn't be included here.
//
// It is incumbent upon the user of this library to reject the RRSet if
// or avoid constructing such an RRSet that:
// - "mandatory" is included as one of the keys of mandatory
// - no key is listed multiple times in mandatory
// - all keys listed in mandatory are present
// - escape sequences are not used in mandatory
// - mandatory, when present, lists at least one key
//
// Basic use pattern for creating a mandatory option:
//
// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
// e := new(dns.SVCBMandatory)
// e.Code = []uint16{dns.SVCB_ALPN}
// s.Value = append(s.Value, e)
// t := new(dns.SVCBAlpn)
// t.Alpn = []string{"xmpp-client"}
// s.Value = append(s.Value, t)
type SVCBMandatory struct {
Code []SVCBKey
}
func (*SVCBMandatory) Key() SVCBKey { return SVCB_MANDATORY }
func (s *SVCBMandatory) String() string {
str := make([]string, len(s.Code))
for i, e := range s.Code {
str[i] = e.String()
}
return strings.Join(str, ",")
}
func (s *SVCBMandatory) pack() ([]byte, error) {
codes := append([]SVCBKey(nil), s.Code...)
sort.Slice(codes, func(i, j int) bool {
return codes[i] < codes[j]
})
b := make([]byte, 2*len(codes))
for i, e := range codes {
binary.BigEndian.PutUint16(b[2*i:], uint16(e))
}
return b, nil
}
func (s *SVCBMandatory) unpack(b []byte) error {
if len(b)%2 != 0 {
return errors.New("dns: svcbmandatory: value length is not a multiple of 2")
}
codes := make([]SVCBKey, 0, len(b)/2)
for i := 0; i < len(b); i += 2 {
// We assume strictly increasing order.
codes = append(codes, SVCBKey(binary.BigEndian.Uint16(b[i:])))
}
s.Code = codes
return nil
}
func (s *SVCBMandatory) parse(b string) error {
str := strings.Split(b, ",")
codes := make([]SVCBKey, 0, len(str))
for _, e := range str {
codes = append(codes, svcbStringToKey(e))
}
s.Code = codes
return nil
}
func (s *SVCBMandatory) len() int {
return 2 * len(s.Code)
}
func (s *SVCBMandatory) copy() SVCBKeyValue {
return &SVCBMandatory{
append([]SVCBKey(nil), s.Code...),
}
}
// SVCBAlpn pair is used to list supported connection protocols.
// The user of this library must ensure that at least one protocol is listed when alpn is present.
// Protocol IDs can be found at:
// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
// Basic use pattern for creating an alpn option:
//
// h := new(dns.HTTPS)
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
// e := new(dns.SVCBAlpn)
// e.Alpn = []string{"h2", "http/1.1"}
// h.Value = append(h.Value, e)
type SVCBAlpn struct {
Alpn []string
}
func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN }
func (s *SVCBAlpn) String() string {
// An ALPN value is a comma-separated list of values, each of which can be
// an arbitrary binary value. In order to allow parsing, the comma and
// backslash characters are themselves escaped.
//
// However, this escaping is done in addition to the normal escaping which
// happens in zone files, meaning that these values must be
// double-escaped. This looks terrible, so if you see a never-ending
// sequence of backslash in a zone file this may be why.
//
// https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-08#appendix-A.1
var str strings.Builder
for i, alpn := range s.Alpn {
// 4*len(alpn) is the worst case where we escape every character in the alpn as \123, plus 1 byte for the ',' separating the alpn from others
str.Grow(4*len(alpn) + 1)
if i > 0 {
str.WriteByte(',')
}
for j := 0; j < len(alpn); j++ {
e := alpn[j]
if ' ' > e || e > '~' {
str.WriteString(escapeByte(e))
continue
}
switch e {
// We escape a few characters which may confuse humans or parsers.
case '"', ';', ' ':
str.WriteByte('\\')
str.WriteByte(e)
// The comma and backslash characters themselves must be
// doubly-escaped. We use `\\` for the first backslash and
// the escaped numeric value for the other value. We especially
// don't want a comma in the output.
case ',':
str.WriteString(`\\\044`)
case '\\':
str.WriteString(`\\\092`)
default:
str.WriteByte(e)
}
}
}
return str.String()
}
func (s *SVCBAlpn) pack() ([]byte, error) {
// Liberally estimate the size of an alpn as 10 octets
b := make([]byte, 0, 10*len(s.Alpn))
for _, e := range s.Alpn {
if e == "" {
return nil, errors.New("dns: svcbalpn: empty alpn-id")
}
if len(e) > 255 {
return nil, errors.New("dns: svcbalpn: alpn-id too long")
}
b = append(b, byte(len(e)))
b = append(b, e...)
}
return b, nil
}
func (s *SVCBAlpn) unpack(b []byte) error {
// Estimate the size of the smallest alpn as 4 bytes
alpn := make([]string, 0, len(b)/4)
for i := 0; i < len(b); {
length := int(b[i])
i++
if i+length > len(b) {
return errors.New("dns: svcbalpn: alpn array overflowing")
}
alpn = append(alpn, string(b[i:i+length]))
i += length
}
s.Alpn = alpn
return nil
}
func (s *SVCBAlpn) parse(b string) error {
if len(b) == 0 {
s.Alpn = []string{}
return nil
}
alpn := []string{}
a := []byte{}
for p := 0; p < len(b); {
c, q := nextByte(b, p)
if q == 0 {
return errors.New("dns: svcbalpn: unterminated escape")
}
p += q
// If we find a comma, we have finished reading an alpn.
if c == ',' {
if len(a) == 0 {
return errors.New("dns: svcbalpn: empty protocol identifier")
}
alpn = append(alpn, string(a))
a = []byte{}
continue
}
// If it's a backslash, we need to handle a comma-separated list.
if c == '\\' {
dc, dq := nextByte(b, p)
if dq == 0 {
return errors.New("dns: svcbalpn: unterminated escape decoding comma-separated list")
}
if dc != '\\' && dc != ',' {
return errors.New("dns: svcbalpn: bad escaped character decoding comma-separated list")
}
p += dq
c = dc
}
a = append(a, c)
}
// Add the final alpn.
if len(a) == 0 {
return errors.New("dns: svcbalpn: last protocol identifier empty")
}
s.Alpn = append(alpn, string(a))
return nil
}
func (s *SVCBAlpn) len() int {
var l int
for _, e := range s.Alpn {
l += 1 + len(e)
}
return l
}
func (s *SVCBAlpn) copy() SVCBKeyValue {
return &SVCBAlpn{
append([]string(nil), s.Alpn...),
}
}
// SVCBNoDefaultAlpn pair signifies no support for default connection protocols.
// Should be used in conjunction with alpn.
// Basic use pattern for creating a no-default-alpn option:
//
// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
// t := new(dns.SVCBAlpn)
// t.Alpn = []string{"xmpp-client"}
// s.Value = append(s.Value, t)
// e := new(dns.SVCBNoDefaultAlpn)
// s.Value = append(s.Value, e)
type SVCBNoDefaultAlpn struct{}
func (*SVCBNoDefaultAlpn) Key() SVCBKey { return SVCB_NO_DEFAULT_ALPN }
func (*SVCBNoDefaultAlpn) copy() SVCBKeyValue { return &SVCBNoDefaultAlpn{} }
func (*SVCBNoDefaultAlpn) pack() ([]byte, error) { return []byte{}, nil }
func (*SVCBNoDefaultAlpn) String() string { return "" }
func (*SVCBNoDefaultAlpn) len() int { return 0 }
func (*SVCBNoDefaultAlpn) unpack(b []byte) error {
if len(b) != 0 {
return errors.New("dns: svcbnodefaultalpn: no-default-alpn must have no value")
}
return nil
}
func (*SVCBNoDefaultAlpn) parse(b string) error {
if b != "" {
return errors.New("dns: svcbnodefaultalpn: no-default-alpn must have no value")
}
return nil
}
// SVCBPort pair defines the port for connection.
// Basic use pattern for creating a port option:
//
// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
// e := new(dns.SVCBPort)
// e.Port = 80
// s.Value = append(s.Value, e)
type SVCBPort struct {
Port uint16
}
func (*SVCBPort) Key() SVCBKey { return SVCB_PORT }
func (*SVCBPort) len() int { return 2 }
func (s *SVCBPort) String() string { return strconv.FormatUint(uint64(s.Port), 10) }
func (s *SVCBPort) copy() SVCBKeyValue { return &SVCBPort{s.Port} }
func (s *SVCBPort) unpack(b []byte) error {
if len(b) != 2 {
return errors.New("dns: svcbport: port length is not exactly 2 octets")
}
s.Port = binary.BigEndian.Uint16(b)
return nil
}
func (s *SVCBPort) pack() ([]byte, error) {
b := make([]byte, 2)
binary.BigEndian.PutUint16(b, s.Port)
return b, nil
}
func (s *SVCBPort) parse(b string) error {
port, err := strconv.ParseUint(b, 10, 16)
if err != nil {
return errors.New("dns: svcbport: port out of range")
}
s.Port = uint16(port)
return nil
}
// SVCBIPv4Hint pair suggests an IPv4 address which may be used to open connections
// if A and AAAA record responses for SVCB's Target domain haven't been received.
// In that case, optionally, A and AAAA requests can be made, after which the connection
// to the hinted IP address may be terminated and a new connection may be opened.
// Basic use pattern for creating an ipv4hint option:
//
// h := new(dns.HTTPS)
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
// e := new(dns.SVCBIPv4Hint)
// e.Hint = []net.IP{net.IPv4(1,1,1,1).To4()}
//
// Or
//
// e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()}
// h.Value = append(h.Value, e)
type SVCBIPv4Hint struct {
Hint []net.IP
}
func (*SVCBIPv4Hint) Key() SVCBKey { return SVCB_IPV4HINT }
func (s *SVCBIPv4Hint) len() int { return 4 * len(s.Hint) }
func (s *SVCBIPv4Hint) pack() ([]byte, error) {
b := make([]byte, 0, 4*len(s.Hint))
for _, e := range s.Hint {
x := e.To4()
if x == nil {
return nil, errors.New("dns: svcbipv4hint: expected ipv4, hint is ipv6")
}
b = append(b, x...)
}
return b, nil
}
func (s *SVCBIPv4Hint) unpack(b []byte) error {
if len(b) == 0 || len(b)%4 != 0 {
return errors.New("dns: svcbipv4hint: ipv4 address byte array length is not a multiple of 4")
}
x := make([]net.IP, 0, len(b)/4)
for i := 0; i < len(b); i += 4 {
x = append(x, net.IP(b[i:i+4]))
}
s.Hint = x
return nil
}
func (s *SVCBIPv4Hint) String() string {
str := make([]string, len(s.Hint))
for i, e := range s.Hint {
x := e.To4()
if x == nil {
return "<nil>"
}
str[i] = x.String()
}
return strings.Join(str, ",")
}
func (s *SVCBIPv4Hint) parse(b string) error {
if strings.Contains(b, ":") {
return errors.New("dns: svcbipv4hint: expected ipv4, got ipv6")
}
str := strings.Split(b, ",")
dst := make([]net.IP, len(str))
for i, e := range str {
ip := net.ParseIP(e).To4()
if ip == nil {
return errors.New("dns: svcbipv4hint: bad ip")
}
dst[i] = ip
}
s.Hint = dst
return nil
}
func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
hint := make([]net.IP, len(s.Hint))
for i, ip := range s.Hint {
hint[i] = copyIP(ip)
}
return &SVCBIPv4Hint{
Hint: hint,
}
}
// SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx].
// Basic use pattern for creating an ech option:
//
// h := new(dns.HTTPS)
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
// e := new(dns.SVCBECHConfig)
// e.ECH = []byte{0xfe, 0x08, ...}
// h.Value = append(h.Value, e)
type SVCBECHConfig struct {
ECH []byte // Specifically ECHConfigList including the redundant length prefix
}
func (*SVCBECHConfig) Key() SVCBKey { return SVCB_ECHCONFIG }
func (s *SVCBECHConfig) String() string { return toBase64(s.ECH) }
func (s *SVCBECHConfig) len() int { return len(s.ECH) }
func (s *SVCBECHConfig) pack() ([]byte, error) {
return append([]byte(nil), s.ECH...), nil
}
func (s *SVCBECHConfig) copy() SVCBKeyValue {
return &SVCBECHConfig{
append([]byte(nil), s.ECH...),
}
}
func (s *SVCBECHConfig) unpack(b []byte) error {
s.ECH = append([]byte(nil), b...)
return nil
}
func (s *SVCBECHConfig) parse(b string) error {
x, err := fromBase64([]byte(b))
if err != nil {
return errors.New("dns: svcbech: bad base64 ech")
}
s.ECH = x
return nil
}
// SVCBIPv6Hint pair suggests an IPv6 address which may be used to open connections
// if A and AAAA record responses for SVCB's Target domain haven't been received.
// In that case, optionally, A and AAAA requests can be made, after which the
// connection to the hinted IP address may be terminated and a new connection may be opened.
// Basic use pattern for creating an ipv6hint option:
//
// h := new(dns.HTTPS)
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
// e := new(dns.SVCBIPv6Hint)
// e.Hint = []net.IP{net.ParseIP("2001:db8::1")}
// h.Value = append(h.Value, e)
type SVCBIPv6Hint struct {
Hint []net.IP
}
func (*SVCBIPv6Hint) Key() SVCBKey { return SVCB_IPV6HINT }
func (s *SVCBIPv6Hint) len() int { return 16 * len(s.Hint) }
func (s *SVCBIPv6Hint) pack() ([]byte, error) {
b := make([]byte, 0, 16*len(s.Hint))
for _, e := range s.Hint {
if len(e) != net.IPv6len || e.To4() != nil {
return nil, errors.New("dns: svcbipv6hint: expected ipv6, hint is ipv4")
}
b = append(b, e...)
}
return b, nil
}
func (s *SVCBIPv6Hint) unpack(b []byte) error {
if len(b) == 0 || len(b)%16 != 0 {
return errors.New("dns: svcbipv6hint: ipv6 address byte array length not a multiple of 16")
}
x := make([]net.IP, 0, len(b)/16)
for i := 0; i < len(b); i += 16 {
ip := net.IP(b[i : i+16])
if ip.To4() != nil {
return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4")
}
x = append(x, ip)
}
s.Hint = x
return nil
}
func (s *SVCBIPv6Hint) String() string {
str := make([]string, len(s.Hint))
for i, e := range s.Hint {
if x := e.To4(); x != nil {
return "<nil>"
}
str[i] = e.String()
}
return strings.Join(str, ",")
}
func (s *SVCBIPv6Hint) parse(b string) error {
str := strings.Split(b, ",")
dst := make([]net.IP, len(str))
for i, e := range str {
ip := net.ParseIP(e)
if ip == nil {
return errors.New("dns: svcbipv6hint: bad ip")
}
if ip.To4() != nil {
return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4-mapped-ipv6")
}
dst[i] = ip
}
s.Hint = dst
return nil
}
func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
hint := make([]net.IP, len(s.Hint))
for i, ip := range s.Hint {
hint[i] = copyIP(ip)
}
return &SVCBIPv6Hint{
Hint: hint,
}
}
// SVCBDoHPath pair is used to indicate the URI template that the
// clients may use to construct a DNS over HTTPS URI.
//
// See RFC xxxx (https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02)
// and RFC yyyy (https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-06).
//
// A basic example of using the dohpath option together with the alpn
// option to indicate support for DNS over HTTPS on a certain path:
//
// s := new(dns.SVCB)
// s.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}
// e := new(dns.SVCBAlpn)
// e.Alpn = []string{"h2", "h3"}
// p := new(dns.SVCBDoHPath)
// p.Template = "/dns-query{?dns}"
// s.Value = append(s.Value, e, p)
//
// The parsing currently doesn't validate that Template is a valid
// RFC 6570 URI template.
type SVCBDoHPath struct {
Template string
}
func (*SVCBDoHPath) Key() SVCBKey { return SVCB_DOHPATH }
func (s *SVCBDoHPath) String() string { return svcbParamToStr([]byte(s.Template)) }
func (s *SVCBDoHPath) len() int { return len(s.Template) }
func (s *SVCBDoHPath) pack() ([]byte, error) { return []byte(s.Template), nil }
func (s *SVCBDoHPath) unpack(b []byte) error {
s.Template = string(b)
return nil
}
func (s *SVCBDoHPath) parse(b string) error {
template, err := svcbParseParam(b)
if err != nil {
return fmt.Errorf("dns: svcbdohpath: %w", err)
}
s.Template = string(template)
return nil
}
func (s *SVCBDoHPath) copy() SVCBKeyValue {
return &SVCBDoHPath{
Template: s.Template,
}
}
// SVCBLocal pair is intended for experimental/private use. The key is recommended
// to be in the range [SVCB_PRIVATE_LOWER, SVCB_PRIVATE_UPPER].
// Basic use pattern for creating a keyNNNNN option:
//
// h := new(dns.HTTPS)
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
// e := new(dns.SVCBLocal)
// e.KeyCode = 65400
// e.Data = []byte("abc")
// h.Value = append(h.Value, e)
type SVCBLocal struct {
KeyCode SVCBKey // Never 65535 or any assigned keys.
Data []byte // All byte sequences are allowed.
}
func (s *SVCBLocal) Key() SVCBKey { return s.KeyCode }
func (s *SVCBLocal) String() string { return svcbParamToStr(s.Data) }
func (s *SVCBLocal) pack() ([]byte, error) { return append([]byte(nil), s.Data...), nil }
func (s *SVCBLocal) len() int { return len(s.Data) }
func (s *SVCBLocal) unpack(b []byte) error {
s.Data = append([]byte(nil), b...)
return nil
}
func (s *SVCBLocal) parse(b string) error {
data, err := svcbParseParam(b)
if err != nil {
return fmt.Errorf("dns: svcblocal: svcb private/experimental key %w", err)
}
s.Data = data
return nil
}
func (s *SVCBLocal) copy() SVCBKeyValue {
return &SVCBLocal{s.KeyCode,
append([]byte(nil), s.Data...),
}
}
func (rr *SVCB) String() string {
s := rr.Hdr.String() +
strconv.Itoa(int(rr.Priority)) + " " +
sprintName(rr.Target)
for _, e := range rr.Value {
s += " " + e.Key().String() + "=\"" + e.String() + "\""
}
return s
}
// areSVCBPairArraysEqual checks if SVCBKeyValue arrays are equal after sorting their
// copies. arrA and arrB have equal lengths, otherwise zduplicate.go wouldn't call this function.
func areSVCBPairArraysEqual(a []SVCBKeyValue, b []SVCBKeyValue) bool {
a = append([]SVCBKeyValue(nil), a...)
b = append([]SVCBKeyValue(nil), b...)
sort.Slice(a, func(i, j int) bool { return a[i].Key() < a[j].Key() })
sort.Slice(b, func(i, j int) bool { return b[i].Key() < b[j].Key() })
for i, e := range a {
if e.Key() != b[i].Key() {
return false
}
b1, err1 := e.pack()
b2, err2 := b[i].pack()
if err1 != nil || err2 != nil || !bytes.Equal(b1, b2) {
return false
}
}
return true
}
// svcbParamStr converts the value of an SVCB parameter into a DNS presentation-format string.
func svcbParamToStr(s []byte) string {
var str strings.Builder
str.Grow(4 * len(s))
for _, e := range s {
if ' ' <= e && e <= '~' {
switch e {
case '"', ';', ' ', '\\':
str.WriteByte('\\')
str.WriteByte(e)
default:
str.WriteByte(e)
}
} else {
str.WriteString(escapeByte(e))
}
}
return str.String()
}
// svcbParseParam parses a DNS presentation-format string into an SVCB parameter value.
func svcbParseParam(b string) ([]byte, error) {
data := make([]byte, 0, len(b))
for i := 0; i < len(b); {
if b[i] != '\\' {
data = append(data, b[i])
i++
continue
}
if i+1 == len(b) {
return nil, errors.New("escape unterminated")
}
if isDigit(b[i+1]) {
if i+3 < len(b) && isDigit(b[i+2]) && isDigit(b[i+3]) {
a, err := strconv.ParseUint(b[i+1:i+4], 10, 8)
if err == nil {
i += 4
data = append(data, byte(a))
continue
}
}
return nil, errors.New("bad escaped octet")
} else {
data = append(data, b[i+1])
i += 2
}
}
return data, nil
}

163
svcb_test.go Normal file
View File

@ -0,0 +1,163 @@
package dns
import (
"testing"
)
// This tests everything valid about SVCB but parsing.
// Parsing tests belong to parse_test.go.
func TestSVCB(t *testing.T) {
svcbs := []struct {
key string
data string
}{
{`mandatory`, `alpn,key65000`},
{`alpn`, `h2,h2c`},
{`port`, `499`},
{`ipv4hint`, `3.4.3.2,1.1.1.1`},
{`no-default-alpn`, ``},
{`ipv6hint`, `1::4:4:4:4,1::3:3:3:3`},
{`ech`, `YUdWc2JHOD0=`},
{`dohpath`, `/dns-query{?dns}`},
{`key65000`, `4\ 3`},
{`key65001`, `\"\ `},
{`key65002`, ``},
{`key65003`, `=\"\"`},
{`key65004`, `\254\ \ \030\000`},
}
for _, o := range svcbs {
keyCode := svcbStringToKey(o.key)
kv := makeSVCBKeyValue(keyCode)
if kv == nil {
t.Error("failed to parse svc key: ", o.key)
continue
}
if kv.Key() != keyCode {
t.Error("key constant is not in sync: ", keyCode)
continue
}
err := kv.parse(o.data)
if err != nil {
t.Error("failed to parse svc pair: ", o.key)
continue
}
b, err := kv.pack()
if err != nil {
t.Error("failed to pack value of svc pair: ", o.key, err)
continue
}
if len(b) != int(kv.len()) {
t.Errorf("expected packed svc value %s to be of length %d but got %d", o.key, int(kv.len()), len(b))
}
err = kv.unpack(b)
if err != nil {
t.Error("failed to unpack value of svc pair: ", o.key, err)
continue
}
if str := kv.String(); str != o.data {
t.Errorf("`%s' should be equal to\n`%s', but is `%s'", o.key, o.data, str)
}
}
}
func TestDecodeBadSVCB(t *testing.T) {
svcbs := []struct {
key SVCBKey
data []byte
}{
{
key: SVCB_ALPN,
data: []byte{3, 0, 0}, // There aren't three octets after 3
},
{
key: SVCB_NO_DEFAULT_ALPN,
data: []byte{0},
},
{
key: SVCB_PORT,
data: []byte{},
},
{
key: SVCB_IPV4HINT,
data: []byte{0, 0, 0},
},
{
key: SVCB_IPV6HINT,
data: []byte{0, 0, 0},
},
}
for _, o := range svcbs {
err := makeSVCBKeyValue(SVCBKey(o.key)).unpack(o.data)
if err == nil {
t.Error("accepted invalid svc value with key ", SVCBKey(o.key).String())
}
}
}
func TestPresentationSVCBAlpn(t *testing.T) {
tests := map[string]string{
"h2": "h2",
"http": "http",
"\xfa": `\250`,
"some\"other,chars": `some\"other\\\044chars`,
}
for input, want := range tests {
e := new(SVCBAlpn)
e.Alpn = []string{input}
if e.String() != want {
t.Errorf("improper conversion with String(), wanted %v got %v", want, e.String())
}
}
}
func TestSVCBAlpn(t *testing.T) {
tests := map[string][]string{
`. 1 IN SVCB 10 one.test. alpn=h2`: {"h2"},
`. 2 IN SVCB 20 two.test. alpn=h2,h3-19`: {"h2", "h3-19"},
`. 3 IN SVCB 30 three.test. alpn="f\\\\oo\\,bar,h2"`: {`f\oo,bar`, "h2"},
`. 4 IN SVCB 40 four.test. alpn="part1,part2,part3\\,part4\\\\"`: {"part1", "part2", `part3,part4\`},
`. 5 IN SVCB 50 five.test. alpn=part1\,\p\a\r\t2\044part3\092,part4\092\\`: {"part1", "part2", `part3,part4\`},
}
for s, v := range tests {
rr, err := NewRR(s)
if err != nil {
t.Error("failed to parse RR: ", err)
continue
}
alpn := rr.(*SVCB).Value[0].(*SVCBAlpn).Alpn
if len(v) != len(alpn) {
t.Fatalf("parsing alpn failed, wanted %v got %v", v, alpn)
}
for i := range v {
if v[i] != alpn[i] {
t.Fatalf("parsing alpn failed, wanted %v got %v", v, alpn)
}
}
}
}
func TestCompareSVCB(t *testing.T) {
val1 := []SVCBKeyValue{
&SVCBPort{
Port: 117,
},
&SVCBAlpn{
Alpn: []string{"h2", "h3"},
},
}
val2 := []SVCBKeyValue{
&SVCBAlpn{
Alpn: []string{"h2", "h3"},
},
&SVCBPort{
Port: 117,
},
}
if !areSVCBPairArraysEqual(val1, val2) {
t.Error("svcb pairs were compared without sorting")
}
if val1[0].Key() != SVCB_PORT || val2[0].Key() != SVCB_ALPN {
t.Error("original svcb pairs were reordered during comparison")
}
}

View File

@ -14,10 +14,7 @@ func (r *TLSA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (
r.MatchingType = uint8(matchingType)
r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert)
if err != nil {
return err
}
return nil
return err
}
// Verify verifies a TLSA record against an SSL certificate. If it is OK

10
tools.go Normal file
View File

@ -0,0 +1,10 @@
//go:build tools
// +build tools
// We include our tool dependencies for `go generate` here to ensure they're
// properly tracked by the go tool. See the Go Wiki for the rationale behind this:
// https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module.
package dns
import _ "golang.org/x/tools/go/packages"

228
tsig.go
View File

@ -2,7 +2,6 @@ package dns
import (
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
@ -16,12 +15,83 @@ import (
// HMAC hashing codes. These are transmitted as domain names.
const (
HmacMD5 = "hmac-md5.sig-alg.reg.int."
HmacSHA1 = "hmac-sha1."
HmacSHA224 = "hmac-sha224."
HmacSHA256 = "hmac-sha256."
HmacSHA384 = "hmac-sha384."
HmacSHA512 = "hmac-sha512."
HmacMD5 = "hmac-md5.sig-alg.reg.int." // Deprecated: HmacMD5 is no longer supported.
)
// TsigProvider provides the API to plug-in a custom TSIG implementation.
type TsigProvider interface {
// Generate is passed the DNS message to be signed and the partial TSIG RR. It returns the signature and nil, otherwise an error.
Generate(msg []byte, t *TSIG) ([]byte, error)
// Verify is passed the DNS message to be verified and the TSIG RR. If the signature is valid it will return nil, otherwise an error.
Verify(msg []byte, t *TSIG) error
}
type tsigHMACProvider string
func (key tsigHMACProvider) Generate(msg []byte, t *TSIG) ([]byte, error) {
// If we barf here, the caller is to blame
rawsecret, err := fromBase64([]byte(key))
if err != nil {
return nil, err
}
var h hash.Hash
switch CanonicalName(t.Algorithm) {
case HmacSHA1:
h = hmac.New(sha1.New, rawsecret)
case HmacSHA224:
h = hmac.New(sha256.New224, rawsecret)
case HmacSHA256:
h = hmac.New(sha256.New, rawsecret)
case HmacSHA384:
h = hmac.New(sha512.New384, rawsecret)
case HmacSHA512:
h = hmac.New(sha512.New, rawsecret)
default:
return nil, ErrKeyAlg
}
h.Write(msg)
return h.Sum(nil), nil
}
func (key tsigHMACProvider) Verify(msg []byte, t *TSIG) error {
b, err := key.Generate(msg, t)
if err != nil {
return err
}
mac, err := hex.DecodeString(t.MAC)
if err != nil {
return err
}
if !hmac.Equal(b, mac) {
return ErrSig
}
return nil
}
type tsigSecretProvider map[string]string
func (ts tsigSecretProvider) Generate(msg []byte, t *TSIG) ([]byte, error) {
key, ok := ts[t.Hdr.Name]
if !ok {
return nil, ErrSecret
}
return tsigHMACProvider(key).Generate(msg, t)
}
func (ts tsigSecretProvider) Verify(msg []byte, t *TSIG) error {
key, ok := ts[t.Hdr.Name]
if !ok {
return ErrSecret
}
return tsigHMACProvider(key).Verify(msg, t)
}
// TSIG is the RR the holds the transaction signature of a message.
// See RFC 2845 and RFC 4635.
type TSIG struct {
@ -40,7 +110,7 @@ type TSIG struct {
// TSIG has no official presentation format, but this will suffice.
func (rr *TSIG) String() string {
s := "\n;; TSIG PSEUDOSECTION:\n"
s := "\n;; TSIG PSEUDOSECTION:\n; " // add another semi-colon to signify TSIG does not have a presentation format
s += rr.Hdr.String() +
" " + rr.Algorithm +
" " + tsigTimeToString(rr.TimeSigned) +
@ -54,6 +124,10 @@ func (rr *TSIG) String() string {
return s
}
func (*TSIG) parse(c *zlexer, origin string) *ParseError {
return &ParseError{err: "TSIG records do not have a presentation format"}
}
// The following values must be put in wireformat, so that the MAC can be calculated.
// RFC 2845, section 3.4.2. TSIG Variables.
type tsigWireFmt struct {
@ -84,22 +158,20 @@ type timerWireFmt struct {
}
// TsigGenerate fills out the TSIG record attached to the message.
// The message should contain
// a "stub" TSIG RR with the algorithm, key name (owner name of the RR),
// time fudge (defaults to 300 seconds) and the current time
// The TSIG MAC is saved in that Tsig RR.
// When TsigGenerate is called for the first time requestMAC is set to the empty string and
// timersOnly is false.
// If something goes wrong an error is returned, otherwise it is nil.
// The message should contain a "stub" TSIG RR with the algorithm, key name
// (owner name of the RR), time fudge (defaults to 300 seconds) and the current
// time The TSIG MAC is saved in that Tsig RR. When TsigGenerate is called for
// the first time requestMAC should be set to the empty string and timersOnly to
// false.
func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) {
return TsigGenerateWithProvider(m, tsigHMACProvider(secret), requestMAC, timersOnly)
}
// TsigGenerateWithProvider is similar to TsigGenerate, but allows for a custom TsigProvider.
func TsigGenerateWithProvider(m *Msg, provider TsigProvider, requestMAC string, timersOnly bool) ([]byte, string, error) {
if m.IsTsig() == nil {
panic("dns: TSIG not last RR in additional")
}
// If we barf here, the caller is to blame
rawsecret, err := fromBase64([]byte(secret))
if err != nil {
return nil, "", err
}
rr := m.Extra[len(m.Extra)-1].(*TSIG)
m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg
@ -107,69 +179,75 @@ func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, s
if err != nil {
return nil, "", err
}
buf := tsigBuffer(mbuf, rr, requestMAC, timersOnly)
t := new(TSIG)
var h hash.Hash
switch strings.ToLower(rr.Algorithm) {
case HmacMD5:
h = hmac.New(md5.New, []byte(rawsecret))
case HmacSHA1:
h = hmac.New(sha1.New, []byte(rawsecret))
case HmacSHA256:
h = hmac.New(sha256.New, []byte(rawsecret))
case HmacSHA512:
h = hmac.New(sha512.New, []byte(rawsecret))
default:
return nil, "", ErrKeyAlg
}
h.Write(buf)
t.MAC = hex.EncodeToString(h.Sum(nil))
t.MACSize = uint16(len(t.MAC) / 2) // Size is half!
t.Hdr = RR_Header{Name: rr.Hdr.Name, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0}
t.Fudge = rr.Fudge
t.TimeSigned = rr.TimeSigned
t.Algorithm = rr.Algorithm
t.OrigId = m.Id
tbuf := make([]byte, t.len())
if off, err := PackRR(t, tbuf, 0, nil, false); err == nil {
tbuf = tbuf[:off] // reset to actual size used
} else {
buf, err := tsigBuffer(mbuf, rr, requestMAC, timersOnly)
if err != nil {
return nil, "", err
}
mbuf = append(mbuf, tbuf...)
t := new(TSIG)
// Copy all TSIG fields except MAC, its size, and time signed which are filled when signing.
*t = *rr
t.TimeSigned = 0
t.MAC = ""
t.MACSize = 0
// Sign unless there is a key or MAC validation error (RFC 8945 5.3.2)
if rr.Error != RcodeBadKey && rr.Error != RcodeBadSig {
mac, err := provider.Generate(buf, rr)
if err != nil {
return nil, "", err
}
t.TimeSigned = rr.TimeSigned
t.MAC = hex.EncodeToString(mac)
t.MACSize = uint16(len(t.MAC) / 2) // Size is half!
}
tbuf := make([]byte, Len(t))
off, err := PackRR(t, tbuf, 0, nil, false)
if err != nil {
return nil, "", err
}
mbuf = append(mbuf, tbuf[:off]...)
// Update the ArCount directly in the buffer.
binary.BigEndian.PutUint16(mbuf[10:], uint16(len(m.Extra)+1))
return mbuf, t.MAC, nil
}
// TsigVerify verifies the TSIG on a message.
// If the signature does not validate err contains the
// error, otherwise it is nil.
// TsigVerify verifies the TSIG on a message. If the signature does not
// validate the returned error contains the cause. If the signature is OK, the
// error is nil.
func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
rawsecret, err := fromBase64([]byte(secret))
if err != nil {
return err
}
return tsigVerify(msg, tsigHMACProvider(secret), requestMAC, timersOnly, uint64(time.Now().Unix()))
}
// TsigVerifyWithProvider is similar to TsigVerify, but allows for a custom TsigProvider.
func TsigVerifyWithProvider(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool) error {
return tsigVerify(msg, provider, requestMAC, timersOnly, uint64(time.Now().Unix()))
}
// actual implementation of TsigVerify, taking the current time ('now') as a parameter for the convenience of tests.
func tsigVerify(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool, now uint64) error {
// Strip the TSIG from the incoming msg
stripped, tsig, err := stripTsig(msg)
if err != nil {
return err
}
msgMAC, err := hex.DecodeString(tsig.MAC)
buf, err := tsigBuffer(stripped, tsig, requestMAC, timersOnly)
if err != nil {
return err
}
buf := tsigBuffer(stripped, tsig, requestMAC, timersOnly)
if err := provider.Verify(buf, tsig); err != nil {
return err
}
// Fudge factor works both ways. A message can arrive before it was signed because
// of clock skew.
now := uint64(time.Now().Unix())
// We check this after verifying the signature, following draft-ietf-dnsop-rfc2845bis
// instead of RFC2845, in order to prevent a security vulnerability as reported in CVE-2017-3142/3143.
ti := now - tsig.TimeSigned
if now < tsig.TimeSigned {
ti = tsig.TimeSigned - now
@ -178,28 +256,11 @@ func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
return ErrTime
}
var h hash.Hash
switch strings.ToLower(tsig.Algorithm) {
case HmacMD5:
h = hmac.New(md5.New, rawsecret)
case HmacSHA1:
h = hmac.New(sha1.New, rawsecret)
case HmacSHA256:
h = hmac.New(sha256.New, rawsecret)
case HmacSHA512:
h = hmac.New(sha512.New, rawsecret)
default:
return ErrKeyAlg
}
h.Write(buf)
if !hmac.Equal(h.Sum(nil), msgMAC) {
return ErrSig
}
return nil
}
// Create a wiredata buffer for the MAC calculation.
func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []byte {
func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) ([]byte, error) {
var buf []byte
if rr.TimeSigned == 0 {
rr.TimeSigned = uint64(time.Now().Unix())
@ -216,7 +277,10 @@ func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []b
m.MACSize = uint16(len(requestMAC) / 2)
m.MAC = requestMAC
buf = make([]byte, len(requestMAC)) // long enough
n, _ := packMacWire(m, buf)
n, err := packMacWire(m, buf)
if err != nil {
return nil, err
}
buf = buf[:n]
}
@ -225,20 +289,26 @@ func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []b
tsig := new(timerWireFmt)
tsig.TimeSigned = rr.TimeSigned
tsig.Fudge = rr.Fudge
n, _ := packTimerWire(tsig, tsigvar)
n, err := packTimerWire(tsig, tsigvar)
if err != nil {
return nil, err
}
tsigvar = tsigvar[:n]
} else {
tsig := new(tsigWireFmt)
tsig.Name = strings.ToLower(rr.Hdr.Name)
tsig.Name = CanonicalName(rr.Hdr.Name)
tsig.Class = ClassANY
tsig.Ttl = rr.Hdr.Ttl
tsig.Algorithm = strings.ToLower(rr.Algorithm)
tsig.Algorithm = CanonicalName(rr.Algorithm)
tsig.TimeSigned = rr.TimeSigned
tsig.Fudge = rr.Fudge
tsig.Error = rr.Error
tsig.OtherLen = rr.OtherLen
tsig.OtherData = rr.OtherData
n, _ := packTsigWire(tsig, tsigvar)
n, err := packTsigWire(tsig, tsigvar)
if err != nil {
return nil, err
}
tsigvar = tsigvar[:n]
}
@ -248,7 +318,7 @@ func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []b
} else {
buf = append(msgbuf, tsigvar...)
}
return buf
return buf, nil
}
// Strip the TSIG from the raw message.

View File

@ -2,6 +2,10 @@ package dns
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"strings"
"testing"
"time"
)
@ -14,7 +18,7 @@ func newTsig(algo string) *Msg {
}
func TestTsig(t *testing.T) {
m := newTsig(HmacMD5)
m := newTsig(HmacSHA256)
buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
if err != nil {
t.Fatal(err)
@ -26,13 +30,13 @@ func TestTsig(t *testing.T) {
// TSIG accounts for ID substitution. This means if the message ID is
// changed by a forwarder, we should still be able to verify the TSIG.
m = newTsig(HmacMD5)
m = newTsig(HmacSHA256)
buf, _, err = TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
if err != nil {
t.Fatal(err)
}
binary.BigEndian.PutUint16(buf[0:2], uint16(42))
binary.BigEndian.PutUint16(buf[0:2], 42)
err = TsigVerify(buf, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
if err != nil {
t.Fatal(err)
@ -40,7 +44,7 @@ func TestTsig(t *testing.T) {
}
func TestTsigCase(t *testing.T) {
m := newTsig("HmAc-mD5.sig-ALg.rEg.int.") // HmacMD5
m := newTsig(strings.ToUpper(HmacSHA256))
buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
if err != nil {
t.Fatal(err)
@ -50,3 +54,356 @@ func TestTsigCase(t *testing.T) {
t.Fatal(err)
}
}
func TestTsigErrorResponse(t *testing.T) {
for _, rcode := range []uint16{RcodeBadSig, RcodeBadKey} {
m := newTsig(strings.ToUpper(HmacSHA256))
m.IsTsig().Error = rcode
buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
if err != nil {
t.Fatal(err)
}
err = m.Unpack(buf)
if err != nil {
t.Fatal(err)
}
mTsig := m.IsTsig()
if mTsig.MAC != "" {
t.Error("Expected empty MAC")
}
if mTsig.MACSize != 0 {
t.Error("Expected 0 MACSize")
}
if mTsig.TimeSigned != 0 {
t.Errorf("Expected TimeSigned to be 0, got %v", mTsig.TimeSigned)
}
}
}
func TestTsigBadTimeResponse(t *testing.T) {
clientTime := uint64(time.Now().Unix()) - 3600
m := newTsig(strings.ToUpper(HmacSHA256))
m.IsTsig().Error = RcodeBadTime
m.IsTsig().TimeSigned = clientTime
buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
if err != nil {
t.Fatal(err)
}
err = m.Unpack(buf)
if err != nil {
t.Fatal(err)
}
mTsig := m.IsTsig()
if mTsig.MAC == "" {
t.Error("Expected non-empty MAC")
}
if int(mTsig.MACSize) != len(mTsig.MAC)/2 {
t.Errorf("Expected MACSize %v, got %v", len(mTsig.MAC)/2, mTsig.MACSize)
}
if mTsig.TimeSigned != clientTime {
t.Errorf("Expected TimeSigned %v to be retained, got %v", clientTime, mTsig.TimeSigned)
}
}
const (
// A template wire-format DNS message (in hex form) containing a TSIG RR.
// Its time signed field will be filled by tests.
wireMsg = "c60028000001000000010001076578616d706c6503636f6d00000600010161c00c0001000100000e100004c0000201077465" +
"73746b65790000fa00ff00000000003d0b686d61632d73686132353600" +
"%012x" + // placeholder for the "time signed" field
"012c00208cf23e0081d915478a182edcea7ff48ad102948e6c7ef8e887536957d1fa5616c60000000000"
// A secret (in base64 format) with which the TSIG in wireMsg will be validated
testSecret = "NoTCJU+DMqFWywaPyxSijrDEA/eC3nK0xi3AMEZuPVk="
// the 'time signed' field value that would make the TSIG RR valid with testSecret
timeSigned uint64 = 1594855491
)
func TestTsigErrors(t *testing.T) {
// Helper shortcut to build wire-format test message.
// TsigVerify can modify the slice, so we need to create a new one for each test case below.
buildMsgData := func(tm uint64) []byte {
msgData, err := hex.DecodeString(fmt.Sprintf(wireMsg, tm))
if err != nil {
t.Fatal(err)
}
return msgData
}
// the signature is valid but 'time signed' is too far from the "current time".
if err := tsigVerify(buildMsgData(timeSigned), tsigHMACProvider(testSecret), "", false, timeSigned+301); err != ErrTime {
t.Fatalf("expected an error '%v' but got '%v'", ErrTime, err)
}
if err := tsigVerify(buildMsgData(timeSigned), tsigHMACProvider(testSecret), "", false, timeSigned-301); err != ErrTime {
t.Fatalf("expected an error '%v' but got '%v'", ErrTime, err)
}
// the signature is invalid and 'time signed' is too far.
// the signature should be checked first, so we should see ErrSig.
if err := tsigVerify(buildMsgData(timeSigned+301), tsigHMACProvider(testSecret), "", false, timeSigned); err != ErrSig {
t.Fatalf("expected an error '%v' but got '%v'", ErrSig, err)
}
// tweak the algorithm name in the wire data, resulting in the "unknown algorithm" error.
msgData := buildMsgData(timeSigned)
copy(msgData[67:], "bogus")
if err := tsigVerify(msgData, tsigHMACProvider(testSecret), "", false, timeSigned); err != ErrKeyAlg {
t.Fatalf("expected an error '%v' but got '%v'", ErrKeyAlg, err)
}
// call TsigVerify with a message that doesn't contain a TSIG
msgData, tsig, err := stripTsig(buildMsgData(timeSigned))
if err != nil {
t.Fatal(err)
}
if err := tsigVerify(msgData, tsigHMACProvider(testSecret), "", false, timeSigned); err != ErrNoSig {
t.Fatalf("expected an error '%v' but got '%v'", ErrNoSig, err)
}
// replace the test TSIG with a bogus one with large "other data", which would cause overflow in TsigVerify.
// The overflow should be caught without disruption.
tsig.OtherData = strings.Repeat("00", 4096)
tsig.OtherLen = uint16(len(tsig.OtherData) / 2)
msg := new(Msg)
if err = msg.Unpack(msgData); err != nil {
t.Fatal(err)
}
msg.Extra = append(msg.Extra, tsig)
if msgData, err = msg.Pack(); err != nil {
t.Fatal(err)
}
err = tsigVerify(msgData, tsigHMACProvider(testSecret), "", false, timeSigned)
if err == nil || !strings.Contains(err.Error(), "overflow") {
t.Errorf("expected error to contain %q, but got %v", "overflow", err)
}
}
// This test exercises some more corner cases for TsigGenerate.
func TestTsigGenerate(t *testing.T) {
// This is a template TSIG to be used for signing.
tsig := TSIG{
Hdr: RR_Header{Name: "testkey.", Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0},
Algorithm: HmacSHA256,
TimeSigned: timeSigned,
Fudge: 300,
OrigId: 42,
Error: RcodeBadTime, // use a non-0 value to make sure it's indeed used
}
tests := []struct {
desc string // test description
requestMAC string // request MAC to be passed to TsigGenerate (arbitrary choice)
otherData string // other data specified in the TSIG (arbitrary choice)
expectedMAC string // pre-computed expected (correct) MAC in hex form
}{
{"with request MAC", "3684c225", "",
"c110e3f62694755c10761dc8717462431ee34340b7c9d1eee09449150757c5b1"},
{"no request MAC", "", "",
"385449a425c6d52b9bf2c65c0726eefa0ad8084cdaf488f24547e686605b9610"},
{"with other data", "3684c225", "666f6f",
"15b91571ca80b3b410a77e2b44f8cc4f35ace22b26020138439dd94803e23b5d"},
}
for _, tc := range tests {
tc := tc
t.Run(tc.desc, func(t *testing.T) {
// Build TSIG for signing from the template
testTSIG := tsig
testTSIG.OtherLen = uint16(len(tc.otherData) / 2)
testTSIG.OtherData = tc.otherData
req := &Msg{
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
Extra: []RR{&testTSIG},
}
// Call generate, and check the returned MAC against the expected value
msgData, mac, err := TsigGenerate(req, testSecret, tc.requestMAC, false)
if err != nil {
t.Error(err)
}
if mac != tc.expectedMAC {
t.Fatalf("MAC doesn't match: expected '%s', but got '%s'", tc.expectedMAC, mac)
}
// Retrieve the TSIG to be sent out, confirm the MAC in it
_, outTSIG, err := stripTsig(msgData)
if err != nil {
t.Error(err)
}
if outTSIG.MAC != tc.expectedMAC {
t.Fatalf("MAC doesn't match: expected '%s', but got '%s'", tc.expectedMAC, outTSIG.MAC)
}
// Confirm other fields of MAC.
// RDLENGTH should be valid as stripTsig succeeded, so we exclude it from comparison
outTSIG.MACSize = 0
outTSIG.MAC = ""
testTSIG.Hdr.Rdlength = outTSIG.Hdr.Rdlength
if *outTSIG != testTSIG {
t.Fatalf("TSIG RR doesn't match: expected '%v', but got '%v'", *outTSIG, testTSIG)
}
})
}
}
func TestTSIGHMAC224And384(t *testing.T) {
tests := []struct {
algorithm string // TSIG algorithm, also used as test description
secret string // (arbitrarily chosen) secret suitable for the algorithm in base64 format
expectedMAC string // pre-computed expected (correct) MAC in hex form
}{
{HmacSHA224, "hVEkQuAqnTmBuRrT9KF1Udr91gOMGWPw9LaTtw==",
"d6daf9ea189e48bc38f9aed63d6cc4140cdfa38a7a333ee2eefdbd31",
},
{HmacSHA384, "Qjer2TL2lAdpq9w6Gjs98/ClCQx/L3vtgVHCmrZ8l/oKEPjqUUMFO18gMCRwd5H4",
"89a48936d29187870c325cbdba5ad71609bd038d0459d6010c844d659c570e881d3650e4fe7310be53ebe5178d0d1001",
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.algorithm, func(t *testing.T) {
// Build a DNS message with TSIG for the test scenario
tsig := TSIG{
Hdr: RR_Header{Name: "testkey.", Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0},
Algorithm: tc.algorithm,
TimeSigned: timeSigned,
Fudge: 300,
OrigId: 42,
}
req := &Msg{
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
Extra: []RR{&tsig},
}
// Confirm both Generate and Verify recognize the algorithm and handle it correctly
msgData, mac, err := TsigGenerate(req, tc.secret, "", false)
if err != nil {
t.Error(err)
}
if mac != tc.expectedMAC {
t.Fatalf("MAC doesn't match: expected '%s' but got '%s'", tc.expectedMAC, mac)
}
if err = tsigVerify(msgData, tsigHMACProvider(tc.secret), "", false, timeSigned); err != nil {
t.Error(err)
}
})
}
}
const testGoodKeyName = "goodkey."
var (
errBadKey = errors.New("this is an intentional error")
testGoodMAC = []byte{0, 1, 2, 3}
)
// testProvider always generates the same MAC and only accepts the one signature
type testProvider struct {
GenerateAllKeys bool
}
func (provider *testProvider) Generate(_ []byte, t *TSIG) ([]byte, error) {
if t.Hdr.Name == testGoodKeyName || provider.GenerateAllKeys {
return testGoodMAC, nil
}
return nil, errBadKey
}
func (*testProvider) Verify(_ []byte, t *TSIG) error {
if t.Hdr.Name == testGoodKeyName {
return nil
}
return errBadKey
}
func TestTsigGenerateProvider(t *testing.T) {
tables := []struct {
keyname string
mac []byte
err error
}{
{
testGoodKeyName,
testGoodMAC,
nil,
},
{
"badkey.",
nil,
errBadKey,
},
}
for _, table := range tables {
t.Run(table.keyname, func(t *testing.T) {
tsig := TSIG{
Hdr: RR_Header{Name: table.keyname, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0},
Algorithm: HmacSHA1,
TimeSigned: timeSigned,
Fudge: 300,
OrigId: 42,
}
req := &Msg{
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
Extra: []RR{&tsig},
}
_, mac, err := TsigGenerateWithProvider(req, new(testProvider), "", false)
if err != table.err {
t.Fatalf("error doesn't match: expected '%s' but got '%s'", table.err, err)
}
expectedMAC := hex.EncodeToString(table.mac)
if mac != expectedMAC {
t.Fatalf("MAC doesn't match: expected '%s' but got '%s'", table.mac, expectedMAC)
}
})
}
}
func TestTsigVerifyProvider(t *testing.T) {
tables := []struct {
keyname string
err error
}{
{
testGoodKeyName,
nil,
},
{
"badkey.",
errBadKey,
},
}
for _, table := range tables {
t.Run(table.keyname, func(t *testing.T) {
tsig := TSIG{
Hdr: RR_Header{Name: table.keyname, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0},
Algorithm: HmacSHA1,
TimeSigned: timeSigned,
Fudge: 300,
OrigId: 42,
}
req := &Msg{
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
Extra: []RR{&tsig},
}
provider := &testProvider{true}
msgData, _, err := TsigGenerateWithProvider(req, provider, "", false)
if err != nil {
t.Error(err)
}
if err = tsigVerify(msgData, provider, "", false, timeSigned); err != table.err {
t.Fatalf("error doesn't match: expected '%s' but got '%s'", table.err, err)
}
})
}
}

474
types.go
View File

@ -1,6 +1,7 @@
package dns
import (
"bytes"
"fmt"
"net"
"strconv"
@ -61,6 +62,7 @@ const (
TypeCERT uint16 = 37
TypeDNAME uint16 = 39
TypeOPT uint16 = 41 // EDNS
TypeAPL uint16 = 42
TypeDS uint16 = 43
TypeSSHFP uint16 = 44
TypeRRSIG uint16 = 46
@ -79,6 +81,9 @@ const (
TypeCDNSKEY uint16 = 60
TypeOPENPGPKEY uint16 = 61
TypeCSYNC uint16 = 62
TypeZONEMD uint16 = 63
TypeSVCB uint16 = 64
TypeHTTPS uint16 = 65
TypeSPF uint16 = 99
TypeUINFO uint16 = 100
TypeUID uint16 = 101
@ -146,6 +151,14 @@ const (
OpcodeUpdate = 5
)
// Used in ZONEMD https://tools.ietf.org/html/rfc8976
const (
ZoneMDSchemeSimple = 1
ZoneMDHashAlgSHA384 = 1
ZoneMDHashAlgSHA512 = 2
)
// Header is the wire format for the DNS packet header.
type Header struct {
Id uint16
@ -163,11 +176,11 @@ const (
_RD = 1 << 8 // recursion desired
_RA = 1 << 7 // recursion available
_Z = 1 << 6 // Z
_AD = 1 << 5 // authticated data
_AD = 1 << 5 // authenticated data
_CD = 1 << 4 // checking disabled
)
// Various constants used in the LOC RR, See RFC 1887.
// Various constants used in the LOC RR. See RFC 1887.
const (
LOC_EQUATOR = 1 << 31 // RFC 1876, Section 2.
LOC_PRIMEMERIDIAN = 1 << 31 // RFC 1876, Section 2.
@ -205,21 +218,23 @@ var CertTypeToString = map[uint16]string{
CertOID: "OID",
}
// StringToCertType is the reverseof CertTypeToString.
var StringToCertType = reverseInt16(CertTypeToString)
//go:generate go run types_generate.go
// Question holds a DNS question. There can be multiple questions in the
// question section of a message. Usually there is just one.
// Question holds a DNS question. Usually there is just one. While the
// original DNS RFCs allow multiple questions in the question section of a
// message, in practice it never works. Because most DNS servers see multiple
// questions as an error, it is recommended to only have one question per
// message.
type Question struct {
Name string `dns:"cdomain-name"` // "cdomain-name" specifies encoding (and may be compressed)
Qtype uint16
Qclass uint16
}
func (q *Question) len() int {
return len(q.Name) + 1 + 2 + 2
func (q *Question) len(off int, compression map[string]struct{}) int {
l := domainNameLen(q.Name, off, compression, true)
l += 2 + 2
return l
}
func (q *Question) String() (s string) {
@ -230,7 +245,7 @@ func (q *Question) String() (s string) {
return s
}
// ANY is a wildcard record. See RFC 1035, Section 3.2.3. ANY
// ANY is a wild card record. See RFC 1035, Section 3.2.3. ANY
// is named "*" there.
type ANY struct {
Hdr RR_Header
@ -239,6 +254,25 @@ type ANY struct {
func (rr *ANY) String() string { return rr.Hdr.String() }
func (*ANY) parse(c *zlexer, origin string) *ParseError {
return &ParseError{err: "ANY records do not have a presentation format"}
}
// NULL RR. See RFC 1035.
type NULL struct {
Hdr RR_Header
Data string `dns:"any"`
}
func (rr *NULL) String() string {
// There is no presentation format; prefix string with a comment.
return ";" + rr.Hdr.String() + rr.Data
}
func (*NULL) parse(c *zlexer, origin string) *ParseError {
return &ParseError{err: "NULL records do not have a presentation format"}
}
// CNAME RR. See RFC 1034.
type CNAME struct {
Hdr RR_Header
@ -330,7 +364,7 @@ func (rr *MX) String() string {
type AFSDB struct {
Hdr RR_Header
Subtype uint16
Hostname string `dns:"cdomain-name"`
Hostname string `dns:"domain-name"`
}
func (rr *AFSDB) String() string {
@ -351,7 +385,7 @@ func (rr *X25) String() string {
type RT struct {
Hdr RR_Header
Preference uint16
Host string `dns:"cdomain-name"`
Host string `dns:"domain-name"` // RFC 3597 prohibits compressing records not defined in RFC 1035.
}
func (rr *RT) String() string {
@ -386,7 +420,7 @@ type RP struct {
}
func (rr *RP) String() string {
return rr.Hdr.String() + rr.Mbox + " " + sprintTxt([]string{rr.Txt})
return rr.Hdr.String() + sprintName(rr.Mbox) + " " + sprintName(rr.Txt)
}
// SOA RR. See RFC 1035.
@ -419,128 +453,172 @@ type TXT struct {
func (rr *TXT) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) }
func sprintName(s string) string {
src := []byte(s)
dst := make([]byte, 0, len(src))
for i := 0; i < len(src); {
if i+1 < len(src) && src[i] == '\\' && src[i+1] == '.' {
dst = append(dst, src[i:i+2]...)
i += 2
} else {
b, n := nextByte(src, i)
if n == 0 {
i++ // dangling back slash
} else if b == '.' {
dst = append(dst, b)
} else {
dst = appendDomainNameByte(dst, b)
var dst strings.Builder
for i := 0; i < len(s); {
if s[i] == '.' {
if dst.Len() != 0 {
dst.WriteByte('.')
}
i += n
i++
continue
}
b, n := nextByte(s, i)
if n == 0 {
// Drop "dangling" incomplete escapes.
if dst.Len() == 0 {
return s[:i]
}
break
}
if isDomainNameLabelSpecial(b) {
if dst.Len() == 0 {
dst.Grow(len(s) * 2)
dst.WriteString(s[:i])
}
dst.WriteByte('\\')
dst.WriteByte(b)
} else if b < ' ' || b > '~' { // unprintable, use \DDD
if dst.Len() == 0 {
dst.Grow(len(s) * 2)
dst.WriteString(s[:i])
}
dst.WriteString(escapeByte(b))
} else {
if dst.Len() != 0 {
dst.WriteByte(b)
}
}
i += n
}
return string(dst)
if dst.Len() == 0 {
return s
}
return dst.String()
}
func sprintTxtOctet(s string) string {
src := []byte(s)
dst := make([]byte, 0, len(src))
dst = append(dst, '"')
for i := 0; i < len(src); {
if i+1 < len(src) && src[i] == '\\' && src[i+1] == '.' {
dst = append(dst, src[i:i+2]...)
var dst strings.Builder
dst.Grow(2 + len(s))
dst.WriteByte('"')
for i := 0; i < len(s); {
if i+1 < len(s) && s[i] == '\\' && s[i+1] == '.' {
dst.WriteString(s[i : i+2])
i += 2
} else {
b, n := nextByte(src, i)
if n == 0 {
i++ // dangling back slash
} else if b == '.' {
dst = append(dst, b)
} else {
if b < ' ' || b > '~' {
dst = appendByte(dst, b)
} else {
dst = append(dst, b)
}
}
i += n
continue
}
b, n := nextByte(s, i)
if n == 0 {
i++ // dangling back slash
} else {
writeTXTStringByte(&dst, b)
}
i += n
}
dst = append(dst, '"')
return string(dst)
dst.WriteByte('"')
return dst.String()
}
func sprintTxt(txt []string) string {
var out []byte
var out strings.Builder
for i, s := range txt {
out.Grow(3 + len(s))
if i > 0 {
out = append(out, ` "`...)
out.WriteString(` "`)
} else {
out = append(out, '"')
out.WriteByte('"')
}
bs := []byte(s)
for j := 0; j < len(bs); {
b, n := nextByte(bs, j)
for j := 0; j < len(s); {
b, n := nextByte(s, j)
if n == 0 {
break
}
out = appendTXTStringByte(out, b)
writeTXTStringByte(&out, b)
j += n
}
out = append(out, '"')
out.WriteByte('"')
}
return string(out)
return out.String()
}
func appendDomainNameByte(s []byte, b byte) []byte {
func writeTXTStringByte(s *strings.Builder, b byte) {
switch {
case b == '"' || b == '\\':
s.WriteByte('\\')
s.WriteByte(b)
case b < ' ' || b > '~':
s.WriteString(escapeByte(b))
default:
s.WriteByte(b)
}
}
const (
escapedByteSmall = "" +
`\000\001\002\003\004\005\006\007\008\009` +
`\010\011\012\013\014\015\016\017\018\019` +
`\020\021\022\023\024\025\026\027\028\029` +
`\030\031`
escapedByteLarge = `\127\128\129` +
`\130\131\132\133\134\135\136\137\138\139` +
`\140\141\142\143\144\145\146\147\148\149` +
`\150\151\152\153\154\155\156\157\158\159` +
`\160\161\162\163\164\165\166\167\168\169` +
`\170\171\172\173\174\175\176\177\178\179` +
`\180\181\182\183\184\185\186\187\188\189` +
`\190\191\192\193\194\195\196\197\198\199` +
`\200\201\202\203\204\205\206\207\208\209` +
`\210\211\212\213\214\215\216\217\218\219` +
`\220\221\222\223\224\225\226\227\228\229` +
`\230\231\232\233\234\235\236\237\238\239` +
`\240\241\242\243\244\245\246\247\248\249` +
`\250\251\252\253\254\255`
)
// escapeByte returns the \DDD escaping of b which must
// satisfy b < ' ' || b > '~'.
func escapeByte(b byte) string {
if b < ' ' {
return escapedByteSmall[b*4 : b*4+4]
}
b -= '~' + 1
// The cast here is needed as b*4 may overflow byte.
return escapedByteLarge[int(b)*4 : int(b)*4+4]
}
// isDomainNameLabelSpecial returns true if
// a domain name label byte should be prefixed
// with an escaping backslash.
func isDomainNameLabelSpecial(b byte) bool {
switch b {
case '.', ' ', '\'', '@', ';', '(', ')': // additional chars to escape
return append(s, '\\', b)
case '.', ' ', '\'', '@', ';', '(', ')', '"', '\\':
return true
}
return appendTXTStringByte(s, b)
return false
}
func appendTXTStringByte(s []byte, b byte) []byte {
switch b {
case '"', '\\':
return append(s, '\\', b)
}
if b < ' ' || b > '~' {
return appendByte(s, b)
}
return append(s, b)
}
func appendByte(s []byte, b byte) []byte {
var buf [3]byte
bufs := strconv.AppendInt(buf[:0], int64(b), 10)
s = append(s, '\\')
for i := 0; i < 3-len(bufs); i++ {
s = append(s, '0')
}
for _, r := range bufs {
s = append(s, r)
}
return s
}
func nextByte(b []byte, offset int) (byte, int) {
if offset >= len(b) {
func nextByte(s string, offset int) (byte, int) {
if offset >= len(s) {
return 0, 0
}
if b[offset] != '\\' {
if s[offset] != '\\' {
// not an escape sequence
return b[offset], 1
return s[offset], 1
}
switch len(b) - offset {
switch len(s) - offset {
case 1: // dangling escape
return 0, 0
case 2, 3: // too short to be \ddd
default: // maybe \ddd
if isDigit(b[offset+1]) && isDigit(b[offset+2]) && isDigit(b[offset+3]) {
return dddToByte(b[offset+1:]), 4
if isDigit(s[offset+1]) && isDigit(s[offset+2]) && isDigit(s[offset+3]) {
return dddStringToByte(s[offset+1:]), 4
}
}
// not \ddd, just an RFC 1035 "quoted" character
return b[offset+1], 2
return s[offset+1], 2
}
// SPF RR. See RFC 4408, Section 3.1.1.
@ -694,8 +772,8 @@ type LOC struct {
Altitude uint32
}
// cmToM takes a cm value expressed in RFC1876 SIZE mantissa/exponent
// format and returns a string in m (two decimals for the cm)
// cmToM takes a cm value expressed in RFC 1876 SIZE mantissa/exponent
// format and returns a string in m (two decimals for the cm).
func cmToM(m, e uint8) string {
if e < 2 {
if e == 1 {
@ -728,7 +806,7 @@ func (rr *LOC) String() string {
lat = lat % LOC_DEGREES
m := lat / LOC_HOURS
lat = lat % LOC_HOURS
s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, (float64(lat) / 1000), ns)
s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, float64(lat)/1000, ns)
lon := rr.Longitude
ew := "E"
@ -742,7 +820,7 @@ func (rr *LOC) String() string {
lon = lon % LOC_DEGREES
m = lon / LOC_HOURS
lon = lon % LOC_HOURS
s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, (float64(lon) / 1000), ew)
s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, float64(lon)/1000, ew)
var alt = float64(rr.Altitude) / 100
alt -= LOC_ALTITUDEBASE
@ -752,9 +830,9 @@ func (rr *LOC) String() string {
s += fmt.Sprintf("%.0fm ", alt)
}
s += cmToM((rr.Size&0xf0)>>4, rr.Size&0x0f) + "m "
s += cmToM((rr.HorizPre&0xf0)>>4, rr.HorizPre&0x0f) + "m "
s += cmToM((rr.VertPre&0xf0)>>4, rr.VertPre&0x0f) + "m"
s += cmToM(rr.Size&0xf0>>4, rr.Size&0x0f) + "m "
s += cmToM(rr.HorizPre&0xf0>>4, rr.HorizPre&0x0f) + "m "
s += cmToM(rr.VertPre&0xf0>>4, rr.VertPre&0x0f) + "m"
return s
}
@ -801,22 +879,16 @@ type NSEC struct {
func (rr *NSEC) String() string {
s := rr.Hdr.String() + sprintName(rr.NextDomain)
for i := 0; i < len(rr.TypeBitMap); i++ {
s += " " + Type(rr.TypeBitMap[i]).String()
for _, t := range rr.TypeBitMap {
s += " " + Type(t).String()
}
return s
}
func (rr *NSEC) len() int {
l := rr.Hdr.len() + len(rr.NextDomain) + 1
lastwindow := uint32(2 ^ 32 + 1)
for _, t := range rr.TypeBitMap {
window := t / 256
if uint32(window) != lastwindow {
l += 1 + 32
}
lastwindow = uint32(window)
}
func (rr *NSEC) len(off int, compression map[string]struct{}) int {
l := rr.Hdr.len(off, compression)
l += domainNameLen(rr.NextDomain, off+l, compression, false)
l += typeBitMapLen(rr.TypeBitMap)
return l
}
@ -966,22 +1038,16 @@ func (rr *NSEC3) String() string {
" " + strconv.Itoa(int(rr.Iterations)) +
" " + saltToString(rr.Salt) +
" " + rr.NextDomain
for i := 0; i < len(rr.TypeBitMap); i++ {
s += " " + Type(rr.TypeBitMap[i]).String()
for _, t := range rr.TypeBitMap {
s += " " + Type(t).String()
}
return s
}
func (rr *NSEC3) len() int {
l := rr.Hdr.len() + 6 + len(rr.Salt)/2 + 1 + len(rr.NextDomain) + 1
lastwindow := uint32(2 ^ 32 + 1)
for _, t := range rr.TypeBitMap {
window := t / 256
if uint32(window) != lastwindow {
l += 1 + 32
}
lastwindow = uint32(window)
}
func (rr *NSEC3) len(off int, compression map[string]struct{}) int {
l := rr.Hdr.len(off, compression)
l += 6 + len(rr.Salt)/2 + 1 + len(rr.NextDomain) + 1
l += typeBitMapLen(rr.TypeBitMap)
return l
}
@ -1020,10 +1086,16 @@ type TKEY struct {
// TKEY has no official presentation format, but this will suffice.
func (rr *TKEY) String() string {
s := "\n;; TKEY PSEUDOSECTION:\n"
s += rr.Hdr.String() + " " + rr.Algorithm + " " +
strconv.Itoa(int(rr.KeySize)) + " " + rr.Key + " " +
strconv.Itoa(int(rr.OtherLen)) + " " + rr.OtherData
s := ";" + rr.Hdr.String() +
" " + rr.Algorithm +
" " + TimeToString(rr.Inception) +
" " + TimeToString(rr.Expiration) +
" " + strconv.Itoa(int(rr.Mode)) +
" " + strconv.Itoa(int(rr.Error)) +
" " + strconv.Itoa(int(rr.KeySize)) +
" " + rr.Key +
" " + strconv.Itoa(int(rr.OtherLen)) +
" " + rr.OtherData
return s
}
@ -1059,6 +1131,7 @@ type URI struct {
Target string `dns:"octet"`
}
// rr.Target to be parsed as a sequence of character encoded octets according to RFC 3986
func (rr *URI) String() string {
return rr.Hdr.String() + strconv.Itoa(int(rr.Priority)) +
" " + strconv.Itoa(int(rr.Weight)) + " " + sprintTxtOctet(rr.Target)
@ -1220,6 +1293,7 @@ type CAA struct {
Value string `dns:"octet"`
}
// rr.Value Is the character-string encoding of the value field as specified in RFC 1035, Section 5.1.
func (rr *CAA) String() string {
return rr.Hdr.String() + strconv.Itoa(int(rr.Flag)) + " " + rr.Tag + " " + sprintTxtOctet(rr.Value)
}
@ -1283,34 +1357,127 @@ type CSYNC struct {
func (rr *CSYNC) String() string {
s := rr.Hdr.String() + strconv.FormatInt(int64(rr.Serial), 10) + " " + strconv.Itoa(int(rr.Flags))
for i := 0; i < len(rr.TypeBitMap); i++ {
s += " " + Type(rr.TypeBitMap[i]).String()
for _, t := range rr.TypeBitMap {
s += " " + Type(t).String()
}
return s
}
func (rr *CSYNC) len() int {
l := rr.Hdr.len() + 4 + 2
lastwindow := uint32(2 ^ 32 + 1)
for _, t := range rr.TypeBitMap {
window := t / 256
if uint32(window) != lastwindow {
l += 1 + 32
}
lastwindow = uint32(window)
}
func (rr *CSYNC) len(off int, compression map[string]struct{}) int {
l := rr.Hdr.len(off, compression)
l += 4 + 2
l += typeBitMapLen(rr.TypeBitMap)
return l
}
// ZONEMD RR, from draft-ietf-dnsop-dns-zone-digest
type ZONEMD struct {
Hdr RR_Header
Serial uint32
Scheme uint8
Hash uint8
Digest string `dns:"hex"`
}
func (rr *ZONEMD) String() string {
return rr.Hdr.String() +
strconv.Itoa(int(rr.Serial)) +
" " + strconv.Itoa(int(rr.Scheme)) +
" " + strconv.Itoa(int(rr.Hash)) +
" " + rr.Digest
}
// APL RR. See RFC 3123.
type APL struct {
Hdr RR_Header
Prefixes []APLPrefix `dns:"apl"`
}
// APLPrefix is an address prefix hold by an APL record.
type APLPrefix struct {
Negation bool
Network net.IPNet
}
// String returns presentation form of the APL record.
func (rr *APL) String() string {
var sb strings.Builder
sb.WriteString(rr.Hdr.String())
for i, p := range rr.Prefixes {
if i > 0 {
sb.WriteByte(' ')
}
sb.WriteString(p.str())
}
return sb.String()
}
// str returns presentation form of the APL prefix.
func (a *APLPrefix) str() string {
var sb strings.Builder
if a.Negation {
sb.WriteByte('!')
}
switch len(a.Network.IP) {
case net.IPv4len:
sb.WriteByte('1')
case net.IPv6len:
sb.WriteByte('2')
}
sb.WriteByte(':')
switch len(a.Network.IP) {
case net.IPv4len:
sb.WriteString(a.Network.IP.String())
case net.IPv6len:
// add prefix for IPv4-mapped IPv6
if v4 := a.Network.IP.To4(); v4 != nil {
sb.WriteString("::ffff:")
}
sb.WriteString(a.Network.IP.String())
}
sb.WriteByte('/')
prefix, _ := a.Network.Mask.Size()
sb.WriteString(strconv.Itoa(prefix))
return sb.String()
}
// equals reports whether two APL prefixes are identical.
func (a *APLPrefix) equals(b *APLPrefix) bool {
return a.Negation == b.Negation &&
bytes.Equal(a.Network.IP, b.Network.IP) &&
bytes.Equal(a.Network.Mask, b.Network.Mask)
}
// copy returns a copy of the APL prefix.
func (a *APLPrefix) copy() APLPrefix {
return APLPrefix{
Negation: a.Negation,
Network: copyNet(a.Network),
}
}
// len returns size of the prefix in wire format.
func (a *APLPrefix) len() int {
// 4-byte header and the network address prefix (see Section 4 of RFC 3123)
prefix, _ := a.Network.Mask.Size()
return 4 + (prefix+7)/8
}
// TimeToString translates the RRSIG's incep. and expir. times to the
// string representation used when printing the record.
// It takes serial arithmetic (RFC 1982) into account.
func TimeToString(t uint32) string {
mod := ((int64(t) - time.Now().Unix()) / year68) - 1
mod := (int64(t)-time.Now().Unix())/year68 - 1
if mod < 0 {
mod = 0
}
ti := time.Unix(int64(t)-(mod*year68), 0).UTC()
ti := time.Unix(int64(t)-mod*year68, 0).UTC()
return ti.Format("20060102150405")
}
@ -1322,16 +1489,16 @@ func StringToTime(s string) (uint32, error) {
if err != nil {
return 0, err
}
mod := (t.Unix() / year68) - 1
mod := t.Unix()/year68 - 1
if mod < 0 {
mod = 0
}
return uint32(t.Unix() - (mod * year68)), nil
return uint32(t.Unix() - mod*year68), nil
}
// saltToString converts a NSECX salt to uppercase and returns "-" when it is empty.
func saltToString(s string) string {
if len(s) == 0 {
if s == "" {
return "-"
}
return strings.ToUpper(s)
@ -1358,6 +1525,17 @@ func copyIP(ip net.IP) net.IP {
return p
}
// copyNet returns a copy of a subnet.
func copyNet(n net.IPNet) net.IPNet {
m := make(net.IPMask, len(n.Mask))
copy(m, n.Mask)
return net.IPNet{
IP: copyIP(n.IP),
Mask: m,
}
}
// SplitN splits a string into N sized string chunks.
// This might become an exported function once.
func splitN(s string, n int) []string {

View File

@ -1,4 +1,5 @@
//+build ignore
//go:build ignore
// +build ignore
// types_generate.go is meant to run with go generate. It will use
// go/{importer,types} to track down all the RR struct types. Then for each type
@ -11,12 +12,13 @@ import (
"bytes"
"fmt"
"go/format"
"go/importer"
"go/types"
"log"
"os"
"strings"
"text/template"
"golang.org/x/tools/go/packages"
)
var skipLen = map[string]struct{}{
@ -71,6 +73,9 @@ func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
if !ok {
return nil, false
}
if st.NumFields() == 0 {
return nil, false
}
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
return st, false
}
@ -81,9 +86,19 @@ func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
return nil, false
}
// loadModule retrieves package description for a given module.
func loadModule(name string) (*types.Package, error) {
conf := packages.Config{Mode: packages.NeedTypes | packages.NeedTypesInfo}
pkgs, err := packages.Load(&conf, name)
if err != nil {
return nil, err
}
return pkgs[0].Types, nil
}
func main() {
// Import and type-check the package
pkg, err := importer.Default().Import("github.com/miekg/dns")
pkg, err := loadModule("github.com/miekg/dns")
fatalIfErr(err)
scope := pkg.Scope()
@ -153,8 +168,8 @@ func main() {
if isEmbedded {
continue
}
fmt.Fprintf(b, "func (rr *%s) len() int {\n", name)
fmt.Fprintf(b, "l := rr.Hdr.len()\n")
fmt.Fprintf(b, "func (rr *%s) len(off int, compression map[string]struct{}) int {\n", name)
fmt.Fprintf(b, "l := rr.Hdr.len(off, compression)\n")
for i := 1; i < st.NumFields(); i++ {
o := func(s string) { fmt.Fprintf(b, s, st.Field(i).Name()) }
@ -162,8 +177,16 @@ func main() {
switch st.Tag(i) {
case `dns:"-"`:
// ignored
case `dns:"cdomain-name"`, `dns:"domain-name"`, `dns:"txt"`:
case `dns:"cdomain-name"`:
o("for _, x := range rr.%s { l += domainNameLen(x, off+l, compression, true) }\n")
case `dns:"domain-name"`:
o("for _, x := range rr.%s { l += domainNameLen(x, off+l, compression, false) }\n")
case `dns:"txt"`:
o("for _, x := range rr.%s { l += len(x) + 1 }\n")
case `dns:"apl"`:
o("for _, x := range rr.%s { l += x.len() }\n")
case `dns:"pairs"`:
o("for _, x := range rr.%s { l += 4 + int(x.len()) }\n")
default:
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
}
@ -173,8 +196,10 @@ func main() {
switch {
case st.Tag(i) == `dns:"-"`:
// ignored
case st.Tag(i) == `dns:"cdomain-name"`, st.Tag(i) == `dns:"domain-name"`:
o("l += len(rr.%s) + 1\n")
case st.Tag(i) == `dns:"cdomain-name"`:
o("l += domainNameLen(rr.%s, off+l, compression, true)\n")
case st.Tag(i) == `dns:"domain-name"`:
o("l += domainNameLen(rr.%s, off+l, compression, false)\n")
case st.Tag(i) == `dns:"octet"`:
o("l += len(rr.%s)\n")
case strings.HasPrefix(st.Tag(i), `dns:"size-base64`):
@ -183,14 +208,14 @@ func main() {
o("l += base64.StdEncoding.DecodedLen(len(rr.%s))\n")
case strings.HasPrefix(st.Tag(i), `dns:"size-hex:`): // this has an extra field where the length is stored
o("l += len(rr.%s)/2\n")
case strings.HasPrefix(st.Tag(i), `dns:"size-hex`):
fallthrough
case st.Tag(i) == `dns:"hex"`:
o("l += len(rr.%s)/2 + 1\n")
o("l += len(rr.%s)/2\n")
case st.Tag(i) == `dns:"any"`:
o("l += len(rr.%s)\n")
case st.Tag(i) == `dns:"a"`:
o("l += net.IPv4len // %s\n")
o("if len(rr.%s) != 0 { l += net.IPv4len }\n")
case st.Tag(i) == `dns:"aaaa"`:
o("l += net.IPv6len // %s\n")
o("if len(rr.%s) != 0 { l += net.IPv6len }\n")
case st.Tag(i) == `dns:"txt"`:
o("for _, t := range rr.%s { l += len(t) + 1 }\n")
case st.Tag(i) == `dns:"uint48"`:
@ -222,11 +247,15 @@ func main() {
for _, name := range namedTypes {
o := scope.Lookup(name)
st, isEmbedded := getTypeStruct(o.Type(), scope)
if isEmbedded {
continue
}
fmt.Fprintf(b, "func (rr *%s) copy() RR {\n", name)
fields := []string{"*rr.Hdr.copyHeader()"}
fields := make([]string, 0, st.NumFields())
if isEmbedded {
a, _ := o.Type().Underlying().(*types.Struct)
parent := a.Field(0).Name()
fields = append(fields, "*rr."+parent+".copy().(*"+parent+")")
goto WriteCopy
}
fields = append(fields, "rr.Hdr")
for i := 1; i < st.NumFields(); i++ {
f := st.Field(i).Name()
if sl, ok := st.Field(i).Type().(*types.Slice); ok {
@ -236,6 +265,25 @@ func main() {
splits := strings.Split(t, ".")
t = splits[len(splits)-1]
}
// For the EDNS0 interface (used in the OPT RR), we need to call the copy method on each element.
if t == "EDNS0" {
fmt.Fprintf(b, "%s := make([]%s, len(rr.%s));\nfor i,e := range rr.%s {\n %s[i] = e.copy()\n}\n",
f, t, f, f, f)
fields = append(fields, f)
continue
}
if t == "APLPrefix" {
fmt.Fprintf(b, "%s := make([]%s, len(rr.%s));\nfor i,e := range rr.%s {\n %s[i] = e.copy()\n}\n",
f, t, f, f, f)
fields = append(fields, f)
continue
}
if t == "SVCBKeyValue" {
fmt.Fprintf(b, "%s := make([]%s, len(rr.%s));\nfor i,e := range rr.%s {\n %s[i] = e.copy()\n}\n",
f, t, f, f, f)
fields = append(fields, f)
continue
}
fmt.Fprintf(b, "%s := make([]%s, len(rr.%s)); copy(%s, rr.%s)\n",
f, t, f, f, f)
fields = append(fields, f)
@ -247,6 +295,7 @@ func main() {
}
fields = append(fields, "rr."+f)
}
WriteCopy:
fmt.Fprintf(b, "return &%s{%s}\n", name, strings.Join(fields, ","))
fmt.Fprintf(b, "}\n")
}

View File

@ -72,3 +72,122 @@ func TestSplitN(t *testing.T) {
t.Errorf("failure to split 510 char long string: %d", len(xs))
}
}
func TestSprintName(t *testing.T) {
tests := map[string]string{
// Non-numeric escaping of special printable characters.
" '@;()\"\\..example": `\ \'\@\;\(\)\"\..example`,
"\\032\\039\\064\\059\\040\\041\\034\\046\\092.example": `\ \'\@\;\(\)\"\.\\.example`,
// Numeric escaping of nonprintable characters.
"\x00\x07\x09\x0a\x1f.\x7f\x80\xad\xef\xff": `\000\007\009\010\031.\127\128\173\239\255`,
"\\000\\007\\009\\010\\031.\\127\\128\\173\\239\\255": `\000\007\009\010\031.\127\128\173\239\255`,
// No escaping of other printable characters, at least after a prior escape.
";[a-zA-Z0-9_]+/*.~": `\;[a-zA-Z0-9_]+/*.~`,
";\\091\\097\\045\\122\\065\\045\\090\\048\\045\\057\\095\\093\\043\\047\\042.\\126": `\;[a-zA-Z0-9_]+/*.~`,
// "\\091\\097\\045\\122\\065\\045\\090\\048\\045\\057\\095\\093\\043\\047\\042.\\126": `[a-zA-Z0-9_]+/*.~`,
// Incomplete "dangling" escapes are dropped regardless of prior escaping.
"a\\": `a`,
";\\": `\;`,
// Escaped dots stay escaped regardless of prior escaping.
"a\\.\\046.\\.\\046": `a\.\..\.\.`,
"a\\046\\..\\046\\.": `a\.\..\.\.`,
}
for input, want := range tests {
got := sprintName(input)
if got != want {
t.Errorf("input %q: expected %q, got %q", input, want, got)
}
}
}
func TestSprintTxtOctet(t *testing.T) {
got := sprintTxtOctet("abc\\.def\007\"\127@\255\x05\xef\\")
if want := "\"abc\\.def\\007\\\"W@\\173\\005\\239\""; got != want {
t.Errorf("expected %q, got %q", want, got)
}
}
func TestSprintTxt(t *testing.T) {
got := sprintTxt([]string{
"abc\\.def\007\"\127@\255\x05\xef\\",
"example.com",
})
if want := "\"abc.def\\007\\\"W@\\173\\005\\239\" \"example.com\""; got != want {
t.Errorf("expected %q, got %q", want, got)
}
}
func TestRPStringer(t *testing.T) {
rp := &RP{
Hdr: RR_Header{
Name: "test.example.com.",
Rrtype: TypeRP,
Class: ClassINET,
Ttl: 600,
},
Mbox: "\x05first.example.com.",
Txt: "second.\x07example.com.",
}
const expected = "test.example.com.\t600\tIN\tRP\t\\005first.example.com. second.\\007example.com."
if rp.String() != expected {
t.Errorf("expected %v, got %v", expected, rp)
}
_, err := NewRR(rp.String())
if err != nil {
t.Fatalf("error parsing %q: %v", rp, err)
}
}
func BenchmarkSprintName(b *testing.B) {
for n := 0; n < b.N; n++ {
got := sprintName("abc\\.def\007\"\127@\255\x05\xef\\")
if want := "abc\\.def\\007\\\"W\\@\\173\\005\\239"; got != want {
b.Fatalf("expected %q, got %q", want, got)
}
}
}
func BenchmarkSprintName_NoEscape(b *testing.B) {
for n := 0; n < b.N; n++ {
got := sprintName("large.example.com")
if want := "large.example.com"; got != want {
b.Fatalf("expected %q, got %q", want, got)
}
}
}
func BenchmarkSprintTxtOctet(b *testing.B) {
for n := 0; n < b.N; n++ {
got := sprintTxtOctet("abc\\.def\007\"\127@\255\x05\xef\\")
if want := "\"abc\\.def\\007\\\"W@\\173\\005\\239\""; got != want {
b.Fatalf("expected %q, got %q", want, got)
}
}
}
func BenchmarkSprintTxt(b *testing.B) {
txt := []string{
"abc\\.def\007\"\127@\255\x05\xef\\",
"example.com",
}
b.ResetTimer()
for n := 0; n < b.N; n++ {
got := sprintTxt(txt)
if want := "\"abc.def\\007\\\"W@\\173\\005\\239\" \"example.com\""; got != want {
b.Fatalf("expected %q, got %q", got, want)
}
}
}

1
udp.go
View File

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package dns

View File

@ -1,3 +1,4 @@
//go:build linux && !appengine
// +build linux,!appengine
package dns
@ -35,7 +36,8 @@ func TestSetUDPSocketOptions(t *testing.T) {
b := make([]byte, 1)
_, sess, err := ReadFromSessionUDP(c, b)
if err != nil {
t.Fatalf("failed to read from conn: %v", err)
t.Errorf("failed to read from conn: %v", err)
// fallthrough to chan send below
}
ch <- sess
}()
@ -48,6 +50,10 @@ func TestSetUDPSocketOptions(t *testing.T) {
t.Fatalf("failed to write to conn: %v", err)
}
sess := <-ch
if sess == nil {
// t.Error was already called in the goroutine above.
t.FailNow()
}
if len(sess.context) == 0 {
t.Fatalf("empty session context: %v", sess)
}

View File

@ -1,3 +1,4 @@
//go:build windows
// +build windows
package dns
@ -20,15 +21,13 @@ func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) {
if err != nil {
return n, nil, err
}
session := &SessionUDP{raddr.(*net.UDPAddr)}
return n, session, err
return n, &SessionUDP{raddr.(*net.UDPAddr)}, err
}
// WriteToSessionUDP acts just like net.UDPConn.WriteTo(), but uses a *SessionUDP instead of a net.Addr.
// TODO(fastest963): Once go1.10 is released, use WriteMsgUDP.
func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) {
n, err := conn.WriteTo(b, session.raddr)
return n, err
return conn.WriteTo(b, session.raddr)
}
// TODO(fastest963): Once go1.10 is released and we can use *MsgUDP methods

View File

@ -32,7 +32,9 @@ func (u *Msg) Used(rr []RR) {
u.Answer = make([]RR, 0, len(rr))
}
for _, r := range rr {
r.Header().Class = u.Question[0].Qclass
hdr := r.Header()
hdr.Class = u.Question[0].Qclass
hdr.Ttl = 0
u.Answer = append(u.Answer, r)
}
}
@ -44,7 +46,8 @@ func (u *Msg) RRsetUsed(rr []RR) {
u.Answer = make([]RR, 0, len(rr))
}
for _, r := range rr {
u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassANY}})
h := r.Header()
u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: h.Name, Ttl: 0, Rrtype: h.Rrtype, Class: ClassANY}})
}
}
@ -55,7 +58,8 @@ func (u *Msg) RRsetNotUsed(rr []RR) {
u.Answer = make([]RR, 0, len(rr))
}
for _, r := range rr {
u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassNONE}})
h := r.Header()
u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: h.Name, Ttl: 0, Rrtype: h.Rrtype, Class: ClassNONE}})
}
}
@ -79,7 +83,8 @@ func (u *Msg) RemoveRRset(rr []RR) {
u.Ns = make([]RR, 0, len(rr))
}
for _, r := range rr {
u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassANY}})
h := r.Header()
u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: h.Name, Ttl: 0, Rrtype: h.Rrtype, Class: ClassANY}})
}
}
@ -99,8 +104,9 @@ func (u *Msg) Remove(rr []RR) {
u.Ns = make([]RR, 0, len(rr))
}
for _, r := range rr {
r.Header().Class = ClassNONE
r.Header().Ttl = 0
h := r.Header()
h.Class = ClassNONE
h.Ttl = 0
u.Ns = append(u.Ns, r)
}
}

View File

@ -6,15 +6,28 @@ import (
)
func TestDynamicUpdateParsing(t *testing.T) {
prefix := "example.com. IN "
for _, typ := range TypeToString {
if typ == "OPT" || typ == "AXFR" || typ == "IXFR" || typ == "ANY" || typ == "TKEY" ||
typ == "TSIG" || typ == "ISDN" || typ == "UNSPEC" || typ == "NULL" || typ == "ATMA" ||
typ == "Reserved" || typ == "None" || typ == "NXT" || typ == "MAILB" || typ == "MAILA" {
const prefix = "example.com. IN "
for typ, name := range TypeToString {
switch typ {
case TypeNone, TypeReserved:
continue
case TypeANY:
// ANY is ambiguous here and ends up parsed as a CLASS.
//
// TODO(tmthrgd): Using TYPE255 here doesn't seem to work and also
// seems to fail for some other record types. Investigate.
continue
}
if _, err := NewRR(prefix + typ); err != nil {
t.Errorf("failure to parse: %s %s: %v", prefix, typ, err)
s := prefix + name
if _, err := NewRR(s); err != nil {
t.Errorf("failure to parse: %s: %v", s, err)
}
s += " \\# 0"
if _, err := NewRR(s); err != nil {
t.Errorf("failure to parse: %s: %v", s, err)
}
}
}
@ -79,7 +92,7 @@ func TestRemoveRRset(t *testing.T) {
}
func TestPreReqAndRemovals(t *testing.T) {
// Build a list of multiple prereqs and then somes removes followed by an insert.
// Build a list of multiple prereqs and then some removes followed by an insert.
// We should be able to add multiple prereqs and updates.
m := new(Msg)
m.SetUpdate("example.org.")
@ -122,7 +135,7 @@ func TestPreReqAndRemovals(t *testing.T) {
name_used. 0 CLASS255 ANY
name_not_used. 0 NONE ANY
rrset_used1. 0 CLASS255 A
rrset_used2. 3600 IN A 127.0.0.1
rrset_used2. 0 IN A 127.0.0.1
rrset_not_used. 0 NONE A
;; AUTHORITY SECTION:

View File

@ -1,10 +0,0 @@
# Treat all files in this repo as binary, with no git magic updating
# line endings. Windows users contributing to Go will need to use a
# modern version of git and editors capable of LF line endings.
#
# We'll prevent accidental CRLF line endings from entering the repo
# via the git-review gofmt checks.
#
# See golang.org/issue/9281
* -text

View File

@ -1,2 +0,0 @@
# Add no patterns to .hgignore except for files generated by the build.
last-change

3
vendor/golang.org/x/crypto/AUTHORS generated vendored
View File

@ -1,3 +0,0 @@
# This source code refers to The Go Authors for copyright purposes.
# The master list of authors is in the main Go distribution,
# visible at https://tip.golang.org/AUTHORS.

View File

@ -1,31 +0,0 @@
# Contributing to Go
Go is an open source project.
It is the work of hundreds of contributors. We appreciate your help!
## Filing issues
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
1. What version of Go are you using (`go version`)?
2. What operating system and processor architecture are you using?
3. What did you do?
4. What did you expect to see?
5. What did you see instead?
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
## Contributing code
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
before sending patches.
**We do not accept GitHub pull requests**
(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review).
Unless otherwise noted, the Go source files are distributed under
the BSD-style license found in the LICENSE file.

View File

@ -1,3 +0,0 @@
# This source code was written by the Go contributors.
# The master list of contributors is in the main Go distribution,
# visible at https://tip.golang.org/CONTRIBUTORS.

27
vendor/golang.org/x/crypto/LICENSE generated vendored
View File

@ -1,27 +0,0 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

22
vendor/golang.org/x/crypto/PATENTS generated vendored
View File

@ -1,22 +0,0 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

21
vendor/golang.org/x/crypto/README.md generated vendored
View File

@ -1,21 +0,0 @@
# Go Cryptography
This repository holds supplementary Go cryptography libraries.
## Download/Install
The easiest way to install is to run `go get -u golang.org/x/crypto/...`. You
can also manually git clone the repository to `$GOPATH/src/golang.org/x/crypto`.
## Report Issues / Send Patches
This repository uses Gerrit for code changes. To learn how to submit changes to
this repository, see https://golang.org/doc/contribute.html.
The main issue tracker for the crypto repository is located at
https://github.com/golang/go/issues. Prefix your issue with "x/crypto:" in the
subject line, so it is easy to find.
Note that contributions to the cryptography package receive additional scrutiny
due to their sensitive nature. Patches may take longer than normal to receive
feedback.

1058
vendor/golang.org/x/crypto/acme/acme.go generated vendored

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More