Compare commits
107 Commits
Author | SHA1 | Date |
---|---|---|
Spencer Comfort | d8fbd0a755 | |
dnschecktool | f8a185d39e | |
Kian-Meng Ang | 0089167cae | |
Ali Mosajjal | fe20d5d323 | |
Tom Thorogood | 41a7730f43 | |
Miek Gieben | 4bd038eb76 | |
Sam Therapy | caa3fe0583 | |
Miek Gieben | 3b7e0b9bdd | |
Caleb Jasik | 8c643eba82 | |
Simon Elsbrock | 16b12df562 | |
Mike Schinkel | 4822b271aa | |
Miek Gieben | b3dfea0715 | |
Miek Gieben | 69924a02cf | |
João Oliveirinha | ff611cdc4b | |
Miek Gieben | eb4745b695 | |
Shane Kerr | 7413c83334 | |
Miek Gieben | 5521648610 | |
Shane Kerr | bfcbf0fd23 | |
Shane Kerr | feda877277 | |
Miek Gieben | 0d2c95b99c | |
Miek Gieben | c760d3c7f1 | |
Ainar Garipov | 656b7409ac | |
Ainar Garipov | 08c2616301 | |
Miek Gieben | dedee46bd4 | |
Miek Gieben | 49c1b2e20f | |
Miek Gieben | 045ac4ec6c | |
Olivier Poitrey | 57e2e627a6 | |
DesWurstes | 2f577ca35d | |
Miek Gieben | d70eb7b9e1 | |
Dimitris Mavrommatis | d48e92a0e6 | |
Miek Gieben | 05140a3136 | |
Miek Gieben | af1ebf55eb | |
Miek Gieben | 84af068d46 | |
Tom Thorogood | 33e64002b6 | |
JeremyRand | 51afb90ed3 | |
Tom Thorogood | 0544c8bb11 | |
bshea3 | af5144a5ca | |
Mark | 32b1ed5f32 | |
Miek Gieben | 294c41a1d8 | |
Andrey Meshkov | ba44371638 | |
Miek Gieben | f4af58267c | |
Chris O'Haver | 3a58872b63 | |
Konstantine | 3b8982ccc6 | |
Tom Sellers | 7318b01e11 | |
Alexandru Ionut Tripon | 1630ffe2ca | |
Johan Knutzen | 4e8fe099f4 | |
Tom Thorogood | df84acab71 | |
Miek Gieben | ab67aa6423 | |
Manabu Sonoda | af0c865ab3 | |
Aveline | c23d8b3ce0 | |
Miek Gieben | 996478ee91 | |
Miek Gieben | bd70190c4d | |
Malte Granderath | 595ee5aa98 | |
Miek Gieben | ce48a4b9ef | |
joseph-stanton-ax | 9922549621 | |
Miek Gieben | d2b5d38d4f | |
Miek Gieben | 9f8f2e3a3c | |
Miek Gieben | 21ccaf84aa | |
Miek Gieben | cce7f43db4 | |
Fredrik Lönnegren | 2a9acc8d83 | |
Andrey Meshkov | c99ea652e3 | |
Miek Gieben | 88913150f0 | |
Daniel Tang | 83b388a80c | |
Miek Gieben | 40060b4a4b | |
Miek Gieben | ad89e5bc70 | |
Miek Gieben | 797f1f2953 | |
cesarkuroiwa | a614451ab3 | |
Tom Thorogood | e5407eb800 | |
Tom Thorogood | 40ce7c7df7 | |
Tom Thorogood | b694ab3d93 | |
Miek Gieben | 4fdbc51bbd | |
Miek Gieben | db96610e5e | |
madestro | 375601dc88 | |
Josh Soref | 883641f4a9 | |
Shubhendra Singh Chauhan | 2f14d104f3 | |
Miek Gieben | 9884b9f446 | |
R+ | 2543d8bb2d | |
Nikolay Nikolaev | c08efdcc07 | |
Miek Gieben | 4ec3e54a9e | |
Miek Gieben | 4495f1939f | |
Andrey Meshkov | 67bd57debd | |
unknowndev233 | 7d5e1ea350 | |
chantra | ee8fef6743 | |
Miek Gieben | 35023fab5c | |
Tom Thorogood | 2fd5af9f92 | |
Tom Thorogood | e6df8867af | |
Miek Gieben | ba2d042a57 | |
Miek Gieben | a1362108be | |
Miek Gieben | a25c26b5ed | |
Tom Thorogood | 13238cb6ad | |
Tom Thorogood | f9dc403cff | |
Matt Dainty | 731b191cab | |
Matt Dainty | 59aea23afe | |
Janik at Cloudflare | 23c4faca9d | |
Till! | 428cef3187 | |
Miek Gieben | 91eca25c30 | |
Catena cyber | 9732cfa6b8 | |
Miek Gieben | 1ba9487b18 | |
Miek Gieben | 3b41a31342 | |
Tom Thorogood | fa528cceb7 | |
Tom Thorogood | 0e1c4e69dd | |
Tom Thorogood | a3ad44419a | |
Tom Thorogood | 93945c2844 | |
Tom Thorogood | db53c847ca | |
Tom Thorogood | be51022368 | |
Peter Wu | 6d41f43022 | |
Laurent Demailly | 04c41addaf |
|
@ -1,23 +0,0 @@
|
||||||
name: CIFuzz
|
|
||||||
on: [pull_request]
|
|
||||||
jobs:
|
|
||||||
Fuzzing:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Build Fuzzers
|
|
||||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
|
|
||||||
with:
|
|
||||||
oss-fuzz-project-name: 'go-dns'
|
|
||||||
dry-run: false
|
|
||||||
- name: Run Fuzzers
|
|
||||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
|
|
||||||
with:
|
|
||||||
oss-fuzz-project-name: 'go-dns'
|
|
||||||
fuzz-seconds: 600
|
|
||||||
dry-run: false
|
|
||||||
- name: Upload Crash
|
|
||||||
uses: actions/upload-artifact@v1
|
|
||||||
if: failure()
|
|
||||||
with:
|
|
||||||
name: artifacts
|
|
||||||
path: ./out/artifacts
|
|
|
@ -4,7 +4,6 @@ on:
|
||||||
push:
|
push:
|
||||||
branches: [master, ]
|
branches: [master, ]
|
||||||
pull_request:
|
pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [master]
|
branches: [master]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 23 * * 5'
|
- cron: '0 23 * * 5'
|
||||||
|
@ -16,39 +15,18 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
# We must fetch at least the immediate parents so that if this is
|
|
||||||
# a pull request then we can checkout the head.
|
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
# If this run was triggered by a pull request event, then checkout
|
|
||||||
# the head of the pull request instead of the merge commit.
|
|
||||||
- run: git checkout HEAD^2
|
- run: git checkout HEAD^2
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v2
|
||||||
# Override language selection by uncommenting this and choosing your languages
|
|
||||||
# with:
|
|
||||||
# languages: go, javascript, csharp, python, cpp, java
|
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 https://git.io/JvXDl
|
|
||||||
|
|
||||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
||||||
# and modify them (or add more) to build your code if your project
|
|
||||||
# uses a compiled language
|
|
||||||
|
|
||||||
#- run: |
|
|
||||||
# make bootstrap
|
|
||||||
# make release
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v2
|
||||||
|
|
|
@ -5,28 +5,21 @@ jobs:
|
||||||
build:
|
build:
|
||||||
name: Build and Test
|
name: Build and Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go: [ 1.19.x, 1.20.x ]
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.14
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.14
|
go-version: ${{ matrix.go }}
|
||||||
id: go
|
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Get dependencies
|
|
||||||
run: go get -v -t -d ./...
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -v ./...
|
run: go build -v ./...
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test -v ./...
|
run: go test -v ./...
|
||||||
|
|
||||||
- name: Fuzz
|
|
||||||
run: |
|
|
||||||
export GOPATH=$GOROOT
|
|
||||||
make -f Makefile.fuzz get
|
|
||||||
make -f Makefile.fuzz
|
|
||||||
|
|
17
.travis.yml
17
.travis.yml
|
@ -1,17 +0,0 @@
|
||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.14.x
|
|
||||||
- 1.15.x
|
|
||||||
- tip
|
|
||||||
|
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go generate ./... && test `git ls-files --modified | wc -l` = 0
|
|
||||||
- go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
|
49
LICENSE
49
LICENSE
|
@ -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
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are met:
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
notice, this list of conditions and the following disclaimer.
|
list of conditions and the following disclaimer.
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
this list of conditions and the following disclaimer in the documentation
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
and/or other materials provided with the distribution.
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
contributors may be used to endorse or promote products derived from
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
this software without specific prior written permission.
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
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.
|
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
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Makefile for releasing.
|
# Makefile for releasing.
|
||||||
#
|
#
|
||||||
# The release is controlled from version.go. The version found there is
|
# The release is controlled from version.go. The version found there is
|
||||||
# used to tag the git repo, we're not building any artifects so there is nothing
|
# used to tag the git repo, we're not building any artifacts so there is nothing
|
||||||
# to upload to github.
|
# to upload to github.
|
||||||
#
|
#
|
||||||
# * Up the version in version.go
|
# * Up the version in version.go
|
||||||
|
|
16
README.md
16
README.md
|
@ -68,6 +68,19 @@ A not-so-up-to-date-list-that-may-be-actually-current:
|
||||||
* https://domainr.com/
|
* https://domainr.com/
|
||||||
* https://zonedb.org/
|
* https://zonedb.org/
|
||||||
* https://router7.org/
|
* https://router7.org/
|
||||||
|
* https://github.com/fortio/dnsping
|
||||||
|
* https://github.com/Luzilla/dnsbl_exporter
|
||||||
|
* https://github.com/bodgit/tsig
|
||||||
|
* https://github.com/v2fly/v2ray-core (test only)
|
||||||
|
* https://kuma.io/
|
||||||
|
* https://www.misaka.io/services/dns
|
||||||
|
* https://ping.sx/dig
|
||||||
|
* https://fleetdeck.io/
|
||||||
|
* https://github.com/markdingo/autoreverse
|
||||||
|
* https://github.com/slackhq/nebula
|
||||||
|
* https://github.com/dnschecktool/dow-proxy
|
||||||
|
* https://dnscheck.tools/
|
||||||
|
|
||||||
|
|
||||||
Send pull request if you want to be listed here.
|
Send pull request if you want to be listed here.
|
||||||
|
|
||||||
|
@ -164,6 +177,9 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
|
||||||
* 7873 - Domain Name System (DNS) Cookies
|
* 7873 - Domain Name System (DNS) Cookies
|
||||||
* 8080 - EdDSA for DNSSEC
|
* 8080 - EdDSA for DNSSEC
|
||||||
* 8499 - DNS Terminology
|
* 8499 - DNS Terminology
|
||||||
|
* 8659 - DNS Certification Authority Authorization (CAA) Resource Record
|
||||||
|
* 8914 - Extended DNS Errors
|
||||||
|
* 8976 - Message Digest for DNS Zones (ZONEMD RR)
|
||||||
|
|
||||||
## Loosely Based Upon
|
## Loosely Based Upon
|
||||||
|
|
||||||
|
|
|
@ -12,19 +12,19 @@ type MsgAcceptFunc func(dh Header) MsgAcceptAction
|
||||||
//
|
//
|
||||||
// * Zero bit isn't zero
|
// * 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 1 RR in the Answer section
|
||||||
//
|
//
|
||||||
// * has more than 0 RRs in the Authority section
|
// * has more than 0 RRs in the Authority section
|
||||||
//
|
//
|
||||||
// * has more than 2 RRs in the Additional section
|
// * has more than 2 RRs in the Additional section
|
||||||
//
|
|
||||||
var DefaultMsgAcceptFunc MsgAcceptFunc = defaultMsgAcceptFunc
|
var DefaultMsgAcceptFunc MsgAcceptFunc = defaultMsgAcceptFunc
|
||||||
|
|
||||||
// MsgAcceptAction represents the action to be taken.
|
// MsgAcceptAction represents the action to be taken.
|
||||||
type MsgAcceptAction int
|
type MsgAcceptAction int
|
||||||
|
|
||||||
|
// Allowed returned values from a MsgAcceptFunc.
|
||||||
const (
|
const (
|
||||||
MsgAccept MsgAcceptAction = iota // Accept the message
|
MsgAccept MsgAcceptAction = iota // Accept the message
|
||||||
MsgReject // Reject the message with a RcodeFormatError
|
MsgReject // Reject the message with a RcodeFormatError
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
|
|
||||||
func TestAcceptNotify(t *testing.T) {
|
func TestAcceptNotify(t *testing.T) {
|
||||||
HandleFunc("example.org.", handleNotify)
|
HandleFunc("example.org.", handleNotify)
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
|
132
client.go
132
client.go
|
@ -18,14 +18,35 @@ const (
|
||||||
tcpIdleTimeout time.Duration = 8 * time.Second
|
tcpIdleTimeout time.Duration = 8 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func isPacketConn(c net.Conn) bool {
|
||||||
|
if _, ok := c.(net.PacketConn); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ua, ok := c.LocalAddr().(*net.UnixAddr); ok {
|
||||||
|
return ua.Net == "unixgram" || ua.Net == "unixpacket"
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// A Conn represents a connection to a DNS server.
|
// A Conn represents a connection to a DNS server.
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
net.Conn // a net.Conn holding the connection
|
net.Conn // a net.Conn holding the connection
|
||||||
UDPSize uint16 // minimum receive buffer for UDP messages
|
UDPSize uint16 // minimum receive buffer for UDP messages
|
||||||
TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
|
TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
|
||||||
|
TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
|
||||||
tsigRequestMAC string
|
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.
|
// A Client defines parameters for a DNS client.
|
||||||
type Client struct {
|
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)
|
Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP)
|
||||||
|
@ -40,6 +61,7 @@ type Client struct {
|
||||||
ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
|
ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
|
||||||
WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
|
WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
|
||||||
TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
|
TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
|
||||||
|
TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
|
||||||
SingleInflight bool // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass
|
SingleInflight bool // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass
|
||||||
group singleflight
|
group singleflight
|
||||||
}
|
}
|
||||||
|
@ -80,6 +102,12 @@ func (c *Client) writeTimeout() time.Duration {
|
||||||
|
|
||||||
// Dial connects to the address on the named network.
|
// Dial connects to the address on the named network.
|
||||||
func (c *Client) Dial(address string) (conn *Conn, err error) {
|
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
|
// create a new dialer with the appropriate timeout
|
||||||
var d net.Dialer
|
var d net.Dialer
|
||||||
if c.Dialer == nil {
|
if c.Dialer == nil {
|
||||||
|
@ -99,9 +127,17 @@ func (c *Client) Dial(address string) (conn *Conn, err error) {
|
||||||
if useTLS {
|
if useTLS {
|
||||||
network = strings.TrimSuffix(network, "-tls")
|
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)
|
conn.Conn, err = tls.DialWithDialer(&d, network, address, c.TLSConfig)
|
||||||
} else {
|
} else {
|
||||||
conn.Conn, err = d.Dial(network, address)
|
conn.Conn, err = d.DialContext(ctx, network, address)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -124,7 +160,6 @@ func (c *Client) Dial(address string) (conn *Conn, err error) {
|
||||||
// of 512 bytes
|
// of 512 bytes
|
||||||
// To specify a local address or a timeout, the caller has to set the `Client.Dialer`
|
// To specify a local address or a timeout, the caller has to set the `Client.Dialer`
|
||||||
// attribute appropriately
|
// attribute appropriately
|
||||||
|
|
||||||
func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, err error) {
|
func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, err error) {
|
||||||
co, err := c.Dial(address)
|
co, err := c.Dial(address)
|
||||||
|
|
||||||
|
@ -138,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
|
// ExchangeWithConn has the same behavior as Exchange, just with a predetermined connection
|
||||||
// that will be used instead of creating a new one.
|
// that will be used instead of creating a new one.
|
||||||
// Usage pattern with a *dns.Client:
|
// Usage pattern with a *dns.Client:
|
||||||
|
//
|
||||||
// c := new(dns.Client)
|
// c := new(dns.Client)
|
||||||
// // connection management logic goes here
|
// // connection management logic goes here
|
||||||
//
|
//
|
||||||
// conn := c.Dial(address)
|
// conn := c.Dial(address)
|
||||||
// in, rtt, err := c.ExchangeWithConn(message, conn)
|
// in, rtt, err := c.ExchangeWithConn(message, conn)
|
||||||
//
|
//
|
||||||
// This allows users of the library to implement their own connection management,
|
// 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
|
// 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.
|
// 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) {
|
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 {
|
if !c.SingleInflight {
|
||||||
return c.exchange(m, conn)
|
return c.exchangeContext(ctx, m, conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
q := m.Question[0]
|
q := m.Question[0]
|
||||||
key := fmt.Sprintf("%s:%d:%d", q.Name, q.Qtype, q.Qclass)
|
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) {
|
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 {
|
if r != nil && shared {
|
||||||
r = r.Copy()
|
r = r.Copy()
|
||||||
|
@ -164,8 +209,7 @@ func (c *Client) ExchangeWithConn(m *Msg, conn *Conn) (r *Msg, rtt time.Duration
|
||||||
return r, rtt, err
|
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()
|
opt := m.IsEdns0()
|
||||||
// If EDNS0 is used use that for size.
|
// If EDNS0 is used use that for size.
|
||||||
if opt != nil && opt.UDPSize() >= MinMsgSize {
|
if opt != nil && opt.UDPSize() >= MinMsgSize {
|
||||||
|
@ -176,16 +220,28 @@ func (c *Client) exchange(m *Msg, co *Conn) (r *Msg, rtt time.Duration, err erro
|
||||||
co.UDPSize = c.UDPSize
|
co.UDPSize = c.UDPSize
|
||||||
}
|
}
|
||||||
|
|
||||||
co.TsigSecret = c.TsigSecret
|
|
||||||
t := time.Now()
|
|
||||||
// write with the appropriate write timeout
|
// 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 {
|
if err = co.WriteMsg(m); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
co.SetReadDeadline(time.Now().Add(c.getTimeoutForRequest(c.readTimeout())))
|
if isPacketConn(co.Conn) {
|
||||||
if _, ok := co.Conn.(net.PacketConn); ok {
|
|
||||||
for {
|
for {
|
||||||
r, err = co.ReadMsg()
|
r, err = co.ReadMsg()
|
||||||
// Ignore replies with mismatched IDs because they might be
|
// Ignore replies with mismatched IDs because they might be
|
||||||
|
@ -223,11 +279,8 @@ func (co *Conn) ReadMsg() (*Msg, error) {
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
if t := m.IsTsig(); t != nil {
|
if t := m.IsTsig(); t != nil {
|
||||||
if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
|
|
||||||
return m, ErrSecret
|
|
||||||
}
|
|
||||||
// Need to work on the original message p, as that was used to calculate the tsig.
|
// Need to work on the original message p, as that was used to calculate the tsig.
|
||||||
err = TsigVerify(p, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false)
|
err = TsigVerifyWithProvider(p, co.tsigProvider(), co.tsigRequestMAC, false)
|
||||||
}
|
}
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
@ -242,7 +295,7 @@ func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if _, ok := co.Conn.(net.PacketConn); ok {
|
if isPacketConn(co.Conn) {
|
||||||
if co.UDPSize > MinMsgSize {
|
if co.UDPSize > MinMsgSize {
|
||||||
p = make([]byte, co.UDPSize)
|
p = make([]byte, co.UDPSize)
|
||||||
} else {
|
} else {
|
||||||
|
@ -282,7 +335,7 @@ func (co *Conn) Read(p []byte) (n int, err error) {
|
||||||
return 0, ErrConnEmpty
|
return 0, ErrConnEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := co.Conn.(net.PacketConn); ok {
|
if isPacketConn(co.Conn) {
|
||||||
// UDP connection
|
// UDP connection
|
||||||
return co.Conn.Read(p)
|
return co.Conn.Read(p)
|
||||||
}
|
}
|
||||||
|
@ -304,13 +357,8 @@ func (co *Conn) Read(p []byte) (n int, err error) {
|
||||||
func (co *Conn) WriteMsg(m *Msg) (err error) {
|
func (co *Conn) WriteMsg(m *Msg) (err error) {
|
||||||
var out []byte
|
var out []byte
|
||||||
if t := m.IsTsig(); t != nil {
|
if t := m.IsTsig(); t != nil {
|
||||||
mac := ""
|
// Set tsigRequestMAC for the next read, although only used in zone transfers.
|
||||||
if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
|
out, co.tsigRequestMAC, err = TsigGenerateWithProvider(m, co.tsigProvider(), co.tsigRequestMAC, false)
|
||||||
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
|
|
||||||
} else {
|
} else {
|
||||||
out, err = m.Pack()
|
out, err = m.Pack()
|
||||||
}
|
}
|
||||||
|
@ -327,15 +375,14 @@ func (co *Conn) Write(p []byte) (int, error) {
|
||||||
return 0, &Error{err: "message too large"}
|
return 0, &Error{err: "message too large"}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := co.Conn.(net.PacketConn); ok {
|
if isPacketConn(co.Conn) {
|
||||||
return co.Conn.Write(p)
|
return co.Conn.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
l := make([]byte, 2)
|
msg := make([]byte, 2+len(p))
|
||||||
binary.BigEndian.PutUint16(l, uint16(len(p)))
|
binary.BigEndian.PutUint16(msg, uint16(len(p)))
|
||||||
|
copy(msg[2:], p)
|
||||||
n, err := (&net.Buffers{l, p}).WriteTo(co.Conn)
|
return co.Conn.Write(msg)
|
||||||
return int(n), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the appropriate timeout for a specific request
|
// Return the appropriate timeout for a specific request
|
||||||
|
@ -371,7 +418,7 @@ func Dial(network, address string) (conn *Conn, err error) {
|
||||||
func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) {
|
func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) {
|
||||||
client := Client{Net: "udp"}
|
client := Client{Net: "udp"}
|
||||||
r, _, err = client.ExchangeContext(ctx, m, a)
|
r, _, err = client.ExchangeContext(ctx, m, a)
|
||||||
// ignorint rtt to leave the original ExchangeContext API unchanged, but
|
// ignoring rtt to leave the original ExchangeContext API unchanged, but
|
||||||
// this function will go away
|
// this function will go away
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
|
@ -384,7 +431,6 @@ func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error)
|
||||||
// co.WriteMsg(m)
|
// co.WriteMsg(m)
|
||||||
// in, _ := co.ReadMsg()
|
// in, _ := co.ReadMsg()
|
||||||
// co.Close()
|
// co.Close()
|
||||||
//
|
|
||||||
func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) {
|
func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) {
|
||||||
println("dns: ExchangeConn: this function is deprecated")
|
println("dns: ExchangeConn: this function is deprecated")
|
||||||
co := new(Conn)
|
co := new(Conn)
|
||||||
|
@ -427,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
|
// context, if present. If there is both a context deadline and a configured
|
||||||
// timeout on the client, the earliest of the two takes effect.
|
// 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) {
|
func (c *Client) ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
|
||||||
var timeout time.Duration
|
conn, err := c.DialContext(ctx, a)
|
||||||
if deadline, ok := ctx.Deadline(); !ok {
|
if err != nil {
|
||||||
timeout = 0
|
return nil, 0, err
|
||||||
} else {
|
|
||||||
timeout = time.Until(deadline)
|
|
||||||
}
|
}
|
||||||
// not passing the context to the underlying calls, as the API does not support
|
defer conn.Close()
|
||||||
// context. For timeouts you should set up Client.Dialer and call Client.Exchange.
|
|
||||||
// TODO(tmthrgd,miekg): this is a race condition.
|
return c.exchangeWithConnContext(ctx, m, conn)
|
||||||
c.Dialer = &net.Dialer{Timeout: timeout}
|
|
||||||
return c.Exchange(m, a)
|
|
||||||
}
|
}
|
||||||
|
|
154
client_test.go
154
client_test.go
|
@ -6,17 +6,113 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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) {
|
func TestDialUDP(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", HelloServer)
|
HandleFunc("miek.nl.", HelloServer)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -39,7 +135,7 @@ func TestClientSync(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", HelloServer)
|
HandleFunc("miek.nl.", HelloServer)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -73,7 +169,7 @@ func TestClientLocalAddress(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", HelloServerEchoAddrPort)
|
HandleFunc("miek.nl.", HelloServerEchoAddrPort)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -117,7 +213,7 @@ func TestClientTLSSyncV4(t *testing.T) {
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
}
|
}
|
||||||
|
|
||||||
s, addrstr, err := RunLocalTLSServer(":0", &config)
|
s, addrstr, _, err := RunLocalTLSServer(":0", &config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -173,7 +269,7 @@ func TestClientSyncBadID(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", HelloServerBadID)
|
HandleFunc("miek.nl.", HelloServerBadID)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -182,23 +278,21 @@ func TestClientSyncBadID(t *testing.T) {
|
||||||
m := new(Msg)
|
m := new(Msg)
|
||||||
m.SetQuestion("miek.nl.", TypeSOA)
|
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{
|
c := &Client{
|
||||||
Timeout: 50 * time.Millisecond,
|
Timeout: 10 * time.Millisecond,
|
||||||
}
|
}
|
||||||
if _, _, err := c.Exchange(m, addrstr); err == nil || !isNetworkTimeout(err) {
|
if _, _, err := c.Exchange(m, addrstr); err == nil || !isNetworkTimeout(err) {
|
||||||
t.Errorf("query did not time out")
|
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) {
|
func TestClientSyncBadThenGoodID(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", HelloServerBadThenGoodID)
|
HandleFunc("miek.nl.", HelloServerBadThenGoodID)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -215,21 +309,13 @@ func TestClientSyncBadThenGoodID(t *testing.T) {
|
||||||
if r.Id != m.Id {
|
if r.Id != m.Id {
|
||||||
t.Errorf("failed to get response with expected 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) {
|
func TestClientSyncTCPBadID(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", HelloServerBadID)
|
HandleFunc("miek.nl.", HelloServerBadID)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalTCPServer(":0")
|
s, addrstr, _, err := RunLocalTCPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -250,7 +336,7 @@ func TestClientEDNS0(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", HelloServer)
|
HandleFunc("miek.nl.", HelloServer)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -297,7 +383,7 @@ func TestClientEDNS0Local(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", handler)
|
HandleFunc("miek.nl.", handler)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %s", err)
|
t.Fatalf("unable to run test server: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -347,7 +433,7 @@ func TestClientConn(t *testing.T) {
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
// This uses TCP just to make it slightly different than TestClientSync
|
// This uses TCP just to make it slightly different than TestClientSync
|
||||||
s, addrstr, err := RunLocalTCPServer(":0")
|
s, addrstr, _, err := RunLocalTCPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -396,6 +482,24 @@ func TestClientConn(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientConnWriteSinglePacket(t *testing.T) {
|
||||||
|
c := &countingConn{}
|
||||||
|
conn := Conn{
|
||||||
|
Conn: c,
|
||||||
|
}
|
||||||
|
m := new(Msg)
|
||||||
|
m.SetQuestion("miek.nl.", TypeTXT)
|
||||||
|
err := conn.WriteMsg(m)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to write: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.writes != 1 {
|
||||||
|
t.Fatalf("incorrect number of Write calls")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTruncatedMsg(t *testing.T) {
|
func TestTruncatedMsg(t *testing.T) {
|
||||||
m := new(Msg)
|
m := new(Msg)
|
||||||
m.SetQuestion("miek.nl.", TypeSRV)
|
m.SetQuestion("miek.nl.", TypeSRV)
|
||||||
|
@ -594,7 +698,7 @@ func TestConcurrentExchanges(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", handler)
|
HandleFunc("miek.nl.", handler)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %s", err)
|
t.Fatalf("unable to run test server: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -631,7 +735,7 @@ func TestExchangeWithConn(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", HelloServer)
|
HandleFunc("miek.nl.", HelloServer)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
|
10
defaults.go
10
defaults.go
|
@ -218,6 +218,11 @@ func IsDomainName(s string) (labels int, ok bool) {
|
||||||
|
|
||||||
wasDot = false
|
wasDot = false
|
||||||
case '.':
|
case '.':
|
||||||
|
if i == 0 && len(s) > 1 {
|
||||||
|
// leading dots are not legal except for the root zone
|
||||||
|
return labels, false
|
||||||
|
}
|
||||||
|
|
||||||
if wasDot {
|
if wasDot {
|
||||||
// two dots back to back is not legal
|
// two dots back to back is not legal
|
||||||
return labels, false
|
return labels, false
|
||||||
|
@ -349,10 +354,7 @@ func ReverseAddr(addr string) (arpa string, err error) {
|
||||||
// Add it, in reverse, to the buffer
|
// Add it, in reverse, to the buffer
|
||||||
for i := len(ip) - 1; i >= 0; i-- {
|
for i := len(ip) - 1; i >= 0; i-- {
|
||||||
v := ip[i]
|
v := ip[i]
|
||||||
buf = append(buf, hexDigit[v&0xF])
|
buf = append(buf, hexDigit[v&0xF], '.', hexDigit[v>>4], '.')
|
||||||
buf = append(buf, '.')
|
|
||||||
buf = append(buf, hexDigit[v>>4])
|
|
||||||
buf = append(buf, '.')
|
|
||||||
}
|
}
|
||||||
// Append "ip6.arpa." and return (buf already has the final .)
|
// Append "ip6.arpa." and return (buf already has the final .)
|
||||||
buf = append(buf, "ip6.arpa."...)
|
buf = append(buf, "ip6.arpa."...)
|
||||||
|
|
30
dns.go
30
dns.go
|
@ -1,6 +1,9 @@
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import "strconv"
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits.
|
year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits.
|
||||||
|
@ -111,7 +114,7 @@ func (h *RR_Header) parse(c *zlexer, origin string) *ParseError {
|
||||||
|
|
||||||
// ToRFC3597 converts a known RR to the unknown RR representation from RFC 3597.
|
// ToRFC3597 converts a known RR to the unknown RR representation from RFC 3597.
|
||||||
func (rr *RFC3597) ToRFC3597(r RR) error {
|
func (rr *RFC3597) ToRFC3597(r RR) error {
|
||||||
buf := make([]byte, Len(r)*2)
|
buf := make([]byte, Len(r))
|
||||||
headerEnd, off, err := packRR(r, buf, 0, compressionMap{}, false)
|
headerEnd, off, err := packRR(r, buf, 0, compressionMap{}, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -126,9 +129,30 @@ func (rr *RFC3597) ToRFC3597(r RR) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = rr.unpack(buf, headerEnd)
|
_, err = rr.unpack(buf, headerEnd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromRFC3597 converts an unknown RR representation from RFC 3597 to the known RR type.
|
||||||
|
func (rr *RFC3597) fromRFC3597(r RR) error {
|
||||||
|
hdr := r.Header()
|
||||||
|
*hdr = rr.Hdr
|
||||||
|
|
||||||
|
// Can't overflow uint16 as the length of Rdata is validated in (*RFC3597).parse.
|
||||||
|
// We can only get here when rr was constructed with that method.
|
||||||
|
hdr.Rdlength = uint16(hex.DecodedLen(len(rr.Rdata)))
|
||||||
|
|
||||||
|
if noRdata(*hdr) {
|
||||||
|
// Dynamic update.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rr.pack requires an extra allocation and a copy so we just decode Rdata
|
||||||
|
// manually, it's simpler anyway.
|
||||||
|
msg, err := hex.DecodeString(rr.Rdata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
_, err = r.unpack(msg, 0)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
111
dnssec.go
111
dnssec.go
|
@ -3,15 +3,14 @@ package dns
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/dsa"
|
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
_ "crypto/md5"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
_ "crypto/sha1"
|
_ "crypto/sha1" // need its init function
|
||||||
_ "crypto/sha256"
|
_ "crypto/sha256" // need its init function
|
||||||
_ "crypto/sha512"
|
_ "crypto/sha512" // need its init function
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
@ -19,8 +18,6 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DNSSEC encryption algorithm codes.
|
// DNSSEC encryption algorithm codes.
|
||||||
|
@ -68,6 +65,9 @@ var AlgorithmToString = map[uint8]string{
|
||||||
}
|
}
|
||||||
|
|
||||||
// AlgorithmToHash is a map of algorithm crypto hash IDs to crypto.Hash's.
|
// 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{
|
var AlgorithmToHash = map[uint8]crypto.Hash{
|
||||||
RSAMD5: crypto.MD5, // Deprecated in RFC 6725
|
RSAMD5: crypto.MD5, // Deprecated in RFC 6725
|
||||||
DSA: crypto.SHA1,
|
DSA: crypto.SHA1,
|
||||||
|
@ -77,7 +77,7 @@ var AlgorithmToHash = map[uint8]crypto.Hash{
|
||||||
ECDSAP256SHA256: crypto.SHA256,
|
ECDSAP256SHA256: crypto.SHA256,
|
||||||
ECDSAP384SHA384: crypto.SHA384,
|
ECDSAP384SHA384: crypto.SHA384,
|
||||||
RSASHA512: crypto.SHA512,
|
RSASHA512: crypto.SHA512,
|
||||||
ED25519: crypto.Hash(0),
|
ED25519: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNSSEC hashing algorithm codes.
|
// DNSSEC hashing algorithm codes.
|
||||||
|
@ -140,12 +140,12 @@ func (k *DNSKEY) KeyTag() uint16 {
|
||||||
var keytag int
|
var keytag int
|
||||||
switch k.Algorithm {
|
switch k.Algorithm {
|
||||||
case RSAMD5:
|
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.
|
// 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))
|
modulus, _ := fromBase64([]byte(k.PublicKey))
|
||||||
if len(modulus) > 1 {
|
if len(modulus) > 1 {
|
||||||
x := binary.BigEndian.Uint16(modulus[len(modulus)-2:])
|
x := binary.BigEndian.Uint16(modulus[len(modulus)-3:])
|
||||||
keytag = int(x)
|
keytag = int(x)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -299,42 +299,27 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, ok := AlgorithmToHash[rr.Algorithm]
|
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return ErrAlg
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch rr.Algorithm {
|
switch rr.Algorithm {
|
||||||
case ED25519:
|
|
||||||
// ed25519 signs the raw message and performs hashing internally.
|
|
||||||
// All other supported signature schemes operate over the pre-hashed
|
|
||||||
// message, and thus ed25519 must be handled separately here.
|
|
||||||
//
|
|
||||||
// The raw message is passed directly into sign and crypto.Hash(0) is
|
|
||||||
// used to signal to the crypto.Signer that the data has not been hashed.
|
|
||||||
signature, err := sign(k, append(signdata, wire...), crypto.Hash(0), rr.Algorithm)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rr.Signature = toBase64(signature)
|
|
||||||
case RSAMD5, DSA, DSANSEC3SHA1:
|
case RSAMD5, DSA, DSANSEC3SHA1:
|
||||||
// See RFC 6944.
|
// See RFC 6944.
|
||||||
return ErrAlg
|
return ErrAlg
|
||||||
default:
|
default:
|
||||||
h := hash.New()
|
|
||||||
h.Write(signdata)
|
h.Write(signdata)
|
||||||
h.Write(wire)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rr.Signature = toBase64(signature)
|
rr.Signature = toBase64(signature)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte, error) {
|
func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte, error) {
|
||||||
|
@ -344,9 +329,8 @@ func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch alg {
|
switch alg {
|
||||||
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
|
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, ED25519:
|
||||||
return signature, nil
|
return signature, nil
|
||||||
|
|
||||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||||
ecdsaSignature := &struct {
|
ecdsaSignature := &struct {
|
||||||
R, S *big.Int
|
R, S *big.Int
|
||||||
|
@ -366,25 +350,16 @@ func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte,
|
||||||
signature := intToBytes(ecdsaSignature.R, intlen)
|
signature := intToBytes(ecdsaSignature.R, intlen)
|
||||||
signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...)
|
signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...)
|
||||||
return signature, nil
|
return signature, nil
|
||||||
|
default:
|
||||||
// There is no defined interface for what a DSA backed crypto.Signer returns
|
return nil, ErrAlg
|
||||||
case DSA, DSANSEC3SHA1:
|
|
||||||
// t := divRoundUp(divRoundUp(p.PublicKey.Y.BitLen(), 8)-64, 8)
|
|
||||||
// signature := []byte{byte(t)}
|
|
||||||
// signature = append(signature, intToBytes(r1, 20)...)
|
|
||||||
// signature = append(signature, intToBytes(s1, 20)...)
|
|
||||||
// rr.Signature = signature
|
|
||||||
|
|
||||||
case ED25519:
|
|
||||||
return signature, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, ErrAlg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify validates an RRSet with the signature and key. This is only the
|
// Verify validates an RRSet with the signature and key. This is only the
|
||||||
// cryptographic test, the signature validity period must be checked separately.
|
// cryptographic test, the signature validity period must be checked separately.
|
||||||
// This function copies the rdata of some RRs (to lowercase domain names) for the validation to work.
|
// This function copies the rdata of some RRs (to lowercase domain names) for the validation to work.
|
||||||
|
// It also checks that the Zone Key bit (RFC 4034 2.1.1) is set on the DNSKEY
|
||||||
|
// and that the Protocol field is set to 3 (RFC 4034 2.1.2).
|
||||||
func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
||||||
// First the easy checks
|
// First the easy checks
|
||||||
if !IsRRset(rrset) {
|
if !IsRRset(rrset) {
|
||||||
|
@ -405,6 +380,12 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
||||||
if k.Protocol != 3 {
|
if k.Protocol != 3 {
|
||||||
return ErrKey
|
return ErrKey
|
||||||
}
|
}
|
||||||
|
// RFC 4034 2.1.1 If bit 7 has value 0, then the DNSKEY record holds some
|
||||||
|
// other type of DNS public key and MUST NOT be used to verify RRSIGs that
|
||||||
|
// cover RRsets.
|
||||||
|
if k.Flags&ZONE == 0 {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
|
||||||
// IsRRset checked that we have at least one RR and that the RRs in
|
// IsRRset checked that we have at least one RR and that the RRs in
|
||||||
// the set have consistent type, class, and name. Also check that type and
|
// the set have consistent type, class, and name. Also check that type and
|
||||||
|
@ -442,23 +423,22 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
||||||
// remove the domain name and assume its ours?
|
// remove the domain name and assume its ours?
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, ok := AlgorithmToHash[rr.Algorithm]
|
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return ErrAlg
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch rr.Algorithm {
|
switch rr.Algorithm {
|
||||||
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, RSAMD5:
|
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
|
||||||
// TODO(mg): this can be done quicker, ie. cache the pubkey data somewhere??
|
// TODO(mg): this can be done quicker, ie. cache the pubkey data somewhere??
|
||||||
pubkey := k.publicKeyRSA() // Get the key
|
pubkey := k.publicKeyRSA() // Get the key
|
||||||
if pubkey == nil {
|
if pubkey == nil {
|
||||||
return ErrKey
|
return ErrKey
|
||||||
}
|
}
|
||||||
|
|
||||||
h := hash.New()
|
|
||||||
h.Write(signeddata)
|
h.Write(signeddata)
|
||||||
h.Write(wire)
|
h.Write(wire)
|
||||||
return rsa.VerifyPKCS1v15(pubkey, hash, h.Sum(nil), sigbuf)
|
return rsa.VerifyPKCS1v15(pubkey, cryptohash, h.Sum(nil), sigbuf)
|
||||||
|
|
||||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||||
pubkey := k.publicKeyECDSA()
|
pubkey := k.publicKeyECDSA()
|
||||||
|
@ -470,7 +450,6 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
||||||
r := new(big.Int).SetBytes(sigbuf[:len(sigbuf)/2])
|
r := new(big.Int).SetBytes(sigbuf[:len(sigbuf)/2])
|
||||||
s := 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(signeddata)
|
||||||
h.Write(wire)
|
h.Write(wire)
|
||||||
if ecdsa.Verify(pubkey, h.Sum(nil), r, s) {
|
if ecdsa.Verify(pubkey, h.Sum(nil), r, s) {
|
||||||
|
@ -512,7 +491,7 @@ func (rr *RRSIG) ValidityPeriod(t time.Time) bool {
|
||||||
return ti <= utc && utc <= te
|
return ti <= utc && utc <= te
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the signatures base64 encodedig sigdata as a byte slice.
|
// Return the signatures base64 encoding sigdata as a byte slice.
|
||||||
func (rr *RRSIG) sigBuf() []byte {
|
func (rr *RRSIG) sigBuf() []byte {
|
||||||
sigbuf, err := fromBase64([]byte(rr.Signature))
|
sigbuf, err := fromBase64([]byte(rr.Signature))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -600,30 +579,6 @@ func (k *DNSKEY) publicKeyECDSA() *ecdsa.PublicKey {
|
||||||
return pubkey
|
return pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *DNSKEY) publicKeyDSA() *dsa.PublicKey {
|
|
||||||
keybuf, err := fromBase64([]byte(k.PublicKey))
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(keybuf) < 22 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t, keybuf := int(keybuf[0]), keybuf[1:]
|
|
||||||
size := 64 + t*8
|
|
||||||
q, keybuf := keybuf[:20], keybuf[20:]
|
|
||||||
if len(keybuf) != 3*size {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
p, keybuf := keybuf[:size], keybuf[size:]
|
|
||||||
g, y := keybuf[:size], keybuf[size:]
|
|
||||||
pubkey := new(dsa.PublicKey)
|
|
||||||
pubkey.Parameters.Q = new(big.Int).SetBytes(q)
|
|
||||||
pubkey.Parameters.P = new(big.Int).SetBytes(p)
|
|
||||||
pubkey.Parameters.G = new(big.Int).SetBytes(g)
|
|
||||||
pubkey.Y = new(big.Int).SetBytes(y)
|
|
||||||
return pubkey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *DNSKEY) publicKeyED25519() ed25519.PublicKey {
|
func (k *DNSKEY) publicKeyED25519() ed25519.PublicKey {
|
||||||
keybuf, err := fromBase64([]byte(k.PublicKey))
|
keybuf, err := fromBase64([]byte(k.PublicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -3,12 +3,11 @@ package dns
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Generate generates a DNSKEY of the given bit size.
|
// Generate generates a DNSKEY of the given bit size.
|
||||||
|
@ -19,8 +18,6 @@ import (
|
||||||
// bits should be set to the size of the algorithm.
|
// bits should be set to the size of the algorithm.
|
||||||
func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) {
|
func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) {
|
||||||
switch k.Algorithm {
|
switch k.Algorithm {
|
||||||
case RSAMD5, DSA, DSANSEC3SHA1:
|
|
||||||
return nil, ErrAlg
|
|
||||||
case RSASHA1, RSASHA256, RSASHA1NSEC3SHA1:
|
case RSASHA1, RSASHA256, RSASHA1NSEC3SHA1:
|
||||||
if bits < 512 || bits > 4096 {
|
if bits < 512 || bits > 4096 {
|
||||||
return nil, ErrKeySize
|
return nil, ErrKeySize
|
||||||
|
@ -41,6 +38,8 @@ func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) {
|
||||||
if bits != 256 {
|
if bits != 256 {
|
||||||
return nil, ErrKeySize
|
return nil, ErrKeySize
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
return nil, ErrAlg
|
||||||
}
|
}
|
||||||
|
|
||||||
switch k.Algorithm {
|
switch k.Algorithm {
|
||||||
|
|
|
@ -4,13 +4,12 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewPrivateKey returns a PrivateKey by parsing the string s.
|
// NewPrivateKey returns a PrivateKey by parsing the string s.
|
||||||
|
@ -43,15 +42,7 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
|
||||||
return nil, ErrPrivKey
|
return nil, ErrPrivKey
|
||||||
}
|
}
|
||||||
switch uint8(algo) {
|
switch uint8(algo) {
|
||||||
case RSAMD5, DSA, DSANSEC3SHA1:
|
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
|
||||||
return nil, ErrAlg
|
|
||||||
case RSASHA1:
|
|
||||||
fallthrough
|
|
||||||
case RSASHA1NSEC3SHA1:
|
|
||||||
fallthrough
|
|
||||||
case RSASHA256:
|
|
||||||
fallthrough
|
|
||||||
case RSASHA512:
|
|
||||||
priv, err := readPrivateKeyRSA(m)
|
priv, err := readPrivateKeyRSA(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -62,11 +53,7 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
|
||||||
}
|
}
|
||||||
priv.PublicKey = *pub
|
priv.PublicKey = *pub
|
||||||
return priv, nil
|
return priv, nil
|
||||||
case ECCGOST:
|
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||||
return nil, ErrPrivKey
|
|
||||||
case ECDSAP256SHA256:
|
|
||||||
fallthrough
|
|
||||||
case ECDSAP384SHA384:
|
|
||||||
priv, err := readPrivateKeyECDSA(m)
|
priv, err := readPrivateKeyECDSA(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -80,7 +67,7 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
|
||||||
case ED25519:
|
case ED25519:
|
||||||
return readPrivateKeyED25519(m)
|
return readPrivateKeyED25519(m)
|
||||||
default:
|
default:
|
||||||
return nil, ErrPrivKey
|
return nil, ErrAlg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,11 @@ package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/dsa"
|
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const format = "Private-key-format: v1.3\n"
|
const format = "Private-key-format: v1.3\n"
|
||||||
|
@ -17,8 +15,8 @@ var bigIntOne = big.NewInt(1)
|
||||||
|
|
||||||
// PrivateKeyString converts a PrivateKey to a string. This string has the same
|
// PrivateKeyString converts a PrivateKey to a string. This string has the same
|
||||||
// format as the private-key-file of BIND9 (Private-key-format: v1.3).
|
// format as the private-key-file of BIND9 (Private-key-format: v1.3).
|
||||||
// It needs some info from the key (the algorithm), so its a method of the DNSKEY
|
// It needs some info from the key (the algorithm), so its a method of the DNSKEY.
|
||||||
// It supports rsa.PrivateKey, ecdsa.PrivateKey and dsa.PrivateKey
|
// It supports *rsa.PrivateKey, *ecdsa.PrivateKey and ed25519.PrivateKey.
|
||||||
func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string {
|
func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string {
|
||||||
algorithm := strconv.Itoa(int(r.Algorithm))
|
algorithm := strconv.Itoa(int(r.Algorithm))
|
||||||
algorithm += " (" + AlgorithmToString[r.Algorithm] + ")"
|
algorithm += " (" + AlgorithmToString[r.Algorithm] + ")"
|
||||||
|
@ -67,21 +65,6 @@ func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string {
|
||||||
"Algorithm: " + algorithm + "\n" +
|
"Algorithm: " + algorithm + "\n" +
|
||||||
"PrivateKey: " + private + "\n"
|
"PrivateKey: " + private + "\n"
|
||||||
|
|
||||||
case *dsa.PrivateKey:
|
|
||||||
T := divRoundUp(divRoundUp(p.PublicKey.Parameters.G.BitLen(), 8)-64, 8)
|
|
||||||
prime := toBase64(intToBytes(p.PublicKey.Parameters.P, 64+T*8))
|
|
||||||
subprime := toBase64(intToBytes(p.PublicKey.Parameters.Q, 20))
|
|
||||||
base := toBase64(intToBytes(p.PublicKey.Parameters.G, 64+T*8))
|
|
||||||
priv := toBase64(intToBytes(p.X, 20))
|
|
||||||
pub := toBase64(intToBytes(p.PublicKey.Y, 64+T*8))
|
|
||||||
return format +
|
|
||||||
"Algorithm: " + algorithm + "\n" +
|
|
||||||
"Prime(p): " + prime + "\n" +
|
|
||||||
"Subprime(q): " + subprime + "\n" +
|
|
||||||
"Base(g): " + base + "\n" +
|
|
||||||
"Private_value(x): " + priv + "\n" +
|
|
||||||
"Public_value(y): " + pub + "\n"
|
|
||||||
|
|
||||||
case ed25519.PrivateKey:
|
case ed25519.PrivateKey:
|
||||||
private := toBase64(p.Seed())
|
private := toBase64(p.Seed())
|
||||||
return format +
|
return format +
|
||||||
|
|
|
@ -3,13 +3,12 @@ package dns
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getSoa() *SOA {
|
func getSoa() *SOA {
|
||||||
|
@ -325,7 +324,7 @@ Activate: 20110302104537`
|
||||||
}
|
}
|
||||||
switch priv := p.(type) {
|
switch priv := p.(type) {
|
||||||
case *rsa.PrivateKey:
|
case *rsa.PrivateKey:
|
||||||
if 65537 != priv.PublicKey.E {
|
if priv.PublicKey.E != 65537 {
|
||||||
t.Error("exponenent should be 65537")
|
t.Error("exponenent should be 65537")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -856,3 +855,16 @@ func TestParseKeyReadError(t *testing.T) {
|
||||||
t.Errorf("expected a nil map, but got %v", m)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,10 +30,10 @@ func AddOrigin(s, origin string) string {
|
||||||
if dns.IsFqdn(s) {
|
if dns.IsFqdn(s) {
|
||||||
return s // s is already a FQDN, no need to mess with it.
|
return s // s is already a FQDN, no need to mess with it.
|
||||||
}
|
}
|
||||||
if len(origin) == 0 {
|
if origin == "" {
|
||||||
return s // Nothing to append.
|
return s // Nothing to append.
|
||||||
}
|
}
|
||||||
if s == "@" || len(s) == 0 {
|
if s == "@" || s == "" {
|
||||||
return origin // Expand apex.
|
return origin // Expand apex.
|
||||||
}
|
}
|
||||||
if origin == "." {
|
if origin == "." {
|
||||||
|
@ -50,7 +50,7 @@ func TrimDomainName(s, origin string) string {
|
||||||
// If the return value ends in a ".", the domain was not the suffix.
|
// If the return value ends in a ".", the domain was not the suffix.
|
||||||
// origin can end in "." or not. Either way the results should be the same.
|
// origin can end in "." or not. Either way the results should be the same.
|
||||||
|
|
||||||
if len(s) == 0 {
|
if s == "" {
|
||||||
return "@"
|
return "@"
|
||||||
}
|
}
|
||||||
// Someone is using TrimDomainName(s, ".") to remove a dot if it exists.
|
// Someone is using TrimDomainName(s, ".") to remove a dot if it exists.
|
||||||
|
|
|
@ -63,7 +63,7 @@ func TestTrimDomainName(t *testing.T) {
|
||||||
// Paranoid tests.
|
// Paranoid tests.
|
||||||
// These test shouldn't be needed but I was weary of off-by-one errors.
|
// 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,
|
// 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 }{
|
tests := []struct{ experiment, expected string }{
|
||||||
{"", "@"},
|
{"", "@"},
|
||||||
{".", "."},
|
{".", "."},
|
||||||
|
|
122
doc.go
122
doc.go
|
@ -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
|
Resource records are native types. They are not stored in wire format. Basic
|
||||||
usage pattern for creating a new resource record:
|
usage pattern for creating a new resource record:
|
||||||
|
|
||||||
r := new(dns.MX)
|
r := new(dns.MX)
|
||||||
r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 3600}
|
r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 3600}
|
||||||
r.Preference = 10
|
r.Preference = 10
|
||||||
r.Mx = "mx.miek.nl."
|
r.Mx = "mx.miek.nl."
|
||||||
|
|
||||||
Or directly from a string:
|
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:
|
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:
|
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
|
In the DNS messages are exchanged, these messages contain resource records
|
||||||
(sets). Use pattern for creating a message:
|
(sets). Use pattern for creating a message:
|
||||||
|
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.SetQuestion("miek.nl.", dns.TypeMX)
|
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||||
|
|
||||||
Or when not certain if the domain name is fully qualified:
|
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:
|
The following is slightly more verbose, but more flexible:
|
||||||
|
|
||||||
m1 := new(dns.Msg)
|
m1 := new(dns.Msg)
|
||||||
m1.Id = dns.Id()
|
m1.Id = dns.Id()
|
||||||
m1.RecursionDesired = true
|
m1.RecursionDesired = true
|
||||||
m1.Question = make([]dns.Question, 1)
|
m1.Question = make([]dns.Question, 1)
|
||||||
m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET}
|
m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET}
|
||||||
|
|
||||||
After creating a message it can be sent. Basic use pattern for synchronous
|
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:
|
querying the DNS at a server configured on 127.0.0.1 and port 53:
|
||||||
|
|
||||||
c := new(dns.Client)
|
c := new(dns.Client)
|
||||||
in, rtt, err := c.Exchange(m1, "127.0.0.1:53")
|
in, rtt, err := c.Exchange(m1, "127.0.0.1:53")
|
||||||
|
|
||||||
Suppressing multiple outstanding queries (with the same question, type and
|
Suppressing multiple outstanding queries (with the same question, type and
|
||||||
class) is as easy as setting:
|
class) is as easy as setting:
|
||||||
|
@ -72,7 +72,7 @@ and port to use for the connection:
|
||||||
Port: 12345,
|
Port: 12345,
|
||||||
Zone: "",
|
Zone: "",
|
||||||
}
|
}
|
||||||
c.Dialer := &net.Dialer{
|
c.Dialer = &net.Dialer{
|
||||||
Timeout: 200 * time.Millisecond,
|
Timeout: 200 * time.Millisecond,
|
||||||
LocalAddr: &laddr,
|
LocalAddr: &laddr,
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ the Answer section:
|
||||||
// do something with t.Txt
|
// 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 domain names and TXT character strings are converted to presentation form
|
||||||
both when unpacked and when converted to strings.
|
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,
|
For domain names, in addition to the above rules brackets, periods, spaces,
|
||||||
semicolons and the at symbol are escaped.
|
semicolons and the at symbol are escaped.
|
||||||
|
|
||||||
DNSSEC
|
# DNSSEC
|
||||||
|
|
||||||
DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It uses
|
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
|
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)
|
Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK)
|
||||||
bit to a request.
|
bit to a request.
|
||||||
|
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.SetEdns0(4096, true)
|
m.SetEdns0(4096, true)
|
||||||
|
|
||||||
Signature generation, signature verification and key generation are all supported.
|
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
|
Dynamic updates reuses the DNS message format, but renames three of the
|
||||||
sections. Question is Zone, Answer is Prerequisite, Authority is Update, only
|
sections. Question is Zone, Answer is Prerequisite, Authority is Update, only
|
||||||
|
@ -133,33 +133,33 @@ 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
|
should be added or removed. The table from RFC 2136 supplemented with the Go
|
||||||
DNS function shows which functions exist to specify the prerequisites.
|
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
|
CLASS TYPE RDATA Meaning Function
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
ANY ANY empty Name is in use dns.NameUsed
|
ANY ANY empty Name is in use dns.NameUsed
|
||||||
ANY rrset empty RRset exists (value indep) dns.RRsetUsed
|
ANY rrset empty RRset exists (value indep) dns.RRsetUsed
|
||||||
NONE ANY empty Name is not in use dns.NameNotUsed
|
NONE ANY empty Name is not in use dns.NameNotUsed
|
||||||
NONE rrset empty RRset does not exist dns.RRsetNotUsed
|
NONE rrset empty RRset does not exist dns.RRsetNotUsed
|
||||||
zone rrset rr RRset exists (value dep) dns.Used
|
zone rrset rr RRset exists (value dep) dns.Used
|
||||||
|
|
||||||
The prerequisite section can also be left empty. If you have decided on the
|
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
|
prerequisites you can tell what RRs should be added or deleted. The next table
|
||||||
shows the options you have and what functions to call.
|
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
|
CLASS TYPE RDATA Meaning Function
|
||||||
---------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
ANY ANY empty Delete all RRsets from name dns.RemoveName
|
ANY ANY empty Delete all RRsets from name dns.RemoveName
|
||||||
ANY rrset empty Delete an RRset dns.RemoveRRset
|
ANY rrset empty Delete an RRset dns.RemoveRRset
|
||||||
NONE rrset rr Delete an RR from RRset dns.Remove
|
NONE rrset rr Delete an RR from RRset dns.Remove
|
||||||
zone rrset rr Add to an RRset dns.Insert
|
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.
|
An TSIG or transaction signature adds a HMAC TSIG record to each message sent.
|
||||||
The supported algorithms include: HmacMD5, HmacSHA1, HmacSHA256 and HmacSHA512.
|
The supported algorithms include: HmacSHA1, HmacSHA256 and HmacSHA512.
|
||||||
|
|
||||||
Basic use pattern when querying with a TSIG name "axfr." (note that these key names
|
Basic use pattern when querying with a TSIG name "axfr." (note that these key names
|
||||||
must be fully qualified - as they are domain names) and the base64 secret
|
must be fully qualified - as they are domain names) and the base64 secret
|
||||||
|
@ -174,7 +174,7 @@ changes to the RRset after calling SetTsig() the signature will be incorrect.
|
||||||
c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
|
c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.SetQuestion("miek.nl.", dns.TypeMX)
|
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||||
m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
|
m.SetTsig("axfr.", dns.HmacSHA256, 300, time.Now().Unix())
|
||||||
...
|
...
|
||||||
// When sending the TSIG RR is calculated and filled in before sending
|
// When sending the TSIG RR is calculated and filled in before sending
|
||||||
|
|
||||||
|
@ -187,13 +187,37 @@ request an AXFR for miek.nl. with TSIG key named "axfr." and secret
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
|
t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
|
||||||
m.SetAxfr("miek.nl.")
|
m.SetAxfr("miek.nl.")
|
||||||
m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
|
m.SetTsig("axfr.", dns.HmacSHA256, 300, time.Now().Unix())
|
||||||
c, err := t.In(m, "176.58.119.54:53")
|
c, err := t.In(m, "176.58.119.54:53")
|
||||||
for r := range c { ... }
|
for r := range c { ... }
|
||||||
|
|
||||||
You can now read the records from the transfer as they come in. Each envelope
|
You can now read the records from the transfer as they come in. Each envelope
|
||||||
is checked with TSIG. If something is not correct an error is returned.
|
is checked with TSIG. If something is not correct an error is returned.
|
||||||
|
|
||||||
|
A custom TSIG implementation can be used. This requires additional code to
|
||||||
|
perform any session establishment and signature generation/verification. The
|
||||||
|
client must be configured with an implementation of the TsigProvider interface:
|
||||||
|
|
||||||
|
type Provider struct{}
|
||||||
|
|
||||||
|
func (*Provider) Generate(msg []byte, tsig *dns.TSIG) ([]byte, error) {
|
||||||
|
// Use tsig.Hdr.Name and tsig.Algorithm in your code to
|
||||||
|
// generate the MAC using msg as the payload.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Provider) Verify(msg []byte, tsig *dns.TSIG) error {
|
||||||
|
// Use tsig.Hdr.Name and tsig.Algorithm in your code to verify
|
||||||
|
// that msg matches the value in tsig.MAC.
|
||||||
|
}
|
||||||
|
|
||||||
|
c := new(dns.Client)
|
||||||
|
c.TsigProvider = new(Provider)
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||||
|
m.SetTsig(keyname, dns.HmacSHA256, 300, time.Now().Unix())
|
||||||
|
...
|
||||||
|
// TSIG RR is calculated by calling your Generate method
|
||||||
|
|
||||||
Basic use pattern validating and replying to a message that has TSIG set.
|
Basic use pattern validating and replying to a message that has TSIG set.
|
||||||
|
|
||||||
server := &dns.Server{Addr: ":53", Net: "udp"}
|
server := &dns.Server{Addr: ":53", Net: "udp"}
|
||||||
|
@ -207,7 +231,7 @@ Basic use pattern validating and replying to a message that has TSIG set.
|
||||||
if r.IsTsig() != nil {
|
if r.IsTsig() != nil {
|
||||||
if w.TsigStatus() == nil {
|
if w.TsigStatus() == nil {
|
||||||
// *Msg r has an TSIG record and it was validated
|
// *Msg r has an TSIG record and it was validated
|
||||||
m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
|
m.SetTsig("axfr.", dns.HmacSHA256, 300, time.Now().Unix())
|
||||||
} else {
|
} else {
|
||||||
// *Msg r has an TSIG records and it was not validated
|
// *Msg r has an TSIG records and it was not validated
|
||||||
}
|
}
|
||||||
|
@ -215,7 +239,7 @@ Basic use pattern validating and replying to a message that has TSIG set.
|
||||||
w.WriteMsg(m)
|
w.WriteMsg(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
PRIVATE RRS
|
# PRIVATE RRS
|
||||||
|
|
||||||
RFC 6895 sets aside a range of type codes for private use. This range is 65,280
|
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
|
- 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these
|
||||||
|
@ -224,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
|
See https://miek.nl/2014/september/21/idn-and-private-rr-in-go-dns/ for more
|
||||||
information.
|
information.
|
||||||
|
|
||||||
EDNS0
|
# EDNS0
|
||||||
|
|
||||||
EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated by
|
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.
|
abused.
|
||||||
|
|
||||||
Basic use pattern for creating an (empty) OPT RR:
|
Basic use pattern for creating an (empty) OPT RR:
|
||||||
|
@ -255,12 +279,12 @@ SIG(0)
|
||||||
|
|
||||||
From RFC 2931:
|
From RFC 2931:
|
||||||
|
|
||||||
SIG(0) provides protection for DNS transactions and requests ....
|
SIG(0) provides protection for DNS transactions and requests ....
|
||||||
... protection for glue records, DNS requests, protection for message headers
|
... protection for glue records, DNS requests, protection for message headers
|
||||||
on requests and responses, and protection of the overall integrity of a response.
|
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
|
It works like TSIG, except that SIG(0) uses public key cryptography, instead of
|
||||||
the shared secret approach in TSIG. Supported algorithms: DSA, ECDSAP256SHA256,
|
the shared secret approach in TSIG. Supported algorithms: ECDSAP256SHA256,
|
||||||
ECDSAP384SHA384, RSASHA1, RSASHA256 and RSASHA512.
|
ECDSAP384SHA384, RSASHA1, RSASHA256 and RSASHA512.
|
||||||
|
|
||||||
Signing subsequent messages in multi-message sessions is not implemented.
|
Signing subsequent messages in multi-message sessions is not implemented.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//+build ignore
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
// types_generate.go is meant to run with go generate. It will use
|
// 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
|
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||||
|
|
263
edns.go
263
edns.go
|
@ -14,6 +14,7 @@ const (
|
||||||
EDNS0LLQ = 0x1 // long lived queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01
|
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
|
EDNS0UL = 0x2 // update lease draft: http://files.dns-sd.org/draft-sekar-dns-ul.txt
|
||||||
EDNS0NSID = 0x3 // nsid (See RFC 5001)
|
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
|
EDNS0DAU = 0x5 // DNSSEC Algorithm Understood
|
||||||
EDNS0DHU = 0x6 // DS Hash Understood
|
EDNS0DHU = 0x6 // DS Hash Understood
|
||||||
EDNS0N3U = 0x7 // NSEC3 Hash Understood
|
EDNS0N3U = 0x7 // NSEC3 Hash Understood
|
||||||
|
@ -22,11 +23,49 @@ const (
|
||||||
EDNS0COOKIE = 0xa // EDNS0 Cookie
|
EDNS0COOKIE = 0xa // EDNS0 Cookie
|
||||||
EDNS0TCPKEEPALIVE = 0xb // EDNS0 tcp keep alive (See RFC 7828)
|
EDNS0TCPKEEPALIVE = 0xb // EDNS0 tcp keep alive (See RFC 7828)
|
||||||
EDNS0PADDING = 0xc // EDNS0 padding (See RFC 7830)
|
EDNS0PADDING = 0xc // EDNS0 padding (See RFC 7830)
|
||||||
|
EDNS0EDE = 0xf // EDNS0 extended DNS errors (See RFC 8914)
|
||||||
EDNS0LOCALSTART = 0xFDE9 // Beginning of range reserved for local/experimental use (See RFC 6891)
|
EDNS0LOCALSTART = 0xFDE9 // Beginning of range reserved for local/experimental use (See RFC 6891)
|
||||||
EDNS0LOCALEND = 0xFFFE // End of range reserved for local/experimental use (See RFC 6891)
|
EDNS0LOCALEND = 0xFFFE // End of range reserved for local/experimental use (See RFC 6891)
|
||||||
_DO = 1 << 15 // DNSSEC OK
|
_DO = 1 << 15 // DNSSEC OK
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// makeDataOpt is used to unpack the EDNS0 option(s) from a message.
|
||||||
|
func makeDataOpt(code uint16) EDNS0 {
|
||||||
|
// All the EDNS0.* constants above need to be in this switch.
|
||||||
|
switch code {
|
||||||
|
case EDNS0LLQ:
|
||||||
|
return new(EDNS0_LLQ)
|
||||||
|
case EDNS0UL:
|
||||||
|
return new(EDNS0_UL)
|
||||||
|
case EDNS0NSID:
|
||||||
|
return new(EDNS0_NSID)
|
||||||
|
case EDNS0DAU:
|
||||||
|
return new(EDNS0_DAU)
|
||||||
|
case EDNS0DHU:
|
||||||
|
return new(EDNS0_DHU)
|
||||||
|
case EDNS0N3U:
|
||||||
|
return new(EDNS0_N3U)
|
||||||
|
case EDNS0SUBNET:
|
||||||
|
return new(EDNS0_SUBNET)
|
||||||
|
case EDNS0EXPIRE:
|
||||||
|
return new(EDNS0_EXPIRE)
|
||||||
|
case EDNS0COOKIE:
|
||||||
|
return new(EDNS0_COOKIE)
|
||||||
|
case EDNS0TCPKEEPALIVE:
|
||||||
|
return new(EDNS0_TCP_KEEPALIVE)
|
||||||
|
case EDNS0PADDING:
|
||||||
|
return new(EDNS0_PADDING)
|
||||||
|
case EDNS0EDE:
|
||||||
|
return new(EDNS0_EDE)
|
||||||
|
case EDNS0ESU:
|
||||||
|
return &EDNS0_ESU{Code: EDNS0ESU}
|
||||||
|
default:
|
||||||
|
e := new(EDNS0_LOCAL)
|
||||||
|
e.Code = code
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// OPT is the EDNS0 RR appended to messages to convey extra (meta) information.
|
// OPT is the EDNS0 RR appended to messages to convey extra (meta) information.
|
||||||
// See RFC 6891.
|
// See RFC 6891.
|
||||||
type OPT struct {
|
type OPT struct {
|
||||||
|
@ -39,7 +78,10 @@ func (rr *OPT) String() string {
|
||||||
if rr.Do() {
|
if rr.Do() {
|
||||||
s += "flags: do; "
|
s += "flags: do; "
|
||||||
} else {
|
} 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()))
|
s += "udp: " + strconv.Itoa(int(rr.UDPSize()))
|
||||||
|
|
||||||
|
@ -59,6 +101,10 @@ func (rr *OPT) String() string {
|
||||||
s += "\n; SUBNET: " + o.String()
|
s += "\n; SUBNET: " + o.String()
|
||||||
case *EDNS0_COOKIE:
|
case *EDNS0_COOKIE:
|
||||||
s += "\n; COOKIE: " + o.String()
|
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:
|
case *EDNS0_UL:
|
||||||
s += "\n; UPDATE LEASE: " + o.String()
|
s += "\n; UPDATE LEASE: " + o.String()
|
||||||
case *EDNS0_LLQ:
|
case *EDNS0_LLQ:
|
||||||
|
@ -73,6 +119,10 @@ func (rr *OPT) String() string {
|
||||||
s += "\n; LOCAL OPT: " + o.String()
|
s += "\n; LOCAL OPT: " + o.String()
|
||||||
case *EDNS0_PADDING:
|
case *EDNS0_PADDING:
|
||||||
s += "\n; PADDING: " + o.String()
|
s += "\n; PADDING: " + o.String()
|
||||||
|
case *EDNS0_EDE:
|
||||||
|
s += "\n; EDE: " + o.String()
|
||||||
|
case *EDNS0_ESU:
|
||||||
|
s += "\n; ESU: " + o.String()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
|
@ -88,11 +138,11 @@ func (rr *OPT) len(off int, compression map[string]struct{}) int {
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rr *OPT) parse(c *zlexer, origin string) *ParseError {
|
func (*OPT) parse(c *zlexer, origin string) *ParseError {
|
||||||
panic("dns: internal error: parse should never be called on OPT")
|
return &ParseError{err: "OPT records do not have a presentation format"}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r1 *OPT) isDuplicate(r2 RR) bool { return false }
|
func (rr *OPT) isDuplicate(r2 RR) bool { return false }
|
||||||
|
|
||||||
// return the old value -> delete SetVersion?
|
// return the old value -> delete SetVersion?
|
||||||
|
|
||||||
|
@ -148,6 +198,16 @@ func (rr *OPT) SetDo(do ...bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Z returns the Z part of the OPT RR as a uint16 with only the 15 least significant bits used.
|
||||||
|
func (rr *OPT) Z() uint16 {
|
||||||
|
return uint16(rr.Hdr.Ttl & 0x7FFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetZ sets the Z part of the OPT RR, note only the 15 least significant bits of z are used.
|
||||||
|
func (rr *OPT) SetZ(z uint16) {
|
||||||
|
rr.Hdr.Ttl = rr.Hdr.Ttl&^0x7FFF | uint32(z&0x7FFF)
|
||||||
|
}
|
||||||
|
|
||||||
// EDNS0 defines an EDNS0 Option. An OPT RR can have multiple options appended to it.
|
// EDNS0 defines an EDNS0 Option. An OPT RR can have multiple options appended to it.
|
||||||
type EDNS0 interface {
|
type EDNS0 interface {
|
||||||
// Option returns the option code for the option.
|
// Option returns the option code for the option.
|
||||||
|
@ -203,7 +263,7 @@ func (e *EDNS0_NSID) copy() EDNS0 { return &EDNS0_NSID{e.Code, e.Nsid}
|
||||||
// o.Hdr.Name = "."
|
// o.Hdr.Name = "."
|
||||||
// o.Hdr.Rrtype = dns.TypeOPT
|
// o.Hdr.Rrtype = dns.TypeOPT
|
||||||
// e := new(dns.EDNS0_SUBNET)
|
// 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.Family = 1 // 1 for IPv4 source address, 2 for IPv6
|
||||||
// e.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6
|
// e.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6
|
||||||
// e.SourceScope = 0
|
// e.SourceScope = 0
|
||||||
|
@ -452,7 +512,7 @@ func (e *EDNS0_LLQ) copy() EDNS0 {
|
||||||
return &EDNS0_LLQ{e.Code, e.Version, e.Opcode, e.Error, e.Id, e.LeaseLife}
|
return &EDNS0_LLQ{e.Code, e.Version, e.Opcode, e.Error, e.Id, e.LeaseLife}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EDNS0_DUA implements the EDNS0 "DNSSEC Algorithm Understood" option. See RFC 6975.
|
// EDNS0_DAU implements the EDNS0 "DNSSEC Algorithm Understood" option. See RFC 6975.
|
||||||
type EDNS0_DAU struct {
|
type EDNS0_DAU struct {
|
||||||
Code uint16 // Always EDNS0DAU
|
Code uint16 // Always EDNS0DAU
|
||||||
AlgCode []uint8
|
AlgCode []uint8
|
||||||
|
@ -525,18 +585,21 @@ func (e *EDNS0_N3U) String() string {
|
||||||
}
|
}
|
||||||
func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} }
|
func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} }
|
||||||
|
|
||||||
// EDNS0_EXPIRE implementes the EDNS0 option as described in RFC 7314.
|
// EDNS0_EXPIRE implements the EDNS0 option as described in RFC 7314.
|
||||||
type EDNS0_EXPIRE struct {
|
type EDNS0_EXPIRE struct {
|
||||||
Code uint16 // Always EDNS0EXPIRE
|
Code uint16 // Always EDNS0EXPIRE
|
||||||
Expire uint32
|
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.
|
// Option implements the EDNS0 interface.
|
||||||
func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE }
|
func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE }
|
||||||
func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) }
|
func (e *EDNS0_EXPIRE) copy() EDNS0 { return &EDNS0_EXPIRE{e.Code, e.Expire, e.Empty} }
|
||||||
func (e *EDNS0_EXPIRE) copy() EDNS0 { return &EDNS0_EXPIRE{e.Code, e.Expire} }
|
|
||||||
|
|
||||||
func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
|
func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
|
||||||
|
if e.Empty {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
b := make([]byte, 4)
|
b := make([]byte, 4)
|
||||||
binary.BigEndian.PutUint32(b, e.Expire)
|
binary.BigEndian.PutUint32(b, e.Expire)
|
||||||
return b, nil
|
return b, nil
|
||||||
|
@ -545,15 +608,24 @@ func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
|
||||||
func (e *EDNS0_EXPIRE) unpack(b []byte) error {
|
func (e *EDNS0_EXPIRE) unpack(b []byte) error {
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
// zero-length EXPIRE query, see RFC 7314 Section 2
|
// zero-length EXPIRE query, see RFC 7314 Section 2
|
||||||
|
e.Empty = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if len(b) < 4 {
|
if len(b) < 4 {
|
||||||
return ErrBuf
|
return ErrBuf
|
||||||
}
|
}
|
||||||
e.Expire = binary.BigEndian.Uint32(b)
|
e.Expire = binary.BigEndian.Uint32(b)
|
||||||
|
e.Empty = false
|
||||||
return nil
|
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
|
// The EDNS0_LOCAL option is used for local/experimental purposes. The option
|
||||||
// code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND]
|
// code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND]
|
||||||
// (RFC6891), although any unassigned code can actually be used. The content of
|
// (RFC6891), although any unassigned code can actually be used. The content of
|
||||||
|
@ -604,57 +676,52 @@ func (e *EDNS0_LOCAL) unpack(b []byte) error {
|
||||||
// EDNS0_TCP_KEEPALIVE is an EDNS0 option that instructs the server to keep
|
// EDNS0_TCP_KEEPALIVE is an EDNS0 option that instructs the server to keep
|
||||||
// the TCP connection alive. See RFC 7828.
|
// the TCP connection alive. See RFC 7828.
|
||||||
type EDNS0_TCP_KEEPALIVE struct {
|
type EDNS0_TCP_KEEPALIVE struct {
|
||||||
Code uint16 // Always EDNSTCPKEEPALIVE
|
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.
|
// 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.
|
// Option implements the EDNS0 interface.
|
||||||
func (e *EDNS0_TCP_KEEPALIVE) Option() uint16 { return EDNS0TCPKEEPALIVE }
|
func (e *EDNS0_TCP_KEEPALIVE) Option() uint16 { return EDNS0TCPKEEPALIVE }
|
||||||
|
|
||||||
func (e *EDNS0_TCP_KEEPALIVE) pack() ([]byte, error) {
|
func (e *EDNS0_TCP_KEEPALIVE) pack() ([]byte, error) {
|
||||||
if e.Timeout != 0 && e.Length != 2 {
|
if e.Timeout > 0 {
|
||||||
return nil, errors.New("dns: timeout specified but length is not 2")
|
b := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(b, e.Timeout)
|
||||||
|
return b, nil
|
||||||
}
|
}
|
||||||
if e.Timeout == 0 && e.Length != 0 {
|
return nil, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *EDNS0_TCP_KEEPALIVE) unpack(b []byte) error {
|
func (e *EDNS0_TCP_KEEPALIVE) unpack(b []byte) error {
|
||||||
if len(b) < 4 {
|
switch len(b) {
|
||||||
return ErrBuf
|
case 0:
|
||||||
}
|
case 2:
|
||||||
e.Length = binary.BigEndian.Uint16(b[2:4])
|
e.Timeout = binary.BigEndian.Uint16(b)
|
||||||
if e.Length != 0 && e.Length != 2 {
|
default:
|
||||||
return errors.New("dns: length mismatch, want 0/2 but got " + strconv.FormatUint(uint64(e.Length), 10))
|
return fmt.Errorf("dns: length mismatch, want 0/2 but got %d", len(b))
|
||||||
}
|
|
||||||
if e.Length == 2 {
|
|
||||||
if len(b) < 6 {
|
|
||||||
return ErrBuf
|
|
||||||
}
|
|
||||||
e.Timeout = binary.BigEndian.Uint16(b[4:6])
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *EDNS0_TCP_KEEPALIVE) String() (s string) {
|
func (e *EDNS0_TCP_KEEPALIVE) String() string {
|
||||||
s = "use tcp keep-alive"
|
s := "use tcp keep-alive"
|
||||||
if e.Length == 0 {
|
if e.Timeout == 0 {
|
||||||
s += ", timeout omitted"
|
s += ", timeout omitted"
|
||||||
} else {
|
} else {
|
||||||
s += fmt.Sprintf(", timeout %dms", e.Timeout*100)
|
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
|
// 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
|
// value of padding SHOULD be 0x0 but other values MAY be used, for instance if
|
||||||
|
@ -673,3 +740,117 @@ func (e *EDNS0_PADDING) copy() EDNS0 {
|
||||||
copy(b, e.Padding)
|
copy(b, e.Padding)
|
||||||
return &EDNS0_PADDING{b}
|
return &EDNS0_PADDING{b}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extended DNS Error Codes (RFC 8914).
|
||||||
|
const (
|
||||||
|
ExtendedErrorCodeOther uint16 = iota
|
||||||
|
ExtendedErrorCodeUnsupportedDNSKEYAlgorithm
|
||||||
|
ExtendedErrorCodeUnsupportedDSDigestType
|
||||||
|
ExtendedErrorCodeStaleAnswer
|
||||||
|
ExtendedErrorCodeForgedAnswer
|
||||||
|
ExtendedErrorCodeDNSSECIndeterminate
|
||||||
|
ExtendedErrorCodeDNSBogus
|
||||||
|
ExtendedErrorCodeSignatureExpired
|
||||||
|
ExtendedErrorCodeSignatureNotYetValid
|
||||||
|
ExtendedErrorCodeDNSKEYMissing
|
||||||
|
ExtendedErrorCodeRRSIGsMissing
|
||||||
|
ExtendedErrorCodeNoZoneKeyBitSet
|
||||||
|
ExtendedErrorCodeNSECMissing
|
||||||
|
ExtendedErrorCodeCachedError
|
||||||
|
ExtendedErrorCodeNotReady
|
||||||
|
ExtendedErrorCodeBlocked
|
||||||
|
ExtendedErrorCodeCensored
|
||||||
|
ExtendedErrorCodeFiltered
|
||||||
|
ExtendedErrorCodeProhibited
|
||||||
|
ExtendedErrorCodeStaleNXDOMAINAnswer
|
||||||
|
ExtendedErrorCodeNotAuthoritative
|
||||||
|
ExtendedErrorCodeNotSupported
|
||||||
|
ExtendedErrorCodeNoReachableAuthority
|
||||||
|
ExtendedErrorCodeNetworkError
|
||||||
|
ExtendedErrorCodeInvalidData
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtendedErrorCodeToString maps extended error info codes to a human readable
|
||||||
|
// description.
|
||||||
|
var ExtendedErrorCodeToString = map[uint16]string{
|
||||||
|
ExtendedErrorCodeOther: "Other",
|
||||||
|
ExtendedErrorCodeUnsupportedDNSKEYAlgorithm: "Unsupported DNSKEY Algorithm",
|
||||||
|
ExtendedErrorCodeUnsupportedDSDigestType: "Unsupported DS Digest Type",
|
||||||
|
ExtendedErrorCodeStaleAnswer: "Stale Answer",
|
||||||
|
ExtendedErrorCodeForgedAnswer: "Forged Answer",
|
||||||
|
ExtendedErrorCodeDNSSECIndeterminate: "DNSSEC Indeterminate",
|
||||||
|
ExtendedErrorCodeDNSBogus: "DNSSEC Bogus",
|
||||||
|
ExtendedErrorCodeSignatureExpired: "Signature Expired",
|
||||||
|
ExtendedErrorCodeSignatureNotYetValid: "Signature Not Yet Valid",
|
||||||
|
ExtendedErrorCodeDNSKEYMissing: "DNSKEY Missing",
|
||||||
|
ExtendedErrorCodeRRSIGsMissing: "RRSIGs Missing",
|
||||||
|
ExtendedErrorCodeNoZoneKeyBitSet: "No Zone Key Bit Set",
|
||||||
|
ExtendedErrorCodeNSECMissing: "NSEC Missing",
|
||||||
|
ExtendedErrorCodeCachedError: "Cached Error",
|
||||||
|
ExtendedErrorCodeNotReady: "Not Ready",
|
||||||
|
ExtendedErrorCodeBlocked: "Blocked",
|
||||||
|
ExtendedErrorCodeCensored: "Censored",
|
||||||
|
ExtendedErrorCodeFiltered: "Filtered",
|
||||||
|
ExtendedErrorCodeProhibited: "Prohibited",
|
||||||
|
ExtendedErrorCodeStaleNXDOMAINAnswer: "Stale NXDOMAIN Answer",
|
||||||
|
ExtendedErrorCodeNotAuthoritative: "Not Authoritative",
|
||||||
|
ExtendedErrorCodeNotSupported: "Not Supported",
|
||||||
|
ExtendedErrorCodeNoReachableAuthority: "No Reachable Authority",
|
||||||
|
ExtendedErrorCodeNetworkError: "Network Error",
|
||||||
|
ExtendedErrorCodeInvalidData: "Invalid Data",
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToExtendedErrorCode is a map from human readable descriptions to
|
||||||
|
// extended error info codes.
|
||||||
|
var StringToExtendedErrorCode = reverseInt16(ExtendedErrorCodeToString)
|
||||||
|
|
||||||
|
// EDNS0_EDE option is used to return additional information about the cause of
|
||||||
|
// DNS errors.
|
||||||
|
type EDNS0_EDE struct {
|
||||||
|
InfoCode uint16
|
||||||
|
ExtraText string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option implements the EDNS0 interface.
|
||||||
|
func (e *EDNS0_EDE) Option() uint16 { return EDNS0EDE }
|
||||||
|
func (e *EDNS0_EDE) copy() EDNS0 { return &EDNS0_EDE{e.InfoCode, e.ExtraText} }
|
||||||
|
|
||||||
|
func (e *EDNS0_EDE) String() string {
|
||||||
|
info := strconv.FormatUint(uint64(e.InfoCode), 10)
|
||||||
|
if s, ok := ExtendedErrorCodeToString[e.InfoCode]; ok {
|
||||||
|
info += fmt.Sprintf(" (%s)", s)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s: (%s)", info, e.ExtraText)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_EDE) pack() ([]byte, error) {
|
||||||
|
b := make([]byte, 2+len(e.ExtraText))
|
||||||
|
binary.BigEndian.PutUint16(b[0:], e.InfoCode)
|
||||||
|
copy(b[2:], []byte(e.ExtraText))
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_EDE) unpack(b []byte) error {
|
||||||
|
if len(b) < 2 {
|
||||||
|
return ErrBuf
|
||||||
|
}
|
||||||
|
e.InfoCode = binary.BigEndian.Uint16(b[0:])
|
||||||
|
e.ExtraText = string(b[2:])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The EDNS0_ESU option for ENUM Source-URI Extension
|
||||||
|
type EDNS0_ESU struct {
|
||||||
|
Code uint16
|
||||||
|
Uri string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option implements the EDNS0 interface.
|
||||||
|
func (e *EDNS0_ESU) Option() uint16 { return EDNS0ESU }
|
||||||
|
func (e *EDNS0_ESU) String() string { return e.Uri }
|
||||||
|
func (e *EDNS0_ESU) copy() EDNS0 { return &EDNS0_ESU{e.Code, e.Uri} }
|
||||||
|
func (e *EDNS0_ESU) pack() ([]byte, error) { return []byte(e.Uri), nil }
|
||||||
|
func (e *EDNS0_ESU) unpack(b []byte) error {
|
||||||
|
e.Uri = string(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
144
edns_test.go
144
edns_test.go
|
@ -1,6 +1,7 @@
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -133,3 +134,146 @@ func TestEDNS0_UL(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestZ(t *testing.T) {
|
||||||
|
e := &OPT{}
|
||||||
|
e.Hdr.Name = "."
|
||||||
|
e.Hdr.Rrtype = TypeOPT
|
||||||
|
e.SetVersion(8)
|
||||||
|
e.SetDo()
|
||||||
|
if e.Z() != 0 {
|
||||||
|
t.Errorf("expected Z of 0, got %d", e.Z())
|
||||||
|
}
|
||||||
|
e.SetZ(5)
|
||||||
|
if e.Z() != 5 {
|
||||||
|
t.Errorf("expected Z of 5, got %d", e.Z())
|
||||||
|
}
|
||||||
|
e.SetZ(0xFFFF)
|
||||||
|
if e.Z() != 0x7FFF {
|
||||||
|
t.Errorf("expected Z of 0x7FFFF, got %d", e.Z())
|
||||||
|
}
|
||||||
|
if e.Version() != 8 {
|
||||||
|
t.Errorf("expected version to still be 8, got %d", e.Version())
|
||||||
|
}
|
||||||
|
if !e.Do() {
|
||||||
|
t.Error("expected DO to be set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEDNS0_ESU(t *testing.T) {
|
||||||
|
p := []byte{
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x29, 0x04,
|
||||||
|
0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||||
|
0x04, 0x00, 0x24, 0x73, 0x69, 0x70, 0x3A, 0x2B,
|
||||||
|
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||||
|
0x39, 0x40, 0x74, 0x65, 0x73, 0x74, 0x2E, 0x63,
|
||||||
|
0x6F, 0x6D, 0x3B, 0x75, 0x73, 0x65, 0x72, 0x3D,
|
||||||
|
0x63, 0x67, 0x72, 0x61, 0x74, 0x65, 0x73,
|
||||||
|
}
|
||||||
|
|
||||||
|
m := new(Msg)
|
||||||
|
if err := m.Unpack(p); err != nil {
|
||||||
|
t.Fatalf("failed to unpack: %v", err)
|
||||||
|
}
|
||||||
|
opt := m.IsEdns0()
|
||||||
|
if opt == nil {
|
||||||
|
t.Fatalf("expected edns0 option")
|
||||||
|
}
|
||||||
|
if len(opt.Option) != 1 {
|
||||||
|
t.Fatalf("expected only one option: %v", opt.Option)
|
||||||
|
}
|
||||||
|
edns0 := opt.Option[0]
|
||||||
|
esu, ok := edns0.(*EDNS0_ESU)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected option of type EDNS0_ESU, got %t", edns0)
|
||||||
|
}
|
||||||
|
expect := "sip:+123456789@test.com;user=cgrates"
|
||||||
|
if esu.Uri != expect {
|
||||||
|
t.Errorf("unpacked option is different; expected %v, got %v", expect, esu.Uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEDNS0_TCP_KEEPALIVE_unpack(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
b []byte
|
||||||
|
expected uint16
|
||||||
|
expectedErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
b: []byte{},
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timeout 1",
|
||||||
|
b: []byte{0, 1},
|
||||||
|
expected: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid",
|
||||||
|
b: []byte{0, 1, 3},
|
||||||
|
expectedErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
e := &EDNS0_TCP_KEEPALIVE{}
|
||||||
|
err := e.unpack(tc.b)
|
||||||
|
if err != nil && !tc.expectedErr {
|
||||||
|
t.Error("failed to unpack, expected no error")
|
||||||
|
}
|
||||||
|
if err == nil && tc.expectedErr {
|
||||||
|
t.Error("unpacked, but expected an error")
|
||||||
|
}
|
||||||
|
if e.Timeout != tc.expected {
|
||||||
|
t.Errorf("invalid timeout, actual: %d, expected: %d", e.Timeout, tc.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEDNS0_TCP_KEEPALIVE_pack(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
edns *EDNS0_TCP_KEEPALIVE
|
||||||
|
expected []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
edns: &EDNS0_TCP_KEEPALIVE{
|
||||||
|
Code: EDNS0TCPKEEPALIVE,
|
||||||
|
Timeout: 0,
|
||||||
|
},
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timeout 1",
|
||||||
|
edns: &EDNS0_TCP_KEEPALIVE{
|
||||||
|
Code: EDNS0TCPKEEPALIVE,
|
||||||
|
Timeout: 1,
|
||||||
|
},
|
||||||
|
expected: []byte{0, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
b, err := tc.edns.pack()
|
||||||
|
if err != nil {
|
||||||
|
t.Error("expected no error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expected == nil && b != nil {
|
||||||
|
t.Errorf("invalid result, expected nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
res := bytes.Compare(b, tc.expected)
|
||||||
|
if res != 0 {
|
||||||
|
t.Errorf("invalid result, expected: %v, actual: %v", tc.expected, b)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ func ExampleMX() {
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.SetQuestion("miek.nl.", dns.TypeMX)
|
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||||
m.RecursionDesired = true
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ func ExampleDS() {
|
||||||
zone := "miek.nl"
|
zone := "miek.nl"
|
||||||
m.SetQuestion(dns.Fqdn(zone), dns.TypeDNSKEY)
|
m.SetQuestion(dns.Fqdn(zone), dns.TypeDNSKEY)
|
||||||
m.SetEdns0(4096, true)
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ type APAIR struct {
|
||||||
func NewAPAIR() dns.PrivateRdata { return new(APAIR) }
|
func NewAPAIR() dns.PrivateRdata { return new(APAIR) }
|
||||||
|
|
||||||
func (rd *APAIR) String() string { return rd.addr[0].String() + " " + rd.addr[1].String() }
|
func (rd *APAIR) String() string { return rd.addr[0].String() + " " + rd.addr[1].String() }
|
||||||
|
|
||||||
func (rd *APAIR) Parse(txt []string) error {
|
func (rd *APAIR) Parse(txt []string) error {
|
||||||
if len(txt) != 2 {
|
if len(txt) != 2 {
|
||||||
return errors.New("two addresses required for APAIR")
|
return errors.New("two addresses required for APAIR")
|
||||||
|
@ -121,21 +122,23 @@ func (rd *APAIR) Len() int {
|
||||||
func ExamplePrivateHandle() {
|
func ExamplePrivateHandle() {
|
||||||
dns.PrivateHandle("APAIR", TypeAPAIR, NewAPAIR)
|
dns.PrivateHandle("APAIR", TypeAPAIR, NewAPAIR)
|
||||||
defer dns.PrivateHandleRemove(TypeAPAIR)
|
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)")
|
rr, err := dns.NewRR("miek.nl. APAIR (1.2.3.4 1.2.3.5)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("could not parse APAIR record: ", err)
|
log.Fatal("could not parse APAIR record: ", err)
|
||||||
}
|
}
|
||||||
fmt.Println(rr)
|
fmt.Println(rr) // see first line of Output below
|
||||||
// Output: miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5
|
|
||||||
|
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.Id = 12345
|
|
||||||
m.SetQuestion("miek.nl.", TypeAPAIR)
|
m.SetQuestion("miek.nl.", TypeAPAIR)
|
||||||
m.Answer = append(m.Answer, rr)
|
m.Answer = append(m.Answer, rr)
|
||||||
|
|
||||||
fmt.Println(m)
|
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
|
// ;; flags: rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
|
||||||
//
|
//
|
||||||
// ;; QUESTION SECTION:
|
// ;; QUESTION SECTION:
|
||||||
|
|
1
fuzz.go
1
fuzz.go
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build fuzz
|
||||||
// +build fuzz
|
// +build fuzz
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
|
@ -78,7 +78,7 @@ func TestPackDataOpt(t *testing.T) {
|
||||||
// "0\x00\v\x00#\b00000\x00\x00\x00\x00\x00\x1a000" +
|
// "0\x00\v\x00#\b00000\x00\x00\x00\x00\x00\x1a000" +
|
||||||
// "000\x00\x00\x00\x00\x1a000000\x00\x00\x00\x00\x1a0" +
|
// "000\x00\x00\x00\x00\x1a000000\x00\x00\x00\x00\x1a0" +
|
||||||
// "00000\x00\v00\a0000000\x00"
|
// "00000\x00\v00\a0000000\x00"
|
||||||
// That byte sequence, when Unpack() and subsequential Pack() created a
|
// That byte sequence, when Unpack() and subsequent Pack() created a
|
||||||
// panic: runtime error: slice bounds out of range
|
// panic: runtime error: slice bounds out of range
|
||||||
// which was attributed to the fact that NSEC RR length computation was different (and smaller)
|
// which was attributed to the fact that NSEC RR length computation was different (and smaller)
|
||||||
// then when within packDataNsec.
|
// then when within packDataNsec.
|
||||||
|
@ -111,7 +111,7 @@ func TestCrashNSEC(t *testing.T) {
|
||||||
// "0000\x00\x00000000\x00\x00200000" +
|
// "0000\x00\x00000000\x00\x00200000" +
|
||||||
// "0\x00\v0000\x00\x00#\x0300\x00\x00\x00\x1a000" +
|
// "0\x00\v0000\x00\x00#\x0300\x00\x00\x00\x1a000" +
|
||||||
// "000\x00\v00\x0200\x00\x03000\x00"
|
// "000\x00\v00\x0200\x00\x03000\x00"
|
||||||
// That byte sequence, when Unpack() and subsequential Pack() created a
|
// That byte sequence, when Unpack() and subsequent Pack() created a
|
||||||
// panic: runtime error: slice bounds out of range
|
// panic: runtime error: slice bounds out of range
|
||||||
// which was attributed to the fact that NSEC3 RR length computation was
|
// which was attributed to the fact that NSEC3 RR length computation was
|
||||||
// different (and smaller) then within NSEC3.pack (which relies on
|
// different (and smaller) then within NSEC3.pack (which relies on
|
||||||
|
|
26
generate.go
26
generate.go
|
@ -75,10 +75,10 @@ func (zp *ZoneParser) generate(l lex) (RR, bool) {
|
||||||
r := &generateReader{
|
r := &generateReader{
|
||||||
s: s,
|
s: s,
|
||||||
|
|
||||||
cur: int(start),
|
cur: start,
|
||||||
start: int(start),
|
start: start,
|
||||||
end: int(end),
|
end: end,
|
||||||
step: int(step),
|
step: step,
|
||||||
|
|
||||||
file: zp.file,
|
file: zp.file,
|
||||||
lex: &l,
|
lex: &l,
|
||||||
|
@ -94,10 +94,10 @@ type generateReader struct {
|
||||||
s string
|
s string
|
||||||
si int
|
si int
|
||||||
|
|
||||||
cur int
|
cur int64
|
||||||
start int
|
start int64
|
||||||
end int
|
end int64
|
||||||
step int
|
step int64
|
||||||
|
|
||||||
mod bytes.Buffer
|
mod bytes.Buffer
|
||||||
|
|
||||||
|
@ -173,7 +173,7 @@ func (r *generateReader) ReadByte() (byte, error) {
|
||||||
return '$', nil
|
return '$', nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var offset int
|
var offset int64
|
||||||
|
|
||||||
// Search for { and }
|
// Search for { and }
|
||||||
if r.s[si+1] == '{' {
|
if r.s[si+1] == '{' {
|
||||||
|
@ -188,7 +188,7 @@ func (r *generateReader) ReadByte() (byte, error) {
|
||||||
if errMsg != "" {
|
if errMsg != "" {
|
||||||
return 0, r.parseError(errMsg, si+3+sep)
|
return 0, r.parseError(errMsg, si+3+sep)
|
||||||
}
|
}
|
||||||
if r.start+offset < 0 || int64(r.end) + int64(offset) > 1<<31-1 {
|
if r.start+offset < 0 || r.end+offset > 1<<31-1 {
|
||||||
return 0, r.parseError("bad offset in $GENERATE", si+3+sep)
|
return 0, r.parseError("bad offset in $GENERATE", si+3+sep)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +208,7 @@ func (r *generateReader) ReadByte() (byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
|
// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
|
||||||
func modToPrintf(s string) (string, int, string) {
|
func modToPrintf(s string) (string, int64, string) {
|
||||||
// Modifier is { offset [ ,width [ ,base ] ] } - provide default
|
// Modifier is { offset [ ,width [ ,base ] ] } - provide default
|
||||||
// values for optional width and type, if necessary.
|
// values for optional width and type, if necessary.
|
||||||
var offStr, widthStr, base string
|
var offStr, widthStr, base string
|
||||||
|
@ -240,8 +240,8 @@ func modToPrintf(s string) (string, int, string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if width == 0 {
|
if width == 0 {
|
||||||
return "%" + base, int(offset), ""
|
return "%" + base, offset, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return "%0" + widthStr + base, int(offset), ""
|
return "%0" + widthStr + base, offset, ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,7 +162,7 @@ func TestGenerateModToPrintf(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
mod string
|
mod string
|
||||||
wantFmt string
|
wantFmt string
|
||||||
wantOffset int
|
wantOffset int64
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"0,0,d", "%d", 0, false},
|
{"0,0,d", "%d", 0, false},
|
||||||
|
|
11
go.mod
11
go.mod
|
@ -1,11 +1,10 @@
|
||||||
module github.com/miekg/dns
|
module github.com/miekg/dns
|
||||||
|
|
||||||
go 1.12
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe
|
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2
|
||||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
50
go.sum
50
go.sum
|
@ -1,39 +1,33 @@
|
||||||
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 h1:Vk3wNqEZwyGyei9yq5ekj7frek2u7HUfffJ1/opblzc=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM=
|
|
||||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
|
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||||
golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 h1:dgd4x4kJt7G4k4m93AYLzM8Ni6h2qLTfh9n9vXJT3/0=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
|
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0=
|
||||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
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-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-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=
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ package dns
|
||||||
// escaped dots (\.) for instance.
|
// escaped dots (\.) for instance.
|
||||||
// s must be a syntactically valid domain name, see IsDomainName.
|
// s must be a syntactically valid domain name, see IsDomainName.
|
||||||
func SplitDomainName(s string) (labels []string) {
|
func SplitDomainName(s string) (labels []string) {
|
||||||
if len(s) == 0 {
|
if s == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fqdnEnd := 0 // offset of the final '.' or the length of the name
|
fqdnEnd := 0 // offset of the final '.' or the length of the name
|
||||||
|
@ -122,7 +122,7 @@ func Split(s string) []int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextLabel returns the index of the start of the next label in the
|
// 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.
|
// The bool end is true when the end of the string has been reached.
|
||||||
// Also see PrevLabel.
|
// Also see PrevLabel.
|
||||||
func NextLabel(s string, offset int) (i int, end bool) {
|
func NextLabel(s string, offset int) (i int, end bool) {
|
||||||
|
|
|
@ -94,7 +94,7 @@ func TestNextLabel(t *testing.T) {
|
||||||
for s, i := range nexts {
|
for s, i := range nexts {
|
||||||
x, ok := NextLabel(s.string, s.int)
|
x, ok := NextLabel(s.string, s.int)
|
||||||
if i != x {
|
if i != x {
|
||||||
t.Errorf("label should be %d, got %d, %t: nexting %d, %s", i, x, ok, s.int, s.string)
|
t.Errorf("label should be %d, got %d, %t: next %d, %s", i, x, ok, s.int, s.string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ func TestPrevLabel(t *testing.T) {
|
||||||
for s, i := range prever {
|
for s, i := range prever {
|
||||||
x, ok := PrevLabel(s.string, s.int)
|
x, ok := PrevLabel(s.string, s.int)
|
||||||
if i != x {
|
if i != x {
|
||||||
t.Errorf("label should be %d, got %d, %t: preving %d, %s", i, x, ok, s.int, s.string)
|
t.Errorf("label should be %d, got %d, %t: previous %d, %s", i, x, ok, s.int, s.string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,7 +176,10 @@ func TestIsDomainName(t *testing.T) {
|
||||||
lab int
|
lab int
|
||||||
}
|
}
|
||||||
names := map[string]*ret{
|
names := map[string]*ret{
|
||||||
"..": {false, 1},
|
".": {true, 1},
|
||||||
|
"..": {false, 0},
|
||||||
|
"double-dot..test": {false, 1},
|
||||||
|
".leading-dot.test": {false, 0},
|
||||||
"@.": {true, 1},
|
"@.": {true, 1},
|
||||||
"www.example.com": {true, 3},
|
"www.example.com": {true, 3},
|
||||||
"www.e%ample.com": {true, 3},
|
"www.e%ample.com": {true, 3},
|
||||||
|
|
|
@ -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
|
// +build !go1.11 !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd
|
||||||
|
|
||||||
package dns
|
package dns
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build go1.11 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd)
|
||||||
// +build go1.11
|
// +build go1.11
|
||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
||||||
|
|
31
msg.go
31
msg.go
|
@ -265,6 +265,11 @@ loop:
|
||||||
|
|
||||||
wasDot = false
|
wasDot = false
|
||||||
case '.':
|
case '.':
|
||||||
|
if i == 0 && len(s) > 1 {
|
||||||
|
// leading dots are not legal except for the root zone
|
||||||
|
return len(msg), ErrRdata
|
||||||
|
}
|
||||||
|
|
||||||
if wasDot {
|
if wasDot {
|
||||||
// two dots back to back is not legal
|
// two dots back to back is not legal
|
||||||
return len(msg), ErrRdata
|
return len(msg), ErrRdata
|
||||||
|
@ -624,11 +629,18 @@ func UnpackRRWithHeader(h RR_Header, msg []byte, off int) (rr RR, off1 int, err
|
||||||
rr = &RFC3597{Hdr: h}
|
rr = &RFC3597{Hdr: h}
|
||||||
}
|
}
|
||||||
|
|
||||||
if noRdata(h) {
|
if off < 0 || off > len(msg) {
|
||||||
return rr, off, nil
|
return &h, off, &Error{err: "bad off"}
|
||||||
}
|
}
|
||||||
|
|
||||||
end := off + int(h.Rdlength)
|
end := off + int(h.Rdlength)
|
||||||
|
if end < off || end > len(msg) {
|
||||||
|
return &h, end, &Error{err: "bad rdlength"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if noRdata(h) {
|
||||||
|
return rr, off, nil
|
||||||
|
}
|
||||||
|
|
||||||
off, err = rr.unpack(msg, off)
|
off, err = rr.unpack(msg, off)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -668,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:
|
// 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 {
|
func (h *MsgHdr) String() string {
|
||||||
if h == nil {
|
if h == nil {
|
||||||
return "<nil> MsgHdr"
|
return "<nil> MsgHdr"
|
||||||
|
@ -735,7 +747,7 @@ func (dns *Msg) packBufferWithCompressionMap(buf []byte, compression compression
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set extended rcode unconditionally if we have an opt, this will allow
|
// Set extended rcode unconditionally if we have an opt, this will allow
|
||||||
// reseting the extended rcode bits if they need to.
|
// resetting the extended rcode bits if they need to.
|
||||||
if opt := dns.IsEdns0(); opt != nil {
|
if opt := dns.IsEdns0(); opt != nil {
|
||||||
opt.SetExtendedRcode(uint16(dns.Rcode))
|
opt.SetExtendedRcode(uint16(dns.Rcode))
|
||||||
} else if dns.Rcode > 0xF {
|
} else if dns.Rcode > 0xF {
|
||||||
|
@ -894,6 +906,11 @@ func (dns *Msg) String() string {
|
||||||
s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", "
|
s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", "
|
||||||
s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", "
|
s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", "
|
||||||
s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
|
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 {
|
if len(dns.Question) > 0 {
|
||||||
s += "\n;; QUESTION SECTION:\n"
|
s += "\n;; QUESTION SECTION:\n"
|
||||||
for _, r := range dns.Question {
|
for _, r := range dns.Question {
|
||||||
|
@ -916,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"
|
s += "\n;; ADDITIONAL SECTION:\n"
|
||||||
for _, r := range dns.Extra {
|
for _, r := range dns.Extra {
|
||||||
if r != nil {
|
if r != nil && r.Header().Rrtype != TypeOPT {
|
||||||
s += r.String() + "\n"
|
s += r.String() + "\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//+build ignore
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
// msg_generate.go is meant to run with go generate. It will use
|
// 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
|
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||||
|
|
|
@ -438,35 +438,6 @@ Option:
|
||||||
return edns, off, nil
|
return edns, off, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeDataOpt(code uint16) EDNS0 {
|
|
||||||
switch code {
|
|
||||||
case EDNS0NSID:
|
|
||||||
return new(EDNS0_NSID)
|
|
||||||
case EDNS0SUBNET:
|
|
||||||
return new(EDNS0_SUBNET)
|
|
||||||
case EDNS0COOKIE:
|
|
||||||
return new(EDNS0_COOKIE)
|
|
||||||
case EDNS0EXPIRE:
|
|
||||||
return new(EDNS0_EXPIRE)
|
|
||||||
case EDNS0UL:
|
|
||||||
return new(EDNS0_UL)
|
|
||||||
case EDNS0LLQ:
|
|
||||||
return new(EDNS0_LLQ)
|
|
||||||
case EDNS0DAU:
|
|
||||||
return new(EDNS0_DAU)
|
|
||||||
case EDNS0DHU:
|
|
||||||
return new(EDNS0_DHU)
|
|
||||||
case EDNS0N3U:
|
|
||||||
return new(EDNS0_N3U)
|
|
||||||
case EDNS0PADDING:
|
|
||||||
return new(EDNS0_PADDING)
|
|
||||||
default:
|
|
||||||
e := new(EDNS0_LOCAL)
|
|
||||||
e.Code = code
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func packDataOpt(options []EDNS0, msg []byte, off int) (int, error) {
|
func packDataOpt(options []EDNS0, msg []byte, off int) (int, error) {
|
||||||
for _, el := range options {
|
for _, el := range options {
|
||||||
b, err := el.pack()
|
b, err := el.pack()
|
||||||
|
@ -505,7 +476,7 @@ func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
|
||||||
length, window, lastwindow := 0, 0, -1
|
length, window, lastwindow := 0, 0, -1
|
||||||
for off < len(msg) {
|
for off < len(msg) {
|
||||||
if off+2 > 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])
|
window = int(msg[off])
|
||||||
length = int(msg[off+1])
|
length = int(msg[off+1])
|
||||||
|
@ -513,17 +484,17 @@ func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
|
||||||
if window <= lastwindow {
|
if window <= lastwindow {
|
||||||
// RFC 4034: Blocks are present in the NSEC RR RDATA in
|
// RFC 4034: Blocks are present in the NSEC RR RDATA in
|
||||||
// increasing numerical order.
|
// 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 {
|
if length == 0 {
|
||||||
// RFC 4034: Blocks with no types present MUST NOT be included.
|
// 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 {
|
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) {
|
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
|
// Walk the bytes in the window and extract the type bits
|
||||||
|
@ -587,6 +558,16 @@ func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) {
|
||||||
if len(bitmap) == 0 {
|
if len(bitmap) == 0 {
|
||||||
return off, nil
|
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
|
var lastwindow, lastlength uint16
|
||||||
for _, t := range bitmap {
|
for _, t := range bitmap {
|
||||||
window := t / 256
|
window := t / 256
|
||||||
|
@ -810,6 +791,8 @@ func unpackDataAplPrefix(msg []byte, off int) (APLPrefix, int, error) {
|
||||||
if off+afdlen > len(msg) {
|
if off+afdlen > len(msg) {
|
||||||
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL address"}
|
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])
|
off += copy(ip, msg[off:off+afdlen])
|
||||||
if afdlen > 0 {
|
if afdlen > 0 {
|
||||||
last := ip[afdlen-1]
|
last := ip[afdlen-1]
|
||||||
|
@ -821,10 +804,6 @@ func unpackDataAplPrefix(msg []byte, off int) (APLPrefix, int, error) {
|
||||||
IP: ip,
|
IP: ip,
|
||||||
Mask: net.CIDRMask(int(prefix), 8*len(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{
|
return APLPrefix{
|
||||||
Negation: (nlen & 0x80) != 0,
|
Negation: (nlen & 0x80) != 0,
|
||||||
|
|
|
@ -19,7 +19,8 @@ func TestPackDataNsec(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want int
|
wantOff int
|
||||||
|
wantBytes []byte
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantErrMsg string
|
wantErrMsg string
|
||||||
}{
|
}{
|
||||||
|
@ -44,14 +45,14 @@ func TestPackDataNsec(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
wantErrMsg: "dns: overflow packing nsec",
|
wantErrMsg: "dns: overflow packing nsec",
|
||||||
want: 31,
|
wantOff: 48,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "disordered nsec bits",
|
name: "disordered nsec bits",
|
||||||
args: args{
|
args: args{
|
||||||
bitmap: []uint16{
|
bitmap: []uint16{
|
||||||
8962,
|
8962,
|
||||||
0,
|
1,
|
||||||
},
|
},
|
||||||
msg: []byte{
|
msg: []byte{
|
||||||
48, 48, 48, 48, 0, 0, 0, 1, 0, 0, 0, 0,
|
48, 48, 48, 48, 0, 0, 0, 1, 0, 0, 0, 0,
|
||||||
|
@ -72,13 +73,13 @@ func TestPackDataNsec(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
wantErrMsg: "dns: nsec bits out of order",
|
wantErrMsg: "dns: nsec bits out of order",
|
||||||
want: 155,
|
wantOff: 155,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "simple message with only one window",
|
name: "simple message with only one window",
|
||||||
args: args{
|
args: args{
|
||||||
bitmap: []uint16{
|
bitmap: []uint16{
|
||||||
0,
|
1,
|
||||||
},
|
},
|
||||||
msg: []byte{
|
msg: []byte{
|
||||||
48, 48, 48, 48, 0, 0,
|
48, 48, 48, 48, 0, 0,
|
||||||
|
@ -89,13 +90,33 @@ func TestPackDataNsec(t *testing.T) {
|
||||||
},
|
},
|
||||||
off: 0,
|
off: 0,
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
want: 3,
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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 {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("packDataNsec() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("packDataNsec() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
@ -104,13 +125,49 @@ func TestPackDataNsec(t *testing.T) {
|
||||||
t.Errorf("packDataNsec() error msg = %v, wantErrMsg %v", err.Error(), tt.wantErrMsg)
|
t.Errorf("packDataNsec() error msg = %v, wantErrMsg %v", err.Error(), tt.wantErrMsg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if got != tt.want {
|
if gotOff != tt.wantOff {
|
||||||
t.Errorf("packDataNsec() = %v, want %v", got, tt.want)
|
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) {
|
func TestUnpackString(t *testing.T) {
|
||||||
msg := []byte("\x00abcdef\x0f\\\"ghi\x04mmm\x7f")
|
msg := []byte("\x00abcdef\x0f\\\"ghi\x04mmm\x7f")
|
||||||
msg[0] = byte(len(msg) - 1)
|
msg[0] = byte(len(msg) - 1)
|
||||||
|
@ -296,14 +353,14 @@ func TestPackDataAplPrefix_BufferBounds(t *testing.T) {
|
||||||
|
|
||||||
func TestPackDataApl(t *testing.T) {
|
func TestPackDataApl(t *testing.T) {
|
||||||
in := []APLPrefix{
|
in := []APLPrefix{
|
||||||
APLPrefix{
|
{
|
||||||
Negation: true,
|
Negation: true,
|
||||||
Network: net.IPNet{
|
Network: net.IPNet{
|
||||||
IP: net.ParseIP("198.51.0.0").To4(),
|
IP: net.ParseIP("198.51.0.0").To4(),
|
||||||
Mask: net.CIDRMask(16, 32),
|
Mask: net.CIDRMask(16, 32),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
APLPrefix{
|
{
|
||||||
Negation: false,
|
Negation: false,
|
||||||
Network: net.IPNet{
|
Network: net.IPNet{
|
||||||
IP: net.ParseIP("2001:db8:beef::"),
|
IP: net.ParseIP("2001:db8:beef::"),
|
||||||
|
@ -391,34 +448,42 @@ func TestUnpackDataAplPrefix_Errors(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
wire []byte
|
wire []byte
|
||||||
|
want string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"incomplete header",
|
"incomplete header",
|
||||||
[]byte{0x00, 0x01, 0x18},
|
[]byte{0x00, 0x01, 0x18},
|
||||||
|
"dns: overflow unpacking APL prefix",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"unrecognized family",
|
"unrecognized family",
|
||||||
[]byte{0x00, 0x03, 0x00, 0x00},
|
[]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},
|
[]byte{0x00, 0x01, 0x21, 0x04, 192, 0, 2, 0},
|
||||||
|
"dns: APL prefix too long",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"address with extra byte",
|
"prefix too large for family IPv6",
|
||||||
[]byte{0x00, 0x01, 0x10, 0x03, 192, 0, 2},
|
[]byte{0x00, 0x02, 0x81, 0x85, 0x20, 0x01, 0x0d, 0xb8, 0x80},
|
||||||
|
"dns: APL prefix too long",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"incomplete buffer",
|
"afdlen too long for address family IPv4",
|
||||||
[]byte{0x00, 0x01, 0x10, 0x02, 192},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"extra bits set",
|
|
||||||
[]byte{0x00, 0x01, 22, 0x03, 192, 0, 2},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"afdlen invalid",
|
|
||||||
[]byte{0x00, 0x01, 22, 0x05, 192, 0, 2, 0, 0},
|
[]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 {
|
for _, tt := range tests {
|
||||||
|
@ -427,6 +492,10 @@ func TestUnpackDataAplPrefix_Errors(t *testing.T) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error, got none")
|
t.Fatal("expected error, got none")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err.Error() != tt.want {
|
||||||
|
t.Errorf("expected %s, got %s", tt.want, err.Error())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,11 @@ package dns
|
||||||
// record adding as many records as possible without exceeding the
|
// record adding as many records as possible without exceeding the
|
||||||
// requested buffer size.
|
// requested buffer size.
|
||||||
//
|
//
|
||||||
|
// If the message fits within the requested size without compression,
|
||||||
|
// Truncate will set the message's Compress attribute to false. It is
|
||||||
|
// the caller's responsibility to set it back to true if they wish to
|
||||||
|
// compress the payload regardless of size.
|
||||||
|
//
|
||||||
// The TC bit will be set if any records were excluded from the message.
|
// The TC bit will be set if any records were excluded from the message.
|
||||||
// If the TC bit is already set on the message it will be retained.
|
// If the TC bit is already set on the message it will be retained.
|
||||||
// TC indicates that the client should retry over TCP.
|
// TC indicates that the client should retry over TCP.
|
||||||
|
|
|
@ -18,7 +18,7 @@ func TestRequestTruncateAnswer(t *testing.T) {
|
||||||
|
|
||||||
reply.Truncate(MinMsgSize)
|
reply.Truncate(MinMsgSize)
|
||||||
if want, got := MinMsgSize, reply.Len(); want < got {
|
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 {
|
if !reply.Truncated {
|
||||||
t.Errorf("truncated bit should be set")
|
t.Errorf("truncated bit should be set")
|
||||||
|
@ -38,7 +38,7 @@ func TestRequestTruncateExtra(t *testing.T) {
|
||||||
|
|
||||||
reply.Truncate(MinMsgSize)
|
reply.Truncate(MinMsgSize)
|
||||||
if want, got := MinMsgSize, reply.Len(); want < got {
|
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 {
|
if !reply.Truncated {
|
||||||
t.Errorf("truncated bit should be set")
|
t.Errorf("truncated bit should be set")
|
||||||
|
@ -62,7 +62,7 @@ func TestRequestTruncateExtraEdns0(t *testing.T) {
|
||||||
|
|
||||||
reply.Truncate(size)
|
reply.Truncate(size)
|
||||||
if want, got := size, reply.Len(); want < got {
|
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 {
|
if !reply.Truncated {
|
||||||
t.Errorf("truncated bit should be set")
|
t.Errorf("truncated bit should be set")
|
||||||
|
@ -94,7 +94,7 @@ func TestRequestTruncateExtraRegression(t *testing.T) {
|
||||||
|
|
||||||
reply.Truncate(size)
|
reply.Truncate(size)
|
||||||
if want, got := size, reply.Len(); want < got {
|
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 {
|
if !reply.Truncated {
|
||||||
t.Errorf("truncated bit should be set")
|
t.Errorf("truncated bit should be set")
|
||||||
|
@ -130,7 +130,7 @@ func TestTruncation(t *testing.T) {
|
||||||
|
|
||||||
copy.Truncate(bufsize)
|
copy.Truncate(bufsize)
|
||||||
if want, got := bufsize, copy.Len(); want < got {
|
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)
|
reply.Truncate(size)
|
||||||
if want, got := size, reply.Len(); want < got {
|
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 {
|
if expected := 52; len(reply.Answer) != expected {
|
||||||
t.Errorf("wrong number of answers; expected %d, got %d", expected, len(reply.Answer))
|
t.Errorf("wrong number of answers; expected %d, got %d", expected, len(reply.Answer))
|
||||||
|
|
169
parse_test.go
169
parse_test.go
|
@ -245,8 +245,7 @@ func testTXTRRQuick(t *testing.T) {
|
||||||
rrbytes := make([]byte, 0, len(owner)+2+2+4+2+len(rdata))
|
rrbytes := make([]byte, 0, len(owner)+2+2+4+2+len(rdata))
|
||||||
rrbytes = append(rrbytes, owner...)
|
rrbytes = append(rrbytes, owner...)
|
||||||
rrbytes = append(rrbytes, typeAndClass...)
|
rrbytes = append(rrbytes, typeAndClass...)
|
||||||
rrbytes = append(rrbytes, byte(len(rdata)>>8))
|
rrbytes = append(rrbytes, byte(len(rdata)>>8), byte(len(rdata)))
|
||||||
rrbytes = append(rrbytes, byte(len(rdata)))
|
|
||||||
rrbytes = append(rrbytes, rdata...)
|
rrbytes = append(rrbytes, rdata...)
|
||||||
rr, _, err := UnpackRR(rrbytes, 0)
|
rr, _, err := UnpackRR(rrbytes, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -267,7 +266,7 @@ func testTXTRRQuick(t *testing.T) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(rdata) == 0 {
|
if len(rdata) == 0 {
|
||||||
// string'ing won't produce any data to parse
|
// stringifying won't produce any data to parse
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
rrString := rr.String()
|
rrString := rr.String()
|
||||||
|
@ -374,11 +373,13 @@ func TestNSEC(t *testing.T) {
|
||||||
func TestParseLOC(t *testing.T) {
|
func TestParseLOC(t *testing.T) {
|
||||||
lt := map[string]string{
|
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 30 12.748 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 30 12.748 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m",
|
||||||
"SW1A2AA.find.me.uk. LOC 51 0 0.0 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 00 0.000 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m",
|
"SW1A2AA.find.me.uk. LOC 51 0 0.0 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 00 0.000 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m",
|
||||||
"SW1A2AA.find.me.uk. LOC 51 30 12.748 N 00 07 39.611 W 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 30 12.748 N 00 07 39.611 W 0m 1m 10000m 10m",
|
"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
|
// 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",
|
"SW1A2AA.find.me.uk. LOC 89 59 59.999 N 179 59 59.999 W -100000 90000000.00m 90000000.00m 90000000m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t89 59 59.999 N 179 59 59.999 W -100000m 90000000m 90000000m 90000000m",
|
||||||
|
// use float64 to have enough precision.
|
||||||
|
"example.com. LOC 42 21 43.952 N 71 5 6.344 W -24m 1m 200m 10m": "example.com.\t3600\tIN\tLOC\t42 21 43.952 N 71 05 6.344 W -24m 1m 200m 10m",
|
||||||
}
|
}
|
||||||
for i, o := range lt {
|
for i, o := range lt {
|
||||||
rr, err := NewRR(i)
|
rr, err := NewRR(i)
|
||||||
|
@ -531,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",
|
"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
|
// 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. 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",
|
"t.example.com. CLASS255 A 127.0.0.1": "t.example.com. 3600 CLASS255 A 127.0.0.1",
|
||||||
}
|
}
|
||||||
for i, o := range tests {
|
for i, o := range tests {
|
||||||
|
@ -1208,8 +1209,8 @@ func TestTypeXXXX(t *testing.T) {
|
||||||
t.Errorf("this should not work, for TYPE655341")
|
t.Errorf("this should not work, for TYPE655341")
|
||||||
}
|
}
|
||||||
_, err = NewRR("example.com IN TYPE1 \\# 4 0a000001")
|
_, err = NewRR("example.com IN TYPE1 \\# 4 0a000001")
|
||||||
if err == nil {
|
if err != nil {
|
||||||
t.Errorf("this should not work")
|
t.Errorf("failed to parse TYPE1 RR: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1514,10 +1515,10 @@ func TestParseSSHFP(t *testing.T) {
|
||||||
|
|
||||||
func TestParseHINFO(t *testing.T) {
|
func TestParseHINFO(t *testing.T) {
|
||||||
dt := map[string]string{
|
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\"": "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 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\"",
|
// "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
|
// 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
|
// but effectively, even Bind would replace it to correctly formed text when you AXFR
|
||||||
|
@ -1537,9 +1538,9 @@ func TestParseHINFO(t *testing.T) {
|
||||||
|
|
||||||
func TestParseCAA(t *testing.T) {
|
func TestParseCAA(t *testing.T) {
|
||||||
lt := map[string]string{
|
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 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. 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\"",
|
"example.net. TYPE257 0 issue \"symantec.com\"": "example.net.\t3600\tIN\tCAA\t0 issue \"symantec.com\"",
|
||||||
}
|
}
|
||||||
|
@ -1635,9 +1636,25 @@ func TestParseCSYNC(t *testing.T) {
|
||||||
|
|
||||||
func TestParseSVCB(t *testing.T) {
|
func TestParseSVCB(t *testing.T) {
|
||||||
svcbs := map[string]string{
|
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. 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="\ "`,
|
`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 {
|
for s, o := range svcbs {
|
||||||
rr, err := NewRR(s)
|
rr, err := NewRR(s)
|
||||||
|
@ -1654,7 +1671,6 @@ func TestParseSVCB(t *testing.T) {
|
||||||
func TestParseBadSVCB(t *testing.T) {
|
func TestParseBadSVCB(t *testing.T) {
|
||||||
header := `example.com. 3600 IN HTTPS `
|
header := `example.com. 3600 IN HTTPS `
|
||||||
evils := []string{
|
evils := []string{
|
||||||
`0 . no-default-alpn`, // aliasform
|
|
||||||
`65536 . no-default-alpn`, // bad priority
|
`65536 . no-default-alpn`, // bad priority
|
||||||
`1 ..`, // bad domain
|
`1 ..`, // bad domain
|
||||||
`1 . no-default-alpn=1`, // value illegal
|
`1 . no-default-alpn=1`, // value illegal
|
||||||
|
@ -1672,21 +1688,27 @@ func TestParseBadSVCB(t *testing.T) {
|
||||||
`1 . key065534`, // key can't be padded
|
`1 . key065534`, // key can't be padded
|
||||||
`1 . key65534="f`, // unterminated value
|
`1 . key65534="f`, // unterminated value
|
||||||
`1 . key65534="`, // unterminated value
|
`1 . key65534="`, // unterminated value
|
||||||
`1 . key65534=\2`, // invalid numberic escape
|
`1 . key65534=\2`, // invalid numeric escape
|
||||||
`1 . key65534=\24`, // invalid numberic escape
|
`1 . key65534=\24`, // invalid numeric escape
|
||||||
`1 . key65534=\256`, // invalid numberic escape
|
`1 . key65534=\256`, // invalid numeric escape
|
||||||
`1 . key65534=\`, // invalid numberic escape
|
`1 . key65534=\`, // invalid numeric escape
|
||||||
`1 . key65534=""alpn`, // zQuote ending needs whitespace
|
`1 . key65534=""alpn`, // zQuote ending needs whitespace
|
||||||
`1 . key65534="a"alpn`, // zQuote ending needs whitespace
|
`1 . key65534="a"alpn`, // zQuote ending needs whitespace
|
||||||
`1 . ipv6hint=1.1.1.1`, // not ipv6
|
`1 . ipv6hint=1.1.1.1`, // not ipv6
|
||||||
`1 . ipv6hint=1:1:1:1`, // not ipv6
|
`1 . ipv6hint=1:1:1:1`, // not ipv6
|
||||||
`1 . ipv6hint=a`, // not ipv6
|
`1 . ipv6hint=a`, // not ipv6
|
||||||
|
`1 . ipv6hint=`, // empty ipv6
|
||||||
`1 . ipv4hint=1.1.1.1.1`, // not ipv4
|
`1 . ipv4hint=1.1.1.1.1`, // not ipv4
|
||||||
`1 . ipv4hint=::fc`, // not ipv4
|
`1 . ipv4hint=::fc`, // not ipv4
|
||||||
`1 . ipv4hint=..11`, // not ipv4
|
`1 . ipv4hint=..11`, // not ipv4
|
||||||
`1 . ipv4hint=a`, // not ipv4
|
`1 . ipv4hint=a`, // not ipv4
|
||||||
|
`1 . ipv4hint=`, // empty ipv4
|
||||||
`1 . port=`, // empty port
|
`1 . port=`, // empty port
|
||||||
`1 . echconfig=YUd`, // bad base64
|
`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 {
|
for _, o := range evils {
|
||||||
_, err := NewRR(header + o)
|
_, err := NewRR(header + o)
|
||||||
|
@ -1884,3 +1906,112 @@ func TestParseAPLErrors(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnpackRRWithHeaderInvalidLengths(t *testing.T) {
|
||||||
|
rr, err := NewRR("test.example.org. 300 IN SSHFP 1 2 BC6533CDC95A79078A39A56EA7635984ED655318ADA9B6159E30723665DA95BB")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse SSHFP record: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, Len(rr))
|
||||||
|
headerEnd, end, err := packRR(rr, buf, 0, compressionMap{}, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to pack A record: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rr.Header().Rdlength = uint16(end - headerEnd)
|
||||||
|
for _, off := range []int{
|
||||||
|
-1,
|
||||||
|
end + 1,
|
||||||
|
1<<16 - 1,
|
||||||
|
} {
|
||||||
|
_, _, err := UnpackRRWithHeader(*rr.Header(), buf, off)
|
||||||
|
if de, ok := err.(*Error); !ok || de.err != "bad off" {
|
||||||
|
t.Errorf("UnpackRRWithHeader with bad offset (%d) returned wrong or no error: %v", off, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rdlength := range []uint16{
|
||||||
|
uint16(end - headerEnd + 1),
|
||||||
|
uint16(end),
|
||||||
|
1<<16 - 1,
|
||||||
|
} {
|
||||||
|
rr.Header().Rdlength = rdlength
|
||||||
|
|
||||||
|
_, _, err := UnpackRRWithHeader(*rr.Header(), buf, headerEnd)
|
||||||
|
if de, ok := err.(*Error); !ok || de.err != "bad rdlength" {
|
||||||
|
t.Errorf("UnpackRRWithHeader with bad rdlength (%d) returned wrong or no error: %v", rdlength, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseZONEMD(t *testing.T) {
|
||||||
|
// Uses examples from https://tools.ietf.org/html/rfc8976
|
||||||
|
dt := map[string]string{
|
||||||
|
// Simple Zone
|
||||||
|
`example. 86400 IN ZONEMD 2018031900 1 1 (
|
||||||
|
c68090d90a7aed71
|
||||||
|
6bc459f9340e3d7c
|
||||||
|
1370d4d24b7e2fc3
|
||||||
|
a1ddc0b9a87153b9
|
||||||
|
a9713b3c9ae5cc27
|
||||||
|
777f98b8e730044c )
|
||||||
|
`: "example.\t86400\tIN\tZONEMD\t2018031900 1 1 c68090d90a7aed716bc459f9340e3d7c1370d4d24b7e2fc3a1ddc0b9a87153b9a9713b3c9ae5cc27777f98b8e730044c",
|
||||||
|
// Complex Zone
|
||||||
|
`example. 86400 IN ZONEMD 2018031900 1 1 (
|
||||||
|
a3b69bad980a3504
|
||||||
|
e1cffcb0fd6397f9
|
||||||
|
3848071c93151f55
|
||||||
|
2ae2f6b1711d4bd2
|
||||||
|
d8b39808226d7b9d
|
||||||
|
b71e34b72077f8fe )
|
||||||
|
`: "example.\t86400\tIN\tZONEMD\t2018031900 1 1 a3b69bad980a3504e1cffcb0fd6397f93848071c93151f552ae2f6b1711d4bd2d8b39808226d7b9db71e34b72077f8fe",
|
||||||
|
// Multiple Digests Zone
|
||||||
|
`example. 86400 IN ZONEMD 2018031900 1 1 (
|
||||||
|
62e6cf51b02e54b9
|
||||||
|
b5f967d547ce4313
|
||||||
|
6792901f9f88e637
|
||||||
|
493daaf401c92c27
|
||||||
|
9dd10f0edb1c56f8
|
||||||
|
080211f8480ee306 )
|
||||||
|
`: "example.\t86400\tIN\tZONEMD\t2018031900 1 1 62e6cf51b02e54b9b5f967d547ce43136792901f9f88e637493daaf401c92c279dd10f0edb1c56f8080211f8480ee306",
|
||||||
|
`example. 86400 IN ZONEMD 2018031900 1 2 (
|
||||||
|
08cfa1115c7b948c
|
||||||
|
4163a901270395ea
|
||||||
|
226a930cd2cbcf2f
|
||||||
|
a9a5e6eb85f37c8a
|
||||||
|
4e114d884e66f176
|
||||||
|
eab121cb02db7d65
|
||||||
|
2e0cc4827e7a3204
|
||||||
|
f166b47e5613fd27 )
|
||||||
|
`: "example.\t86400\tIN\tZONEMD\t2018031900 1 2 08cfa1115c7b948c4163a901270395ea226a930cd2cbcf2fa9a5e6eb85f37c8a4e114d884e66f176eab121cb02db7d652e0cc4827e7a3204f166b47e5613fd27",
|
||||||
|
`example. 86400 IN ZONEMD 2018031900 1 240 (
|
||||||
|
e2d523f654b9422a
|
||||||
|
96c5a8f44607bbee )
|
||||||
|
`: "example. 86400 IN ZONEMD 2018031900 1 240 e2d523f654b9422a96c5a8f44607bbee",
|
||||||
|
`example. 86400 IN ZONEMD 2018031900 241 1 (
|
||||||
|
e1846540e33a9e41
|
||||||
|
89792d18d5d131f6
|
||||||
|
05fc283e )
|
||||||
|
`: "example. 86400 IN ZONEMD 2018031900 241 1 e1846540e33a9e4189792d18d5d131f605fc283e",
|
||||||
|
// URI.ARPA zone
|
||||||
|
`uri.arpa. 3600 IN ZONEMD 2018100702 1 1 (
|
||||||
|
0dbc3c4dbfd75777c12ca19c337854b1577799901307c482e9d91d5d15
|
||||||
|
cd934d16319d98e30c4201cf25a1d5a0254960 )`: "uri.arpa.\t3600\tIN\tZONEMD\t2018100702 1 1 0dbc3c4dbfd75777c12ca19c337854b1577799901307c482e9d91d5d15cd934d16319d98e30c4201cf25a1d5a0254960",
|
||||||
|
// ROOT-SERVERS.NET Zone
|
||||||
|
`root-servers.net. 3600000 IN ZONEMD 2018091100 1 1 (
|
||||||
|
f1ca0ccd91bd5573d9f431c00ee0101b2545c97602be0a97
|
||||||
|
8a3b11dbfc1c776d5b3e86ae3d973d6b5349ba7f04340f79 )
|
||||||
|
`: "root-servers.net.\t3600000\tIN\tZONEMD\t2018091100 1 1 f1ca0ccd91bd5573d9f431c00ee0101b2545c97602be0a978a3b11dbfc1c776d5b3e86ae3d973d6b5349ba7f04340f79",
|
||||||
|
}
|
||||||
|
for i, o := range dt {
|
||||||
|
rr, err := NewRR(i)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to parse RR: ", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rr.String() != o {
|
||||||
|
t.Errorf("`%s' should be equal to\n`%s', but is `%s'", i, o, rr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import "strings"
|
||||||
// RFC 6895. This allows one to experiment with new RR types, without requesting an
|
// RFC 6895. This allows one to experiment with new RR types, without requesting an
|
||||||
// official type code. Also see dns.PrivateHandle and dns.PrivateHandleRemove.
|
// official type code. Also see dns.PrivateHandle and dns.PrivateHandleRemove.
|
||||||
type PrivateRdata interface {
|
type PrivateRdata interface {
|
||||||
// String returns the text presentaton of the Rdata of the Private RR.
|
// String returns the text presentation of the Rdata of the Private RR.
|
||||||
String() string
|
String() string
|
||||||
// Parse parses the Rdata of the private RR.
|
// Parse parses the Rdata of the private RR.
|
||||||
Parse([]string) error
|
Parse([]string) error
|
||||||
|
@ -90,7 +90,7 @@ Fetch:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r1 *PrivateRR) isDuplicate(r2 RR) bool { return false }
|
func (r *PrivateRR) isDuplicate(r2 RR) bool { return false }
|
||||||
|
|
||||||
// PrivateHandle registers a private resource record type. It requires
|
// PrivateHandle registers a private resource record type. It requires
|
||||||
// string and numeric representation of private RR type and generator function as argument.
|
// string and numeric representation of private RR type and generator function as argument.
|
||||||
|
|
58
scan.go
58
scan.go
|
@ -150,6 +150,9 @@ func ReadRR(r io.Reader, file string) (RR, error) {
|
||||||
// The text "; this is comment" is returned from Comment. Comments inside
|
// The text "; this is comment" is returned from Comment. Comments inside
|
||||||
// the RR are returned concatenated along with the RR. Comments on a line
|
// the RR are returned concatenated along with the RR. Comments on a line
|
||||||
// by themselves are discarded.
|
// by themselves are discarded.
|
||||||
|
//
|
||||||
|
// Callers should not assume all returned data in an Resource Record is
|
||||||
|
// syntactically correct, e.g. illegal base64 in RRSIGs will be returned as-is.
|
||||||
type ZoneParser struct {
|
type ZoneParser struct {
|
||||||
c *zlexer
|
c *zlexer
|
||||||
|
|
||||||
|
@ -577,10 +580,23 @@ func (zp *ZoneParser) Next() (RR, bool) {
|
||||||
|
|
||||||
st = zExpectRdata
|
st = zExpectRdata
|
||||||
case zExpectRdata:
|
case zExpectRdata:
|
||||||
var rr RR
|
var (
|
||||||
if newFn, ok := TypeToRR[h.Rrtype]; ok && canParseAsRR(h.Rrtype) {
|
rr RR
|
||||||
|
parseAsRFC3597 bool
|
||||||
|
)
|
||||||
|
if newFn, ok := TypeToRR[h.Rrtype]; ok {
|
||||||
rr = newFn()
|
rr = newFn()
|
||||||
*rr.Header() = *h
|
*rr.Header() = *h
|
||||||
|
|
||||||
|
// We may be parsing a known RR type using the RFC3597 format.
|
||||||
|
// If so, we handle that here in a generic way.
|
||||||
|
//
|
||||||
|
// This is also true for PrivateRR types which will have the
|
||||||
|
// RFC3597 parsing done for them and the Unpack method called
|
||||||
|
// to populate the RR instead of simply deferring to Parse.
|
||||||
|
if zp.c.Peek().token == "\\#" {
|
||||||
|
parseAsRFC3597 = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
rr = &RFC3597{Hdr: *h}
|
rr = &RFC3597{Hdr: *h}
|
||||||
}
|
}
|
||||||
|
@ -600,13 +616,18 @@ func (zp *ZoneParser) Next() (RR, bool) {
|
||||||
return zp.setParseError("unexpected newline", l)
|
return zp.setParseError("unexpected newline", l)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rr.parse(zp.c, zp.origin); err != nil {
|
parseAsRR := rr
|
||||||
|
if parseAsRFC3597 {
|
||||||
|
parseAsRR = &RFC3597{Hdr: *h}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parseAsRR.parse(zp.c, zp.origin); err != nil {
|
||||||
// err is a concrete *ParseError without the file field set.
|
// err is a concrete *ParseError without the file field set.
|
||||||
// The setParseError call below will construct a new
|
// The setParseError call below will construct a new
|
||||||
// *ParseError with file set to zp.file.
|
// *ParseError with file set to zp.file.
|
||||||
|
|
||||||
// If err.lex is nil than we have encounter an unknown RR type
|
// err.lex may be nil in which case we substitute our current
|
||||||
// in that case we substitute our current lex token.
|
// lex token.
|
||||||
if err.lex == (lex{}) {
|
if err.lex == (lex{}) {
|
||||||
return zp.setParseError(err.err, l)
|
return zp.setParseError(err.err, l)
|
||||||
}
|
}
|
||||||
|
@ -614,6 +635,13 @@ func (zp *ZoneParser) Next() (RR, bool) {
|
||||||
return zp.setParseError(err.err, err.lex)
|
return zp.setParseError(err.err, err.lex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if parseAsRFC3597 {
|
||||||
|
err := parseAsRR.(*RFC3597).fromRFC3597(rr)
|
||||||
|
if err != nil {
|
||||||
|
return zp.setParseError(err.Error(), l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return rr, true
|
return rr, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -623,18 +651,6 @@ func (zp *ZoneParser) Next() (RR, bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// canParseAsRR returns true if the record type can be parsed as a
|
|
||||||
// concrete RR. It blacklists certain record types that must be parsed
|
|
||||||
// according to RFC 3597 because they lack a presentation format.
|
|
||||||
func canParseAsRR(rrtype uint16) bool {
|
|
||||||
switch rrtype {
|
|
||||||
case TypeANY, TypeNULL, TypeOPT, TypeTSIG:
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type zlexer struct {
|
type zlexer struct {
|
||||||
br io.ByteReader
|
br io.ByteReader
|
||||||
|
|
||||||
|
@ -1220,7 +1236,7 @@ func stringToCm(token string) (e, m uint8, ok bool) {
|
||||||
// 'nn.1' must be treated as 'nn-meters and 10cm, not 1cm.
|
// 'nn.1' must be treated as 'nn-meters and 10cm, not 1cm.
|
||||||
cmeters *= 10
|
cmeters *= 10
|
||||||
}
|
}
|
||||||
if len(s[0]) == 0 {
|
if s[0] == "" {
|
||||||
// This will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm).
|
// This will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm).
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -1290,7 +1306,7 @@ func appendOrigin(name, origin string) string {
|
||||||
|
|
||||||
// LOC record helper function
|
// LOC record helper function
|
||||||
func locCheckNorth(token string, latitude uint32) (uint32, bool) {
|
func locCheckNorth(token string, latitude uint32) (uint32, bool) {
|
||||||
if latitude > 90 * 1000 * 60 * 60 {
|
if latitude > 90*1000*60*60 {
|
||||||
return latitude, false
|
return latitude, false
|
||||||
}
|
}
|
||||||
switch token {
|
switch token {
|
||||||
|
@ -1304,7 +1320,7 @@ func locCheckNorth(token string, latitude uint32) (uint32, bool) {
|
||||||
|
|
||||||
// LOC record helper function
|
// LOC record helper function
|
||||||
func locCheckEast(token string, longitude uint32) (uint32, bool) {
|
func locCheckEast(token string, longitude uint32) (uint32, bool) {
|
||||||
if longitude > 180 * 1000 * 60 * 60 {
|
if longitude > 180*1000*60*60 {
|
||||||
return longitude, false
|
return longitude, false
|
||||||
}
|
}
|
||||||
switch token {
|
switch token {
|
||||||
|
@ -1339,7 +1355,7 @@ func stringToNodeID(l lex) (uint64, *ParseError) {
|
||||||
if len(l.token) < 19 {
|
if len(l.token) < 19 {
|
||||||
return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
|
return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
|
||||||
}
|
}
|
||||||
// There must be three colons at fixes postitions, if not its a parse error
|
// There must be three colons at fixes positions, if not its a parse error
|
||||||
if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' {
|
if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' {
|
||||||
return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
|
return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
|
||||||
}
|
}
|
||||||
|
|
56
scan_rr.go
56
scan_rr.go
|
@ -609,7 +609,7 @@ func (rr *LOC) parse(c *zlexer, o string) *ParseError {
|
||||||
|
|
||||||
c.Next() // zBlank
|
c.Next() // zBlank
|
||||||
l, _ = c.Next()
|
l, _ = c.Next()
|
||||||
if i, err := strconv.ParseFloat(l.token, 32); err != nil || l.err || i < 0 || i >= 60 {
|
if i, err := strconv.ParseFloat(l.token, 64); err != nil || l.err || i < 0 || i >= 60 {
|
||||||
return &ParseError{"", "bad LOC Latitude seconds", l}
|
return &ParseError{"", "bad LOC Latitude seconds", l}
|
||||||
} else {
|
} else {
|
||||||
rr.Latitude += uint32(1000 * i)
|
rr.Latitude += uint32(1000 * i)
|
||||||
|
@ -645,7 +645,7 @@ East:
|
||||||
}
|
}
|
||||||
c.Next() // zBlank
|
c.Next() // zBlank
|
||||||
l, _ = c.Next()
|
l, _ = c.Next()
|
||||||
if i, err := strconv.ParseFloat(l.token, 32); err != nil || l.err || i < 0 || i >= 60 {
|
if i, err := strconv.ParseFloat(l.token, 64); err != nil || l.err || i < 0 || i >= 60 {
|
||||||
return &ParseError{"", "bad LOC Longitude seconds", l}
|
return &ParseError{"", "bad LOC Longitude seconds", l}
|
||||||
} else {
|
} else {
|
||||||
rr.Longitude += uint32(1000 * i)
|
rr.Longitude += uint32(1000 * i)
|
||||||
|
@ -662,7 +662,7 @@ East:
|
||||||
Altitude:
|
Altitude:
|
||||||
c.Next() // zBlank
|
c.Next() // zBlank
|
||||||
l, _ = c.Next()
|
l, _ = c.Next()
|
||||||
if len(l.token) == 0 || l.err {
|
if l.token == "" || l.err {
|
||||||
return &ParseError{"", "bad LOC Altitude", l}
|
return &ParseError{"", "bad LOC Altitude", l}
|
||||||
}
|
}
|
||||||
if l.token[len(l.token)-1] == 'M' || l.token[len(l.token)-1] == 'm' {
|
if l.token[len(l.token)-1] == 'M' || l.token[len(l.token)-1] == 'm' {
|
||||||
|
@ -722,7 +722,7 @@ func (rr *HIP) parse(c *zlexer, o string) *ParseError {
|
||||||
|
|
||||||
c.Next() // zBlank
|
c.Next() // zBlank
|
||||||
l, _ = c.Next() // zString
|
l, _ = c.Next() // zString
|
||||||
if len(l.token) == 0 || l.err {
|
if l.token == "" || l.err {
|
||||||
return &ParseError{"", "bad HIP Hit", l}
|
return &ParseError{"", "bad HIP Hit", l}
|
||||||
}
|
}
|
||||||
rr.Hit = l.token // This can not contain spaces, see RFC 5205 Section 6.
|
rr.Hit = l.token // This can not contain spaces, see RFC 5205 Section 6.
|
||||||
|
@ -730,11 +730,15 @@ func (rr *HIP) parse(c *zlexer, o string) *ParseError {
|
||||||
|
|
||||||
c.Next() // zBlank
|
c.Next() // zBlank
|
||||||
l, _ = c.Next() // zString
|
l, _ = c.Next() // zString
|
||||||
if len(l.token) == 0 || l.err {
|
if l.token == "" || l.err {
|
||||||
return &ParseError{"", "bad HIP PublicKey", l}
|
return &ParseError{"", "bad HIP PublicKey", l}
|
||||||
}
|
}
|
||||||
rr.PublicKey = l.token // This cannot contain spaces
|
rr.PublicKey = l.token // This cannot contain spaces
|
||||||
rr.PublicKeyLength = uint16(base64.StdEncoding.DecodedLen(len(rr.PublicKey)))
|
decodedPK, decodedPKerr := base64.StdEncoding.DecodeString(rr.PublicKey)
|
||||||
|
if decodedPKerr != nil {
|
||||||
|
return &ParseError{"", "bad HIP PublicKey", l}
|
||||||
|
}
|
||||||
|
rr.PublicKeyLength = uint16(len(decodedPK))
|
||||||
|
|
||||||
// RendezvousServers (if any)
|
// RendezvousServers (if any)
|
||||||
l, _ = c.Next()
|
l, _ = c.Next()
|
||||||
|
@ -846,6 +850,38 @@ func (rr *CSYNC) parse(c *zlexer, o string) *ParseError {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rr *ZONEMD) parse(c *zlexer, o string) *ParseError {
|
||||||
|
l, _ := c.Next()
|
||||||
|
i, e := strconv.ParseUint(l.token, 10, 32)
|
||||||
|
if e != nil || l.err {
|
||||||
|
return &ParseError{"", "bad ZONEMD Serial", l}
|
||||||
|
}
|
||||||
|
rr.Serial = uint32(i)
|
||||||
|
|
||||||
|
c.Next() // zBlank
|
||||||
|
l, _ = c.Next()
|
||||||
|
i, e1 := strconv.ParseUint(l.token, 10, 8)
|
||||||
|
if e1 != nil || l.err {
|
||||||
|
return &ParseError{"", "bad ZONEMD Scheme", l}
|
||||||
|
}
|
||||||
|
rr.Scheme = uint8(i)
|
||||||
|
|
||||||
|
c.Next() // zBlank
|
||||||
|
l, _ = c.Next()
|
||||||
|
i, err := strconv.ParseUint(l.token, 10, 8)
|
||||||
|
if err != nil || l.err {
|
||||||
|
return &ParseError{"", "bad ZONEMD Hash Algorithm", l}
|
||||||
|
}
|
||||||
|
rr.Hash = uint8(i)
|
||||||
|
|
||||||
|
s, e2 := endingToString(c, "bad ZONEMD Digest")
|
||||||
|
if e2 != nil {
|
||||||
|
return e2
|
||||||
|
}
|
||||||
|
rr.Digest = s
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (rr *SIG) parse(c *zlexer, o string) *ParseError { return rr.RRSIG.parse(c, o) }
|
func (rr *SIG) parse(c *zlexer, o string) *ParseError { return rr.RRSIG.parse(c, o) }
|
||||||
|
|
||||||
func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
|
func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
|
||||||
|
@ -997,7 +1033,7 @@ func (rr *NSEC3) parse(c *zlexer, o string) *ParseError {
|
||||||
rr.Iterations = uint16(i)
|
rr.Iterations = uint16(i)
|
||||||
c.Next()
|
c.Next()
|
||||||
l, _ = c.Next()
|
l, _ = c.Next()
|
||||||
if len(l.token) == 0 || l.err {
|
if l.token == "" || l.err {
|
||||||
return &ParseError{"", "bad NSEC3 Salt", l}
|
return &ParseError{"", "bad NSEC3 Salt", l}
|
||||||
}
|
}
|
||||||
if l.token != "-" {
|
if l.token != "-" {
|
||||||
|
@ -1007,7 +1043,7 @@ func (rr *NSEC3) parse(c *zlexer, o string) *ParseError {
|
||||||
|
|
||||||
c.Next()
|
c.Next()
|
||||||
l, _ = c.Next()
|
l, _ = c.Next()
|
||||||
if len(l.token) == 0 || l.err {
|
if l.token == "" || l.err {
|
||||||
return &ParseError{"", "bad NSEC3 NextDomain", l}
|
return &ParseError{"", "bad NSEC3 NextDomain", l}
|
||||||
}
|
}
|
||||||
rr.HashLength = 20 // Fix for NSEC3 (sha1 160 bits)
|
rr.HashLength = 20 // Fix for NSEC3 (sha1 160 bits)
|
||||||
|
@ -1387,7 +1423,7 @@ func (rr *RFC3597) parse(c *zlexer, o string) *ParseError {
|
||||||
|
|
||||||
c.Next() // zBlank
|
c.Next() // zBlank
|
||||||
l, _ = c.Next()
|
l, _ = c.Next()
|
||||||
rdlength, e := strconv.Atoi(l.token)
|
rdlength, e := strconv.ParseUint(l.token, 10, 16)
|
||||||
if e != nil || l.err {
|
if e != nil || l.err {
|
||||||
return &ParseError{"", "bad RFC3597 Rdata ", l}
|
return &ParseError{"", "bad RFC3597 Rdata ", l}
|
||||||
}
|
}
|
||||||
|
@ -1396,7 +1432,7 @@ func (rr *RFC3597) parse(c *zlexer, o string) *ParseError {
|
||||||
if e1 != nil {
|
if e1 != nil {
|
||||||
return e1
|
return e1
|
||||||
}
|
}
|
||||||
if rdlength*2 != len(s) {
|
if int(rdlength)*2 != len(s) {
|
||||||
return &ParseError{"", "bad RFC3597 Rdata", l}
|
return &ParseError{"", "bad RFC3597 Rdata", l}
|
||||||
}
|
}
|
||||||
rr.Rdata = s
|
rr.Rdata = s
|
||||||
|
|
63
scan_test.go
63
scan_test.go
|
@ -145,10 +145,10 @@ func TestZoneParserAddressAAAA(t *testing.T) {
|
||||||
}
|
}
|
||||||
aaaa, ok := got.(*AAAA)
|
aaaa, ok := got.(*AAAA)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("expected *AAAA RR, but got %T", aaaa)
|
t.Fatalf("expected *AAAA RR, but got %T", got)
|
||||||
}
|
}
|
||||||
if g, w := aaaa.AAAA, tc.want.AAAA; !g.Equal(w) {
|
if !aaaa.AAAA.Equal(tc.want.AAAA) {
|
||||||
t.Fatalf("expected AAAA with IP %v, but got %v", g, w)
|
t.Fatalf("expected AAAA with IP %v, but got %v", tc.want.AAAA, aaaa.AAAA)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -229,6 +229,63 @@ example.com. 60 PX (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseRFC3597InvalidLength(t *testing.T) {
|
||||||
|
// We need to space separate the 00s otherwise it will exceed the maximum token size
|
||||||
|
// of the zone lexer.
|
||||||
|
_, err := NewRR("example. 3600 CLASS1 TYPE1 \\# 65536 " + strings.Repeat("00 ", 65536))
|
||||||
|
if err == nil {
|
||||||
|
t.Error("should not have parsed excessively long RFC3579 record")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseKnownRRAsRFC3597(t *testing.T) {
|
||||||
|
t.Run("with RDATA", func(t *testing.T) {
|
||||||
|
// This was found by oss-fuzz.
|
||||||
|
_, err := NewRR("example. 3600 tYpe44 \\# 03 75 0100")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to parse RFC3579 format: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rr, err := NewRR("example. 3600 CLASS1 TYPE1 \\# 4 7f000001")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse RFC3579 format: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rr.Header().Rrtype != TypeA {
|
||||||
|
t.Errorf("expected TypeA (1) Rrtype, but got %v", rr.Header().Rrtype)
|
||||||
|
}
|
||||||
|
|
||||||
|
a, ok := rr.(*A)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected *A RR, but got %T", rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
localhost := net.IPv4(127, 0, 0, 1)
|
||||||
|
if !a.A.Equal(localhost) {
|
||||||
|
t.Errorf("expected A with IP %v, but got %v", localhost, a.A)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("without RDATA", func(t *testing.T) {
|
||||||
|
rr, err := NewRR("example. 3600 CLASS1 TYPE1 \\# 0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse RFC3579 format: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rr.Header().Rrtype != TypeA {
|
||||||
|
t.Errorf("expected TypeA (1) Rrtype, but got %v", rr.Header().Rrtype)
|
||||||
|
}
|
||||||
|
|
||||||
|
a, ok := rr.(*A)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected *A RR, but got %T", rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a.A) != 0 {
|
||||||
|
t.Errorf("expected A with empty IP, but got %v", a.A)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkNewRR(b *testing.B) {
|
func BenchmarkNewRR(b *testing.B) {
|
||||||
const name1 = "12345678901234567890123456789012345.12345678.123."
|
const name1 = "12345678901234567890123456789012345.12345678.123."
|
||||||
const s = name1 + " 3600 IN MX 10 " + name1
|
const s = name1 + " 3600 IN MX 10 " + name1
|
||||||
|
|
152
server.go
152
server.go
|
@ -18,7 +18,7 @@ import (
|
||||||
const maxTCPQueries = 128
|
const maxTCPQueries = 128
|
||||||
|
|
||||||
// aLongTimeAgo is a non-zero time, far in the past, used for
|
// 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)
|
var aLongTimeAgo = time.Unix(1, 0)
|
||||||
|
|
||||||
// Handler is implemented by any value that implements ServeDNS.
|
// Handler is implemented by any value that implements ServeDNS.
|
||||||
|
@ -71,11 +71,12 @@ type response struct {
|
||||||
tsigTimersOnly bool
|
tsigTimersOnly bool
|
||||||
tsigStatus error
|
tsigStatus error
|
||||||
tsigRequestMAC string
|
tsigRequestMAC string
|
||||||
tsigSecret map[string]string // the tsig secrets
|
tsigProvider TsigProvider
|
||||||
udp *net.UDPConn // i/o connection if UDP was used
|
udp net.PacketConn // i/o connection if UDP was used
|
||||||
tcp net.Conn // i/o connection if TCP was used
|
tcp net.Conn // i/o connection if TCP was used
|
||||||
udpSession *SessionUDP // oob data to get egress interface right
|
udpSession *SessionUDP // oob data to get egress interface right
|
||||||
writer Writer // writer to output the raw DNS bits
|
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.
|
// handleRefused returns a HandlerFunc that returns REFUSED for every request it gets.
|
||||||
|
@ -147,12 +148,24 @@ type Reader interface {
|
||||||
ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error)
|
ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultReader is an adapter for the Server struct that implements the Reader interface
|
// PacketConnReader is an optional interface that Readers can implement to support using generic net.PacketConns.
|
||||||
// using the readTCP and readUDP func of the embedded Server.
|
type PacketConnReader interface {
|
||||||
|
Reader
|
||||||
|
|
||||||
|
// ReadPacketConn reads a raw message from a generic net.PacketConn UDP connection. Implementations may
|
||||||
|
// alter connection properties, for example the read-deadline.
|
||||||
|
ReadPacketConn(conn net.PacketConn, timeout time.Duration) ([]byte, net.Addr, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultReader is an adapter for the Server struct that implements the Reader and
|
||||||
|
// PacketConnReader interfaces using the readTCP, readUDP and readPacketConn funcs
|
||||||
|
// of the embedded Server.
|
||||||
type defaultReader struct {
|
type defaultReader struct {
|
||||||
*Server
|
*Server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ PacketConnReader = defaultReader{}
|
||||||
|
|
||||||
func (dr defaultReader) ReadTCP(conn net.Conn, timeout time.Duration) ([]byte, error) {
|
func (dr defaultReader) ReadTCP(conn net.Conn, timeout time.Duration) ([]byte, error) {
|
||||||
return dr.readTCP(conn, timeout)
|
return dr.readTCP(conn, timeout)
|
||||||
}
|
}
|
||||||
|
@ -161,8 +174,14 @@ func (dr defaultReader) ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byt
|
||||||
return dr.readUDP(conn, timeout)
|
return dr.readUDP(conn, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dr defaultReader) ReadPacketConn(conn net.PacketConn, timeout time.Duration) ([]byte, net.Addr, error) {
|
||||||
|
return dr.readPacketConn(conn, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
// DecorateReader is a decorator hook for extending or supplanting the functionality of a Reader.
|
// DecorateReader is a decorator hook for extending or supplanting the functionality of a Reader.
|
||||||
// Implementations should never return a nil Reader.
|
// Implementations should never return a nil Reader.
|
||||||
|
// Readers should also implement the optional PacketConnReader interface.
|
||||||
|
// PacketConnReader is required to use a generic net.PacketConn.
|
||||||
type DecorateReader func(Reader) Reader
|
type DecorateReader func(Reader) Reader
|
||||||
|
|
||||||
// DecorateWriter is a decorator hook for extending or supplanting the functionality of a Writer.
|
// DecorateWriter is a decorator hook for extending or supplanting the functionality of a Writer.
|
||||||
|
@ -192,6 +211,8 @@ type Server struct {
|
||||||
WriteTimeout time.Duration
|
WriteTimeout time.Duration
|
||||||
// TCP idle timeout for multiple queries, if nil, defaults to 8 * time.Second (RFC 5966).
|
// TCP idle timeout for multiple queries, if nil, defaults to 8 * time.Second (RFC 5966).
|
||||||
IdleTimeout func() time.Duration
|
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).
|
// 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
|
TsigSecret map[string]string
|
||||||
// If NotifyStartedFunc is set it is called once the server has started listening.
|
// If NotifyStartedFunc is set it is called once the server has started listening.
|
||||||
|
@ -219,6 +240,16 @@ type Server struct {
|
||||||
udpPool sync.Pool
|
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 {
|
func (srv *Server) isStarted() bool {
|
||||||
srv.lock.RLock()
|
srv.lock.RLock()
|
||||||
started := srv.started
|
started := srv.started
|
||||||
|
@ -302,6 +333,7 @@ func (srv *Server) ListenAndServe() error {
|
||||||
}
|
}
|
||||||
u := l.(*net.UDPConn)
|
u := l.(*net.UDPConn)
|
||||||
if e := setUDPSocketOptions(u); e != nil {
|
if e := setUDPSocketOptions(u); e != nil {
|
||||||
|
u.Close()
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
srv.PacketConn = l
|
srv.PacketConn = l
|
||||||
|
@ -325,24 +357,22 @@ func (srv *Server) ActivateAndServe() error {
|
||||||
|
|
||||||
srv.init()
|
srv.init()
|
||||||
|
|
||||||
pConn := srv.PacketConn
|
if srv.PacketConn != nil {
|
||||||
l := srv.Listener
|
|
||||||
if pConn != nil {
|
|
||||||
// Check PacketConn interface's type is valid and value
|
// Check PacketConn interface's type is valid and value
|
||||||
// is not nil
|
// is not nil
|
||||||
if t, ok := pConn.(*net.UDPConn); ok && t != nil {
|
if t, ok := srv.PacketConn.(*net.UDPConn); ok && t != nil {
|
||||||
if e := setUDPSocketOptions(t); e != nil {
|
if e := setUDPSocketOptions(t); e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
srv.started = true
|
|
||||||
unlock()
|
|
||||||
return srv.serveUDP(t)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if l != nil {
|
|
||||||
srv.started = true
|
srv.started = true
|
||||||
unlock()
|
unlock()
|
||||||
return srv.serveTCP(l)
|
return srv.serveUDP(srv.PacketConn)
|
||||||
|
}
|
||||||
|
if srv.Listener != nil {
|
||||||
|
srv.started = true
|
||||||
|
unlock()
|
||||||
|
return srv.serveTCP(srv.Listener)
|
||||||
}
|
}
|
||||||
return &Error{err: "bad listeners"}
|
return &Error{err: "bad listeners"}
|
||||||
}
|
}
|
||||||
|
@ -446,18 +476,24 @@ func (srv *Server) serveTCP(l net.Listener) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveUDP starts a UDP listener for the server.
|
// serveUDP starts a UDP listener for the server.
|
||||||
func (srv *Server) serveUDP(l *net.UDPConn) error {
|
func (srv *Server) serveUDP(l net.PacketConn) error {
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
|
|
||||||
if srv.NotifyStartedFunc != nil {
|
|
||||||
srv.NotifyStartedFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := Reader(defaultReader{srv})
|
reader := Reader(defaultReader{srv})
|
||||||
if srv.DecorateReader != nil {
|
if srv.DecorateReader != nil {
|
||||||
reader = srv.DecorateReader(reader)
|
reader = srv.DecorateReader(reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lUDP, isUDP := l.(*net.UDPConn)
|
||||||
|
readerPC, canPacketConn := reader.(PacketConnReader)
|
||||||
|
if !isUDP && !canPacketConn {
|
||||||
|
return &Error{err: "PacketConnReader was not implemented on Reader returned from DecorateReader but is required for net.PacketConn"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if srv.NotifyStartedFunc != nil {
|
||||||
|
srv.NotifyStartedFunc()
|
||||||
|
}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
defer func() {
|
defer func() {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
@ -467,7 +503,17 @@ func (srv *Server) serveUDP(l *net.UDPConn) error {
|
||||||
rtimeout := srv.getReadTimeout()
|
rtimeout := srv.getReadTimeout()
|
||||||
// deadline is not used here
|
// deadline is not used here
|
||||||
for srv.isStarted() {
|
for srv.isStarted() {
|
||||||
m, s, err := reader.ReadUDP(l, rtimeout)
|
var (
|
||||||
|
m []byte
|
||||||
|
sPC net.Addr
|
||||||
|
sUDP *SessionUDP
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if isUDP {
|
||||||
|
m, sUDP, err = reader.ReadUDP(lUDP, rtimeout)
|
||||||
|
} else {
|
||||||
|
m, sPC, err = readerPC.ReadPacketConn(l, rtimeout)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !srv.isStarted() {
|
if !srv.isStarted() {
|
||||||
return nil
|
return nil
|
||||||
|
@ -484,7 +530,7 @@ func (srv *Server) serveUDP(l *net.UDPConn) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go srv.serveUDPPacket(&wg, m, l, s)
|
go srv.serveUDPPacket(&wg, m, l, sUDP, sPC)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -492,7 +538,7 @@ func (srv *Server) serveUDP(l *net.UDPConn) error {
|
||||||
|
|
||||||
// Serve a new TCP connection.
|
// Serve a new TCP connection.
|
||||||
func (srv *Server) serveTCPConn(wg *sync.WaitGroup, rw net.Conn) {
|
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 {
|
if srv.DecorateWriter != nil {
|
||||||
w.writer = srv.DecorateWriter(w)
|
w.writer = srv.DecorateWriter(w)
|
||||||
} else {
|
} else {
|
||||||
|
@ -546,8 +592,8 @@ func (srv *Server) serveTCPConn(wg *sync.WaitGroup, rw net.Conn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve a new UDP request.
|
// Serve a new UDP request.
|
||||||
func (srv *Server) serveUDPPacket(wg *sync.WaitGroup, m []byte, u *net.UDPConn, s *SessionUDP) {
|
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: s}
|
w := &response{tsigProvider: srv.tsigProvider(), udp: u, udpSession: udpSession, pcSession: pcSession}
|
||||||
if srv.DecorateWriter != nil {
|
if srv.DecorateWriter != nil {
|
||||||
w.writer = srv.DecorateWriter(w)
|
w.writer = srv.DecorateWriter(w)
|
||||||
} else {
|
} else {
|
||||||
|
@ -598,15 +644,11 @@ func (srv *Server) serveDNS(m []byte, w *response) {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.tsigStatus = nil
|
w.tsigStatus = nil
|
||||||
if w.tsigSecret != nil {
|
if w.tsigProvider != nil {
|
||||||
if t := req.IsTsig(); t != nil {
|
if t := req.IsTsig(); t != nil {
|
||||||
if secret, ok := w.tsigSecret[t.Hdr.Name]; ok {
|
w.tsigStatus = TsigVerifyWithProvider(m, w.tsigProvider, "", false)
|
||||||
w.tsigStatus = TsigVerify(m, secret, "", false)
|
|
||||||
} else {
|
|
||||||
w.tsigStatus = ErrSecret
|
|
||||||
}
|
|
||||||
w.tsigTimersOnly = false
|
w.tsigTimersOnly = false
|
||||||
w.tsigRequestMAC = req.Extra[len(req.Extra)-1].(*TSIG).MAC
|
w.tsigRequestMAC = t.MAC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -659,6 +701,24 @@ func (srv *Server) readUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *S
|
||||||
return m, s, nil
|
return m, s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (srv *Server) readPacketConn(conn net.PacketConn, timeout time.Duration) ([]byte, net.Addr, error) {
|
||||||
|
srv.lock.RLock()
|
||||||
|
if srv.started {
|
||||||
|
// See the comment in readTCP above.
|
||||||
|
conn.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
}
|
||||||
|
srv.lock.RUnlock()
|
||||||
|
|
||||||
|
m := srv.udpPool.Get().([]byte)
|
||||||
|
n, addr, err := conn.ReadFrom(m)
|
||||||
|
if err != nil {
|
||||||
|
srv.udpPool.Put(m)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
m = m[:n]
|
||||||
|
return m, addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
// WriteMsg implements the ResponseWriter.WriteMsg method.
|
// WriteMsg implements the ResponseWriter.WriteMsg method.
|
||||||
func (w *response) WriteMsg(m *Msg) (err error) {
|
func (w *response) WriteMsg(m *Msg) (err error) {
|
||||||
if w.closed {
|
if w.closed {
|
||||||
|
@ -666,9 +726,9 @@ func (w *response) WriteMsg(m *Msg) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var data []byte
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -692,17 +752,19 @@ func (w *response) Write(m []byte) (int, error) {
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case w.udp != nil:
|
case w.udp != nil:
|
||||||
return WriteToSessionUDP(w.udp, m, w.udpSession)
|
if u, ok := w.udp.(*net.UDPConn); ok {
|
||||||
|
return WriteToSessionUDP(u, m, w.udpSession)
|
||||||
|
}
|
||||||
|
return w.udp.WriteTo(m, w.pcSession)
|
||||||
case w.tcp != nil:
|
case w.tcp != nil:
|
||||||
if len(m) > MaxMsgSize {
|
if len(m) > MaxMsgSize {
|
||||||
return 0, &Error{err: "message too large"}
|
return 0, &Error{err: "message too large"}
|
||||||
}
|
}
|
||||||
|
|
||||||
l := make([]byte, 2)
|
msg := make([]byte, 2+len(m))
|
||||||
binary.BigEndian.PutUint16(l, uint16(len(m)))
|
binary.BigEndian.PutUint16(msg, uint16(len(m)))
|
||||||
|
copy(msg[2:], m)
|
||||||
n, err := (&net.Buffers{l, m}).WriteTo(w.tcp)
|
return w.tcp.Write(msg)
|
||||||
return int(n), err
|
|
||||||
default:
|
default:
|
||||||
panic("dns: internal error: udp and tcp both nil")
|
panic("dns: internal error: udp and tcp both nil")
|
||||||
}
|
}
|
||||||
|
@ -725,10 +787,12 @@ func (w *response) RemoteAddr() net.Addr {
|
||||||
switch {
|
switch {
|
||||||
case w.udpSession != nil:
|
case w.udpSession != nil:
|
||||||
return w.udpSession.RemoteAddr()
|
return w.udpSession.RemoteAddr()
|
||||||
|
case w.pcSession != nil:
|
||||||
|
return w.pcSession
|
||||||
case w.tcp != nil:
|
case w.tcp != nil:
|
||||||
return w.tcp.RemoteAddr()
|
return w.tcp.RemoteAddr()
|
||||||
default:
|
default:
|
||||||
panic("dns: internal error: udpSession and tcp both nil")
|
panic("dns: internal error: udpSession, pcSession and tcp are all nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
317
server_test.go
317
server_test.go
|
@ -67,136 +67,170 @@ func AnotherHelloServer(w ResponseWriter, req *Msg) {
|
||||||
w.WriteMsg(m)
|
w.WriteMsg(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunLocalUDPServer(laddr string) (*Server, string, error) {
|
func RunLocalServer(pc net.PacketConn, l net.Listener, opts ...func(*Server)) (*Server, string, chan error, error) {
|
||||||
server, l, _, err := RunLocalUDPServerWithFinChan(laddr)
|
server := &Server{
|
||||||
|
PacketConn: pc,
|
||||||
|
Listener: l,
|
||||||
|
|
||||||
return server, l, err
|
ReadTimeout: time.Hour,
|
||||||
}
|
WriteTimeout: time.Hour,
|
||||||
|
|
||||||
func RunLocalUDPServerWithFinChan(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
|
|
||||||
pc, err := net.ListenPacket("udp", laddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", nil, err
|
|
||||||
}
|
}
|
||||||
server := &Server{PacketConn: pc, ReadTimeout: time.Hour, WriteTimeout: time.Hour}
|
|
||||||
|
|
||||||
waitLock := sync.Mutex{}
|
waitLock := sync.Mutex{}
|
||||||
waitLock.Lock()
|
waitLock.Lock()
|
||||||
server.NotifyStartedFunc = waitLock.Unlock
|
server.NotifyStartedFunc = waitLock.Unlock
|
||||||
|
|
||||||
// fin must be buffered so the goroutine below won't block
|
|
||||||
// forever if fin is never read from. This always happens
|
|
||||||
// in RunLocalUDPServer and can happen in TestShutdownUDP.
|
|
||||||
fin := make(chan error, 1)
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(server)
|
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.
|
||||||
|
fin := make(chan error, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
fin <- server.ActivateAndServe()
|
fin <- server.ActivateAndServe()
|
||||||
pc.Close()
|
closer.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
waitLock.Lock()
|
waitLock.Lock()
|
||||||
return server, pc.LocalAddr().String(), fin, nil
|
return server, addr, fin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunLocalTCPServer(laddr string) (*Server, string, error) {
|
func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
|
||||||
server, l, _, err := RunLocalTCPServerWithFinChan(laddr)
|
pc, err := net.ListenPacket("udp", laddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return server, l, err
|
return RunLocalServer(pc, nil, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunLocalTCPServerWithFinChan(laddr string) (*Server, string, chan error, error) {
|
func RunLocalPacketConnServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
|
||||||
|
return RunLocalUDPServer(laddr, append(opts, func(srv *Server) {
|
||||||
|
// Make srv.PacketConn opaque to trigger the generic code paths.
|
||||||
|
srv.PacketConn = struct{ net.PacketConn }{srv.PacketConn}
|
||||||
|
})...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunLocalTCPServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
|
||||||
l, err := net.Listen("tcp", laddr)
|
l, err := net.Listen("tcp", laddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", nil, err
|
return nil, "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
server := &Server{Listener: l, ReadTimeout: time.Hour, WriteTimeout: time.Hour}
|
return RunLocalServer(nil, l, opts...)
|
||||||
|
|
||||||
waitLock := sync.Mutex{}
|
|
||||||
waitLock.Lock()
|
|
||||||
server.NotifyStartedFunc = waitLock.Unlock
|
|
||||||
|
|
||||||
// See the comment in RunLocalUDPServerWithFinChan 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunLocalTLSServer(laddr string, config *tls.Config) (*Server, string, error) {
|
func RunLocalTLSServer(laddr string, config *tls.Config) (*Server, string, chan error, error) {
|
||||||
l, err := tls.Listen("tcp", laddr, config)
|
return RunLocalTCPServer(laddr, func(srv *Server) {
|
||||||
|
srv.Listener = tls.NewListener(srv.Listener, config)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
server := &Server{Listener: l, ReadTimeout: time.Hour, WriteTimeout: time.Hour}
|
shutdownChan := make(chan interface{})
|
||||||
|
|
||||||
waitLock := sync.Mutex{}
|
|
||||||
waitLock.Lock()
|
|
||||||
server.NotifyStartedFunc = waitLock.Unlock
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
server.ActivateAndServe()
|
pc.Accept()
|
||||||
l.Close()
|
<-shutdownChan
|
||||||
}()
|
}()
|
||||||
|
|
||||||
waitLock.Lock()
|
return shutdownChan, pc.Addr().String(), nil
|
||||||
return server, l.Addr().String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServing(t *testing.T) {
|
func TestServing(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", HelloServer)
|
for _, tc := range []struct {
|
||||||
HandleFunc("example.com.", AnotherHelloServer)
|
name string
|
||||||
defer HandleRemove("miek.nl.")
|
network string
|
||||||
defer HandleRemove("example.com.")
|
runServer func(laddr string, opts ...func(*Server)) (*Server, string, chan error, error)
|
||||||
|
}{
|
||||||
|
{"udp", "udp", RunLocalUDPServer},
|
||||||
|
{"tcp", "tcp", RunLocalTCPServer},
|
||||||
|
{"PacketConn", "udp", RunLocalPacketConnServer},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
HandleFunc("miek.nl.", HelloServer)
|
||||||
|
HandleFunc("example.com.", AnotherHelloServer)
|
||||||
|
defer HandleRemove("miek.nl.")
|
||||||
|
defer HandleRemove("example.com.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := tc.runServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
defer s.Shutdown()
|
defer s.Shutdown()
|
||||||
|
|
||||||
c := new(Client)
|
c := &Client{
|
||||||
m := new(Msg)
|
Net: tc.network,
|
||||||
m.SetQuestion("miek.nl.", TypeTXT)
|
}
|
||||||
r, _, err := c.Exchange(m, addrstr)
|
m := new(Msg)
|
||||||
if err != nil || len(r.Extra) == 0 {
|
m.SetQuestion("miek.nl.", TypeTXT)
|
||||||
t.Fatal("failed to exchange miek.nl", err)
|
r, _, err := c.Exchange(m, addrstr)
|
||||||
}
|
if err != nil || len(r.Extra) == 0 {
|
||||||
txt := r.Extra[0].(*TXT).Txt[0]
|
t.Fatal("failed to exchange miek.nl", err)
|
||||||
if txt != "Hello world" {
|
}
|
||||||
t.Error("unexpected result for miek.nl", txt, "!= Hello world")
|
txt := r.Extra[0].(*TXT).Txt[0]
|
||||||
}
|
if txt != "Hello world" {
|
||||||
|
t.Error("unexpected result for miek.nl", txt, "!= Hello world")
|
||||||
|
}
|
||||||
|
|
||||||
m.SetQuestion("example.com.", TypeTXT)
|
m.SetQuestion("example.com.", TypeTXT)
|
||||||
r, _, err = c.Exchange(m, addrstr)
|
r, _, err = c.Exchange(m, addrstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("failed to exchange example.com", err)
|
t.Fatal("failed to exchange example.com", err)
|
||||||
}
|
}
|
||||||
txt = r.Extra[0].(*TXT).Txt[0]
|
txt = r.Extra[0].(*TXT).Txt[0]
|
||||||
if txt != "Hello example" {
|
if txt != "Hello example" {
|
||||||
t.Error("unexpected result for example.com", txt, "!= Hello example")
|
t.Error("unexpected result for example.com", txt, "!= Hello example")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test Mixes cased as noticed by Ask.
|
// Test Mixes cased as noticed by Ask.
|
||||||
m.SetQuestion("eXaMplE.cOm.", TypeTXT)
|
m.SetQuestion("eXaMplE.cOm.", TypeTXT)
|
||||||
r, _, err = c.Exchange(m, addrstr)
|
r, _, err = c.Exchange(m, addrstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("failed to exchange eXaMplE.cOm", err)
|
t.Error("failed to exchange eXaMplE.cOm", err)
|
||||||
}
|
}
|
||||||
txt = r.Extra[0].(*TXT).Txt[0]
|
txt = r.Extra[0].(*TXT).Txt[0]
|
||||||
if txt != "Hello example" {
|
if txt != "Hello example" {
|
||||||
t.Error("unexpected result for example.com", txt, "!= Hello example")
|
t.Error("unexpected result for example.com", txt, "!= Hello example")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +238,7 @@ func TestServing(t *testing.T) {
|
||||||
func TestServeIgnoresZFlag(t *testing.T) {
|
func TestServeIgnoresZFlag(t *testing.T) {
|
||||||
HandleFunc("example.com.", AnotherHelloServer)
|
HandleFunc("example.com.", AnotherHelloServer)
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -233,7 +267,7 @@ func TestServeNotImplemented(t *testing.T) {
|
||||||
HandleFunc("example.com.", AnotherHelloServer)
|
HandleFunc("example.com.", AnotherHelloServer)
|
||||||
opcode := 15
|
opcode := 15
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -242,7 +276,7 @@ func TestServeNotImplemented(t *testing.T) {
|
||||||
c := new(Client)
|
c := new(Client)
|
||||||
m := new(Msg)
|
m := new(Msg)
|
||||||
|
|
||||||
// Test that Opcode is like the unchanged from request Opcode and that Rcode is set to NotImplemnented
|
// Test that Opcode is like the unchanged from request Opcode and that Rcode is set to NotImplemented
|
||||||
m.SetQuestion("example.com.", TypeTXT)
|
m.SetQuestion("example.com.", TypeTXT)
|
||||||
m.Opcode = opcode
|
m.Opcode = opcode
|
||||||
r, _, err := c.Exchange(m, addrstr)
|
r, _, err := c.Exchange(m, addrstr)
|
||||||
|
@ -272,7 +306,7 @@ func TestServingTLS(t *testing.T) {
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
}
|
}
|
||||||
|
|
||||||
s, addrstr, err := RunLocalTLSServer(":0", &config)
|
s, addrstr, _, err := RunLocalTLSServer(":0", &config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -358,7 +392,7 @@ func TestServingTLSConnectionState(t *testing.T) {
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
}
|
}
|
||||||
|
|
||||||
s, addrstr, err := RunLocalTLSServer(":0", &config)
|
s, addrstr, _, err := RunLocalTLSServer(":0", &config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -381,7 +415,7 @@ func TestServingTLSConnectionState(t *testing.T) {
|
||||||
// UDP DNS Server
|
// UDP DNS Server
|
||||||
HandleFunc(".", tlsHandlerTLS(false))
|
HandleFunc(".", tlsHandlerTLS(false))
|
||||||
defer HandleRemove(".")
|
defer HandleRemove(".")
|
||||||
s, addrstr, err = RunLocalUDPServer(":0")
|
s, addrstr, _, err = RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -395,7 +429,7 @@ func TestServingTLSConnectionState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCP DNS Server
|
// TCP DNS Server
|
||||||
s, addrstr, err = RunLocalTCPServer(":0")
|
s, addrstr, _, err = RunLocalTCPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -479,7 +513,7 @@ func BenchmarkServe(b *testing.B) {
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
a := runtime.GOMAXPROCS(4)
|
a := runtime.GOMAXPROCS(4)
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("unable to run test server: %v", err)
|
b.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -504,7 +538,7 @@ func BenchmarkServe6(b *testing.B) {
|
||||||
HandleFunc("miek.nl.", HelloServer)
|
HandleFunc("miek.nl.", HelloServer)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
a := runtime.GOMAXPROCS(4)
|
a := runtime.GOMAXPROCS(4)
|
||||||
s, addrstr, err := RunLocalUDPServer("[::1]:0")
|
s, addrstr, _, err := RunLocalUDPServer("[::1]:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "bind: cannot assign requested address") {
|
if strings.Contains(err.Error(), "bind: cannot assign requested address") {
|
||||||
b.Skip("missing IPv6 support")
|
b.Skip("missing IPv6 support")
|
||||||
|
@ -541,7 +575,7 @@ func BenchmarkServeCompress(b *testing.B) {
|
||||||
HandleFunc("miek.nl.", HelloServerCompress)
|
HandleFunc("miek.nl.", HelloServerCompress)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
a := runtime.GOMAXPROCS(4)
|
a := runtime.GOMAXPROCS(4)
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("unable to run test server: %v", err)
|
b.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -594,7 +628,7 @@ func TestServingLargeResponses(t *testing.T) {
|
||||||
HandleFunc("example.", HelloServerLargeResponse)
|
HandleFunc("example.", HelloServerLargeResponse)
|
||||||
defer HandleRemove("example.")
|
defer HandleRemove("example.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -634,7 +668,7 @@ func TestServingResponse(t *testing.T) {
|
||||||
t.Skip("skipping test in short mode.")
|
t.Skip("skipping test in short mode.")
|
||||||
}
|
}
|
||||||
HandleFunc("miek.nl.", HelloServer)
|
HandleFunc("miek.nl.", HelloServer)
|
||||||
s, addrstr, err := RunLocalUDPServer(":0")
|
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -657,7 +691,7 @@ func TestServingResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShutdownTCP(t *testing.T) {
|
func TestShutdownTCP(t *testing.T) {
|
||||||
s, _, fin, err := RunLocalTCPServerWithFinChan(":0")
|
s, _, fin, err := RunLocalTCPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -788,7 +822,7 @@ func checkInProgressQueriesAtShutdownServer(t *testing.T, srv *Server, addr stri
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInProgressQueriesAtShutdownTCP(t *testing.T) {
|
func TestInProgressQueriesAtShutdownTCP(t *testing.T) {
|
||||||
s, addr, _, err := RunLocalTCPServerWithFinChan(":0")
|
s, addr, _, err := RunLocalTCPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -807,7 +841,7 @@ func TestShutdownTLS(t *testing.T) {
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
}
|
}
|
||||||
|
|
||||||
s, _, err := RunLocalTLSServer(":0", &config)
|
s, _, _, err := RunLocalTLSServer(":0", &config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -827,7 +861,7 @@ func TestInProgressQueriesAtShutdownTLS(t *testing.T) {
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
}
|
}
|
||||||
|
|
||||||
s, addr, err := RunLocalTLSServer(":0", &config)
|
s, addr, _, err := RunLocalTLSServer(":0", &config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -842,7 +876,6 @@ func TestInProgressQueriesAtShutdownTLS(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandlerCloseTCP(t *testing.T) {
|
func TestHandlerCloseTCP(t *testing.T) {
|
||||||
|
|
||||||
ln, err := net.Listen("tcp", ":0")
|
ln, err := net.Listen("tcp", ":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -887,7 +920,26 @@ func TestHandlerCloseTCP(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShutdownUDP(t *testing.T) {
|
func TestShutdownUDP(t *testing.T) {
|
||||||
s, _, fin, err := RunLocalUDPServerWithFinChan(":0")
|
s, _, fin, err := RunLocalUDPServer(":0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
|
}
|
||||||
|
err = s.Shutdown()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not shutdown test UDP server, %v", err)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case err := <-fin:
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error returned from ActivateAndServe, %v", err)
|
||||||
|
}
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Error("could not shutdown test UDP server. Gave up waiting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShutdownPacketConn(t *testing.T) {
|
||||||
|
s, _, fin, err := RunLocalPacketConnServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -906,7 +958,17 @@ func TestShutdownUDP(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInProgressQueriesAtShutdownUDP(t *testing.T) {
|
func TestInProgressQueriesAtShutdownUDP(t *testing.T) {
|
||||||
s, addr, _, err := RunLocalUDPServerWithFinChan(":0")
|
s, addr, _, err := RunLocalUDPServer(":0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &Client{Net: "udp"}
|
||||||
|
checkInProgressQueriesAtShutdownServer(t, s, addr, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInProgressQueriesAtShutdownPacketConn(t *testing.T) {
|
||||||
|
s, addr, _, err := RunLocalPacketConnServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %v", err)
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -919,7 +981,7 @@ func TestServerStartStopRace(t *testing.T) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
s, _, _, err := RunLocalUDPServerWithFinChan(":0")
|
s, _, _, err := RunLocalUDPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not start server: %s", err)
|
t.Fatalf("could not start server: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -982,7 +1044,7 @@ func TestServerReuseport(t *testing.T) {
|
||||||
func TestServerRoundtripTsig(t *testing.T) {
|
func TestServerRoundtripTsig(t *testing.T) {
|
||||||
secret := map[string]string{"test.": "so6ZGir4GPAqINNh9U5c3A=="}
|
secret := map[string]string{"test.": "so6ZGir4GPAqINNh9U5c3A=="}
|
||||||
|
|
||||||
s, addrstr, _, err := RunLocalUDPServerWithFinChan(":0", func(srv *Server) {
|
s, addrstr, _, err := RunLocalUDPServer(":0", func(srv *Server) {
|
||||||
srv.TsigSecret = secret
|
srv.TsigSecret = secret
|
||||||
srv.MsgAcceptFunc = func(dh Header) MsgAcceptAction {
|
srv.MsgAcceptFunc = func(dh Header) MsgAcceptAction {
|
||||||
// defaultMsgAcceptFunc does reject UPDATE queries
|
// defaultMsgAcceptFunc does reject UPDATE queries
|
||||||
|
@ -1004,9 +1066,9 @@ func TestServerRoundtripTsig(t *testing.T) {
|
||||||
status := w.TsigStatus()
|
status := w.TsigStatus()
|
||||||
if status == nil {
|
if status == nil {
|
||||||
// *Msg r has an TSIG record and it was validated
|
// *Msg r has an TSIG record and it was validated
|
||||||
m.SetTsig("test.", HmacMD5, 300, time.Now().Unix())
|
m.SetTsig("test.", HmacSHA256, 300, time.Now().Unix())
|
||||||
} else {
|
} else {
|
||||||
// *Msg r has an TSIG records and it was not valided
|
// *Msg r has an TSIG records and it was not validated
|
||||||
t.Errorf("invalid TSIG: %v", status)
|
t.Errorf("invalid TSIG: %v", status)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1031,7 +1093,7 @@ func TestServerRoundtripTsig(t *testing.T) {
|
||||||
Target: "bar.example.com.",
|
Target: "bar.example.com.",
|
||||||
}}
|
}}
|
||||||
c.TsigSecret = secret
|
c.TsigSecret = secret
|
||||||
m.SetTsig("test.", HmacMD5, 300, time.Now().Unix())
|
m.SetTsig("test.", HmacSHA256, 300, time.Now().Unix())
|
||||||
_, _, err = c.Exchange(m, addrstr)
|
_, _, err = c.Exchange(m, addrstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("failed to exchange", err)
|
t.Fatal("failed to exchange", err)
|
||||||
|
@ -1075,6 +1137,37 @@ func TestResponseDoubleClose(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type countingConn struct {
|
||||||
|
net.Conn
|
||||||
|
writes int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countingConn) Write(p []byte) (int, error) {
|
||||||
|
c.writes++
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResponseWriteSinglePacket(t *testing.T) {
|
||||||
|
c := &countingConn{}
|
||||||
|
rw := &response{
|
||||||
|
tcp: c,
|
||||||
|
}
|
||||||
|
rw.writer = rw
|
||||||
|
|
||||||
|
m := new(Msg)
|
||||||
|
m.SetQuestion("miek.nl.", TypeTXT)
|
||||||
|
m.Response = true
|
||||||
|
err := rw.WriteMsg(m)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to write: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.writes != 1 {
|
||||||
|
t.Fatalf("incorrect number of Write calls")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type ExampleFrameLengthWriter struct {
|
type ExampleFrameLengthWriter struct {
|
||||||
Writer
|
Writer
|
||||||
}
|
}
|
||||||
|
|
67
sig0.go
67
sig0.go
|
@ -2,8 +2,8 @@ package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/dsa"
|
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
@ -18,7 +18,7 @@ func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) {
|
||||||
if k == nil {
|
if k == nil {
|
||||||
return nil, ErrPrivKey
|
return nil, ErrPrivKey
|
||||||
}
|
}
|
||||||
if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 {
|
if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 {
|
||||||
return nil, ErrKey
|
return nil, ErrKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,18 +39,17 @@ func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) {
|
||||||
}
|
}
|
||||||
buf = buf[:off:cap(buf)]
|
buf = buf[:off:cap(buf)]
|
||||||
|
|
||||||
hash, ok := AlgorithmToHash[rr.Algorithm]
|
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return nil, ErrAlg
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hasher := hash.New()
|
|
||||||
// Write SIG rdata
|
// 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
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -79,24 +78,14 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
|
||||||
if k == nil {
|
if k == nil {
|
||||||
return ErrKey
|
return ErrKey
|
||||||
}
|
}
|
||||||
if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 {
|
if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 {
|
||||||
return ErrKey
|
return ErrKey
|
||||||
}
|
}
|
||||||
|
|
||||||
var hash crypto.Hash
|
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||||
switch rr.Algorithm {
|
if err != nil {
|
||||||
case DSA, RSASHA1:
|
return err
|
||||||
hash = crypto.SHA1
|
|
||||||
case RSASHA256, ECDSAP256SHA256:
|
|
||||||
hash = crypto.SHA256
|
|
||||||
case ECDSAP384SHA384:
|
|
||||||
hash = crypto.SHA384
|
|
||||||
case RSASHA512:
|
|
||||||
hash = crypto.SHA512
|
|
||||||
default:
|
|
||||||
return ErrAlg
|
|
||||||
}
|
}
|
||||||
hasher := hash.New()
|
|
||||||
|
|
||||||
buflen := len(buf)
|
buflen := len(buf)
|
||||||
qdc := binary.BigEndian.Uint16(buf[4:])
|
qdc := binary.BigEndian.Uint16(buf[4:])
|
||||||
|
@ -104,7 +93,6 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
|
||||||
auc := binary.BigEndian.Uint16(buf[8:])
|
auc := binary.BigEndian.Uint16(buf[8:])
|
||||||
adc := binary.BigEndian.Uint16(buf[10:])
|
adc := binary.BigEndian.Uint16(buf[10:])
|
||||||
offset := headerSize
|
offset := headerSize
|
||||||
var err error
|
|
||||||
for i := uint16(0); i < qdc && offset < buflen; i++ {
|
for i := uint16(0); i < qdc && offset < buflen; i++ {
|
||||||
_, offset, err = UnpackDomainName(buf, offset)
|
_, offset, err = UnpackDomainName(buf, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -167,32 +155,21 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
|
||||||
return &Error{err: "signer name doesn't match key name"}
|
return &Error{err: "signer name doesn't match key name"}
|
||||||
}
|
}
|
||||||
sigend := offset
|
sigend := offset
|
||||||
hasher.Write(buf[sigstart:sigend])
|
h.Write(buf[sigstart:sigend])
|
||||||
hasher.Write(buf[:10])
|
h.Write(buf[:10])
|
||||||
hasher.Write([]byte{
|
h.Write([]byte{
|
||||||
byte((adc - 1) << 8),
|
byte((adc - 1) << 8),
|
||||||
byte(adc - 1),
|
byte(adc - 1),
|
||||||
})
|
})
|
||||||
hasher.Write(buf[12:bodyend])
|
h.Write(buf[12:bodyend])
|
||||||
|
|
||||||
hashed := hasher.Sum(nil)
|
hashed := h.Sum(nil)
|
||||||
sig := buf[sigend:]
|
sig := buf[sigend:]
|
||||||
switch k.Algorithm {
|
switch k.Algorithm {
|
||||||
case DSA:
|
|
||||||
pk := k.publicKeyDSA()
|
|
||||||
sig = sig[1:]
|
|
||||||
r := new(big.Int).SetBytes(sig[:len(sig)/2])
|
|
||||||
s := new(big.Int).SetBytes(sig[len(sig)/2:])
|
|
||||||
if pk != nil {
|
|
||||||
if dsa.Verify(pk, hashed, r, s) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ErrSig
|
|
||||||
}
|
|
||||||
case RSASHA1, RSASHA256, RSASHA512:
|
case RSASHA1, RSASHA256, RSASHA512:
|
||||||
pk := k.publicKeyRSA()
|
pk := k.publicKeyRSA()
|
||||||
if pk != nil {
|
if pk != nil {
|
||||||
return rsa.VerifyPKCS1v15(pk, hash, hashed, sig)
|
return rsa.VerifyPKCS1v15(pk, cryptohash, hashed, sig)
|
||||||
}
|
}
|
||||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||||
pk := k.publicKeyECDSA()
|
pk := k.publicKeyECDSA()
|
||||||
|
@ -204,6 +181,14 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
|
||||||
}
|
}
|
||||||
return ErrSig
|
return ErrSig
|
||||||
}
|
}
|
||||||
|
case ED25519:
|
||||||
|
pk := k.publicKeyED25519()
|
||||||
|
if pk != nil {
|
||||||
|
if ed25519.Verify(pk, hashed, sig) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrSig
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ErrKeyAlg
|
return ErrKeyAlg
|
||||||
}
|
}
|
||||||
|
|
18
sig0_test.go
18
sig0_test.go
|
@ -12,7 +12,7 @@ func TestSIG0(t *testing.T) {
|
||||||
}
|
}
|
||||||
m := new(Msg)
|
m := new(Msg)
|
||||||
m.SetQuestion("example.org.", TypeSOA)
|
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]
|
algstr := AlgorithmToString[alg]
|
||||||
keyrr := new(KEY)
|
keyrr := new(KEY)
|
||||||
keyrr.Hdr.Name = algstr + "."
|
keyrr.Hdr.Name = algstr + "."
|
||||||
|
@ -21,7 +21,7 @@ func TestSIG0(t *testing.T) {
|
||||||
keyrr.Algorithm = alg
|
keyrr.Algorithm = alg
|
||||||
keysize := 512
|
keysize := 512
|
||||||
switch alg {
|
switch alg {
|
||||||
case ECDSAP256SHA256:
|
case ECDSAP256SHA256, ED25519:
|
||||||
keysize = 256
|
keysize = 256
|
||||||
case ECDSAP384SHA384:
|
case ECDSAP384SHA384:
|
||||||
keysize = 384
|
keysize = 384
|
||||||
|
@ -30,7 +30,7 @@ func TestSIG0(t *testing.T) {
|
||||||
}
|
}
|
||||||
pk, err := keyrr.Generate(keysize)
|
pk, err := keyrr.Generate(keysize)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
now := uint32(time.Now().Unix())
|
now := uint32(time.Now().Unix())
|
||||||
|
@ -45,16 +45,16 @@ func TestSIG0(t *testing.T) {
|
||||||
sigrr.SignerName = keyrr.Hdr.Name
|
sigrr.SignerName = keyrr.Hdr.Name
|
||||||
mb, err := sigrr.Sign(pk.(crypto.Signer), m)
|
mb, err := sigrr.Sign(pk.(crypto.Signer), m)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
m := new(Msg)
|
m := new(Msg)
|
||||||
if err := m.Unpack(mb); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if len(m.Extra) != 1 {
|
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
|
continue
|
||||||
}
|
}
|
||||||
var sigrrwire *SIG
|
var sigrrwire *SIG
|
||||||
|
@ -71,20 +71,20 @@ func TestSIG0(t *testing.T) {
|
||||||
id = "sigrrwire"
|
id = "sigrrwire"
|
||||||
}
|
}
|
||||||
if err := rr.Verify(keyrr, mb); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mb[13]++
|
mb[13]++
|
||||||
if err := sigrr.Verify(keyrr, mb); err == nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
sigrr.Expiration = 2
|
sigrr.Expiration = 2
|
||||||
sigrr.Inception = 1
|
sigrr.Inception = 1
|
||||||
mb, _ = sigrr.Sign(pk.(crypto.Signer), m)
|
mb, _ = sigrr.Sign(pk.(crypto.Signer), m)
|
||||||
if err := sigrr.Verify(keyrr, mb); err == nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
376
svcb.go
376
svcb.go
|
@ -4,24 +4,28 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SVCBKey is the type of the keys used in the SVCB RR.
|
||||||
type SVCBKey uint16
|
type SVCBKey uint16
|
||||||
|
|
||||||
// Keys defined in draft-ietf-dnsop-svcb-https-02 Section 11.1.2.
|
// Keys defined in draft-ietf-dnsop-svcb-https-08 Section 14.3.2.
|
||||||
const (
|
const (
|
||||||
SVCB_MANDATORY SVCBKey = 0
|
SVCB_MANDATORY SVCBKey = iota
|
||||||
SVCB_ALPN SVCBKey = 1
|
SVCB_ALPN
|
||||||
SVCB_NO_DEFAULT_ALPN SVCBKey = 2
|
SVCB_NO_DEFAULT_ALPN
|
||||||
SVCB_PORT SVCBKey = 3
|
SVCB_PORT
|
||||||
SVCB_IPV4HINT SVCBKey = 4
|
SVCB_IPV4HINT
|
||||||
SVCB_ECHCONFIG SVCBKey = 5
|
SVCB_ECHCONFIG
|
||||||
SVCB_IPV6HINT SVCBKey = 6
|
SVCB_IPV6HINT
|
||||||
svcb_RESERVED SVCBKey = 65535
|
SVCB_DOHPATH // draft-ietf-add-svcb-dns-02 Section 9
|
||||||
|
|
||||||
|
svcb_RESERVED SVCBKey = 65535
|
||||||
)
|
)
|
||||||
|
|
||||||
var svcbKeyToStringMap = map[SVCBKey]string{
|
var svcbKeyToStringMap = map[SVCBKey]string{
|
||||||
|
@ -30,8 +34,9 @@ var svcbKeyToStringMap = map[SVCBKey]string{
|
||||||
SVCB_NO_DEFAULT_ALPN: "no-default-alpn",
|
SVCB_NO_DEFAULT_ALPN: "no-default-alpn",
|
||||||
SVCB_PORT: "port",
|
SVCB_PORT: "port",
|
||||||
SVCB_IPV4HINT: "ipv4hint",
|
SVCB_IPV4HINT: "ipv4hint",
|
||||||
SVCB_ECHCONFIG: "echconfig",
|
SVCB_ECHCONFIG: "ech",
|
||||||
SVCB_IPV6HINT: "ipv6hint",
|
SVCB_IPV6HINT: "ipv6hint",
|
||||||
|
SVCB_DOHPATH: "dohpath",
|
||||||
}
|
}
|
||||||
|
|
||||||
var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap)
|
var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap)
|
||||||
|
@ -166,10 +171,14 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
|
||||||
}
|
}
|
||||||
l, _ = c.Next()
|
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
|
rr.Value = xs
|
||||||
if rr.Priority == 0 && len(xs) > 0 {
|
|
||||||
return &ParseError{l.token, "SVCB aliasform can't have values", l}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,6 +199,8 @@ func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
|
||||||
return new(SVCBECHConfig)
|
return new(SVCBECHConfig)
|
||||||
case SVCB_IPV6HINT:
|
case SVCB_IPV6HINT:
|
||||||
return new(SVCBIPv6Hint)
|
return new(SVCBIPv6Hint)
|
||||||
|
case SVCB_DOHPATH:
|
||||||
|
return new(SVCBDoHPath)
|
||||||
case svcb_RESERVED:
|
case svcb_RESERVED:
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
|
@ -199,16 +210,24 @@ func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SVCB RR. See RFC xxxx (https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-02).
|
// 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 {
|
type SVCB struct {
|
||||||
Hdr RR_Header
|
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"`
|
Target string `dns:"domain-name"`
|
||||||
Value []SVCBKeyValue `dns:"pairs"` // Value must be empty if Priority is non-zero.
|
Value []SVCBKeyValue `dns:"pairs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPS RR. Everything valid for SVCB applies to HTTPS as well.
|
// 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.
|
// 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 {
|
type HTTPS struct {
|
||||||
SVCB
|
SVCB
|
||||||
}
|
}
|
||||||
|
@ -234,15 +253,29 @@ type SVCBKeyValue interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SVCBMandatory pair adds to required keys that must be interpreted for the RR
|
// 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:
|
// Basic use pattern for creating a mandatory option:
|
||||||
//
|
//
|
||||||
// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
|
// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
|
||||||
// e := new(dns.SVCBMandatory)
|
// e := new(dns.SVCBMandatory)
|
||||||
// e.Code = []uint16{65403}
|
// e.Code = []uint16{dns.SVCB_ALPN}
|
||||||
// s.Value = append(s.Value, e)
|
// s.Value = append(s.Value, e)
|
||||||
|
// t := new(dns.SVCBAlpn)
|
||||||
|
// t.Alpn = []string{"xmpp-client"}
|
||||||
|
// s.Value = append(s.Value, t)
|
||||||
type SVCBMandatory struct {
|
type SVCBMandatory struct {
|
||||||
Code []SVCBKey // Must not include mandatory
|
Code []SVCBKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*SVCBMandatory) Key() SVCBKey { return SVCB_MANDATORY }
|
func (*SVCBMandatory) Key() SVCBKey { return SVCB_MANDATORY }
|
||||||
|
@ -301,26 +334,72 @@ func (s *SVCBMandatory) copy() SVCBKeyValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SVCBAlpn pair is used to list supported connection protocols.
|
// 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
|
// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
|
||||||
// Basic use pattern for creating an alpn option:
|
// Basic use pattern for creating an alpn option:
|
||||||
//
|
//
|
||||||
// h := &dns.HTTPS{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}}
|
// h := new(dns.HTTPS)
|
||||||
|
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||||
// e := new(dns.SVCBAlpn)
|
// e := new(dns.SVCBAlpn)
|
||||||
// e.Alpn = []string{"h2", "http/1.1"}
|
// e.Alpn = []string{"h2", "http/1.1"}
|
||||||
// h.Value = append(o.Value, e)
|
// h.Value = append(h.Value, e)
|
||||||
type SVCBAlpn struct {
|
type SVCBAlpn struct {
|
||||||
Alpn []string
|
Alpn []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN }
|
func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN }
|
||||||
func (s *SVCBAlpn) String() string { return strings.Join(s.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) {
|
func (s *SVCBAlpn) pack() ([]byte, error) {
|
||||||
// Liberally estimate the size of an alpn as 10 octets
|
// Liberally estimate the size of an alpn as 10 octets
|
||||||
b := make([]byte, 0, 10*len(s.Alpn))
|
b := make([]byte, 0, 10*len(s.Alpn))
|
||||||
for _, e := range s.Alpn {
|
for _, e := range s.Alpn {
|
||||||
if len(e) == 0 {
|
if e == "" {
|
||||||
return nil, errors.New("dns: svcbalpn: empty alpn-id")
|
return nil, errors.New("dns: svcbalpn: empty alpn-id")
|
||||||
}
|
}
|
||||||
if len(e) > 255 {
|
if len(e) > 255 {
|
||||||
|
@ -349,7 +428,47 @@ func (s *SVCBAlpn) unpack(b []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SVCBAlpn) parse(b string) 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,9 +487,13 @@ func (s *SVCBAlpn) copy() SVCBKeyValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SVCBNoDefaultAlpn pair signifies no support for default connection protocols.
|
// 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:
|
// Basic use pattern for creating a no-default-alpn option:
|
||||||
//
|
//
|
||||||
// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
|
// 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)
|
// e := new(dns.SVCBNoDefaultAlpn)
|
||||||
// s.Value = append(s.Value, e)
|
// s.Value = append(s.Value, e)
|
||||||
type SVCBNoDefaultAlpn struct{}
|
type SVCBNoDefaultAlpn struct{}
|
||||||
|
@ -383,14 +506,14 @@ func (*SVCBNoDefaultAlpn) len() int { return 0 }
|
||||||
|
|
||||||
func (*SVCBNoDefaultAlpn) unpack(b []byte) error {
|
func (*SVCBNoDefaultAlpn) unpack(b []byte) error {
|
||||||
if len(b) != 0 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*SVCBNoDefaultAlpn) parse(b string) error {
|
func (*SVCBNoDefaultAlpn) parse(b string) error {
|
||||||
if len(b) != 0 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -440,14 +563,15 @@ func (s *SVCBPort) parse(b string) error {
|
||||||
// to the hinted IP address may be terminated and a new connection may be opened.
|
// to the hinted IP address may be terminated and a new connection may be opened.
|
||||||
// Basic use pattern for creating an ipv4hint option:
|
// Basic use pattern for creating an ipv4hint option:
|
||||||
//
|
//
|
||||||
// h := &dns.HTTPS{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}}
|
// h := new(dns.HTTPS)
|
||||||
// e := new(dns.SVCBIPv4Hint)
|
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||||
// e.Hint = []net.IP{net.IPv4(1,1,1,1).To4()}
|
// 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()}
|
// e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()}
|
||||||
// h.Value = append(h.Value, e)
|
// h.Value = append(h.Value, e)
|
||||||
type SVCBIPv4Hint struct {
|
type SVCBIPv4Hint struct {
|
||||||
Hint []net.IP
|
Hint []net.IP
|
||||||
}
|
}
|
||||||
|
@ -509,20 +633,26 @@ func (s *SVCBIPv4Hint) parse(b string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
|
func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
|
||||||
|
hint := make([]net.IP, len(s.Hint))
|
||||||
|
for i, ip := range s.Hint {
|
||||||
|
hint[i] = copyIP(ip)
|
||||||
|
}
|
||||||
|
|
||||||
return &SVCBIPv4Hint{
|
return &SVCBIPv4Hint{
|
||||||
append([]net.IP(nil), s.Hint...),
|
Hint: hint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx].
|
// 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 := &dns.HTTPS{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}}
|
// h := new(dns.HTTPS)
|
||||||
|
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||||
// e := new(dns.SVCBECHConfig)
|
// e := new(dns.SVCBECHConfig)
|
||||||
// e.ECH = "/wH...="
|
// e.ECH = []byte{0xfe, 0x08, ...}
|
||||||
// h.Value = append(h.Value, e)
|
// h.Value = append(h.Value, e)
|
||||||
type SVCBECHConfig struct {
|
type SVCBECHConfig struct {
|
||||||
ECH []byte
|
ECH []byte // Specifically ECHConfigList including the redundant length prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*SVCBECHConfig) Key() SVCBKey { return SVCB_ECHCONFIG }
|
func (*SVCBECHConfig) Key() SVCBKey { return SVCB_ECHCONFIG }
|
||||||
|
@ -546,7 +676,7 @@ func (s *SVCBECHConfig) unpack(b []byte) error {
|
||||||
func (s *SVCBECHConfig) parse(b string) error {
|
func (s *SVCBECHConfig) parse(b string) error {
|
||||||
x, err := fromBase64([]byte(b))
|
x, err := fromBase64([]byte(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("dns: svcbechconfig: bad base64 echconfig")
|
return errors.New("dns: svcbech: bad base64 ech")
|
||||||
}
|
}
|
||||||
s.ECH = x
|
s.ECH = x
|
||||||
return nil
|
return nil
|
||||||
|
@ -558,7 +688,8 @@ func (s *SVCBECHConfig) parse(b string) error {
|
||||||
// connection to the hinted IP address may be terminated and a new connection may be opened.
|
// connection to the hinted IP address may be terminated and a new connection may be opened.
|
||||||
// Basic use pattern for creating an ipv6hint option:
|
// Basic use pattern for creating an ipv6hint option:
|
||||||
//
|
//
|
||||||
// h := &dns.HTTPS{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}}
|
// h := new(dns.HTTPS)
|
||||||
|
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||||
// e := new(dns.SVCBIPv6Hint)
|
// e := new(dns.SVCBIPv6Hint)
|
||||||
// e.Hint = []net.IP{net.ParseIP("2001:db8::1")}
|
// e.Hint = []net.IP{net.ParseIP("2001:db8::1")}
|
||||||
// h.Value = append(h.Value, e)
|
// h.Value = append(h.Value, e)
|
||||||
|
@ -608,9 +739,6 @@ func (s *SVCBIPv6Hint) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SVCBIPv6Hint) parse(b string) error {
|
func (s *SVCBIPv6Hint) parse(b string) error {
|
||||||
if strings.Contains(b, ".") {
|
|
||||||
return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4")
|
|
||||||
}
|
|
||||||
str := strings.Split(b, ",")
|
str := strings.Split(b, ",")
|
||||||
dst := make([]net.IP, len(str))
|
dst := make([]net.IP, len(str))
|
||||||
for i, e := range str {
|
for i, e := range str {
|
||||||
|
@ -618,6 +746,9 @@ func (s *SVCBIPv6Hint) parse(b string) error {
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return errors.New("dns: svcbipv6hint: bad ip")
|
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
|
dst[i] = ip
|
||||||
}
|
}
|
||||||
s.Hint = dst
|
s.Hint = dst
|
||||||
|
@ -625,8 +756,61 @@ func (s *SVCBIPv6Hint) parse(b string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
|
func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
|
||||||
|
hint := make([]net.IP, len(s.Hint))
|
||||||
|
for i, ip := range s.Hint {
|
||||||
|
hint[i] = copyIP(ip)
|
||||||
|
}
|
||||||
|
|
||||||
return &SVCBIPv6Hint{
|
return &SVCBIPv6Hint{
|
||||||
append([]net.IP(nil), s.Hint...),
|
Hint: hint,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SVCBDoHPath pair is used to indicate the URI template that the
|
||||||
|
// clients may use to construct a DNS over HTTPS URI.
|
||||||
|
//
|
||||||
|
// See RFC xxxx (https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02)
|
||||||
|
// and RFC yyyy (https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-06).
|
||||||
|
//
|
||||||
|
// A basic example of using the dohpath option together with the alpn
|
||||||
|
// option to indicate support for DNS over HTTPS on a certain path:
|
||||||
|
//
|
||||||
|
// s := new(dns.SVCB)
|
||||||
|
// s.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}
|
||||||
|
// e := new(dns.SVCBAlpn)
|
||||||
|
// e.Alpn = []string{"h2", "h3"}
|
||||||
|
// p := new(dns.SVCBDoHPath)
|
||||||
|
// p.Template = "/dns-query{?dns}"
|
||||||
|
// s.Value = append(s.Value, e, p)
|
||||||
|
//
|
||||||
|
// The parsing currently doesn't validate that Template is a valid
|
||||||
|
// RFC 6570 URI template.
|
||||||
|
type SVCBDoHPath struct {
|
||||||
|
Template string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SVCBDoHPath) Key() SVCBKey { return SVCB_DOHPATH }
|
||||||
|
func (s *SVCBDoHPath) String() string { return svcbParamToStr([]byte(s.Template)) }
|
||||||
|
func (s *SVCBDoHPath) len() int { return len(s.Template) }
|
||||||
|
func (s *SVCBDoHPath) pack() ([]byte, error) { return []byte(s.Template), nil }
|
||||||
|
|
||||||
|
func (s *SVCBDoHPath) unpack(b []byte) error {
|
||||||
|
s.Template = string(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SVCBDoHPath) parse(b string) error {
|
||||||
|
template, err := svcbParseParam(b)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dns: svcbdohpath: %w", err)
|
||||||
|
}
|
||||||
|
s.Template = string(template)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SVCBDoHPath) copy() SVCBKeyValue {
|
||||||
|
return &SVCBDoHPath{
|
||||||
|
Template: s.Template,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -634,7 +818,8 @@ func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
|
||||||
// to be in the range [SVCB_PRIVATE_LOWER, SVCB_PRIVATE_UPPER].
|
// to be in the range [SVCB_PRIVATE_LOWER, SVCB_PRIVATE_UPPER].
|
||||||
// Basic use pattern for creating a keyNNNNN option:
|
// Basic use pattern for creating a keyNNNNN option:
|
||||||
//
|
//
|
||||||
// h := &dns.HTTPS{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}}
|
// h := new(dns.HTTPS)
|
||||||
|
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||||
// e := new(dns.SVCBLocal)
|
// e := new(dns.SVCBLocal)
|
||||||
// e.KeyCode = 65400
|
// e.KeyCode = 65400
|
||||||
// e.Data = []byte("abc")
|
// e.Data = []byte("abc")
|
||||||
|
@ -645,6 +830,7 @@ type SVCBLocal struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SVCBLocal) Key() SVCBKey { return s.KeyCode }
|
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) pack() ([]byte, error) { return append([]byte(nil), s.Data...), nil }
|
||||||
func (s *SVCBLocal) len() int { return len(s.Data) }
|
func (s *SVCBLocal) len() int { return len(s.Data) }
|
||||||
|
|
||||||
|
@ -653,50 +839,10 @@ func (s *SVCBLocal) unpack(b []byte) error {
|
||||||
return nil
|
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 {
|
func (s *SVCBLocal) parse(b string) error {
|
||||||
data := make([]byte, 0, len(b))
|
data, err := svcbParseParam(b)
|
||||||
for i := 0; i < len(b); {
|
if err != nil {
|
||||||
if b[i] != '\\' {
|
return fmt.Errorf("dns: svcblocal: svcb private/experimental key %w", err)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
s.Data = data
|
s.Data = data
|
||||||
return nil
|
return nil
|
||||||
|
@ -737,3 +883,53 @@ func areSVCBPairArraysEqual(a []SVCBKeyValue, b []SVCBKeyValue) bool {
|
||||||
}
|
}
|
||||||
return true
|
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
|
||||||
|
}
|
||||||
|
|
45
svcb_test.go
45
svcb_test.go
|
@ -17,7 +17,8 @@ func TestSVCB(t *testing.T) {
|
||||||
{`ipv4hint`, `3.4.3.2,1.1.1.1`},
|
{`ipv4hint`, `3.4.3.2,1.1.1.1`},
|
||||||
{`no-default-alpn`, ``},
|
{`no-default-alpn`, ``},
|
||||||
{`ipv6hint`, `1::4:4:4:4,1::3:3:3:3`},
|
{`ipv6hint`, `1::4:4:4:4,1::3:3:3:3`},
|
||||||
{`echconfig`, `YUdWc2JHOD0=`},
|
{`ech`, `YUdWc2JHOD0=`},
|
||||||
|
{`dohpath`, `/dns-query{?dns}`},
|
||||||
{`key65000`, `4\ 3`},
|
{`key65000`, `4\ 3`},
|
||||||
{`key65001`, `\"\ `},
|
{`key65001`, `\"\ `},
|
||||||
{`key65002`, ``},
|
{`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) {
|
func TestCompareSVCB(t *testing.T) {
|
||||||
val1 := []SVCBKeyValue{
|
val1 := []SVCBKeyValue{
|
||||||
&SVCBPort{
|
&SVCBPort{
|
||||||
|
|
|
@ -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"
|
181
tsig.go
181
tsig.go
|
@ -2,7 +2,6 @@ package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
|
@ -16,14 +15,83 @@ import (
|
||||||
|
|
||||||
// HMAC hashing codes. These are transmitted as domain names.
|
// HMAC hashing codes. These are transmitted as domain names.
|
||||||
const (
|
const (
|
||||||
HmacMD5 = "hmac-md5.sig-alg.reg.int."
|
|
||||||
HmacSHA1 = "hmac-sha1."
|
HmacSHA1 = "hmac-sha1."
|
||||||
HmacSHA224 = "hmac-sha224."
|
HmacSHA224 = "hmac-sha224."
|
||||||
HmacSHA256 = "hmac-sha256."
|
HmacSHA256 = "hmac-sha256."
|
||||||
HmacSHA384 = "hmac-sha384."
|
HmacSHA384 = "hmac-sha384."
|
||||||
HmacSHA512 = "hmac-sha512."
|
HmacSHA512 = "hmac-sha512."
|
||||||
|
|
||||||
|
HmacMD5 = "hmac-md5.sig-alg.reg.int." // Deprecated: HmacMD5 is no longer supported.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TsigProvider provides the API to plug-in a custom TSIG implementation.
|
||||||
|
type TsigProvider interface {
|
||||||
|
// Generate is passed the DNS message to be signed and the partial TSIG RR. It returns the signature and nil, otherwise an error.
|
||||||
|
Generate(msg []byte, t *TSIG) ([]byte, error)
|
||||||
|
// Verify is passed the DNS message to be verified and the TSIG RR. If the signature is valid it will return nil, otherwise an error.
|
||||||
|
Verify(msg []byte, t *TSIG) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type tsigHMACProvider string
|
||||||
|
|
||||||
|
func (key tsigHMACProvider) Generate(msg []byte, t *TSIG) ([]byte, error) {
|
||||||
|
// If we barf here, the caller is to blame
|
||||||
|
rawsecret, err := fromBase64([]byte(key))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var h hash.Hash
|
||||||
|
switch CanonicalName(t.Algorithm) {
|
||||||
|
case HmacSHA1:
|
||||||
|
h = hmac.New(sha1.New, rawsecret)
|
||||||
|
case HmacSHA224:
|
||||||
|
h = hmac.New(sha256.New224, rawsecret)
|
||||||
|
case HmacSHA256:
|
||||||
|
h = hmac.New(sha256.New, rawsecret)
|
||||||
|
case HmacSHA384:
|
||||||
|
h = hmac.New(sha512.New384, rawsecret)
|
||||||
|
case HmacSHA512:
|
||||||
|
h = hmac.New(sha512.New, rawsecret)
|
||||||
|
default:
|
||||||
|
return nil, ErrKeyAlg
|
||||||
|
}
|
||||||
|
h.Write(msg)
|
||||||
|
return h.Sum(nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key tsigHMACProvider) Verify(msg []byte, t *TSIG) error {
|
||||||
|
b, err := key.Generate(msg, t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mac, err := hex.DecodeString(t.MAC)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !hmac.Equal(b, mac) {
|
||||||
|
return ErrSig
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tsigSecretProvider map[string]string
|
||||||
|
|
||||||
|
func (ts tsigSecretProvider) Generate(msg []byte, t *TSIG) ([]byte, error) {
|
||||||
|
key, ok := ts[t.Hdr.Name]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrSecret
|
||||||
|
}
|
||||||
|
return tsigHMACProvider(key).Generate(msg, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts tsigSecretProvider) Verify(msg []byte, t *TSIG) error {
|
||||||
|
key, ok := ts[t.Hdr.Name]
|
||||||
|
if !ok {
|
||||||
|
return ErrSecret
|
||||||
|
}
|
||||||
|
return tsigHMACProvider(key).Verify(msg, t)
|
||||||
|
}
|
||||||
|
|
||||||
// TSIG is the RR the holds the transaction signature of a message.
|
// TSIG is the RR the holds the transaction signature of a message.
|
||||||
// See RFC 2845 and RFC 4635.
|
// See RFC 2845 and RFC 4635.
|
||||||
type TSIG struct {
|
type TSIG struct {
|
||||||
|
@ -56,8 +124,8 @@ func (rr *TSIG) String() string {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rr *TSIG) parse(c *zlexer, origin string) *ParseError {
|
func (*TSIG) parse(c *zlexer, origin string) *ParseError {
|
||||||
panic("dns: internal error: parse should never be called on TSIG")
|
return &ParseError{err: "TSIG records do not have a presentation format"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following values must be put in wireformat, so that the MAC can be calculated.
|
// The following values must be put in wireformat, so that the MAC can be calculated.
|
||||||
|
@ -90,22 +158,20 @@ type timerWireFmt struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TsigGenerate fills out the TSIG record attached to the message.
|
// TsigGenerate fills out the TSIG record attached to the message.
|
||||||
// The message should contain
|
// The message should contain a "stub" TSIG RR with the algorithm, key name
|
||||||
// a "stub" TSIG RR with the algorithm, key name (owner name of the RR),
|
// (owner name of the RR), time fudge (defaults to 300 seconds) and the current
|
||||||
// time fudge (defaults to 300 seconds) and the current time
|
// time The TSIG MAC is saved in that Tsig RR. When TsigGenerate is called for
|
||||||
// The TSIG MAC is saved in that Tsig RR.
|
// the first time requestMAC should be set to the empty string and timersOnly to
|
||||||
// When TsigGenerate is called for the first time requestMAC is set to the empty string and
|
// false.
|
||||||
// timersOnly is false.
|
|
||||||
// If something goes wrong an error is returned, otherwise it is nil.
|
|
||||||
func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) {
|
func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) {
|
||||||
|
return TsigGenerateWithProvider(m, tsigHMACProvider(secret), requestMAC, timersOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TsigGenerateWithProvider is similar to TsigGenerate, but allows for a custom TsigProvider.
|
||||||
|
func TsigGenerateWithProvider(m *Msg, provider TsigProvider, requestMAC string, timersOnly bool) ([]byte, string, error) {
|
||||||
if m.IsTsig() == nil {
|
if m.IsTsig() == nil {
|
||||||
panic("dns: TSIG not last RR in additional")
|
panic("dns: TSIG not last RR in additional")
|
||||||
}
|
}
|
||||||
// If we barf here, the caller is to blame
|
|
||||||
rawsecret, err := fromBase64([]byte(secret))
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
rr := m.Extra[len(m.Extra)-1].(*TSIG)
|
rr := m.Extra[len(m.Extra)-1].(*TSIG)
|
||||||
m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg
|
m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg
|
||||||
|
@ -113,34 +179,29 @@ func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := tsigBuffer(mbuf, rr, requestMAC, timersOnly)
|
buf, err := tsigBuffer(mbuf, rr, requestMAC, timersOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
t := new(TSIG)
|
t := new(TSIG)
|
||||||
var h hash.Hash
|
// Copy all TSIG fields except MAC, its size, and time signed which are filled when signing.
|
||||||
switch CanonicalName(rr.Algorithm) {
|
|
||||||
case HmacMD5:
|
|
||||||
h = hmac.New(md5.New, rawsecret)
|
|
||||||
case HmacSHA1:
|
|
||||||
h = hmac.New(sha1.New, rawsecret)
|
|
||||||
case HmacSHA224:
|
|
||||||
h = hmac.New(sha256.New224, rawsecret)
|
|
||||||
case HmacSHA256:
|
|
||||||
h = hmac.New(sha256.New, rawsecret)
|
|
||||||
case HmacSHA384:
|
|
||||||
h = hmac.New(sha512.New384, rawsecret)
|
|
||||||
case HmacSHA512:
|
|
||||||
h = hmac.New(sha512.New, rawsecret)
|
|
||||||
default:
|
|
||||||
return nil, "", ErrKeyAlg
|
|
||||||
}
|
|
||||||
h.Write(buf)
|
|
||||||
// Copy all TSIG fields except MAC and its size, which are filled using the computed digest.
|
|
||||||
*t = *rr
|
*t = *rr
|
||||||
t.MAC = hex.EncodeToString(h.Sum(nil))
|
t.TimeSigned = 0
|
||||||
t.MACSize = uint16(len(t.MAC) / 2) // Size is half!
|
t.MAC = ""
|
||||||
|
t.MACSize = 0
|
||||||
|
|
||||||
|
// Sign unless there is a key or MAC validation error (RFC 8945 5.3.2)
|
||||||
|
if rr.Error != RcodeBadKey && rr.Error != RcodeBadSig {
|
||||||
|
mac, err := provider.Generate(buf, rr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
t.TimeSigned = rr.TimeSigned
|
||||||
|
t.MAC = hex.EncodeToString(mac)
|
||||||
|
t.MACSize = uint16(len(t.MAC) / 2) // Size is half!
|
||||||
|
}
|
||||||
|
|
||||||
tbuf := make([]byte, Len(t))
|
tbuf := make([]byte, Len(t))
|
||||||
off, err := PackRR(t, tbuf, 0, nil, false)
|
off, err := PackRR(t, tbuf, 0, nil, false)
|
||||||
|
@ -154,55 +215,33 @@ func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, s
|
||||||
return mbuf, t.MAC, nil
|
return mbuf, t.MAC, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TsigVerify verifies the TSIG on a message.
|
// TsigVerify verifies the TSIG on a message. If the signature does not
|
||||||
// If the signature does not validate err contains the
|
// validate the returned error contains the cause. If the signature is OK, the
|
||||||
// error, otherwise it is nil.
|
// error is nil.
|
||||||
func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
|
func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
|
||||||
return tsigVerify(msg, secret, requestMAC, timersOnly, uint64(time.Now().Unix()))
|
return tsigVerify(msg, tsigHMACProvider(secret), requestMAC, timersOnly, uint64(time.Now().Unix()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TsigVerifyWithProvider is similar to TsigVerify, but allows for a custom TsigProvider.
|
||||||
|
func TsigVerifyWithProvider(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool) error {
|
||||||
|
return tsigVerify(msg, provider, requestMAC, timersOnly, uint64(time.Now().Unix()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// actual implementation of TsigVerify, taking the current time ('now') as a parameter for the convenience of tests.
|
// actual implementation of TsigVerify, taking the current time ('now') as a parameter for the convenience of tests.
|
||||||
func tsigVerify(msg []byte, secret, requestMAC string, timersOnly bool, now uint64) error {
|
func tsigVerify(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool, now uint64) error {
|
||||||
rawsecret, err := fromBase64([]byte(secret))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Strip the TSIG from the incoming msg
|
// Strip the TSIG from the incoming msg
|
||||||
stripped, tsig, err := stripTsig(msg)
|
stripped, tsig, err := stripTsig(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
msgMAC, err := hex.DecodeString(tsig.MAC)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := tsigBuffer(stripped, tsig, requestMAC, timersOnly)
|
buf, err := tsigBuffer(stripped, tsig, requestMAC, timersOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var h hash.Hash
|
if err := provider.Verify(buf, tsig); err != nil {
|
||||||
switch CanonicalName(tsig.Algorithm) {
|
return err
|
||||||
case HmacMD5:
|
|
||||||
h = hmac.New(md5.New, rawsecret)
|
|
||||||
case HmacSHA1:
|
|
||||||
h = hmac.New(sha1.New, rawsecret)
|
|
||||||
case HmacSHA224:
|
|
||||||
h = hmac.New(sha256.New224, rawsecret)
|
|
||||||
case HmacSHA256:
|
|
||||||
h = hmac.New(sha256.New, rawsecret)
|
|
||||||
case HmacSHA384:
|
|
||||||
h = hmac.New(sha512.New384, rawsecret)
|
|
||||||
case HmacSHA512:
|
|
||||||
h = hmac.New(sha512.New, rawsecret)
|
|
||||||
default:
|
|
||||||
return ErrKeyAlg
|
|
||||||
}
|
|
||||||
h.Write(buf)
|
|
||||||
if !hmac.Equal(h.Sum(nil), msgMAC) {
|
|
||||||
return ErrSig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fudge factor works both ways. A message can arrive before it was signed because
|
// Fudge factor works both ways. A message can arrive before it was signed because
|
||||||
|
|
196
tsig_test.go
196
tsig_test.go
|
@ -3,6 +3,7 @@ package dns
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -17,7 +18,7 @@ func newTsig(algo string) *Msg {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTsig(t *testing.T) {
|
func TestTsig(t *testing.T) {
|
||||||
m := newTsig(HmacMD5)
|
m := newTsig(HmacSHA256)
|
||||||
buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
|
buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -29,7 +30,7 @@ func TestTsig(t *testing.T) {
|
||||||
|
|
||||||
// TSIG accounts for ID substitution. This means if the message ID is
|
// TSIG accounts for ID substitution. This means if the message ID is
|
||||||
// changed by a forwarder, we should still be able to verify the TSIG.
|
// changed by a forwarder, we should still be able to verify the TSIG.
|
||||||
m = newTsig(HmacMD5)
|
m = newTsig(HmacSHA256)
|
||||||
buf, _, err = TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
|
buf, _, err = TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -43,7 +44,7 @@ func TestTsig(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTsigCase(t *testing.T) {
|
func TestTsigCase(t *testing.T) {
|
||||||
m := newTsig("HmAc-mD5.sig-ALg.rEg.int.") // HmacMD5
|
m := newTsig(strings.ToUpper(HmacSHA256))
|
||||||
buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
|
buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -54,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 (
|
const (
|
||||||
// A template wire-format DNS message (in hex form) containing a TSIG RR.
|
// A template wire-format DNS message (in hex form) containing a TSIG RR.
|
||||||
// Its time signed field will be filled by tests.
|
// Its time signed field will be filled by tests.
|
||||||
|
@ -62,7 +119,7 @@ const (
|
||||||
"%012x" + // placeholder for the "time signed" field
|
"%012x" + // placeholder for the "time signed" field
|
||||||
"012c00208cf23e0081d915478a182edcea7ff48ad102948e6c7ef8e887536957d1fa5616c60000000000"
|
"012c00208cf23e0081d915478a182edcea7ff48ad102948e6c7ef8e887536957d1fa5616c60000000000"
|
||||||
// A secret (in base64 format) with which the TSIG in wireMsg will be validated
|
// A secret (in base64 format) with which the TSIG in wireMsg will be validated
|
||||||
testSecret = "NoTCJU+DMqFWywaPyxSijrDEA/eC3nK0xi3AMEZuPVk="
|
testSecret = "NoTCJU+DMqFWywaPyxSijrDEA/eC3nK0xi3AMEZuPVk="
|
||||||
// the 'time signed' field value that would make the TSIG RR valid with testSecret
|
// the 'time signed' field value that would make the TSIG RR valid with testSecret
|
||||||
timeSigned uint64 = 1594855491
|
timeSigned uint64 = 1594855491
|
||||||
)
|
)
|
||||||
|
@ -79,23 +136,23 @@ func TestTsigErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// the signature is valid but 'time signed' is too far from the "current time".
|
// the signature is valid but 'time signed' is too far from the "current time".
|
||||||
if err := tsigVerify(buildMsgData(timeSigned), testSecret, "", false, timeSigned+301); err != ErrTime {
|
if err := tsigVerify(buildMsgData(timeSigned), tsigHMACProvider(testSecret), "", false, timeSigned+301); err != ErrTime {
|
||||||
t.Fatalf("expected an error '%v' but got '%v'", ErrTime, err)
|
t.Fatalf("expected an error '%v' but got '%v'", ErrTime, err)
|
||||||
}
|
}
|
||||||
if err := tsigVerify(buildMsgData(timeSigned), testSecret, "", false, timeSigned-301); err != ErrTime {
|
if err := tsigVerify(buildMsgData(timeSigned), tsigHMACProvider(testSecret), "", false, timeSigned-301); err != ErrTime {
|
||||||
t.Fatalf("expected an error '%v' but got '%v'", ErrTime, err)
|
t.Fatalf("expected an error '%v' but got '%v'", ErrTime, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the signature is invalid and 'time signed' is too far.
|
// the signature is invalid and 'time signed' is too far.
|
||||||
// the signature should be checked first, so we should see ErrSig.
|
// the signature should be checked first, so we should see ErrSig.
|
||||||
if err := tsigVerify(buildMsgData(timeSigned+301), testSecret, "", false, timeSigned); err != ErrSig {
|
if err := tsigVerify(buildMsgData(timeSigned+301), tsigHMACProvider(testSecret), "", false, timeSigned); err != ErrSig {
|
||||||
t.Fatalf("expected an error '%v' but got '%v'", ErrSig, err)
|
t.Fatalf("expected an error '%v' but got '%v'", ErrSig, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tweak the algorithm name in the wire data, resulting in the "unknown algorithm" error.
|
// tweak the algorithm name in the wire data, resulting in the "unknown algorithm" error.
|
||||||
msgData := buildMsgData(timeSigned)
|
msgData := buildMsgData(timeSigned)
|
||||||
copy(msgData[67:], "bogus")
|
copy(msgData[67:], "bogus")
|
||||||
if err := tsigVerify(msgData, testSecret, "", false, timeSigned); err != ErrKeyAlg {
|
if err := tsigVerify(msgData, tsigHMACProvider(testSecret), "", false, timeSigned); err != ErrKeyAlg {
|
||||||
t.Fatalf("expected an error '%v' but got '%v'", ErrKeyAlg, err)
|
t.Fatalf("expected an error '%v' but got '%v'", ErrKeyAlg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +161,7 @@ func TestTsigErrors(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := tsigVerify(msgData, testSecret, "", false, timeSigned); err != ErrNoSig {
|
if err := tsigVerify(msgData, tsigHMACProvider(testSecret), "", false, timeSigned); err != ErrNoSig {
|
||||||
t.Fatalf("expected an error '%v' but got '%v'", ErrNoSig, err)
|
t.Fatalf("expected an error '%v' but got '%v'", ErrNoSig, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +177,7 @@ func TestTsigErrors(t *testing.T) {
|
||||||
if msgData, err = msg.Pack(); err != nil {
|
if msgData, err = msg.Pack(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
err = tsigVerify(msgData, testSecret, "", false, timeSigned)
|
err = tsigVerify(msgData, tsigHMACProvider(testSecret), "", false, timeSigned)
|
||||||
if err == nil || !strings.Contains(err.Error(), "overflow") {
|
if err == nil || !strings.Contains(err.Error(), "overflow") {
|
||||||
t.Errorf("expected error to contain %q, but got %v", "overflow", err)
|
t.Errorf("expected error to contain %q, but got %v", "overflow", err)
|
||||||
}
|
}
|
||||||
|
@ -160,7 +217,7 @@ func TestTsigGenerate(t *testing.T) {
|
||||||
testTSIG.OtherData = tc.otherData
|
testTSIG.OtherData = tc.otherData
|
||||||
req := &Msg{
|
req := &Msg{
|
||||||
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
|
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
|
||||||
Question: []Question{Question{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
|
Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
|
||||||
Extra: []RR{&testTSIG},
|
Extra: []RR{&testTSIG},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +276,7 @@ func TestTSIGHMAC224And384(t *testing.T) {
|
||||||
}
|
}
|
||||||
req := &Msg{
|
req := &Msg{
|
||||||
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
|
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
|
||||||
Question: []Question{Question{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
|
Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
|
||||||
Extra: []RR{&tsig},
|
Extra: []RR{&tsig},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,9 +288,122 @@ func TestTSIGHMAC224And384(t *testing.T) {
|
||||||
if mac != tc.expectedMAC {
|
if mac != tc.expectedMAC {
|
||||||
t.Fatalf("MAC doesn't match: expected '%s' but got '%s'", tc.expectedMAC, mac)
|
t.Fatalf("MAC doesn't match: expected '%s' but got '%s'", tc.expectedMAC, mac)
|
||||||
}
|
}
|
||||||
if err = tsigVerify(msgData, tc.secret, "", false, timeSigned); err != nil {
|
if err = tsigVerify(msgData, tsigHMACProvider(tc.secret), "", false, timeSigned); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const testGoodKeyName = "goodkey."
|
||||||
|
|
||||||
|
var (
|
||||||
|
errBadKey = errors.New("this is an intentional error")
|
||||||
|
testGoodMAC = []byte{0, 1, 2, 3}
|
||||||
|
)
|
||||||
|
|
||||||
|
// testProvider always generates the same MAC and only accepts the one signature
|
||||||
|
type testProvider struct {
|
||||||
|
GenerateAllKeys bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *testProvider) Generate(_ []byte, t *TSIG) ([]byte, error) {
|
||||||
|
if t.Hdr.Name == testGoodKeyName || provider.GenerateAllKeys {
|
||||||
|
return testGoodMAC, nil
|
||||||
|
}
|
||||||
|
return nil, errBadKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*testProvider) Verify(_ []byte, t *TSIG) error {
|
||||||
|
if t.Hdr.Name == testGoodKeyName {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errBadKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTsigGenerateProvider(t *testing.T) {
|
||||||
|
tables := []struct {
|
||||||
|
keyname string
|
||||||
|
mac []byte
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testGoodKeyName,
|
||||||
|
testGoodMAC,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badkey.",
|
||||||
|
nil,
|
||||||
|
errBadKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, table := range tables {
|
||||||
|
t.Run(table.keyname, func(t *testing.T) {
|
||||||
|
tsig := TSIG{
|
||||||
|
Hdr: RR_Header{Name: table.keyname, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0},
|
||||||
|
Algorithm: HmacSHA1,
|
||||||
|
TimeSigned: timeSigned,
|
||||||
|
Fudge: 300,
|
||||||
|
OrigId: 42,
|
||||||
|
}
|
||||||
|
req := &Msg{
|
||||||
|
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
|
||||||
|
Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
|
||||||
|
Extra: []RR{&tsig},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, mac, err := TsigGenerateWithProvider(req, new(testProvider), "", false)
|
||||||
|
if err != table.err {
|
||||||
|
t.Fatalf("error doesn't match: expected '%s' but got '%s'", table.err, err)
|
||||||
|
}
|
||||||
|
expectedMAC := hex.EncodeToString(table.mac)
|
||||||
|
if mac != expectedMAC {
|
||||||
|
t.Fatalf("MAC doesn't match: expected '%s' but got '%s'", table.mac, expectedMAC)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTsigVerifyProvider(t *testing.T) {
|
||||||
|
tables := []struct {
|
||||||
|
keyname string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testGoodKeyName,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badkey.",
|
||||||
|
errBadKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, table := range tables {
|
||||||
|
t.Run(table.keyname, func(t *testing.T) {
|
||||||
|
tsig := TSIG{
|
||||||
|
Hdr: RR_Header{Name: table.keyname, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0},
|
||||||
|
Algorithm: HmacSHA1,
|
||||||
|
TimeSigned: timeSigned,
|
||||||
|
Fudge: 300,
|
||||||
|
OrigId: 42,
|
||||||
|
}
|
||||||
|
req := &Msg{
|
||||||
|
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
|
||||||
|
Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
|
||||||
|
Extra: []RR{&tsig},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := &testProvider{true}
|
||||||
|
msgData, _, err := TsigGenerateWithProvider(req, provider, "", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err = tsigVerify(msgData, provider, "", false, timeSigned); err != table.err {
|
||||||
|
t.Fatalf("error doesn't match: expected '%s' but got '%s'", table.err, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
62
types.go
62
types.go
|
@ -81,6 +81,7 @@ const (
|
||||||
TypeCDNSKEY uint16 = 60
|
TypeCDNSKEY uint16 = 60
|
||||||
TypeOPENPGPKEY uint16 = 61
|
TypeOPENPGPKEY uint16 = 61
|
||||||
TypeCSYNC uint16 = 62
|
TypeCSYNC uint16 = 62
|
||||||
|
TypeZONEMD uint16 = 63
|
||||||
TypeSVCB uint16 = 64
|
TypeSVCB uint16 = 64
|
||||||
TypeHTTPS uint16 = 65
|
TypeHTTPS uint16 = 65
|
||||||
TypeSPF uint16 = 99
|
TypeSPF uint16 = 99
|
||||||
|
@ -150,6 +151,14 @@ const (
|
||||||
OpcodeUpdate = 5
|
OpcodeUpdate = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Used in ZONEMD https://tools.ietf.org/html/rfc8976
|
||||||
|
const (
|
||||||
|
ZoneMDSchemeSimple = 1
|
||||||
|
|
||||||
|
ZoneMDHashAlgSHA384 = 1
|
||||||
|
ZoneMDHashAlgSHA512 = 2
|
||||||
|
)
|
||||||
|
|
||||||
// Header is the wire format for the DNS packet header.
|
// Header is the wire format for the DNS packet header.
|
||||||
type Header struct {
|
type Header struct {
|
||||||
Id uint16
|
Id uint16
|
||||||
|
@ -245,8 +254,8 @@ type ANY struct {
|
||||||
|
|
||||||
func (rr *ANY) String() string { return rr.Hdr.String() }
|
func (rr *ANY) String() string { return rr.Hdr.String() }
|
||||||
|
|
||||||
func (rr *ANY) parse(c *zlexer, origin string) *ParseError {
|
func (*ANY) parse(c *zlexer, origin string) *ParseError {
|
||||||
panic("dns: internal error: parse should never be called on ANY")
|
return &ParseError{err: "ANY records do not have a presentation format"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NULL RR. See RFC 1035.
|
// NULL RR. See RFC 1035.
|
||||||
|
@ -260,8 +269,8 @@ func (rr *NULL) String() string {
|
||||||
return ";" + rr.Hdr.String() + rr.Data
|
return ";" + rr.Hdr.String() + rr.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rr *NULL) parse(c *zlexer, origin string) *ParseError {
|
func (*NULL) parse(c *zlexer, origin string) *ParseError {
|
||||||
panic("dns: internal error: parse should never be called on NULL")
|
return &ParseError{err: "NULL records do not have a presentation format"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CNAME RR. See RFC 1034.
|
// CNAME RR. See RFC 1034.
|
||||||
|
@ -1361,6 +1370,23 @@ func (rr *CSYNC) len(off int, compression map[string]struct{}) int {
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ZONEMD RR, from draft-ietf-dnsop-dns-zone-digest
|
||||||
|
type ZONEMD struct {
|
||||||
|
Hdr RR_Header
|
||||||
|
Serial uint32
|
||||||
|
Scheme uint8
|
||||||
|
Hash uint8
|
||||||
|
Digest string `dns:"hex"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *ZONEMD) String() string {
|
||||||
|
return rr.Hdr.String() +
|
||||||
|
strconv.Itoa(int(rr.Serial)) +
|
||||||
|
" " + strconv.Itoa(int(rr.Scheme)) +
|
||||||
|
" " + strconv.Itoa(int(rr.Hash)) +
|
||||||
|
" " + rr.Digest
|
||||||
|
}
|
||||||
|
|
||||||
// APL RR. See RFC 3123.
|
// APL RR. See RFC 3123.
|
||||||
type APL struct {
|
type APL struct {
|
||||||
Hdr RR_Header
|
Hdr RR_Header
|
||||||
|
@ -1387,13 +1413,13 @@ func (rr *APL) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// str returns presentation form of the APL prefix.
|
// str returns presentation form of the APL prefix.
|
||||||
func (p *APLPrefix) str() string {
|
func (a *APLPrefix) str() string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
if p.Negation {
|
if a.Negation {
|
||||||
sb.WriteByte('!')
|
sb.WriteByte('!')
|
||||||
}
|
}
|
||||||
|
|
||||||
switch len(p.Network.IP) {
|
switch len(a.Network.IP) {
|
||||||
case net.IPv4len:
|
case net.IPv4len:
|
||||||
sb.WriteByte('1')
|
sb.WriteByte('1')
|
||||||
case net.IPv6len:
|
case net.IPv6len:
|
||||||
|
@ -1402,20 +1428,20 @@ func (p *APLPrefix) str() string {
|
||||||
|
|
||||||
sb.WriteByte(':')
|
sb.WriteByte(':')
|
||||||
|
|
||||||
switch len(p.Network.IP) {
|
switch len(a.Network.IP) {
|
||||||
case net.IPv4len:
|
case net.IPv4len:
|
||||||
sb.WriteString(p.Network.IP.String())
|
sb.WriteString(a.Network.IP.String())
|
||||||
case net.IPv6len:
|
case net.IPv6len:
|
||||||
// add prefix for IPv4-mapped IPv6
|
// add prefix for IPv4-mapped IPv6
|
||||||
if v4 := p.Network.IP.To4(); v4 != nil {
|
if v4 := a.Network.IP.To4(); v4 != nil {
|
||||||
sb.WriteString("::ffff:")
|
sb.WriteString("::ffff:")
|
||||||
}
|
}
|
||||||
sb.WriteString(p.Network.IP.String())
|
sb.WriteString(a.Network.IP.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteByte('/')
|
sb.WriteByte('/')
|
||||||
|
|
||||||
prefix, _ := p.Network.Mask.Size()
|
prefix, _ := a.Network.Mask.Size()
|
||||||
sb.WriteString(strconv.Itoa(prefix))
|
sb.WriteString(strconv.Itoa(prefix))
|
||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
|
@ -1429,17 +1455,17 @@ func (a *APLPrefix) equals(b *APLPrefix) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy returns a copy of the APL prefix.
|
// copy returns a copy of the APL prefix.
|
||||||
func (p *APLPrefix) copy() APLPrefix {
|
func (a *APLPrefix) copy() APLPrefix {
|
||||||
return APLPrefix{
|
return APLPrefix{
|
||||||
Negation: p.Negation,
|
Negation: a.Negation,
|
||||||
Network: copyNet(p.Network),
|
Network: copyNet(a.Network),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// len returns size of the prefix in wire format.
|
// len returns size of the prefix in wire format.
|
||||||
func (p *APLPrefix) len() int {
|
func (a *APLPrefix) len() int {
|
||||||
// 4-byte header and the network address prefix (see Section 4 of RFC 3123)
|
// 4-byte header and the network address prefix (see Section 4 of RFC 3123)
|
||||||
prefix, _ := p.Network.Mask.Size()
|
prefix, _ := a.Network.Mask.Size()
|
||||||
return 4 + (prefix+7)/8
|
return 4 + (prefix+7)/8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1472,7 +1498,7 @@ func StringToTime(s string) (uint32, error) {
|
||||||
|
|
||||||
// saltToString converts a NSECX salt to uppercase and returns "-" when it is empty.
|
// saltToString converts a NSECX salt to uppercase and returns "-" when it is empty.
|
||||||
func saltToString(s string) string {
|
func saltToString(s string) string {
|
||||||
if len(s) == 0 {
|
if s == "" {
|
||||||
return "-"
|
return "-"
|
||||||
}
|
}
|
||||||
return strings.ToUpper(s)
|
return strings.ToUpper(s)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//+build ignore
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
// types_generate.go is meant to run with go generate. It will use
|
// 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
|
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||||
|
|
1
udp.go
1
udp.go
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !windows
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build linux && !appengine
|
||||||
// +build linux,!appengine
|
// +build linux,!appengine
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
|
@ -32,7 +32,9 @@ func (u *Msg) Used(rr []RR) {
|
||||||
u.Answer = make([]RR, 0, len(rr))
|
u.Answer = make([]RR, 0, len(rr))
|
||||||
}
|
}
|
||||||
for _, r := range 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)
|
u.Answer = append(u.Answer, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,28 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDynamicUpdateParsing(t *testing.T) {
|
func TestDynamicUpdateParsing(t *testing.T) {
|
||||||
prefix := "example.com. IN "
|
const prefix = "example.com. IN "
|
||||||
for _, typ := range TypeToString {
|
|
||||||
if typ == "OPT" || typ == "AXFR" || typ == "IXFR" || typ == "ANY" || typ == "TKEY" ||
|
for typ, name := range TypeToString {
|
||||||
typ == "TSIG" || typ == "ISDN" || typ == "UNSPEC" || typ == "NULL" || typ == "ATMA" ||
|
switch typ {
|
||||||
typ == "Reserved" || typ == "None" || typ == "NXT" || typ == "MAILB" || typ == "MAILA" {
|
case TypeNone, TypeReserved:
|
||||||
|
continue
|
||||||
|
case TypeANY:
|
||||||
|
// ANY is ambiguous here and ends up parsed as a CLASS.
|
||||||
|
//
|
||||||
|
// TODO(tmthrgd): Using TYPE255 here doesn't seem to work and also
|
||||||
|
// seems to fail for some other record types. Investigate.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, err := NewRR(prefix + typ); err != nil {
|
|
||||||
t.Errorf("failure to parse: %s %s: %v", prefix, typ, err)
|
s := prefix + name
|
||||||
|
if _, err := NewRR(s); err != nil {
|
||||||
|
t.Errorf("failure to parse: %s: %v", s, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s += " \\# 0"
|
||||||
|
if _, err := NewRR(s); err != nil {
|
||||||
|
t.Errorf("failure to parse: %s: %v", s, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,7 +92,7 @@ func TestRemoveRRset(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreReqAndRemovals(t *testing.T) {
|
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.
|
// We should be able to add multiple prereqs and updates.
|
||||||
m := new(Msg)
|
m := new(Msg)
|
||||||
m.SetUpdate("example.org.")
|
m.SetUpdate("example.org.")
|
||||||
|
@ -122,7 +135,7 @@ func TestPreReqAndRemovals(t *testing.T) {
|
||||||
name_used. 0 CLASS255 ANY
|
name_used. 0 CLASS255 ANY
|
||||||
name_not_used. 0 NONE ANY
|
name_not_used. 0 NONE ANY
|
||||||
rrset_used1. 0 CLASS255 A
|
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
|
rrset_not_used. 0 NONE A
|
||||||
|
|
||||||
;; AUTHORITY SECTION:
|
;; AUTHORITY SECTION:
|
||||||
|
|
|
@ -3,7 +3,7 @@ package dns
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// Version is current version of this library.
|
// Version is current version of this library.
|
||||||
var Version = v{1, 1, 34}
|
var Version = v{1, 1, 50}
|
||||||
|
|
||||||
// v holds the version of this library.
|
// v holds the version of this library.
|
||||||
type v struct {
|
type v struct {
|
||||||
|
|
28
xfr.go
28
xfr.go
|
@ -17,11 +17,22 @@ type Transfer struct {
|
||||||
DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds
|
DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds
|
||||||
ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, 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
|
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)
|
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
|
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.
|
// In performs an incoming transfer with the server in a.
|
||||||
// If you would like to set the source IP, or some other attribute
|
// 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}
|
// dnscon := &dns.Conn{Conn:con}
|
||||||
// transfer = &dns.Transfer{Conn: dnscon}
|
// transfer = &dns.Transfer{Conn: dnscon}
|
||||||
// channel, err := transfer.In(message, master)
|
// channel, err := transfer.In(message, master)
|
||||||
//
|
|
||||||
func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
|
func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
|
||||||
switch q.Question[0].Qtype {
|
switch q.Question[0].Qtype {
|
||||||
case TypeAXFR, TypeIXFR:
|
case TypeAXFR, TypeIXFR:
|
||||||
|
@ -224,12 +234,9 @@ func (t *Transfer) ReadMsg() (*Msg, error) {
|
||||||
if err := m.Unpack(p); err != nil {
|
if err := m.Unpack(p); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
|
if ts, tp := m.IsTsig(), t.tsigProvider(); ts != nil && tp != nil {
|
||||||
if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
|
|
||||||
return m, ErrSecret
|
|
||||||
}
|
|
||||||
// Need to work on the original message p, as that was used to calculate the tsig.
|
// 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
|
t.tsigRequestMAC = ts.MAC
|
||||||
}
|
}
|
||||||
return m, err
|
return m, err
|
||||||
|
@ -238,11 +245,8 @@ func (t *Transfer) ReadMsg() (*Msg, error) {
|
||||||
// WriteMsg writes a message through the transfer connection t.
|
// WriteMsg writes a message through the transfer connection t.
|
||||||
func (t *Transfer) WriteMsg(m *Msg) (err error) {
|
func (t *Transfer) WriteMsg(m *Msg) (err error) {
|
||||||
var out []byte
|
var out []byte
|
||||||
if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
|
if ts, tp := m.IsTsig(), t.tsigProvider(); ts != nil && tp != nil {
|
||||||
if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
|
out, t.tsigRequestMAC, err = TsigGenerateWithProvider(m, tp, t.tsigRequestMAC, t.tsigTimersOnly)
|
||||||
return ErrSecret
|
|
||||||
}
|
|
||||||
out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
|
|
||||||
} else {
|
} else {
|
||||||
out, err = m.Pack()
|
out, err = m.Pack()
|
||||||
}
|
}
|
||||||
|
|
130
xfr_test.go
130
xfr_test.go
|
@ -1,8 +1,6 @@
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -52,7 +50,7 @@ func TestInvalidXfr(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", InvalidXfrServer)
|
HandleFunc("miek.nl.", InvalidXfrServer)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalTCPServer(":0")
|
s, addrstr, _, err := RunLocalTCPServer(":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %s", err)
|
t.Fatalf("unable to run test server: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -78,86 +76,108 @@ func TestSingleEnvelopeXfr(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", SingleEnvelopeXfrServer)
|
HandleFunc("miek.nl.", SingleEnvelopeXfrServer)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalTCPServerWithTsig(":0", tsigSecret)
|
s, addrstr, _, err := RunLocalTCPServer(":0", func(srv *Server) {
|
||||||
|
srv.TsigSecret = tsigSecret
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %s", err)
|
t.Fatalf("unable to run test server: %s", err)
|
||||||
}
|
}
|
||||||
defer s.Shutdown()
|
defer s.Shutdown()
|
||||||
|
|
||||||
axfrTestingSuite(addrstr)
|
axfrTestingSuite(t, addrstr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiEnvelopeXfr(t *testing.T) {
|
func TestMultiEnvelopeXfr(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", MultipleEnvelopeXfrServer)
|
HandleFunc("miek.nl.", MultipleEnvelopeXfrServer)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
s, addrstr, err := RunLocalTCPServerWithTsig(":0", tsigSecret)
|
s, addrstr, _, err := RunLocalTCPServer(":0", func(srv *Server) {
|
||||||
|
srv.TsigSecret = tsigSecret
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to run test server: %s", err)
|
t.Fatalf("unable to run test server: %s", err)
|
||||||
}
|
}
|
||||||
defer s.Shutdown()
|
defer s.Shutdown()
|
||||||
|
|
||||||
axfrTestingSuite(addrstr)
|
axfrTestingSuite(t, addrstr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunLocalTCPServerWithTsig(laddr string, tsig map[string]string) (*Server, string, error) {
|
func axfrTestingSuite(t *testing.T, addrstr string) {
|
||||||
server, l, _, err := RunLocalTCPServerWithFinChanWithTsig(laddr, tsig)
|
tr := new(Transfer)
|
||||||
|
m := new(Msg)
|
||||||
|
m.SetAxfr("miek.nl.")
|
||||||
|
|
||||||
return server, l, err
|
c, err := tr.In(m, addrstr)
|
||||||
}
|
|
||||||
|
|
||||||
func RunLocalTCPServerWithFinChanWithTsig(laddr string, tsig map[string]string) (*Server, string, chan error, error) {
|
|
||||||
l, err := net.Listen("tcp", laddr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", nil, err
|
t.Fatal("failed to zone transfer in", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
server := &Server{Listener: l, ReadTimeout: time.Hour, WriteTimeout: time.Hour, TsigSecret: tsig}
|
var records []RR
|
||||||
|
for msg := range c {
|
||||||
waitLock := sync.Mutex{}
|
if msg.Error != nil {
|
||||||
waitLock.Lock()
|
t.Fatal(msg.Error)
|
||||||
server.NotifyStartedFunc = waitLock.Unlock
|
|
||||||
|
|
||||||
// See the comment in RunLocalUDPServerWithFinChan 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func axfrTestingSuite(addrstr string) func(*testing.T) {
|
|
||||||
return func(t *testing.T) {
|
|
||||||
tr := new(Transfer)
|
|
||||||
m := new(Msg)
|
|
||||||
m.SetAxfr("miek.nl.")
|
|
||||||
|
|
||||||
c, err := tr.In(m, addrstr)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to zone transfer in", err)
|
|
||||||
}
|
}
|
||||||
|
records = append(records, msg.RR...)
|
||||||
|
}
|
||||||
|
|
||||||
var records []RR
|
if len(records) != len(xfrTestData) {
|
||||||
for msg := range c {
|
t.Fatalf("bad axfr: expected %v, got %v", records, xfrTestData)
|
||||||
if msg.Error != nil {
|
}
|
||||||
t.Fatal(msg.Error)
|
|
||||||
}
|
|
||||||
records = append(records, msg.RR...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(records) != len(xfrTestData) {
|
for i, rr := range records {
|
||||||
|
if !IsDuplicate(rr, xfrTestData[i]) {
|
||||||
t.Fatalf("bad axfr: expected %v, got %v", records, xfrTestData)
|
t.Fatalf("bad axfr: expected %v, got %v", records, xfrTestData)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for i := range records {
|
func axfrTestingSuiteWithCustomTsig(t *testing.T, addrstr string, provider TsigProvider) {
|
||||||
if !IsDuplicate(records[i], xfrTestData[i]) {
|
tr := new(Transfer)
|
||||||
t.Fatalf("bad axfr: expected %v, got %v", records, xfrTestData)
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -1317,3 +1317,24 @@ func (r1 *X25) isDuplicate(_r2 RR) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r1 *ZONEMD) isDuplicate(_r2 RR) bool {
|
||||||
|
r2, ok := _r2.(*ZONEMD)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_ = r2
|
||||||
|
if r1.Serial != r2.Serial {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.Scheme != r2.Scheme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.Hash != r2.Hash {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.Digest != r2.Digest {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
52
zmsg.go
52
zmsg.go
|
@ -1118,6 +1118,26 @@ func (rr *X25) pack(msg []byte, off int, compression compressionMap, compress bo
|
||||||
return off, nil
|
return off, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rr *ZONEMD) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
||||||
|
off, err = packUint32(rr.Serial, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packUint8(rr.Scheme, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packUint8(rr.Hash, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packStringHex(rr.Digest, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
|
||||||
// unpack*() functions
|
// unpack*() functions
|
||||||
|
|
||||||
func (rr *A) unpack(msg []byte, off int) (off1 int, err error) {
|
func (rr *A) unpack(msg []byte, off int) (off1 int, err error) {
|
||||||
|
@ -2821,3 +2841,35 @@ func (rr *X25) unpack(msg []byte, off int) (off1 int, err error) {
|
||||||
}
|
}
|
||||||
return off, nil
|
return off, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rr *ZONEMD) unpack(msg []byte, off int) (off1 int, err error) {
|
||||||
|
rdStart := off
|
||||||
|
_ = rdStart
|
||||||
|
|
||||||
|
rr.Serial, off, err = unpackUint32(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
if off == len(msg) {
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
rr.Scheme, off, err = unpackUint8(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
if off == len(msg) {
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
rr.Hash, off, err = unpackUint8(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
if off == len(msg) {
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
rr.Digest, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength))
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
|
14
ztypes.go
14
ztypes.go
|
@ -82,6 +82,7 @@ var TypeToRR = map[uint16]func() RR{
|
||||||
TypeUINFO: func() RR { return new(UINFO) },
|
TypeUINFO: func() RR { return new(UINFO) },
|
||||||
TypeURI: func() RR { return new(URI) },
|
TypeURI: func() RR { return new(URI) },
|
||||||
TypeX25: func() RR { return new(X25) },
|
TypeX25: func() RR { return new(X25) },
|
||||||
|
TypeZONEMD: func() RR { return new(ZONEMD) },
|
||||||
}
|
}
|
||||||
|
|
||||||
// TypeToString is a map of strings for each RR type.
|
// TypeToString is a map of strings for each RR type.
|
||||||
|
@ -168,6 +169,7 @@ var TypeToString = map[uint16]string{
|
||||||
TypeUNSPEC: "UNSPEC",
|
TypeUNSPEC: "UNSPEC",
|
||||||
TypeURI: "URI",
|
TypeURI: "URI",
|
||||||
TypeX25: "X25",
|
TypeX25: "X25",
|
||||||
|
TypeZONEMD: "ZONEMD",
|
||||||
TypeNSAPPTR: "NSAP-PTR",
|
TypeNSAPPTR: "NSAP-PTR",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,6 +247,7 @@ func (rr *UID) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *UINFO) Header() *RR_Header { return &rr.Hdr }
|
func (rr *UINFO) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *URI) Header() *RR_Header { return &rr.Hdr }
|
func (rr *URI) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *X25) Header() *RR_Header { return &rr.Hdr }
|
func (rr *X25) Header() *RR_Header { return &rr.Hdr }
|
||||||
|
func (rr *ZONEMD) Header() *RR_Header { return &rr.Hdr }
|
||||||
|
|
||||||
// len() functions
|
// len() functions
|
||||||
func (rr *A) len(off int, compression map[string]struct{}) int {
|
func (rr *A) len(off int, compression map[string]struct{}) int {
|
||||||
|
@ -684,6 +687,14 @@ func (rr *X25) len(off int, compression map[string]struct{}) int {
|
||||||
l += len(rr.PSDNAddress) + 1
|
l += len(rr.PSDNAddress) + 1
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
func (rr *ZONEMD) len(off int, compression map[string]struct{}) int {
|
||||||
|
l := rr.Hdr.len(off, compression)
|
||||||
|
l += 4 // Serial
|
||||||
|
l++ // Scheme
|
||||||
|
l++ // Hash
|
||||||
|
l += len(rr.Digest) / 2
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
// copy() functions
|
// copy() functions
|
||||||
func (rr *A) copy() RR {
|
func (rr *A) copy() RR {
|
||||||
|
@ -936,3 +947,6 @@ func (rr *URI) copy() RR {
|
||||||
func (rr *X25) copy() RR {
|
func (rr *X25) copy() RR {
|
||||||
return &X25{rr.Hdr, rr.PSDNAddress}
|
return &X25{rr.Hdr, rr.PSDNAddress}
|
||||||
}
|
}
|
||||||
|
func (rr *ZONEMD) copy() RR {
|
||||||
|
return &ZONEMD{rr.Hdr, rr.Serial, rr.Scheme, rr.Hash, rr.Digest}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue