Compare commits

...

50 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
48 changed files with 1354 additions and 477 deletions

View File

@ -15,7 +15,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 2
@ -23,10 +23,10 @@ jobs:
if: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@ -7,16 +7,16 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go: [ 1.15.x, 1.16.x ]
go: [ 1.19.x, 1.20.x ]
steps:
- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: Check out code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Build
run: go build -v ./...

49
LICENSE
View File

@ -1,30 +1,29 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
BSD 3-Clause License
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.
As this is fork of the official Go code the same license applies.
Extensions of the original work are copyright (c) 2011 Miek Gieben

View File

@ -73,6 +73,13 @@ A not-so-up-to-date-list-that-may-be-actually-current:
* 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.

View File

@ -12,14 +12,13 @@ type MsgAcceptFunc func(dh Header) MsgAcceptAction
//
// * Zero bit isn't zero
//
// * has more than 1 question in the question section
// * 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.

128
client.go
View File

@ -18,6 +18,18 @@ const (
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
@ -27,6 +39,14 @@ type Conn struct {
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)
@ -82,6 +102,12 @@ 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 {
@ -101,9 +127,17 @@ func (c *Client) Dial(address string) (conn *Conn, err error) {
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
@ -139,24 +173,34 @@ func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, er
// 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.
// 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.exchange(m, conn)
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) {
return c.exchange(m, conn)
// 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()
@ -165,8 +209,7 @@ func (c *Client) ExchangeWithConn(m *Msg, conn *Conn) (r *Msg, rtt time.Duration
return r, rtt, err
}
func (c *Client) exchange(m *Msg, co *Conn) (r *Msg, rtt time.Duration, err error) {
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 {
@ -177,16 +220,28 @@ func (c *Client) exchange(m *Msg, co *Conn) (r *Msg, rtt time.Duration, err erro
co.UDPSize = c.UDPSize
}
co.TsigSecret, co.TsigProvider = c.TsigSecret, c.TsigProvider
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())))
if _, ok := co.Conn.(net.PacketConn); ok {
if isPacketConn(co.Conn) {
for {
r, err = co.ReadMsg()
// Ignore replies with mismatched IDs because they might be
@ -224,15 +279,8 @@ func (co *Conn) ReadMsg() (*Msg, error) {
return m, err
}
if t := m.IsTsig(); t != nil {
if co.TsigProvider != nil {
err = tsigVerifyProvider(p, co.TsigProvider, co.tsigRequestMAC, false)
} else {
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)
}
// Need to work on the original message p, as that was used to calculate the tsig.
err = TsigVerifyWithProvider(p, co.tsigProvider(), co.tsigRequestMAC, false)
}
return m, err
}
@ -247,7 +295,7 @@ func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
err error
)
if _, ok := co.Conn.(net.PacketConn); ok {
if isPacketConn(co.Conn) {
if co.UDPSize > MinMsgSize {
p = make([]byte, co.UDPSize)
} else {
@ -287,7 +335,7 @@ func (co *Conn) Read(p []byte) (n int, err error) {
return 0, ErrConnEmpty
}
if _, ok := co.Conn.(net.PacketConn); ok {
if isPacketConn(co.Conn) {
// UDP connection
return co.Conn.Read(p)
}
@ -309,17 +357,8 @@ 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 co.TsigProvider != nil {
out, mac, err = tsigGenerateProvider(m, co.TsigProvider, co.tsigRequestMAC, false)
} else {
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()
}
@ -336,7 +375,7 @@ func (co *Conn) Write(p []byte) (int, error) {
return 0, &Error{err: "message too large"}
}
if _, ok := co.Conn.(net.PacketConn); ok {
if isPacketConn(co.Conn) {
return co.Conn.Write(p)
}
@ -392,7 +431,6 @@ func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error)
// 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)
@ -435,15 +473,11 @@ func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout
// 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 = time.Until(deadline)
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.
// TODO(tmthrgd,miekg): this is a race condition.
c.Dialer = &net.Dialer{Timeout: timeout}
return c.Exchange(m, a)
defer conn.Close()
return c.exchangeWithConnContext(ctx, m, conn)
}

View File

@ -6,12 +6,108 @@ import (
"errors"
"fmt"
"net"
"path/filepath"
"strconv"
"strings"
"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.")
@ -182,16 +278,14 @@ func TestClientSyncBadID(t *testing.T) {
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: 50 * time.Millisecond,
Timeout: 10 * time.Millisecond,
}
if _, _, err := c.Exchange(m, addrstr); err == nil || !isNetworkTimeout(err) {
t.Errorf("query did not time out")
}
// And now with plain Exchange().
if _, err = Exchange(m, addrstr); err == nil || !isNetworkTimeout(err) {
t.Errorf("query did not time out")
}
}
func TestClientSyncBadThenGoodID(t *testing.T) {
@ -215,14 +309,6 @@ func TestClientSyncBadThenGoodID(t *testing.T) {
if r.Id != m.Id {
t.Errorf("failed to get response with expected Id")
}
// And now with plain Exchange().
r, err = Exchange(m, addrstr)
if err != nil {
t.Errorf("failed to exchange: %v", err)
}
if r.Id != m.Id {
t.Errorf("failed to get response with expected Id")
}
}
func TestClientSyncTCPBadID(t *testing.T) {

View File

@ -218,6 +218,11 @@ func IsDomainName(s string) (labels int, ok bool) {
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

View File

@ -65,6 +65,9 @@ var AlgorithmToString = map[uint8]string{
}
// 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,
@ -74,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.
@ -137,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.
// 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:
@ -296,35 +299,20 @@ 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)
return nil
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
}
@ -341,7 +329,7 @@ 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 {
@ -362,8 +350,6 @@ 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
case ED25519:
return signature, nil
default:
return nil, ErrAlg
}
@ -437,9 +423,9 @@ 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 {
@ -450,10 +436,9 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
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()
@ -465,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) {

View File

@ -855,3 +855,16 @@ func TestParseKeyReadError(t *testing.T) {
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

@ -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 }{
{"", "@"},
{".", "."},

88
doc.go
View File

@ -13,28 +13,28 @@ 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:
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:
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:
@ -45,17 +45,17 @@ 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:
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:
@ -72,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,
}
@ -96,7 +96,7 @@ 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.
@ -108,7 +108,7 @@ 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.
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
@ -117,12 +117,12 @@ 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.
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
@ -133,30 +133,30 @@ 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.
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: HmacSHA1, HmacSHA256 and HmacSHA512.
@ -239,7 +239,7 @@ Basic use pattern validating and replying to a message that has TSIG set.
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
@ -248,10 +248,10 @@ can be used, before requesting an official type code from IANA.
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
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:
@ -279,9 +279,9 @@ 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: ECDSAP256SHA256,

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

107
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
@ -56,6 +57,8 @@ func makeDataOpt(code uint16) EDNS0 {
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
@ -75,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()))
@ -95,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:
@ -111,6 +121,8 @@ func (rr *OPT) String() string {
s += "\n; PADDING: " + o.String()
case *EDNS0_EDE:
s += "\n; EDE: " + o.String()
case *EDNS0_ESU:
s += "\n; ESU: " + o.String()
}
}
return s
@ -251,7 +263,7 @@ func (e *EDNS0_NSID) copy() EDNS0 { return &EDNS0_NSID{e.Code, 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
@ -577,14 +589,17 @@ func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} }
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} }
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)
binary.BigEndian.PutUint32(b, e.Expire)
return b, nil
@ -593,15 +608,24 @@ func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
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
@ -652,57 +676,52 @@ 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.Length, e.Timeout} }
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
@ -819,3 +838,19 @@ func (e *EDNS0_EDE) unpack(b []byte) error {
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,7 @@
package dns
import (
"bytes"
"net"
"testing"
)
@ -158,3 +159,121 @@ func TestZ(t *testing.T) {
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

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

5
go.mod
View File

@ -3,7 +3,8 @@ module github.com/miekg/dns
go 1.14
require (
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
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-20210303074136-134d130e1a04
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2
)

31
go.sum
View File

@ -1,10 +1,33 @@
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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-20210303074136-134d130e1a04 h1:cEhElsAv9LUt9ZUUocxzWe05oFLVd+AA2nstydTeI8g=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/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

@ -122,7 +122,7 @@ 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) {

View File

@ -176,7 +176,10 @@ func TestIsDomainName(t *testing.T) {
lab int
}
names := map[string]*ret{
"..": {false, 1},
".": {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},

View File

@ -1,3 +1,4 @@
//go:build !go1.11 || (!aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd)
// +build !go1.11 !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd
package dns

View File

@ -1,3 +1,4 @@
//go:build go1.11 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd)
// +build go1.11
// +build aix darwin dragonfly freebsd linux netbsd openbsd

18
msg.go
View File

@ -265,6 +265,11 @@ loop:
wasDot = false
case '.':
if i == 0 && len(s) > 1 {
// leading dots are not legal except for the root zone
return len(msg), ErrRdata
}
if wasDot {
// two dots back to back is not legal
return len(msg), ErrRdata
@ -675,9 +680,9 @@ func unpackRRslice(l int, msg []byte, off int) (dst1 []RR, off1 int, err error)
// Convert a MsgHdr to a string, with dig-like headers:
//
//;; opcode: QUERY, status: NOERROR, id: 48404
// ;; opcode: QUERY, status: NOERROR, id: 48404
//
//;; flags: qr aa rd ra;
// ;; flags: qr aa rd ra;
func (h *MsgHdr) String() string {
if h == nil {
return "<nil> MsgHdr"
@ -901,6 +906,11 @@ func (dns *Msg) String() string {
s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", "
s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", "
s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
opt := dns.IsEdns0()
if opt != nil {
// OPT PSEUDOSECTION
s += opt.String() + "\n"
}
if len(dns.Question) > 0 {
s += "\n;; QUESTION SECTION:\n"
for _, r := range dns.Question {
@ -923,10 +933,10 @@ func (dns *Msg) String() string {
}
}
}
if len(dns.Extra) > 0 {
if len(dns.Extra) > 0 && (opt == nil || len(dns.Extra) > 1) {
s += "\n;; ADDITIONAL SECTION:\n"
for _, r := range dns.Extra {
if r != nil {
if r != nil && r.Header().Rrtype != TypeOPT {
s += r.String() + "\n"
}
}

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

View File

@ -476,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])
@ -484,17 +484,17 @@ 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
@ -558,6 +558,16 @@ 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 _, t := range bitmap {
window := t / 256
@ -781,6 +791,8 @@ func unpackDataAplPrefix(msg []byte, off int) (APLPrefix, int, error) {
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]
@ -792,10 +804,6 @@ func unpackDataAplPrefix(msg []byte, off int) (APLPrefix, int, error) {
IP: ip,
Mask: net.CIDRMask(int(prefix), 8*len(ip)),
}
network := ipnet.IP.Mask(ipnet.Mask)
if !network.Equal(ipnet.IP) {
return APLPrefix{}, len(msg), &Error{err: "invalid APL address length"}
}
return APLPrefix{
Negation: (nlen & 0x80) != 0,

View File

@ -19,7 +19,8 @@ func TestPackDataNsec(t *testing.T) {
tests := []struct {
name string
args args
want int
wantOff int
wantBytes []byte
wantErr bool
wantErrMsg string
}{
@ -44,14 +45,14 @@ func TestPackDataNsec(t *testing.T) {
},
wantErr: true,
wantErrMsg: "dns: overflow packing nsec",
want: 31,
wantOff: 48,
},
{
name: "disordered nsec bits",
args: args{
bitmap: []uint16{
8962,
0,
1,
},
msg: []byte{
48, 48, 48, 48, 0, 0, 0, 1, 0, 0, 0, 0,
@ -72,13 +73,13 @@ func TestPackDataNsec(t *testing.T) {
},
wantErr: true,
wantErrMsg: "dns: nsec bits out of order",
want: 155,
wantOff: 155,
},
{
name: "simple message with only one window",
args: args{
bitmap: []uint16{
0,
1,
},
msg: []byte{
48, 48, 48, 48, 0, 0,
@ -89,13 +90,33 @@ func TestPackDataNsec(t *testing.T) {
},
off: 0,
},
wantErr: false,
want: 3,
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) {
got, err := packDataNsec(tt.args.bitmap, tt.args.msg, tt.args.off)
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
@ -104,13 +125,49 @@ func TestPackDataNsec(t *testing.T) {
t.Errorf("packDataNsec() error msg = %v, wantErrMsg %v", err.Error(), tt.wantErrMsg)
return
}
if got != tt.want {
t.Errorf("packDataNsec() = %v, want %v", got, tt.want)
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)
@ -391,34 +448,42 @@ 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 length exceeded",
"prefix too large for family IPv4",
[]byte{0x00, 0x01, 0x21, 0x04, 192, 0, 2, 0},
"dns: APL prefix too long",
},
{
"address with extra byte",
[]byte{0x00, 0x01, 0x10, 0x03, 192, 0, 2},
"prefix too large for family IPv6",
[]byte{0x00, 0x02, 0x81, 0x85, 0x20, 0x01, 0x0d, 0xb8, 0x80},
"dns: APL prefix too long",
},
{
"incomplete buffer",
[]byte{0x00, 0x01, 0x10, 0x02, 192},
},
{
"extra bits set",
[]byte{0x00, 0x01, 22, 0x03, 192, 0, 2},
},
{
"afdlen invalid",
"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 {
@ -427,6 +492,10 @@ func TestUnpackDataAplPrefix_Errors(t *testing.T) {
if err == nil {
t.Fatal("expected error, got none")
}
if err.Error() != tt.want {
t.Errorf("expected %s, got %s", tt.want, err.Error())
}
})
}
}

View File

@ -18,7 +18,7 @@ func TestRequestTruncateAnswer(t *testing.T) {
reply.Truncate(MinMsgSize)
if want, got := MinMsgSize, reply.Len(); want < got {
t.Errorf("message length should be bellow %d bytes, got %d bytes", 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")
@ -38,7 +38,7 @@ func TestRequestTruncateExtra(t *testing.T) {
reply.Truncate(MinMsgSize)
if want, got := MinMsgSize, reply.Len(); want < got {
t.Errorf("message length should be bellow %d bytes, got %d bytes", 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")
@ -62,7 +62,7 @@ func TestRequestTruncateExtraEdns0(t *testing.T) {
reply.Truncate(size)
if want, got := size, reply.Len(); want < got {
t.Errorf("message length should be bellow %d bytes, got %d bytes", 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")
@ -94,7 +94,7 @@ func TestRequestTruncateExtraRegression(t *testing.T) {
reply.Truncate(size)
if want, got := size, reply.Len(); want < got {
t.Errorf("message length should be bellow %d bytes, got %d bytes", 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")
@ -130,7 +130,7 @@ func TestTruncation(t *testing.T) {
copy.Truncate(bufsize)
if want, got := bufsize, copy.Len(); want < got {
t.Errorf("message length should be bellow %d bytes, got %d bytes", want, got)
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
}
}
}
@ -150,7 +150,7 @@ func TestRequestTruncateAnswerExact(t *testing.T) {
reply.Truncate(size)
if want, got := size, reply.Len(); want < got {
t.Errorf("message length should be bellow %d bytes, got %d bytes", 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))

View File

@ -373,10 +373,10 @@ func TestNSEC(t *testing.T) {
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 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",
"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 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",
@ -532,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 {
@ -1515,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
@ -1538,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\"",
}
@ -1636,9 +1636,25 @@ 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 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)
@ -1655,7 +1671,6 @@ func TestParseSVCB(t *testing.T) {
func TestParseBadSVCB(t *testing.T) {
header := `example.com. 3600 IN HTTPS `
evils := []string{
`0 . no-default-alpn`, // aliasform
`65536 . no-default-alpn`, // bad priority
`1 ..`, // bad domain
`1 . no-default-alpn=1`, // value illegal
@ -1682,12 +1697,18 @@ func TestParseBadSVCB(t *testing.T) {
`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)

View File

@ -18,7 +18,7 @@ import (
const maxTCPQueries = 128
// aLongTimeAgo is a non-zero time, far in the past, used for
// immediate cancelation of network operations.
// immediate cancellation of network operations.
var aLongTimeAgo = time.Unix(1, 0)
// Handler is implemented by any value that implements ServeDNS.
@ -71,12 +71,12 @@ type response struct {
tsigTimersOnly bool
tsigStatus error
tsigRequestMAC string
tsigSecret map[string]string // the tsig secrets
udp net.PacketConn // i/o connection if UDP was used
tcp net.Conn // i/o connection if TCP was used
udpSession *SessionUDP // oob data to get egress interface right
pcSession net.Addr // address to use when writing to a generic net.PacketConn
writer Writer // writer to output the raw DNS bits
tsigProvider TsigProvider
udp net.PacketConn // i/o connection if UDP was used
tcp net.Conn // i/o connection if TCP was used
udpSession *SessionUDP // oob data to get egress interface right
pcSession net.Addr // address to use when writing to a generic net.PacketConn
writer Writer // writer to output the raw DNS bits
}
// handleRefused returns a HandlerFunc that returns REFUSED for every request it gets.
@ -211,6 +211,8 @@ type Server struct {
WriteTimeout time.Duration
// TCP idle timeout for multiple queries, if nil, defaults to 8 * time.Second (RFC 5966).
IdleTimeout func() time.Duration
// An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
TsigProvider TsigProvider
// Secret(s) for Tsig map[<zonename>]<base64 secret>. The zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2).
TsigSecret map[string]string
// If NotifyStartedFunc is set it is called once the server has started listening.
@ -238,6 +240,16 @@ type Server struct {
udpPool sync.Pool
}
func (srv *Server) tsigProvider() TsigProvider {
if srv.TsigProvider != nil {
return srv.TsigProvider
}
if srv.TsigSecret != nil {
return tsigSecretProvider(srv.TsigSecret)
}
return nil
}
func (srv *Server) isStarted() bool {
srv.lock.RLock()
started := srv.started
@ -526,7 +538,7 @@ func (srv *Server) serveUDP(l net.PacketConn) error {
// Serve a new TCP connection.
func (srv *Server) serveTCPConn(wg *sync.WaitGroup, rw net.Conn) {
w := &response{tsigSecret: srv.TsigSecret, tcp: rw}
w := &response{tsigProvider: srv.tsigProvider(), tcp: rw}
if srv.DecorateWriter != nil {
w.writer = srv.DecorateWriter(w)
} else {
@ -581,7 +593,7 @@ func (srv *Server) serveTCPConn(wg *sync.WaitGroup, rw net.Conn) {
// Serve a new UDP request.
func (srv *Server) serveUDPPacket(wg *sync.WaitGroup, m []byte, u net.PacketConn, udpSession *SessionUDP, pcSession net.Addr) {
w := &response{tsigSecret: srv.TsigSecret, udp: u, udpSession: udpSession, pcSession: pcSession}
w := &response{tsigProvider: srv.tsigProvider(), udp: u, udpSession: udpSession, pcSession: pcSession}
if srv.DecorateWriter != nil {
w.writer = srv.DecorateWriter(w)
} else {
@ -632,15 +644,11 @@ func (srv *Server) serveDNS(m []byte, w *response) {
}
w.tsigStatus = nil
if w.tsigSecret != nil {
if w.tsigProvider != nil {
if t := req.IsTsig(); t != nil {
if secret, ok := w.tsigSecret[t.Hdr.Name]; ok {
w.tsigStatus = TsigVerify(m, secret, "", false)
} else {
w.tsigStatus = ErrSecret
}
w.tsigStatus = TsigVerifyWithProvider(m, w.tsigProvider, "", false)
w.tsigTimersOnly = false
w.tsigRequestMAC = req.Extra[len(req.Extra)-1].(*TSIG).MAC
w.tsigRequestMAC = t.MAC
}
}
@ -718,9 +726,9 @@ func (w *response) WriteMsg(m *Msg) (err error) {
}
var data []byte
if w.tsigSecret != nil { // if no secrets, dont check for the tsig (which is a longer check)
if w.tsigProvider != nil { // if no provider, dont check for the tsig (which is a longer check)
if t := m.IsTsig(); t != nil {
data, w.tsigRequestMAC, err = TsigGenerate(m, w.tsigSecret[t.Hdr.Name], w.tsigRequestMAC, w.tsigTimersOnly)
data, w.tsigRequestMAC, err = TsigGenerateWithProvider(m, w.tsigProvider, w.tsigRequestMAC, w.tsigTimersOnly)
if err != nil {
return err
}

View File

@ -67,12 +67,14 @@ func AnotherHelloServer(w ResponseWriter, req *Msg) {
w.WriteMsg(m)
}
func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
pc, err := net.ListenPacket("udp", laddr)
if err != nil {
return nil, "", nil, err
func RunLocalServer(pc net.PacketConn, l net.Listener, opts ...func(*Server)) (*Server, string, chan error, error) {
server := &Server{
PacketConn: pc,
Listener: l,
ReadTimeout: time.Hour,
WriteTimeout: time.Hour,
}
server := &Server{PacketConn: pc, ReadTimeout: time.Hour, WriteTimeout: time.Hour}
waitLock := sync.Mutex{}
waitLock.Lock()
@ -82,6 +84,18 @@ func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, ch
opt(server)
}
var (
addr string
closer io.Closer
)
if l != nil {
addr = l.Addr().String()
closer = l
} else {
addr = pc.LocalAddr().String()
closer = pc
}
// fin must be buffered so the goroutine below won't block
// forever if fin is never read from. This always happens
// if the channel is discarded and can happen in TestShutdownUDP.
@ -89,11 +103,20 @@ func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, ch
go func() {
fin <- server.ActivateAndServe()
pc.Close()
closer.Close()
}()
waitLock.Lock()
return server, pc.LocalAddr().String(), fin, nil
return server, addr, fin, nil
}
func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
pc, err := net.ListenPacket("udp", laddr)
if err != nil {
return nil, "", nil, err
}
return RunLocalServer(pc, nil, opts...)
}
func RunLocalPacketConnServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
@ -109,26 +132,7 @@ func RunLocalTCPServer(laddr string, opts ...func(*Server)) (*Server, string, ch
return nil, "", nil, err
}
server := &Server{Listener: l, ReadTimeout: time.Hour, WriteTimeout: time.Hour}
waitLock := sync.Mutex{}
waitLock.Lock()
server.NotifyStartedFunc = waitLock.Unlock
for _, opt := range opts {
opt(server)
}
// See the comment in RunLocalUDPServer as to why fin must be buffered.
fin := make(chan error, 1)
go func() {
fin <- server.ActivateAndServe()
l.Close()
}()
waitLock.Lock()
return server, l.Addr().String(), fin, nil
return RunLocalServer(nil, l, opts...)
}
func RunLocalTLSServer(laddr string, config *tls.Config) (*Server, string, chan error, error) {
@ -137,6 +141,39 @@ func RunLocalTLSServer(laddr string, config *tls.Config) (*Server, string, chan
})
}
func RunLocalUnixServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
l, err := net.Listen("unix", laddr)
if err != nil {
return nil, "", nil, err
}
return RunLocalServer(nil, l, opts...)
}
func RunLocalUnixGramServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
pc, err := net.ListenPacket("unixgram", laddr)
if err != nil {
return nil, "", nil, err
}
return RunLocalServer(pc, nil, opts...)
}
func RunLocalUnixSeqPacketServer(laddr string) (chan interface{}, string, error) {
pc, err := net.Listen("unixpacket", laddr)
if err != nil {
return nil, "", err
}
shutdownChan := make(chan interface{})
go func() {
pc.Accept()
<-shutdownChan
}()
return shutdownChan, pc.Addr().String(), nil
}
func TestServing(t *testing.T) {
for _, tc := range []struct {
name string

51
sig0.go
View File

@ -3,6 +3,7 @@ package dns
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"encoding/binary"
"math/big"
@ -38,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
}
@ -82,20 +82,10 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
return ErrKey
}
var hash crypto.Hash
switch rr.Algorithm {
case 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:])
@ -103,7 +93,6 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
auc := binary.BigEndian.Uint16(buf[8:])
adc := binary.BigEndian.Uint16(buf[10:])
offset := headerSize
var err error
for i := uint16(0); i < qdc && offset < buflen; i++ {
_, offset, err = UnpackDomainName(buf, offset)
if err != nil {
@ -166,21 +155,21 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
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 RSASHA1, RSASHA256, RSASHA512:
pk := k.publicKeyRSA()
if pk != nil {
return rsa.VerifyPKCS1v15(pk, hash, hashed, sig)
return rsa.VerifyPKCS1v15(pk, cryptohash, hashed, sig)
}
case ECDSAP256SHA256, ECDSAP384SHA384:
pk := k.publicKeyECDSA()
@ -192,6 +181,14 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
}
return ErrSig
}
case ED25519:
pk := k.publicKeyED25519()
if pk != nil {
if ed25519.Verify(pk, hashed, sig) {
return nil
}
return ErrSig
}
}
return ErrKeyAlg
}

View File

@ -12,7 +12,7 @@ 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 + "."
@ -21,7 +21,7 @@ func TestSIG0(t *testing.T) {
keyrr.Algorithm = alg
keysize := 512
switch alg {
case ECDSAP256SHA256:
case ECDSAP256SHA256, ED25519:
keysize = 256
case ECDSAP384SHA384:
keysize = 384
@ -30,7 +30,7 @@ func TestSIG0(t *testing.T) {
}
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())
@ -45,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
@ -71,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
}
}

344
svcb.go
View File

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"net"
"sort"
"strconv"
@ -13,16 +14,18 @@ import (
// SVCBKey is the type of the keys used in the SVCB RR.
type SVCBKey uint16
// Keys defined in draft-ietf-dnsop-svcb-https-01 Section 12.3.2.
// Keys defined in draft-ietf-dnsop-svcb-https-08 Section 14.3.2.
const (
SVCB_MANDATORY SVCBKey = 0
SVCB_ALPN SVCBKey = 1
SVCB_NO_DEFAULT_ALPN SVCBKey = 2
SVCB_PORT SVCBKey = 3
SVCB_IPV4HINT SVCBKey = 4
SVCB_ECHCONFIG SVCBKey = 5
SVCB_IPV6HINT SVCBKey = 6
svcb_RESERVED SVCBKey = 65535
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{
@ -31,8 +34,9 @@ var svcbKeyToStringMap = map[SVCBKey]string{
SVCB_NO_DEFAULT_ALPN: "no-default-alpn",
SVCB_PORT: "port",
SVCB_IPV4HINT: "ipv4hint",
SVCB_ECHCONFIG: "echconfig",
SVCB_ECHCONFIG: "ech",
SVCB_IPV6HINT: "ipv6hint",
SVCB_DOHPATH: "dohpath",
}
var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap)
@ -167,10 +171,14 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
}
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
if rr.Priority == 0 && len(xs) > 0 {
return &ParseError{l.token, "SVCB aliasform can't have values", l}
}
return nil
}
@ -191,6 +199,8 @@ func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
return new(SVCBECHConfig)
case SVCB_IPV6HINT:
return new(SVCBIPv6Hint)
case SVCB_DOHPATH:
return new(SVCBDoHPath)
case svcb_RESERVED:
return nil
default:
@ -200,16 +210,24 @@ func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
}
}
// SVCB RR. See RFC xxxx (https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-01).
// 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
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"` // Value must be empty if Priority is zero.
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
}
@ -235,15 +253,29 @@ type SVCBKeyValue interface {
}
// SVCBMandatory pair adds to required keys that must be interpreted for the RR
// to be functional.
// 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{65403}
// 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 // Must not include mandatory
Code []SVCBKey
}
func (*SVCBMandatory) Key() SVCBKey { return SVCB_MANDATORY }
@ -302,7 +334,8 @@ func (s *SVCBMandatory) copy() SVCBKeyValue {
}
// SVCBAlpn pair is used to list supported connection protocols.
// Protocol ids can be found at:
// 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:
//
@ -310,13 +343,57 @@ func (s *SVCBMandatory) copy() SVCBKeyValue {
// 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(o.Value, e)
// h.Value = append(h.Value, e)
type SVCBAlpn struct {
Alpn []string
}
func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN }
func (s *SVCBAlpn) String() string { return strings.Join(s.Alpn, ",") }
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
@ -351,7 +428,47 @@ func (s *SVCBAlpn) unpack(b []byte) error {
}
func (s *SVCBAlpn) parse(b string) error {
s.Alpn = strings.Split(b, ",")
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
}
@ -370,9 +487,13 @@ func (s *SVCBAlpn) copy() SVCBKeyValue {
}
// 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{}
@ -385,14 +506,14 @@ 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 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 errors.New("dns: svcbnodefaultalpn: no-default-alpn must have no value")
}
return nil
}
@ -442,15 +563,15 @@ func (s *SVCBPort) parse(b string) error {
// 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()}
// 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
// Or
//
// e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()}
// h.Value = append(h.Value, e)
// e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()}
// h.Value = append(h.Value, e)
type SVCBIPv4Hint struct {
Hint []net.IP
}
@ -523,7 +644,7 @@ func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
}
// SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx].
// Basic use pattern for creating an echconfig option:
// Basic use pattern for creating an ech option:
//
// h := new(dns.HTTPS)
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
@ -531,7 +652,7 @@ func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
// e.ECH = []byte{0xfe, 0x08, ...}
// h.Value = append(h.Value, e)
type SVCBECHConfig struct {
ECH []byte
ECH []byte // Specifically ECHConfigList including the redundant length prefix
}
func (*SVCBECHConfig) Key() SVCBKey { return SVCB_ECHCONFIG }
@ -555,7 +676,7 @@ func (s *SVCBECHConfig) unpack(b []byte) error {
func (s *SVCBECHConfig) parse(b string) error {
x, err := fromBase64([]byte(b))
if err != nil {
return errors.New("dns: svcbechconfig: bad base64 echconfig")
return errors.New("dns: svcbech: bad base64 ech")
}
s.ECH = x
return nil
@ -618,9 +739,6 @@ func (s *SVCBIPv6Hint) String() string {
}
func (s *SVCBIPv6Hint) parse(b string) error {
if strings.Contains(b, ".") {
return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4")
}
str := strings.Split(b, ",")
dst := make([]net.IP, len(str))
for i, e := range str {
@ -628,6 +746,9 @@ func (s *SVCBIPv6Hint) parse(b string) error {
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
@ -645,6 +766,54 @@ func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
}
}
// 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:
@ -661,6 +830,7 @@ type SVCBLocal struct {
}
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) }
@ -669,50 +839,10 @@ func (s *SVCBLocal) unpack(b []byte) error {
return nil
}
func (s *SVCBLocal) String() string {
var str strings.Builder
str.Grow(4 * len(s.Data))
for _, e := range s.Data {
if ' ' <= e && e <= '~' {
switch e {
case '"', ';', ' ', '\\':
str.WriteByte('\\')
str.WriteByte(e)
default:
str.WriteByte(e)
}
} else {
str.WriteString(escapeByte(e))
}
}
return str.String()
}
func (s *SVCBLocal) parse(b string) 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 errors.New("dns: svcblocal: svcb private/experimental key 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 errors.New("dns: svcblocal: svcb private/experimental key bad escaped octet")
} else {
data = append(data, b[i+1])
i += 2
}
data, err := svcbParseParam(b)
if err != nil {
return fmt.Errorf("dns: svcblocal: svcb private/experimental key %w", err)
}
s.Data = data
return nil
@ -753,3 +883,53 @@ func areSVCBPairArraysEqual(a []SVCBKeyValue, b []SVCBKeyValue) bool {
}
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
}

View File

@ -17,7 +17,8 @@ func TestSVCB(t *testing.T) {
{`ipv4hint`, `3.4.3.2,1.1.1.1`},
{`no-default-alpn`, ``},
{`ipv6hint`, `1::4:4:4:4,1::3:3:3:3`},
{`echconfig`, `YUdWc2JHOD0=`},
{`ech`, `YUdWc2JHOD0=`},
{`dohpath`, `/dns-query{?dns}`},
{`key65000`, `4\ 3`},
{`key65001`, `\"\ `},
{`key65002`, ``},
@ -94,6 +95,48 @@ func TestDecodeBadSVCB(t *testing.T) {
}
}
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{

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"

65
tsig.go
View File

@ -74,6 +74,24 @@ func (key tsigHMACProvider) Verify(msg []byte, t *TSIG) error {
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 {
@ -140,18 +158,17 @@ 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 tsigGenerateProvider(m, tsigHMACProvider(secret), requestMAC, timersOnly)
return TsigGenerateWithProvider(m, tsigHMACProvider(secret), requestMAC, timersOnly)
}
func tsigGenerateProvider(m *Msg, provider TsigProvider, requestMAC string, timersOnly bool) ([]byte, string, error) {
// 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")
}
@ -162,20 +179,29 @@ func tsigGenerateProvider(m *Msg, provider TsigProvider, requestMAC string, time
if err != nil {
return nil, "", err
}
buf, err := tsigBuffer(mbuf, rr, requestMAC, timersOnly)
if err != nil {
return nil, "", err
}
t := new(TSIG)
// Copy all TSIG fields except MAC and its size, which are filled using the computed digest.
// Copy all TSIG fields except MAC, its size, and time signed which are filled when signing.
*t = *rr
mac, err := provider.Generate(buf, rr)
if err != nil {
return nil, "", err
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!
}
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)
@ -189,14 +215,15 @@ func tsigGenerateProvider(m *Msg, provider TsigProvider, requestMAC string, time
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 {
return tsigVerify(msg, tsigHMACProvider(secret), requestMAC, timersOnly, uint64(time.Now().Unix()))
}
func tsigVerifyProvider(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool) error {
// 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()))
}

View File

@ -55,6 +55,62 @@ func TestTsigCase(t *testing.T) {
}
}
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.
@ -298,7 +354,7 @@ func TestTsigGenerateProvider(t *testing.T) {
Extra: []RR{&tsig},
}
_, mac, err := tsigGenerateProvider(req, new(testProvider), "", false)
_, 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)
}
@ -341,7 +397,7 @@ func TestTsigVerifyProvider(t *testing.T) {
}
provider := &testProvider{true}
msgData, _, err := tsigGenerateProvider(req, provider, "", false)
msgData, _, err := TsigGenerateWithProvider(req, provider, "", false)
if err != nil {
t.Error(err)
}

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

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

View File

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

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)
}
}

View File

@ -92,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.")
@ -135,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

@ -3,7 +3,7 @@ package dns
import "fmt"
// Version is current version of this library.
var Version = v{1, 1, 43}
var Version = v{1, 1, 50}
// v holds the version of this library.
type v struct {

28
xfr.go
View File

@ -17,11 +17,22 @@ type Transfer struct {
DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds
ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds
WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds
TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
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)
tsigTimersOnly bool
}
// Think we need to away to stop the transfer
func (t *Transfer) tsigProvider() TsigProvider {
if t.TsigProvider != nil {
return t.TsigProvider
}
if t.TsigSecret != nil {
return tsigSecretProvider(t.TsigSecret)
}
return nil
}
// TODO: Think we need to away to stop the transfer
// In performs an incoming transfer with the server in a.
// If you would like to set the source IP, or some other attribute
@ -33,7 +44,6 @@ type Transfer struct {
// dnscon := &dns.Conn{Conn:con}
// transfer = &dns.Transfer{Conn: dnscon}
// channel, err := transfer.In(message, master)
//
func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
switch q.Question[0].Qtype {
case TypeAXFR, TypeIXFR:
@ -224,12 +234,9 @@ func (t *Transfer) ReadMsg() (*Msg, error) {
if err := m.Unpack(p); err != nil {
return nil, err
}
if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
return m, ErrSecret
}
if ts, tp := m.IsTsig(), t.tsigProvider(); ts != nil && tp != nil {
// Need to work on the original message p, as that was used to calculate the tsig.
err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
err = TsigVerifyWithProvider(p, tp, t.tsigRequestMAC, t.tsigTimersOnly)
t.tsigRequestMAC = ts.MAC
}
return m, err
@ -238,11 +245,8 @@ func (t *Transfer) ReadMsg() (*Msg, error) {
// WriteMsg writes a message through the transfer connection t.
func (t *Transfer) WriteMsg(m *Msg) (err error) {
var out []byte
if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
return ErrSecret
}
out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
if ts, tp := m.IsTsig(), t.tsigProvider(); ts != nil && tp != nil {
out, t.tsigRequestMAC, err = TsigGenerateWithProvider(m, tp, t.tsigRequestMAC, t.tsigTimersOnly)
} else {
out, err = m.Pack()
}

View File

@ -1,6 +1,9 @@
package dns
import "testing"
import (
"testing"
"time"
)
var (
tsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
@ -127,3 +130,54 @@ func axfrTestingSuite(t *testing.T, addrstr string) {
}
}
}
func axfrTestingSuiteWithCustomTsig(t *testing.T, addrstr string, provider TsigProvider) {
tr := new(Transfer)
m := new(Msg)
var err error
tr.Conn, err = Dial("tcp", addrstr)
if err != nil {
t.Fatal("failed to dial", err)
}
tr.TsigProvider = provider
m.SetAxfr("miek.nl.")
m.SetTsig("axfr.", HmacSHA256, 300, time.Now().Unix())
c, err := tr.In(m, addrstr)
if err != nil {
t.Fatal("failed to zone transfer in", err)
}
var records []RR
for msg := range c {
if msg.Error != nil {
t.Fatal(msg.Error)
}
records = append(records, msg.RR...)
}
if len(records) != len(xfrTestData) {
t.Fatalf("bad axfr: expected %v, got %v", records, xfrTestData)
}
for i, rr := range records {
if !IsDuplicate(rr, xfrTestData[i]) {
t.Errorf("bad axfr: expected %v, got %v", records, xfrTestData)
}
}
}
func TestCustomTsigProvider(t *testing.T) {
HandleFunc("miek.nl.", SingleEnvelopeXfrServer)
defer HandleRemove("miek.nl.")
s, addrstr, _, err := RunLocalTCPServer(":0", func(srv *Server) {
srv.TsigProvider = tsigSecretProvider(tsigSecret)
})
if err != nil {
t.Fatalf("unable to run test server: %s", err)
}
defer s.Shutdown()
axfrTestingSuiteWithCustomTsig(t, addrstr, tsigSecretProvider(tsigSecret))
}