Compare commits
40 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 |
|
@ -15,7 +15,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
|
@ -23,10 +23,10 @@ jobs:
|
|||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
|
|
@ -7,16 +7,16 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go: [ 1.15.x, 1.16.x ]
|
||||
go: [ 1.19.x, 1.20.x ]
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
|
49
LICENSE
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
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
As this is fork of the official Go code the same license applies.
|
||||
Extensions of the original work are copyright (c) 2011 Miek Gieben
|
||||
|
|
|
@ -76,6 +76,10 @@ A not-so-up-to-date-list-that-may-be-actually-current:
|
|||
* 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.
|
||||
|
|
|
@ -19,7 +19,6 @@ type MsgAcceptFunc func(dh Header) MsgAcceptAction
|
|||
// * has more than 0 RRs in the Authority section
|
||||
//
|
||||
// * has more than 2 RRs in the Additional section
|
||||
//
|
||||
var DefaultMsgAcceptFunc MsgAcceptFunc = defaultMsgAcceptFunc
|
||||
|
||||
// MsgAcceptAction represents the action to be taken.
|
||||
|
|
57
client.go
57
client.go
|
@ -18,6 +18,18 @@ const (
|
|||
tcpIdleTimeout time.Duration = 8 * time.Second
|
||||
)
|
||||
|
||||
func isPacketConn(c net.Conn) bool {
|
||||
if _, ok := c.(net.PacketConn); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if ua, ok := c.LocalAddr().(*net.UnixAddr); ok {
|
||||
return ua.Net == "unixgram" || ua.Net == "unixpacket"
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// A Conn represents a connection to a DNS server.
|
||||
type Conn struct {
|
||||
net.Conn // a net.Conn holding the connection
|
||||
|
@ -27,6 +39,14 @@ type Conn struct {
|
|||
tsigRequestMAC string
|
||||
}
|
||||
|
||||
func (co *Conn) tsigProvider() TsigProvider {
|
||||
if co.TsigProvider != nil {
|
||||
return co.TsigProvider
|
||||
}
|
||||
// tsigSecretProvider will return ErrSecret if co.TsigSecret is nil.
|
||||
return tsigSecretProvider(co.TsigSecret)
|
||||
}
|
||||
|
||||
// A Client defines parameters for a DNS client.
|
||||
type Client struct {
|
||||
Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP)
|
||||
|
@ -165,7 +185,7 @@ func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, er
|
|||
// 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 cancelation from canceling all outstanding requests.
|
||||
// prevent one cancellation from canceling all outstanding requests.
|
||||
func (c *Client) ExchangeWithConn(m *Msg, conn *Conn) (r *Msg, rtt time.Duration, err error) {
|
||||
return c.exchangeWithConnContext(context.Background(), m, conn)
|
||||
}
|
||||
|
@ -178,7 +198,7 @@ func (c *Client) exchangeWithConnContext(ctx context.Context, m *Msg, conn *Conn
|
|||
q := m.Question[0]
|
||||
key := fmt.Sprintf("%s:%d:%d", q.Name, q.Qtype, q.Qclass)
|
||||
r, rtt, err, shared := c.group.Do(key, func() (*Msg, time.Duration, error) {
|
||||
// When we're doing singleflight we don't want one context cancelation, cancel _all_ outstanding queries.
|
||||
// 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)
|
||||
})
|
||||
|
@ -221,7 +241,7 @@ func (c *Client) exchangeContext(ctx context.Context, m *Msg, co *Conn) (r *Msg,
|
|||
return nil, 0, err
|
||||
}
|
||||
|
||||
if _, ok := co.Conn.(net.PacketConn); ok {
|
||||
if isPacketConn(co.Conn) {
|
||||
for {
|
||||
r, err = co.ReadMsg()
|
||||
// Ignore replies with mismatched IDs because they might be
|
||||
|
@ -259,15 +279,8 @@ func (co *Conn) ReadMsg() (*Msg, error) {
|
|||
return m, err
|
||||
}
|
||||
if t := m.IsTsig(); t != nil {
|
||||
if co.TsigProvider != nil {
|
||||
err = tsigVerifyProvider(p, co.TsigProvider, co.tsigRequestMAC, false)
|
||||
} else {
|
||||
if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
|
||||
return m, ErrSecret
|
||||
}
|
||||
// Need to work on the original message p, as that was used to calculate the tsig.
|
||||
err = TsigVerify(p, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false)
|
||||
}
|
||||
// Need to work on the original message p, as that was used to calculate the tsig.
|
||||
err = TsigVerifyWithProvider(p, co.tsigProvider(), co.tsigRequestMAC, false)
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
@ -282,7 +295,7 @@ func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
|
|||
err error
|
||||
)
|
||||
|
||||
if _, ok := co.Conn.(net.PacketConn); ok {
|
||||
if isPacketConn(co.Conn) {
|
||||
if co.UDPSize > MinMsgSize {
|
||||
p = make([]byte, co.UDPSize)
|
||||
} else {
|
||||
|
@ -322,7 +335,7 @@ func (co *Conn) Read(p []byte) (n int, err error) {
|
|||
return 0, ErrConnEmpty
|
||||
}
|
||||
|
||||
if _, ok := co.Conn.(net.PacketConn); ok {
|
||||
if isPacketConn(co.Conn) {
|
||||
// UDP connection
|
||||
return co.Conn.Read(p)
|
||||
}
|
||||
|
@ -344,17 +357,8 @@ func (co *Conn) Read(p []byte) (n int, err error) {
|
|||
func (co *Conn) WriteMsg(m *Msg) (err error) {
|
||||
var out []byte
|
||||
if t := m.IsTsig(); t != nil {
|
||||
mac := ""
|
||||
if co.TsigProvider != nil {
|
||||
out, mac, err = tsigGenerateProvider(m, co.TsigProvider, co.tsigRequestMAC, false)
|
||||
} else {
|
||||
if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
|
||||
return ErrSecret
|
||||
}
|
||||
out, mac, err = TsigGenerate(m, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false)
|
||||
}
|
||||
// Set for the next read, although only used in zone transfers
|
||||
co.tsigRequestMAC = mac
|
||||
// Set tsigRequestMAC for the next read, although only used in zone transfers.
|
||||
out, co.tsigRequestMAC, err = TsigGenerateWithProvider(m, co.tsigProvider(), co.tsigRequestMAC, false)
|
||||
} else {
|
||||
out, err = m.Pack()
|
||||
}
|
||||
|
@ -371,7 +375,7 @@ func (co *Conn) Write(p []byte) (int, error) {
|
|||
return 0, &Error{err: "message too large"}
|
||||
}
|
||||
|
||||
if _, ok := co.Conn.(net.PacketConn); ok {
|
||||
if isPacketConn(co.Conn) {
|
||||
return co.Conn.Write(p)
|
||||
}
|
||||
|
||||
|
@ -427,7 +431,6 @@ func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error)
|
|||
// co.WriteMsg(m)
|
||||
// in, _ := co.ReadMsg()
|
||||
// co.Close()
|
||||
//
|
||||
func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) {
|
||||
println("dns: ExchangeConn: this function is deprecated")
|
||||
co := new(Conn)
|
||||
|
|
112
client_test.go
112
client_test.go
|
@ -6,12 +6,108 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIsPacketConn(t *testing.T) {
|
||||
// UDP
|
||||
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
c, err := net.Dial("udp", addrstr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
if !isPacketConn(c) {
|
||||
t.Error("UDP connection should be a packet conn")
|
||||
}
|
||||
if !isPacketConn(struct{ *net.UDPConn }{c.(*net.UDPConn)}) {
|
||||
t.Error("UDP connection (wrapped type) should be a packet conn")
|
||||
}
|
||||
|
||||
// TCP
|
||||
s, addrstr, _, err = RunLocalTCPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
c, err = net.Dial("tcp", addrstr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
if isPacketConn(c) {
|
||||
t.Error("TCP connection should not be a packet conn")
|
||||
}
|
||||
if isPacketConn(struct{ *net.TCPConn }{c.(*net.TCPConn)}) {
|
||||
t.Error("TCP connection (wrapped type) should not be a packet conn")
|
||||
}
|
||||
|
||||
// Unix datagram
|
||||
s, addrstr, _, err = RunLocalUnixGramServer(filepath.Join(t.TempDir(), "unixgram.sock"))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
c, err = net.Dial("unixgram", addrstr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
if !isPacketConn(c) {
|
||||
t.Error("Unix datagram connection should be a packet conn")
|
||||
}
|
||||
if !isPacketConn(struct{ *net.UnixConn }{c.(*net.UnixConn)}) {
|
||||
t.Error("Unix datagram connection (wrapped type) should be a packet conn")
|
||||
}
|
||||
|
||||
// Unix Seqpacket
|
||||
shutChan, addrstr, err := RunLocalUnixSeqPacketServer(filepath.Join(t.TempDir(), "unixpacket.sock"))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
shutChan <- &struct{}{}
|
||||
}()
|
||||
c, err = net.Dial("unixpacket", addrstr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
if !isPacketConn(c) {
|
||||
t.Error("Unix datagram connection should be a packet conn")
|
||||
}
|
||||
if !isPacketConn(struct{ *net.UnixConn }{c.(*net.UnixConn)}) {
|
||||
t.Error("Unix datagram connection (wrapped type) should be a packet conn")
|
||||
}
|
||||
|
||||
// Unix stream
|
||||
s, addrstr, _, err = RunLocalUnixServer(filepath.Join(t.TempDir(), "unixstream.sock"))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
c, err = net.Dial("unix", addrstr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
if isPacketConn(c) {
|
||||
t.Error("Unix stream connection should not be a packet conn")
|
||||
}
|
||||
if isPacketConn(struct{ *net.UnixConn }{c.(*net.UnixConn)}) {
|
||||
t.Error("Unix stream connection (wrapped type) should not be a packet conn")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialUDP(t *testing.T) {
|
||||
HandleFunc("miek.nl.", HelloServer)
|
||||
defer HandleRemove("miek.nl.")
|
||||
|
@ -182,16 +278,14 @@ func TestClientSyncBadID(t *testing.T) {
|
|||
m := new(Msg)
|
||||
m.SetQuestion("miek.nl.", TypeSOA)
|
||||
|
||||
// Test with client.Exchange, the plain Exchange function is just a wrapper, so
|
||||
// we don't need to test that separately.
|
||||
c := &Client{
|
||||
Timeout: 50 * time.Millisecond,
|
||||
Timeout: 10 * time.Millisecond,
|
||||
}
|
||||
if _, _, err := c.Exchange(m, addrstr); err == nil || !isNetworkTimeout(err) {
|
||||
t.Errorf("query did not time out")
|
||||
}
|
||||
// And now with plain Exchange().
|
||||
if _, err = Exchange(m, addrstr); err == nil || !isNetworkTimeout(err) {
|
||||
t.Errorf("query did not time out")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientSyncBadThenGoodID(t *testing.T) {
|
||||
|
@ -215,14 +309,6 @@ func TestClientSyncBadThenGoodID(t *testing.T) {
|
|||
if r.Id != m.Id {
|
||||
t.Errorf("failed to get response with expected Id")
|
||||
}
|
||||
// And now with plain Exchange().
|
||||
r, err = Exchange(m, addrstr)
|
||||
if err != nil {
|
||||
t.Errorf("failed to exchange: %v", err)
|
||||
}
|
||||
if r.Id != m.Id {
|
||||
t.Errorf("failed to get response with expected Id")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientSyncTCPBadID(t *testing.T) {
|
||||
|
|
|
@ -218,6 +218,11 @@ func IsDomainName(s string) (labels int, ok bool) {
|
|||
|
||||
wasDot = false
|
||||
case '.':
|
||||
if i == 0 && len(s) > 1 {
|
||||
// leading dots are not legal except for the root zone
|
||||
return labels, false
|
||||
}
|
||||
|
||||
if wasDot {
|
||||
// two dots back to back is not legal
|
||||
return labels, false
|
||||
|
|
48
dnssec.go
48
dnssec.go
|
@ -65,6 +65,9 @@ var AlgorithmToString = map[uint8]string{
|
|||
}
|
||||
|
||||
// AlgorithmToHash is a map of algorithm crypto hash IDs to crypto.Hash's.
|
||||
// For newer algorithm that do their own hashing (i.e. ED25519) the returned value
|
||||
// is 0, implying no (external) hashing should occur. The non-exported identityHash is then
|
||||
// used.
|
||||
var AlgorithmToHash = map[uint8]crypto.Hash{
|
||||
RSAMD5: crypto.MD5, // Deprecated in RFC 6725
|
||||
DSA: crypto.SHA1,
|
||||
|
@ -74,7 +77,7 @@ var AlgorithmToHash = map[uint8]crypto.Hash{
|
|||
ECDSAP256SHA256: crypto.SHA256,
|
||||
ECDSAP384SHA384: crypto.SHA384,
|
||||
RSASHA512: crypto.SHA512,
|
||||
ED25519: crypto.Hash(0),
|
||||
ED25519: 0,
|
||||
}
|
||||
|
||||
// DNSSEC hashing algorithm codes.
|
||||
|
@ -137,12 +140,12 @@ func (k *DNSKEY) KeyTag() uint16 {
|
|||
var keytag int
|
||||
switch k.Algorithm {
|
||||
case RSAMD5:
|
||||
// Look at the bottom two bytes of the modules, which the last
|
||||
// item in the pubkey.
|
||||
// This algorithm has been deprecated, but keep this key-tag calculation.
|
||||
// Look at the bottom two bytes of the modules, which the last item in the pubkey.
|
||||
// See https://www.rfc-editor.org/errata/eid193 .
|
||||
modulus, _ := fromBase64([]byte(k.PublicKey))
|
||||
if len(modulus) > 1 {
|
||||
x := binary.BigEndian.Uint16(modulus[len(modulus)-2:])
|
||||
x := binary.BigEndian.Uint16(modulus[len(modulus)-3:])
|
||||
keytag = int(x)
|
||||
}
|
||||
default:
|
||||
|
@ -296,35 +299,20 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
|
|||
return err
|
||||
}
|
||||
|
||||
hash, ok := AlgorithmToHash[rr.Algorithm]
|
||||
if !ok {
|
||||
return ErrAlg
|
||||
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch rr.Algorithm {
|
||||
case ED25519:
|
||||
// ed25519 signs the raw message and performs hashing internally.
|
||||
// All other supported signature schemes operate over the pre-hashed
|
||||
// message, and thus ed25519 must be handled separately here.
|
||||
//
|
||||
// The raw message is passed directly into sign and crypto.Hash(0) is
|
||||
// used to signal to the crypto.Signer that the data has not been hashed.
|
||||
signature, err := sign(k, append(signdata, wire...), crypto.Hash(0), rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rr.Signature = toBase64(signature)
|
||||
return nil
|
||||
case RSAMD5, DSA, DSANSEC3SHA1:
|
||||
// See RFC 6944.
|
||||
return ErrAlg
|
||||
default:
|
||||
h := hash.New()
|
||||
h.Write(signdata)
|
||||
h.Write(wire)
|
||||
|
||||
signature, err := sign(k, h.Sum(nil), hash, rr.Algorithm)
|
||||
signature, err := sign(k, h.Sum(nil), cryptohash, rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -341,7 +329,7 @@ func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte,
|
|||
}
|
||||
|
||||
switch alg {
|
||||
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
|
||||
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, ED25519:
|
||||
return signature, nil
|
||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||
ecdsaSignature := &struct {
|
||||
|
@ -362,8 +350,6 @@ func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte,
|
|||
signature := intToBytes(ecdsaSignature.R, intlen)
|
||||
signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...)
|
||||
return signature, nil
|
||||
case ED25519:
|
||||
return signature, nil
|
||||
default:
|
||||
return nil, ErrAlg
|
||||
}
|
||||
|
@ -437,9 +423,9 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
|||
// remove the domain name and assume its ours?
|
||||
}
|
||||
|
||||
hash, ok := AlgorithmToHash[rr.Algorithm]
|
||||
if !ok {
|
||||
return ErrAlg
|
||||
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch rr.Algorithm {
|
||||
|
@ -450,10 +436,9 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
|||
return ErrKey
|
||||
}
|
||||
|
||||
h := hash.New()
|
||||
h.Write(signeddata)
|
||||
h.Write(wire)
|
||||
return rsa.VerifyPKCS1v15(pubkey, hash, h.Sum(nil), sigbuf)
|
||||
return rsa.VerifyPKCS1v15(pubkey, cryptohash, h.Sum(nil), sigbuf)
|
||||
|
||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||
pubkey := k.publicKeyECDSA()
|
||||
|
@ -465,7 +450,6 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
|||
r := new(big.Int).SetBytes(sigbuf[:len(sigbuf)/2])
|
||||
s := new(big.Int).SetBytes(sigbuf[len(sigbuf)/2:])
|
||||
|
||||
h := hash.New()
|
||||
h.Write(signeddata)
|
||||
h.Write(wire)
|
||||
if ecdsa.Verify(pubkey, h.Sum(nil), r, s) {
|
||||
|
|
|
@ -855,3 +855,16 @@ func TestParseKeyReadError(t *testing.T) {
|
|||
t.Errorf("expected a nil map, but got %v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSAMD5KeyTag(t *testing.T) {
|
||||
rr1, _ := NewRR("test. IN DNSKEY 257 3 1 AwEAAcntNdoMnY8pvyPcpDTAaiqHyAhf53XUBANq166won/fjBFvmuzhTuP5r4el/pV0tzEBL73zpoU48BqF66uiL+qRijXCySJiaBUvLNll5rpwuduAOoVpmwOmkC4fV6izHOAx/Uy8c+pYP0YR8+1P7GuTFxgnMmt9sUGtoe+la0X/ ;{id = 27461 (ksk), size = 1024b}")
|
||||
rr2, _ := NewRR("test. IN DNSKEY 257 3 1 AwEAAf0bKO/m45ylk5BlSLmQHQRBLx1m/ZUXvyPFB387bJXxnTk6so3ub97L1RQ+8bOoiRh3Qm5EaYihjco7J8b/W5WbS3tVsE79nY584RfTKT2zcZ9AoFP2XLChXxPIf/6l0H9n6sH0aBjsG8vabEIp8e06INM3CXVPiMRPPeGNa0Ub ;{id = 27461 (ksk), size = 1024b}")
|
||||
|
||||
exp := uint16(27461)
|
||||
if x := rr1.(*DNSKEY).KeyTag(); x != exp {
|
||||
t.Errorf("expected %d, got %d, as keytag for rr1", exp, x)
|
||||
}
|
||||
if x := rr2.(*DNSKEY).KeyTag(); x != exp { // yes, same key tag
|
||||
t.Errorf("expected %d, got %d, as keytag for rr2", exp, x)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ func TestTrimDomainName(t *testing.T) {
|
|||
// Paranoid tests.
|
||||
// These test shouldn't be needed but I was weary of off-by-one errors.
|
||||
// In theory, these can't happen because there are no single-letter TLDs,
|
||||
// but it is good to exercize the code this way.
|
||||
// but it is good to exercise the code this way.
|
||||
tests := []struct{ experiment, expected string }{
|
||||
{"", "@"},
|
||||
{".", "."},
|
||||
|
|
88
doc.go
88
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
|
||||
usage pattern for creating a new resource record:
|
||||
|
||||
r := new(dns.MX)
|
||||
r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 3600}
|
||||
r.Preference = 10
|
||||
r.Mx = "mx.miek.nl."
|
||||
r := new(dns.MX)
|
||||
r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 3600}
|
||||
r.Preference = 10
|
||||
r.Mx = "mx.miek.nl."
|
||||
|
||||
Or directly from a string:
|
||||
|
||||
mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.")
|
||||
mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.")
|
||||
|
||||
Or when the default origin (.) and TTL (3600) and class (IN) suit you:
|
||||
|
||||
mx, err := dns.NewRR("miek.nl MX 10 mx.miek.nl")
|
||||
mx, err := dns.NewRR("miek.nl MX 10 mx.miek.nl")
|
||||
|
||||
Or even:
|
||||
|
||||
mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek")
|
||||
mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek")
|
||||
|
||||
In the DNS messages are exchanged, these messages contain resource records
|
||||
(sets). Use pattern for creating a message:
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||
|
||||
Or when not certain if the domain name is fully qualified:
|
||||
|
||||
|
@ -45,17 +45,17 @@ records for the miek.nl. zone.
|
|||
|
||||
The following is slightly more verbose, but more flexible:
|
||||
|
||||
m1 := new(dns.Msg)
|
||||
m1.Id = dns.Id()
|
||||
m1.RecursionDesired = true
|
||||
m1.Question = make([]dns.Question, 1)
|
||||
m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET}
|
||||
m1 := new(dns.Msg)
|
||||
m1.Id = dns.Id()
|
||||
m1.RecursionDesired = true
|
||||
m1.Question = make([]dns.Question, 1)
|
||||
m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET}
|
||||
|
||||
After creating a message it can be sent. Basic use pattern for synchronous
|
||||
querying the DNS at a server configured on 127.0.0.1 and port 53:
|
||||
|
||||
c := new(dns.Client)
|
||||
in, rtt, err := c.Exchange(m1, "127.0.0.1:53")
|
||||
c := new(dns.Client)
|
||||
in, rtt, err := c.Exchange(m1, "127.0.0.1:53")
|
||||
|
||||
Suppressing multiple outstanding queries (with the same question, type and
|
||||
class) is as easy as setting:
|
||||
|
@ -72,7 +72,7 @@ and port to use for the connection:
|
|||
Port: 12345,
|
||||
Zone: "",
|
||||
}
|
||||
c.Dialer := &net.Dialer{
|
||||
c.Dialer = &net.Dialer{
|
||||
Timeout: 200 * time.Millisecond,
|
||||
LocalAddr: &laddr,
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ the Answer section:
|
|||
// do something with t.Txt
|
||||
}
|
||||
|
||||
Domain Name and TXT Character String Representations
|
||||
# Domain Name and TXT Character String Representations
|
||||
|
||||
Both domain names and TXT character strings are converted to presentation form
|
||||
both when unpacked and when converted to strings.
|
||||
|
@ -108,7 +108,7 @@ be escaped. Bytes below 32 and above 127 will be converted to \DDD form.
|
|||
For domain names, in addition to the above rules brackets, periods, spaces,
|
||||
semicolons and the at symbol are escaped.
|
||||
|
||||
DNSSEC
|
||||
# DNSSEC
|
||||
|
||||
DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It uses
|
||||
public key cryptography to sign resource records. The public keys are stored in
|
||||
|
@ -117,12 +117,12 @@ DNSKEY records and the signatures in RRSIG records.
|
|||
Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK)
|
||||
bit to a request.
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetEdns0(4096, true)
|
||||
m := new(dns.Msg)
|
||||
m.SetEdns0(4096, true)
|
||||
|
||||
Signature generation, signature verification and key generation are all supported.
|
||||
|
||||
DYNAMIC UPDATES
|
||||
# DYNAMIC UPDATES
|
||||
|
||||
Dynamic updates reuses the DNS message format, but renames three of the
|
||||
sections. Question is Zone, Answer is Prerequisite, Authority is Update, only
|
||||
|
@ -133,30 +133,30 @@ certain resource records or names in a zone to specify if resource records
|
|||
should be added or removed. The table from RFC 2136 supplemented with the Go
|
||||
DNS function shows which functions exist to specify the prerequisites.
|
||||
|
||||
3.2.4 - Table Of Metavalues Used In Prerequisite Section
|
||||
3.2.4 - Table Of Metavalues Used In Prerequisite Section
|
||||
|
||||
CLASS TYPE RDATA Meaning Function
|
||||
--------------------------------------------------------------
|
||||
ANY ANY empty Name is in use dns.NameUsed
|
||||
ANY rrset empty RRset exists (value indep) dns.RRsetUsed
|
||||
NONE ANY empty Name is not in use dns.NameNotUsed
|
||||
NONE rrset empty RRset does not exist dns.RRsetNotUsed
|
||||
zone rrset rr RRset exists (value dep) dns.Used
|
||||
CLASS TYPE RDATA Meaning Function
|
||||
--------------------------------------------------------------
|
||||
ANY ANY empty Name is in use dns.NameUsed
|
||||
ANY rrset empty RRset exists (value indep) dns.RRsetUsed
|
||||
NONE ANY empty Name is not in use dns.NameNotUsed
|
||||
NONE rrset empty RRset does not exist dns.RRsetNotUsed
|
||||
zone rrset rr RRset exists (value dep) dns.Used
|
||||
|
||||
The prerequisite section can also be left empty. If you have decided on the
|
||||
prerequisites you can tell what RRs should be added or deleted. The next table
|
||||
shows the options you have and what functions to call.
|
||||
|
||||
3.4.2.6 - Table Of Metavalues Used In Update Section
|
||||
3.4.2.6 - Table Of Metavalues Used In Update Section
|
||||
|
||||
CLASS TYPE RDATA Meaning Function
|
||||
---------------------------------------------------------------
|
||||
ANY ANY empty Delete all RRsets from name dns.RemoveName
|
||||
ANY rrset empty Delete an RRset dns.RemoveRRset
|
||||
NONE rrset rr Delete an RR from RRset dns.Remove
|
||||
zone rrset rr Add to an RRset dns.Insert
|
||||
CLASS TYPE RDATA Meaning Function
|
||||
---------------------------------------------------------------
|
||||
ANY ANY empty Delete all RRsets from name dns.RemoveName
|
||||
ANY rrset empty Delete an RRset dns.RemoveRRset
|
||||
NONE rrset rr Delete an RR from RRset dns.Remove
|
||||
zone rrset rr Add to an RRset dns.Insert
|
||||
|
||||
TRANSACTION SIGNATURE
|
||||
# TRANSACTION SIGNATURE
|
||||
|
||||
An TSIG or transaction signature adds a HMAC TSIG record to each message sent.
|
||||
The supported algorithms include: HmacSHA1, HmacSHA256 and HmacSHA512.
|
||||
|
@ -239,7 +239,7 @@ Basic use pattern validating and replying to a message that has TSIG set.
|
|||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
PRIVATE RRS
|
||||
# PRIVATE RRS
|
||||
|
||||
RFC 6895 sets aside a range of type codes for private use. This range is 65,280
|
||||
- 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these
|
||||
|
@ -248,10 +248,10 @@ can be used, before requesting an official type code from IANA.
|
|||
See https://miek.nl/2014/september/21/idn-and-private-rr-in-go-dns/ for more
|
||||
information.
|
||||
|
||||
EDNS0
|
||||
# EDNS0
|
||||
|
||||
EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated by
|
||||
RFC 6891. It defines an new RR type, the OPT RR, which is then completely
|
||||
RFC 6891. It defines a new RR type, the OPT RR, which is then completely
|
||||
abused.
|
||||
|
||||
Basic use pattern for creating an (empty) OPT RR:
|
||||
|
@ -279,9 +279,9 @@ SIG(0)
|
|||
|
||||
From RFC 2931:
|
||||
|
||||
SIG(0) provides protection for DNS transactions and requests ....
|
||||
... protection for glue records, DNS requests, protection for message headers
|
||||
on requests and responses, and protection of the overall integrity of a response.
|
||||
SIG(0) provides protection for DNS transactions and requests ....
|
||||
... protection for glue records, DNS requests, protection for message headers
|
||||
on requests and responses, and protection of the overall integrity of a response.
|
||||
|
||||
It works like TSIG, except that SIG(0) uses public key cryptography, instead of
|
||||
the shared secret approach in TSIG. Supported algorithms: ECDSAP256SHA256,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//+build ignore
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
// types_generate.go is meant to run with go generate. It will use
|
||||
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||
|
|
86
edns.go
86
edns.go
|
@ -78,7 +78,10 @@ func (rr *OPT) String() string {
|
|||
if rr.Do() {
|
||||
s += "flags: do; "
|
||||
} else {
|
||||
s += "flags: ; "
|
||||
s += "flags:; "
|
||||
}
|
||||
if rr.Hdr.Ttl&0x7FFF != 0 {
|
||||
s += fmt.Sprintf("MBZ: 0x%04x, ", rr.Hdr.Ttl&0x7FFF)
|
||||
}
|
||||
s += "udp: " + strconv.Itoa(int(rr.UDPSize()))
|
||||
|
||||
|
@ -98,6 +101,10 @@ func (rr *OPT) String() string {
|
|||
s += "\n; SUBNET: " + o.String()
|
||||
case *EDNS0_COOKIE:
|
||||
s += "\n; COOKIE: " + o.String()
|
||||
case *EDNS0_EXPIRE:
|
||||
s += "\n; EXPIRE: " + o.String()
|
||||
case *EDNS0_TCP_KEEPALIVE:
|
||||
s += "\n; KEEPALIVE: " + o.String()
|
||||
case *EDNS0_UL:
|
||||
s += "\n; UPDATE LEASE: " + o.String()
|
||||
case *EDNS0_LLQ:
|
||||
|
@ -256,7 +263,7 @@ func (e *EDNS0_NSID) copy() EDNS0 { return &EDNS0_NSID{e.Code, e.Nsid}
|
|||
// o.Hdr.Name = "."
|
||||
// o.Hdr.Rrtype = dns.TypeOPT
|
||||
// e := new(dns.EDNS0_SUBNET)
|
||||
// e.Code = dns.EDNS0SUBNET
|
||||
// e.Code = dns.EDNS0SUBNET // by default this is filled in through unpacking OPT packets (unpackDataOpt)
|
||||
// e.Family = 1 // 1 for IPv4 source address, 2 for IPv6
|
||||
// e.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6
|
||||
// e.SourceScope = 0
|
||||
|
@ -582,14 +589,17 @@ func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} }
|
|||
type EDNS0_EXPIRE struct {
|
||||
Code uint16 // Always EDNS0EXPIRE
|
||||
Expire uint32
|
||||
Empty bool // Empty is used to signal an empty Expire option in a backwards compatible way, it's not used on the wire.
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE }
|
||||
func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) }
|
||||
func (e *EDNS0_EXPIRE) copy() EDNS0 { return &EDNS0_EXPIRE{e.Code, e.Expire} }
|
||||
func (e *EDNS0_EXPIRE) copy() EDNS0 { return &EDNS0_EXPIRE{e.Code, e.Expire, e.Empty} }
|
||||
|
||||
func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
|
||||
if e.Empty {
|
||||
return []byte{}, nil
|
||||
}
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, e.Expire)
|
||||
return b, nil
|
||||
|
@ -598,15 +608,24 @@ func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
|
|||
func (e *EDNS0_EXPIRE) unpack(b []byte) error {
|
||||
if len(b) == 0 {
|
||||
// zero-length EXPIRE query, see RFC 7314 Section 2
|
||||
e.Empty = true
|
||||
return nil
|
||||
}
|
||||
if len(b) < 4 {
|
||||
return ErrBuf
|
||||
}
|
||||
e.Expire = binary.BigEndian.Uint32(b)
|
||||
e.Empty = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_EXPIRE) String() (s string) {
|
||||
if e.Empty {
|
||||
return ""
|
||||
}
|
||||
return strconv.FormatUint(uint64(e.Expire), 10)
|
||||
}
|
||||
|
||||
// The EDNS0_LOCAL option is used for local/experimental purposes. The option
|
||||
// code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND]
|
||||
// (RFC6891), although any unassigned code can actually be used. The content of
|
||||
|
@ -657,57 +676,52 @@ func (e *EDNS0_LOCAL) unpack(b []byte) error {
|
|||
// EDNS0_TCP_KEEPALIVE is an EDNS0 option that instructs the server to keep
|
||||
// the TCP connection alive. See RFC 7828.
|
||||
type EDNS0_TCP_KEEPALIVE struct {
|
||||
Code uint16 // Always EDNSTCPKEEPALIVE
|
||||
Length uint16 // the value 0 if the TIMEOUT is omitted, the value 2 if it is present;
|
||||
Timeout uint16 // an idle timeout value for the TCP connection, specified in units of 100 milliseconds, encoded in network byte order.
|
||||
Code uint16 // Always EDNSTCPKEEPALIVE
|
||||
|
||||
// Timeout is an idle timeout value for the TCP connection, specified in
|
||||
// units of 100 milliseconds, encoded in network byte order. If set to 0,
|
||||
// pack will return a nil slice.
|
||||
Timeout uint16
|
||||
|
||||
// Length is the option's length.
|
||||
// Deprecated: this field is deprecated and is always equal to 0.
|
||||
Length uint16
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
func (e *EDNS0_TCP_KEEPALIVE) Option() uint16 { return EDNS0TCPKEEPALIVE }
|
||||
|
||||
func (e *EDNS0_TCP_KEEPALIVE) pack() ([]byte, error) {
|
||||
if e.Timeout != 0 && e.Length != 2 {
|
||||
return nil, errors.New("dns: timeout specified but length is not 2")
|
||||
if e.Timeout > 0 {
|
||||
b := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(b, e.Timeout)
|
||||
return b, nil
|
||||
}
|
||||
if e.Timeout == 0 && e.Length != 0 {
|
||||
return nil, errors.New("dns: timeout not specified but length is not 0")
|
||||
}
|
||||
b := make([]byte, 4+e.Length)
|
||||
binary.BigEndian.PutUint16(b[0:], e.Code)
|
||||
binary.BigEndian.PutUint16(b[2:], e.Length)
|
||||
if e.Length == 2 {
|
||||
binary.BigEndian.PutUint16(b[4:], e.Timeout)
|
||||
}
|
||||
return b, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_TCP_KEEPALIVE) unpack(b []byte) error {
|
||||
if len(b) < 4 {
|
||||
return ErrBuf
|
||||
}
|
||||
e.Length = binary.BigEndian.Uint16(b[2:4])
|
||||
if e.Length != 0 && e.Length != 2 {
|
||||
return errors.New("dns: length mismatch, want 0/2 but got " + strconv.FormatUint(uint64(e.Length), 10))
|
||||
}
|
||||
if e.Length == 2 {
|
||||
if len(b) < 6 {
|
||||
return ErrBuf
|
||||
}
|
||||
e.Timeout = binary.BigEndian.Uint16(b[4:6])
|
||||
switch len(b) {
|
||||
case 0:
|
||||
case 2:
|
||||
e.Timeout = binary.BigEndian.Uint16(b)
|
||||
default:
|
||||
return fmt.Errorf("dns: length mismatch, want 0/2 but got %d", len(b))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_TCP_KEEPALIVE) String() (s string) {
|
||||
s = "use tcp keep-alive"
|
||||
if e.Length == 0 {
|
||||
func (e *EDNS0_TCP_KEEPALIVE) String() string {
|
||||
s := "use tcp keep-alive"
|
||||
if e.Timeout == 0 {
|
||||
s += ", timeout omitted"
|
||||
} else {
|
||||
s += fmt.Sprintf(", timeout %dms", e.Timeout*100)
|
||||
}
|
||||
return
|
||||
return s
|
||||
}
|
||||
func (e *EDNS0_TCP_KEEPALIVE) copy() EDNS0 { return &EDNS0_TCP_KEEPALIVE{e.Code, e.Length, e.Timeout} }
|
||||
|
||||
func (e *EDNS0_TCP_KEEPALIVE) copy() EDNS0 { return &EDNS0_TCP_KEEPALIVE{e.Code, e.Timeout, e.Length} }
|
||||
|
||||
// EDNS0_PADDING option is used to add padding to a request/response. The default
|
||||
// value of padding SHOULD be 0x0 but other values MAY be used, for instance if
|
||||
|
|
85
edns_test.go
85
edns_test.go
|
@ -1,6 +1,7 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
@ -192,3 +193,87 @@ func TestEDNS0_ESU(t *testing.T) {
|
|||
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.SetQuestion("miek.nl.", dns.TypeMX)
|
||||
m.RecursionDesired = true
|
||||
r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port)
|
||||
r, _, err := c.Exchange(m, net.JoinHostPort(config.Servers[0], config.Port))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ func ExampleDS() {
|
|||
zone := "miek.nl"
|
||||
m.SetQuestion(dns.Fqdn(zone), dns.TypeDNSKEY)
|
||||
m.SetEdns0(4096, true)
|
||||
r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port)
|
||||
r, _, err := c.Exchange(m, net.JoinHostPort(config.Servers[0], config.Port))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ type APAIR struct {
|
|||
func NewAPAIR() dns.PrivateRdata { return new(APAIR) }
|
||||
|
||||
func (rd *APAIR) String() string { return rd.addr[0].String() + " " + rd.addr[1].String() }
|
||||
|
||||
func (rd *APAIR) Parse(txt []string) error {
|
||||
if len(txt) != 2 {
|
||||
return errors.New("two addresses required for APAIR")
|
||||
|
@ -121,21 +122,23 @@ func (rd *APAIR) Len() int {
|
|||
func ExamplePrivateHandle() {
|
||||
dns.PrivateHandle("APAIR", TypeAPAIR, NewAPAIR)
|
||||
defer dns.PrivateHandleRemove(TypeAPAIR)
|
||||
var oldId = dns.Id
|
||||
dns.Id = func() uint16 { return 3 }
|
||||
defer func() { dns.Id = oldId }()
|
||||
|
||||
rr, err := dns.NewRR("miek.nl. APAIR (1.2.3.4 1.2.3.5)")
|
||||
if err != nil {
|
||||
log.Fatal("could not parse APAIR record: ", err)
|
||||
}
|
||||
fmt.Println(rr)
|
||||
// Output: miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5
|
||||
fmt.Println(rr) // see first line of Output below
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.Id = 12345
|
||||
m.SetQuestion("miek.nl.", TypeAPAIR)
|
||||
m.Answer = append(m.Answer, rr)
|
||||
|
||||
fmt.Println(m)
|
||||
// ;; opcode: QUERY, status: NOERROR, id: 12345
|
||||
// Output: miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5
|
||||
// ;; opcode: QUERY, status: NOERROR, id: 3
|
||||
// ;; flags: rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
|
||||
//
|
||||
// ;; QUESTION SECTION:
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -122,7 +122,7 @@ func Split(s string) []int {
|
|||
}
|
||||
|
||||
// NextLabel returns the index of the start of the next label in the
|
||||
// string s starting at offset.
|
||||
// string s starting at offset. A negative offset will cause a panic.
|
||||
// The bool end is true when the end of the string has been reached.
|
||||
// Also see PrevLabel.
|
||||
func NextLabel(s string, offset int) (i int, end bool) {
|
||||
|
|
|
@ -176,7 +176,10 @@ func TestIsDomainName(t *testing.T) {
|
|||
lab int
|
||||
}
|
||||
names := map[string]*ret{
|
||||
"..": {false, 1},
|
||||
".": {true, 1},
|
||||
"..": {false, 0},
|
||||
"double-dot..test": {false, 1},
|
||||
".leading-dot.test": {false, 0},
|
||||
"@.": {true, 1},
|
||||
"www.example.com": {true, 3},
|
||||
"www.e%ample.com": {true, 3},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !go1.11 || (!aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd)
|
||||
// +build !go1.11 !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd
|
||||
|
||||
package dns
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build go1.11 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd)
|
||||
// +build go1.11
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
||||
|
||||
|
|
18
msg.go
18
msg.go
|
@ -265,6 +265,11 @@ loop:
|
|||
|
||||
wasDot = false
|
||||
case '.':
|
||||
if i == 0 && len(s) > 1 {
|
||||
// leading dots are not legal except for the root zone
|
||||
return len(msg), ErrRdata
|
||||
}
|
||||
|
||||
if wasDot {
|
||||
// two dots back to back is not legal
|
||||
return len(msg), ErrRdata
|
||||
|
@ -675,9 +680,9 @@ func unpackRRslice(l int, msg []byte, off int) (dst1 []RR, off1 int, err error)
|
|||
|
||||
// Convert a MsgHdr to a string, with dig-like headers:
|
||||
//
|
||||
//;; opcode: QUERY, status: NOERROR, id: 48404
|
||||
// ;; opcode: QUERY, status: NOERROR, id: 48404
|
||||
//
|
||||
//;; flags: qr aa rd ra;
|
||||
// ;; flags: qr aa rd ra;
|
||||
func (h *MsgHdr) String() string {
|
||||
if h == nil {
|
||||
return "<nil> MsgHdr"
|
||||
|
@ -901,6 +906,11 @@ func (dns *Msg) String() string {
|
|||
s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", "
|
||||
s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", "
|
||||
s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
|
||||
opt := dns.IsEdns0()
|
||||
if opt != nil {
|
||||
// OPT PSEUDOSECTION
|
||||
s += opt.String() + "\n"
|
||||
}
|
||||
if len(dns.Question) > 0 {
|
||||
s += "\n;; QUESTION SECTION:\n"
|
||||
for _, r := range dns.Question {
|
||||
|
@ -923,10 +933,10 @@ func (dns *Msg) String() string {
|
|||
}
|
||||
}
|
||||
}
|
||||
if len(dns.Extra) > 0 {
|
||||
if len(dns.Extra) > 0 && (opt == nil || len(dns.Extra) > 1) {
|
||||
s += "\n;; ADDITIONAL SECTION:\n"
|
||||
for _, r := range dns.Extra {
|
||||
if r != nil {
|
||||
if r != nil && r.Header().Rrtype != TypeOPT {
|
||||
s += r.String() + "\n"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//+build ignore
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
// msg_generate.go is meant to run with go generate. It will use
|
||||
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||
|
|
|
@ -476,7 +476,7 @@ func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
|
|||
length, window, lastwindow := 0, 0, -1
|
||||
for off < len(msg) {
|
||||
if off+2 > len(msg) {
|
||||
return nsec, len(msg), &Error{err: "overflow unpacking nsecx"}
|
||||
return nsec, len(msg), &Error{err: "overflow unpacking NSEC(3)"}
|
||||
}
|
||||
window = int(msg[off])
|
||||
length = int(msg[off+1])
|
||||
|
@ -484,17 +484,17 @@ func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
|
|||
if window <= lastwindow {
|
||||
// RFC 4034: Blocks are present in the NSEC RR RDATA in
|
||||
// increasing numerical order.
|
||||
return nsec, len(msg), &Error{err: "out of order NSEC block"}
|
||||
return nsec, len(msg), &Error{err: "out of order NSEC(3) block in type bitmap"}
|
||||
}
|
||||
if length == 0 {
|
||||
// RFC 4034: Blocks with no types present MUST NOT be included.
|
||||
return nsec, len(msg), &Error{err: "empty NSEC block"}
|
||||
return nsec, len(msg), &Error{err: "empty NSEC(3) block in type bitmap"}
|
||||
}
|
||||
if length > 32 {
|
||||
return nsec, len(msg), &Error{err: "NSEC block too long"}
|
||||
return nsec, len(msg), &Error{err: "NSEC(3) block too long in type bitmap"}
|
||||
}
|
||||
if off+length > len(msg) {
|
||||
return nsec, len(msg), &Error{err: "overflowing NSEC block"}
|
||||
return nsec, len(msg), &Error{err: "overflowing NSEC(3) block in type bitmap"}
|
||||
}
|
||||
|
||||
// Walk the bytes in the window and extract the type bits
|
||||
|
@ -558,6 +558,16 @@ func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) {
|
|||
if len(bitmap) == 0 {
|
||||
return off, nil
|
||||
}
|
||||
if off > len(msg) {
|
||||
return off, &Error{err: "overflow packing nsec"}
|
||||
}
|
||||
toZero := msg[off:]
|
||||
if maxLen := typeBitMapLen(bitmap); maxLen < len(toZero) {
|
||||
toZero = toZero[:maxLen]
|
||||
}
|
||||
for i := range toZero {
|
||||
toZero[i] = 0
|
||||
}
|
||||
var lastwindow, lastlength uint16
|
||||
for _, t := range bitmap {
|
||||
window := t / 256
|
||||
|
|
|
@ -19,7 +19,8 @@ func TestPackDataNsec(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
wantOff int
|
||||
wantBytes []byte
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
|
@ -44,14 +45,14 @@ func TestPackDataNsec(t *testing.T) {
|
|||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "dns: overflow packing nsec",
|
||||
want: 31,
|
||||
wantOff: 48,
|
||||
},
|
||||
{
|
||||
name: "disordered nsec bits",
|
||||
args: args{
|
||||
bitmap: []uint16{
|
||||
8962,
|
||||
0,
|
||||
1,
|
||||
},
|
||||
msg: []byte{
|
||||
48, 48, 48, 48, 0, 0, 0, 1, 0, 0, 0, 0,
|
||||
|
@ -72,13 +73,13 @@ func TestPackDataNsec(t *testing.T) {
|
|||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "dns: nsec bits out of order",
|
||||
want: 155,
|
||||
wantOff: 155,
|
||||
},
|
||||
{
|
||||
name: "simple message with only one window",
|
||||
args: args{
|
||||
bitmap: []uint16{
|
||||
0,
|
||||
1,
|
||||
},
|
||||
msg: []byte{
|
||||
48, 48, 48, 48, 0, 0,
|
||||
|
@ -89,13 +90,33 @@ func TestPackDataNsec(t *testing.T) {
|
|||
},
|
||||
off: 0,
|
||||
},
|
||||
wantErr: false,
|
||||
want: 3,
|
||||
wantErr: false,
|
||||
wantOff: 3,
|
||||
wantBytes: []byte{0, 1, 64},
|
||||
},
|
||||
{
|
||||
name: "multiple types",
|
||||
args: args{
|
||||
bitmap: []uint16{
|
||||
TypeNS, TypeSOA, TypeRRSIG, TypeDNSKEY, TypeNSEC3PARAM,
|
||||
},
|
||||
msg: []byte{
|
||||
48, 48, 48, 48, 0, 0,
|
||||
0, 1, 0, 0, 0, 0,
|
||||
0, 0, 50, 48, 48, 48,
|
||||
48, 48, 48, 0, 54, 48,
|
||||
48, 48, 48, 0, 19, 48, 48,
|
||||
},
|
||||
off: 0,
|
||||
},
|
||||
wantErr: false,
|
||||
wantOff: 9,
|
||||
wantBytes: []byte{0, 7, 34, 0, 0, 0, 0, 2, 144},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := packDataNsec(tt.args.bitmap, tt.args.msg, tt.args.off)
|
||||
gotOff, err := packDataNsec(tt.args.bitmap, tt.args.msg, tt.args.off)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("packDataNsec() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
|
@ -104,13 +125,49 @@ func TestPackDataNsec(t *testing.T) {
|
|||
t.Errorf("packDataNsec() error msg = %v, wantErrMsg %v", err.Error(), tt.wantErrMsg)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("packDataNsec() = %v, want %v", got, tt.want)
|
||||
if gotOff != tt.wantOff {
|
||||
t.Errorf("packDataNsec() = %v, want off %v", gotOff, tt.wantOff)
|
||||
}
|
||||
if err == nil && tt.args.off < len(tt.args.msg) && gotOff < len(tt.args.msg) {
|
||||
if want, got := tt.wantBytes, tt.args.msg[tt.args.off:gotOff]; !bytes.Equal(got, want) {
|
||||
t.Errorf("packDataNsec() = %v, want bytes %v", got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackDataNsecDirtyBuffer(t *testing.T) {
|
||||
zeroBuf := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
dirtyBuf := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
off1, _ := packDataNsec([]uint16{TypeNS, TypeSOA, TypeRRSIG}, zeroBuf, 0)
|
||||
off2, _ := packDataNsec([]uint16{TypeNS, TypeSOA, TypeRRSIG}, dirtyBuf, 0)
|
||||
if off1 != off2 {
|
||||
t.Errorf("off1 %v != off2 %v", off1, off2)
|
||||
}
|
||||
if !bytes.Equal(zeroBuf[:off1], dirtyBuf[:off2]) {
|
||||
t.Errorf("dirty buffer differs from zero buffer: %v, %v", zeroBuf[:off1], dirtyBuf[:off2])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPackDataNsec(b *testing.B) {
|
||||
benches := []struct {
|
||||
name string
|
||||
types []uint16
|
||||
}{
|
||||
{"empty", nil},
|
||||
{"typical", []uint16{TypeNS, TypeSOA, TypeRRSIG, TypeDNSKEY, TypeNSEC3PARAM}},
|
||||
{"multiple_windows", []uint16{1, 300, 350, 10000, 20000}},
|
||||
}
|
||||
for _, bb := range benches {
|
||||
b.Run(bb.name, func(b *testing.B) {
|
||||
buf := make([]byte, 100)
|
||||
for n := 0; n < b.N; n++ {
|
||||
packDataNsec(bb.types, buf, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestUnpackString(t *testing.T) {
|
||||
msg := []byte("\x00abcdef\x0f\\\"ghi\x04mmm\x7f")
|
||||
msg[0] = byte(len(msg) - 1)
|
||||
|
|
|
@ -18,7 +18,7 @@ func TestRequestTruncateAnswer(t *testing.T) {
|
|||
|
||||
reply.Truncate(MinMsgSize)
|
||||
if want, got := MinMsgSize, reply.Len(); want < got {
|
||||
t.Errorf("message length should be bellow %d bytes, got %d bytes", want, got)
|
||||
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
|
||||
}
|
||||
if !reply.Truncated {
|
||||
t.Errorf("truncated bit should be set")
|
||||
|
@ -38,7 +38,7 @@ func TestRequestTruncateExtra(t *testing.T) {
|
|||
|
||||
reply.Truncate(MinMsgSize)
|
||||
if want, got := MinMsgSize, reply.Len(); want < got {
|
||||
t.Errorf("message length should be bellow %d bytes, got %d bytes", want, got)
|
||||
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
|
||||
}
|
||||
if !reply.Truncated {
|
||||
t.Errorf("truncated bit should be set")
|
||||
|
@ -62,7 +62,7 @@ func TestRequestTruncateExtraEdns0(t *testing.T) {
|
|||
|
||||
reply.Truncate(size)
|
||||
if want, got := size, reply.Len(); want < got {
|
||||
t.Errorf("message length should be bellow %d bytes, got %d bytes", want, got)
|
||||
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
|
||||
}
|
||||
if !reply.Truncated {
|
||||
t.Errorf("truncated bit should be set")
|
||||
|
@ -94,7 +94,7 @@ func TestRequestTruncateExtraRegression(t *testing.T) {
|
|||
|
||||
reply.Truncate(size)
|
||||
if want, got := size, reply.Len(); want < got {
|
||||
t.Errorf("message length should be bellow %d bytes, got %d bytes", want, got)
|
||||
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
|
||||
}
|
||||
if !reply.Truncated {
|
||||
t.Errorf("truncated bit should be set")
|
||||
|
@ -130,7 +130,7 @@ func TestTruncation(t *testing.T) {
|
|||
|
||||
copy.Truncate(bufsize)
|
||||
if want, got := bufsize, copy.Len(); want < got {
|
||||
t.Errorf("message length should be bellow %d bytes, got %d bytes", want, got)
|
||||
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ func TestRequestTruncateAnswerExact(t *testing.T) {
|
|||
|
||||
reply.Truncate(size)
|
||||
if want, got := size, reply.Len(); want < got {
|
||||
t.Errorf("message length should be bellow %d bytes, got %d bytes", want, got)
|
||||
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
|
||||
}
|
||||
if expected := 52; len(reply.Answer) != expected {
|
||||
t.Errorf("wrong number of answers; expected %d, got %d", expected, len(reply.Answer))
|
||||
|
|
|
@ -373,10 +373,10 @@ func TestNSEC(t *testing.T) {
|
|||
func TestParseLOC(t *testing.T) {
|
||||
lt := map[string]string{
|
||||
"SW1A2AA.find.me.uk. LOC 51 30 12.748 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 30 12.748 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m",
|
||||
"SW1A2AA.find.me.uk. LOC 51 0 0.0 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 00 0.000 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m",
|
||||
"SW1A2AA.find.me.uk. LOC 51 30 12.748 N 00 07 39.611 W 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 30 12.748 N 00 07 39.611 W 0m 1m 10000m 10m",
|
||||
"SW1A2AA.find.me.uk. LOC 51 0 0.0 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 00 0.000 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m",
|
||||
"SW1A2AA.find.me.uk. LOC 51 30 12.748 N 00 07 39.611 W 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 30 12.748 N 00 07 39.611 W 0m 1m 10000m 10m",
|
||||
// Exercise boundary cases
|
||||
"SW1A2AA.find.me.uk. LOC 90 0 0.0 N 180 0 0.0 W 42849672.95 90000000.00m 90000000.00m 90000000.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t90 00 0.000 N 180 00 0.000 W 42849672.95m 90000000m 90000000m 90000000m",
|
||||
"SW1A2AA.find.me.uk. LOC 90 0 0.0 N 180 0 0.0 W 42849672.95 90000000.00m 90000000.00m 90000000.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t90 00 0.000 N 180 00 0.000 W 42849672.95m 90000000m 90000000m 90000000m",
|
||||
"SW1A2AA.find.me.uk. LOC 89 59 59.999 N 179 59 59.999 W -100000 90000000.00m 90000000.00m 90000000m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t89 59 59.999 N 179 59 59.999 W -100000m 90000000m 90000000m 90000000m",
|
||||
// use float64 to have enough precision.
|
||||
"example.com. LOC 42 21 43.952 N 71 5 6.344 W -24m 1m 200m 10m": "example.com.\t3600\tIN\tLOC\t42 21 43.952 N 71 05 6.344 W -24m 1m 200m 10m",
|
||||
|
@ -532,7 +532,7 @@ func TestParseClass(t *testing.T) {
|
|||
"t.example.com. CH A 127.0.0.1": "t.example.com. 3600 CH A 127.0.0.1",
|
||||
// ClassANY can not occur in zone files
|
||||
// "t.example.com. ANY A 127.0.0.1": "t.example.com. 3600 ANY A 127.0.0.1",
|
||||
"t.example.com. NONE A 127.0.0.1": "t.example.com. 3600 NONE A 127.0.0.1",
|
||||
"t.example.com. NONE A 127.0.0.1": "t.example.com. 3600 NONE A 127.0.0.1",
|
||||
"t.example.com. CLASS255 A 127.0.0.1": "t.example.com. 3600 CLASS255 A 127.0.0.1",
|
||||
}
|
||||
for i, o := range tests {
|
||||
|
@ -1515,10 +1515,10 @@ func TestParseSSHFP(t *testing.T) {
|
|||
|
||||
func TestParseHINFO(t *testing.T) {
|
||||
dt := map[string]string{
|
||||
"example.net. HINFO A B": "example.net. 3600 IN HINFO \"A\" \"B\"",
|
||||
"example.net. HINFO A B": "example.net. 3600 IN HINFO \"A\" \"B\"",
|
||||
"example.net. HINFO \"A\" \"B\"": "example.net. 3600 IN HINFO \"A\" \"B\"",
|
||||
"example.net. HINFO A B C D E F": "example.net. 3600 IN HINFO \"A\" \"B C D E F\"",
|
||||
"example.net. HINFO AB": "example.net. 3600 IN HINFO \"AB\" \"\"",
|
||||
"example.net. HINFO AB": "example.net. 3600 IN HINFO \"AB\" \"\"",
|
||||
// "example.net. HINFO PC-Intel-700mhz \"Redhat Linux 7.1\"": "example.net. 3600 IN HINFO \"PC-Intel-700mhz\" \"Redhat Linux 7.1\"",
|
||||
// This one is recommended in Pro Bind book http://www.zytrax.com/books/dns/ch8/hinfo.html
|
||||
// but effectively, even Bind would replace it to correctly formed text when you AXFR
|
||||
|
@ -1538,9 +1538,9 @@ func TestParseHINFO(t *testing.T) {
|
|||
|
||||
func TestParseCAA(t *testing.T) {
|
||||
lt := map[string]string{
|
||||
"example.net. CAA 0 issue \"symantec.com\"": "example.net.\t3600\tIN\tCAA\t0 issue \"symantec.com\"",
|
||||
"example.net. CAA 0 issue \"symantec.com\"": "example.net.\t3600\tIN\tCAA\t0 issue \"symantec.com\"",
|
||||
"example.net. CAA 0 issuewild \"symantec.com; stuff\"": "example.net.\t3600\tIN\tCAA\t0 issuewild \"symantec.com; stuff\"",
|
||||
"example.net. CAA 128 tbs \"critical\"": "example.net.\t3600\tIN\tCAA\t128 tbs \"critical\"",
|
||||
"example.net. CAA 128 tbs \"critical\"": "example.net.\t3600\tIN\tCAA\t128 tbs \"critical\"",
|
||||
"example.net. CAA 2 auth \"0>09\\006\\010+\\006\\001\\004\\001\\214y\\002\\003\\001\\006\\009`\\134H\\001e\\003\\004\\002\\001\\004 y\\209\\012\\221r\\220\\156Q\\218\\150\\150{\\166\\245:\\231\\182%\\157:\\133\\179}\\1923r\\238\\151\\255\\128q\\145\\002\\001\\000\"": "example.net.\t3600\tIN\tCAA\t2 auth \"0>09\\006\\010+\\006\\001\\004\\001\\214y\\002\\003\\001\\006\\009`\\134H\\001e\\003\\004\\002\\001\\004 y\\209\\012\\221r\\220\\156Q\\218\\150\\150{\\166\\245:\\231\\182%\\157:\\133\\179}\\1923r\\238\\151\\255\\128q\\145\\002\\001\\000\"",
|
||||
"example.net. TYPE257 0 issue \"symantec.com\"": "example.net.\t3600\tIN\tCAA\t0 issue \"symantec.com\"",
|
||||
}
|
||||
|
@ -1636,9 +1636,25 @@ func TestParseCSYNC(t *testing.T) {
|
|||
|
||||
func TestParseSVCB(t *testing.T) {
|
||||
svcbs := map[string]string{
|
||||
`example.com. 3600 IN SVCB 0 cloudflare.com.`: `example.com. 3600 IN SVCB 0 cloudflare.com.`,
|
||||
`example.com. 3600 IN SVCB 0 cloudflare.com.`: `example.com. 3600 IN SVCB 0 cloudflare.com.`,
|
||||
`example.com. 3600 IN SVCB 65000 cloudflare.com. alpn=h2 ipv4hint=3.4.3.2`: `example.com. 3600 IN SVCB 65000 cloudflare.com. alpn="h2" ipv4hint="3.4.3.2"`,
|
||||
`example.com. 3600 IN SVCB 65000 cloudflare.com. key65000=4\ 3 key65001="\" " key65002 key65003= key65004="" key65005== key65006==\"\" key65007=\254 key65008=\032`: `example.com. 3600 IN SVCB 65000 cloudflare.com. key65000="4\ 3" key65001="\"\ " key65002="" key65003="" key65004="" key65005="=" key65006="=\"\"" key65007="\254" key65008="\ "`,
|
||||
// Explained in svcb.go "In AliasMode, records SHOULD NOT include any SvcParams,"
|
||||
`example.com. 3600 IN SVCB 0 no-default-alpn`: `example.com. 3600 IN SVCB 0 no-default-alpn.`,
|
||||
// From the specification
|
||||
`example.com. HTTPS 0 foo.example.com.`: `example.com. 3600 IN HTTPS 0 foo.example.com.`,
|
||||
`example.com. SVCB 1 .`: `example.com. 3600 IN SVCB 1 .`,
|
||||
`example.com. SVCB 16 foo.example.com. port=53`: `example.com. 3600 IN SVCB 16 foo.example.com. port="53"`,
|
||||
`example.com. SVCB 1 foo.example.com. key667=hello`: `example.com. 3600 IN SVCB 1 foo.example.com. key667="hello"`,
|
||||
`example.com. SVCB 1 foo.example.com. key667="hello\210qoo"`: `example.com. 3600 IN SVCB 1 foo.example.com. key667="hello\210qoo"`,
|
||||
`example.com. SVCB 1 foo.example.com. ipv6hint="2001:db8::1,2001:db8::53:1"`: `example.com. 3600 IN SVCB 1 foo.example.com. ipv6hint="2001:db8::1,2001:db8::53:1"`,
|
||||
`example.com. SVCB 1 example.com. ipv6hint="2001:db8::198.51.100.100"`: `example.com. 3600 IN SVCB 1 example.com. ipv6hint="2001:db8::c633:6464"`,
|
||||
`example.com. SVCB 16 foo.example.org. alpn=h2,h3-19 mandatory=ipv4hint,alpn ipv4hint=192.0.2.1`: `example.com. 3600 IN SVCB 16 foo.example.org. alpn="h2,h3-19" mandatory="ipv4hint,alpn" ipv4hint="192.0.2.1"`,
|
||||
`example.com. SVCB 16 foo.example.org. alpn="f\\\\oo\\,bar,h2"`: `example.com. 3600 IN SVCB 16 foo.example.org. alpn="f\\\092oo\\\044bar,h2"`,
|
||||
`example.com. SVCB 16 foo.example.org. alpn=f\\\092oo\092,bar,h2`: `example.com. 3600 IN SVCB 16 foo.example.org. alpn="f\\\092oo\\\044bar,h2"`,
|
||||
// From draft-ietf-add-ddr-06
|
||||
`_dns.example.net. SVCB 1 example.net. alpn=h2 dohpath=/dns-query{?dns}`: `_dns.example.net. 3600 IN SVCB 1 example.net. alpn="h2" dohpath="/dns-query{?dns}"`,
|
||||
`_dns.example.net. SVCB 1 example.net. alpn=h2 dohpath=/dns\045query{\?dns}`: `_dns.example.net. 3600 IN SVCB 1 example.net. alpn="h2" dohpath="/dns-query{?dns}"`,
|
||||
}
|
||||
for s, o := range svcbs {
|
||||
rr, err := NewRR(s)
|
||||
|
@ -1655,7 +1671,6 @@ func TestParseSVCB(t *testing.T) {
|
|||
func TestParseBadSVCB(t *testing.T) {
|
||||
header := `example.com. 3600 IN HTTPS `
|
||||
evils := []string{
|
||||
`0 . no-default-alpn`, // aliasform
|
||||
`65536 . no-default-alpn`, // bad priority
|
||||
`1 ..`, // bad domain
|
||||
`1 . no-default-alpn=1`, // value illegal
|
||||
|
@ -1682,12 +1697,18 @@ func TestParseBadSVCB(t *testing.T) {
|
|||
`1 . ipv6hint=1.1.1.1`, // not ipv6
|
||||
`1 . ipv6hint=1:1:1:1`, // not ipv6
|
||||
`1 . ipv6hint=a`, // not ipv6
|
||||
`1 . ipv6hint=`, // empty ipv6
|
||||
`1 . ipv4hint=1.1.1.1.1`, // not ipv4
|
||||
`1 . ipv4hint=::fc`, // not ipv4
|
||||
`1 . ipv4hint=..11`, // not ipv4
|
||||
`1 . ipv4hint=a`, // not ipv4
|
||||
`1 . ipv4hint=`, // empty ipv4
|
||||
`1 . port=`, // empty port
|
||||
`1 . echconfig=YUd`, // bad base64
|
||||
`1 . alpn=h\`, // unterminated escape
|
||||
`1 . alpn=h2\\.h3`, // comma-separated list with bad character
|
||||
`1 . alpn=h2,,h3`, // empty protocol identifier
|
||||
`1 . alpn=h3,`, // final protocol identifier empty
|
||||
}
|
||||
for _, o := range evils {
|
||||
_, err := NewRR(header + o)
|
||||
|
|
44
server.go
44
server.go
|
@ -18,7 +18,7 @@ import (
|
|||
const maxTCPQueries = 128
|
||||
|
||||
// aLongTimeAgo is a non-zero time, far in the past, used for
|
||||
// immediate cancelation of network operations.
|
||||
// immediate cancellation of network operations.
|
||||
var aLongTimeAgo = time.Unix(1, 0)
|
||||
|
||||
// Handler is implemented by any value that implements ServeDNS.
|
||||
|
@ -71,12 +71,12 @@ type response struct {
|
|||
tsigTimersOnly bool
|
||||
tsigStatus error
|
||||
tsigRequestMAC string
|
||||
tsigSecret map[string]string // the tsig secrets
|
||||
udp net.PacketConn // i/o connection if UDP was used
|
||||
tcp net.Conn // i/o connection if TCP was used
|
||||
udpSession *SessionUDP // oob data to get egress interface right
|
||||
pcSession net.Addr // address to use when writing to a generic net.PacketConn
|
||||
writer Writer // writer to output the raw DNS bits
|
||||
tsigProvider TsigProvider
|
||||
udp net.PacketConn // i/o connection if UDP was used
|
||||
tcp net.Conn // i/o connection if TCP was used
|
||||
udpSession *SessionUDP // oob data to get egress interface right
|
||||
pcSession net.Addr // address to use when writing to a generic net.PacketConn
|
||||
writer Writer // writer to output the raw DNS bits
|
||||
}
|
||||
|
||||
// handleRefused returns a HandlerFunc that returns REFUSED for every request it gets.
|
||||
|
@ -211,6 +211,8 @@ type Server struct {
|
|||
WriteTimeout time.Duration
|
||||
// TCP idle timeout for multiple queries, if nil, defaults to 8 * time.Second (RFC 5966).
|
||||
IdleTimeout func() time.Duration
|
||||
// An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
|
||||
TsigProvider TsigProvider
|
||||
// Secret(s) for Tsig map[<zonename>]<base64 secret>. The zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2).
|
||||
TsigSecret map[string]string
|
||||
// If NotifyStartedFunc is set it is called once the server has started listening.
|
||||
|
@ -238,6 +240,16 @@ type Server struct {
|
|||
udpPool sync.Pool
|
||||
}
|
||||
|
||||
func (srv *Server) tsigProvider() TsigProvider {
|
||||
if srv.TsigProvider != nil {
|
||||
return srv.TsigProvider
|
||||
}
|
||||
if srv.TsigSecret != nil {
|
||||
return tsigSecretProvider(srv.TsigSecret)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) isStarted() bool {
|
||||
srv.lock.RLock()
|
||||
started := srv.started
|
||||
|
@ -526,7 +538,7 @@ func (srv *Server) serveUDP(l net.PacketConn) error {
|
|||
|
||||
// Serve a new TCP connection.
|
||||
func (srv *Server) serveTCPConn(wg *sync.WaitGroup, rw net.Conn) {
|
||||
w := &response{tsigSecret: srv.TsigSecret, tcp: rw}
|
||||
w := &response{tsigProvider: srv.tsigProvider(), tcp: rw}
|
||||
if srv.DecorateWriter != nil {
|
||||
w.writer = srv.DecorateWriter(w)
|
||||
} else {
|
||||
|
@ -581,7 +593,7 @@ func (srv *Server) serveTCPConn(wg *sync.WaitGroup, rw net.Conn) {
|
|||
|
||||
// Serve a new UDP request.
|
||||
func (srv *Server) serveUDPPacket(wg *sync.WaitGroup, m []byte, u net.PacketConn, udpSession *SessionUDP, pcSession net.Addr) {
|
||||
w := &response{tsigSecret: srv.TsigSecret, udp: u, udpSession: udpSession, pcSession: pcSession}
|
||||
w := &response{tsigProvider: srv.tsigProvider(), udp: u, udpSession: udpSession, pcSession: pcSession}
|
||||
if srv.DecorateWriter != nil {
|
||||
w.writer = srv.DecorateWriter(w)
|
||||
} else {
|
||||
|
@ -632,15 +644,11 @@ func (srv *Server) serveDNS(m []byte, w *response) {
|
|||
}
|
||||
|
||||
w.tsigStatus = nil
|
||||
if w.tsigSecret != nil {
|
||||
if w.tsigProvider != nil {
|
||||
if t := req.IsTsig(); t != nil {
|
||||
if secret, ok := w.tsigSecret[t.Hdr.Name]; ok {
|
||||
w.tsigStatus = TsigVerify(m, secret, "", false)
|
||||
} else {
|
||||
w.tsigStatus = ErrSecret
|
||||
}
|
||||
w.tsigStatus = TsigVerifyWithProvider(m, w.tsigProvider, "", false)
|
||||
w.tsigTimersOnly = false
|
||||
w.tsigRequestMAC = req.Extra[len(req.Extra)-1].(*TSIG).MAC
|
||||
w.tsigRequestMAC = t.MAC
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -718,9 +726,9 @@ func (w *response) WriteMsg(m *Msg) (err error) {
|
|||
}
|
||||
|
||||
var data []byte
|
||||
if w.tsigSecret != nil { // if no secrets, dont check for the tsig (which is a longer check)
|
||||
if w.tsigProvider != nil { // if no provider, dont check for the tsig (which is a longer check)
|
||||
if t := m.IsTsig(); t != nil {
|
||||
data, w.tsigRequestMAC, err = TsigGenerate(m, w.tsigSecret[t.Hdr.Name], w.tsigRequestMAC, w.tsigTimersOnly)
|
||||
data, w.tsigRequestMAC, err = TsigGenerateWithProvider(m, w.tsigProvider, w.tsigRequestMAC, w.tsigTimersOnly)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -67,12 +67,14 @@ func AnotherHelloServer(w ResponseWriter, req *Msg) {
|
|||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
|
||||
pc, err := net.ListenPacket("udp", laddr)
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
func RunLocalServer(pc net.PacketConn, l net.Listener, opts ...func(*Server)) (*Server, string, chan error, error) {
|
||||
server := &Server{
|
||||
PacketConn: pc,
|
||||
Listener: l,
|
||||
|
||||
ReadTimeout: time.Hour,
|
||||
WriteTimeout: time.Hour,
|
||||
}
|
||||
server := &Server{PacketConn: pc, ReadTimeout: time.Hour, WriteTimeout: time.Hour}
|
||||
|
||||
waitLock := sync.Mutex{}
|
||||
waitLock.Lock()
|
||||
|
@ -82,6 +84,18 @@ func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, ch
|
|||
opt(server)
|
||||
}
|
||||
|
||||
var (
|
||||
addr string
|
||||
closer io.Closer
|
||||
)
|
||||
if l != nil {
|
||||
addr = l.Addr().String()
|
||||
closer = l
|
||||
} else {
|
||||
addr = pc.LocalAddr().String()
|
||||
closer = pc
|
||||
}
|
||||
|
||||
// fin must be buffered so the goroutine below won't block
|
||||
// forever if fin is never read from. This always happens
|
||||
// if the channel is discarded and can happen in TestShutdownUDP.
|
||||
|
@ -89,11 +103,20 @@ func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, ch
|
|||
|
||||
go func() {
|
||||
fin <- server.ActivateAndServe()
|
||||
pc.Close()
|
||||
closer.Close()
|
||||
}()
|
||||
|
||||
waitLock.Lock()
|
||||
return server, pc.LocalAddr().String(), fin, nil
|
||||
return server, addr, fin, nil
|
||||
}
|
||||
|
||||
func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
|
||||
pc, err := net.ListenPacket("udp", laddr)
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
}
|
||||
|
||||
return RunLocalServer(pc, nil, opts...)
|
||||
}
|
||||
|
||||
func RunLocalPacketConnServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
|
||||
|
@ -109,26 +132,7 @@ func RunLocalTCPServer(laddr string, opts ...func(*Server)) (*Server, string, ch
|
|||
return nil, "", nil, err
|
||||
}
|
||||
|
||||
server := &Server{Listener: l, ReadTimeout: time.Hour, WriteTimeout: time.Hour}
|
||||
|
||||
waitLock := sync.Mutex{}
|
||||
waitLock.Lock()
|
||||
server.NotifyStartedFunc = waitLock.Unlock
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(server)
|
||||
}
|
||||
|
||||
// See the comment in RunLocalUDPServer as to why fin must be buffered.
|
||||
fin := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
fin <- server.ActivateAndServe()
|
||||
l.Close()
|
||||
}()
|
||||
|
||||
waitLock.Lock()
|
||||
return server, l.Addr().String(), fin, nil
|
||||
return RunLocalServer(nil, l, opts...)
|
||||
}
|
||||
|
||||
func RunLocalTLSServer(laddr string, config *tls.Config) (*Server, string, chan error, error) {
|
||||
|
@ -137,6 +141,39 @@ func RunLocalTLSServer(laddr string, config *tls.Config) (*Server, string, chan
|
|||
})
|
||||
}
|
||||
|
||||
func RunLocalUnixServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
|
||||
l, err := net.Listen("unix", laddr)
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
}
|
||||
|
||||
return RunLocalServer(nil, l, opts...)
|
||||
}
|
||||
|
||||
func RunLocalUnixGramServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
|
||||
pc, err := net.ListenPacket("unixgram", laddr)
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
}
|
||||
|
||||
return RunLocalServer(pc, nil, opts...)
|
||||
}
|
||||
|
||||
func RunLocalUnixSeqPacketServer(laddr string) (chan interface{}, string, error) {
|
||||
pc, err := net.Listen("unixpacket", laddr)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
shutdownChan := make(chan interface{})
|
||||
go func() {
|
||||
pc.Accept()
|
||||
<-shutdownChan
|
||||
}()
|
||||
|
||||
return shutdownChan, pc.Addr().String(), nil
|
||||
}
|
||||
|
||||
func TestServing(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
|
|
51
sig0.go
51
sig0.go
|
@ -3,6 +3,7 @@ package dns
|
|||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
|
@ -38,18 +39,17 @@ func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) {
|
|||
}
|
||||
buf = buf[:off:cap(buf)]
|
||||
|
||||
hash, ok := AlgorithmToHash[rr.Algorithm]
|
||||
if !ok {
|
||||
return nil, ErrAlg
|
||||
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hasher := hash.New()
|
||||
// Write SIG rdata
|
||||
hasher.Write(buf[len(mbuf)+1+2+2+4+2:])
|
||||
h.Write(buf[len(mbuf)+1+2+2+4+2:])
|
||||
// Write message
|
||||
hasher.Write(buf[:len(mbuf)])
|
||||
h.Write(buf[:len(mbuf)])
|
||||
|
||||
signature, err := sign(k, hasher.Sum(nil), hash, rr.Algorithm)
|
||||
signature, err := sign(k, h.Sum(nil), cryptohash, rr.Algorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -82,20 +82,10 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
|
|||
return ErrKey
|
||||
}
|
||||
|
||||
var hash crypto.Hash
|
||||
switch rr.Algorithm {
|
||||
case RSASHA1:
|
||||
hash = crypto.SHA1
|
||||
case RSASHA256, ECDSAP256SHA256:
|
||||
hash = crypto.SHA256
|
||||
case ECDSAP384SHA384:
|
||||
hash = crypto.SHA384
|
||||
case RSASHA512:
|
||||
hash = crypto.SHA512
|
||||
default:
|
||||
return ErrAlg
|
||||
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hasher := hash.New()
|
||||
|
||||
buflen := len(buf)
|
||||
qdc := binary.BigEndian.Uint16(buf[4:])
|
||||
|
@ -103,7 +93,6 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
|
|||
auc := binary.BigEndian.Uint16(buf[8:])
|
||||
adc := binary.BigEndian.Uint16(buf[10:])
|
||||
offset := headerSize
|
||||
var err error
|
||||
for i := uint16(0); i < qdc && offset < buflen; i++ {
|
||||
_, offset, err = UnpackDomainName(buf, offset)
|
||||
if err != nil {
|
||||
|
@ -166,21 +155,21 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
|
|||
return &Error{err: "signer name doesn't match key name"}
|
||||
}
|
||||
sigend := offset
|
||||
hasher.Write(buf[sigstart:sigend])
|
||||
hasher.Write(buf[:10])
|
||||
hasher.Write([]byte{
|
||||
h.Write(buf[sigstart:sigend])
|
||||
h.Write(buf[:10])
|
||||
h.Write([]byte{
|
||||
byte((adc - 1) << 8),
|
||||
byte(adc - 1),
|
||||
})
|
||||
hasher.Write(buf[12:bodyend])
|
||||
h.Write(buf[12:bodyend])
|
||||
|
||||
hashed := hasher.Sum(nil)
|
||||
hashed := h.Sum(nil)
|
||||
sig := buf[sigend:]
|
||||
switch k.Algorithm {
|
||||
case RSASHA1, RSASHA256, RSASHA512:
|
||||
pk := k.publicKeyRSA()
|
||||
if pk != nil {
|
||||
return rsa.VerifyPKCS1v15(pk, hash, hashed, sig)
|
||||
return rsa.VerifyPKCS1v15(pk, cryptohash, hashed, sig)
|
||||
}
|
||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||
pk := k.publicKeyECDSA()
|
||||
|
@ -192,6 +181,14 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
|
|||
}
|
||||
return ErrSig
|
||||
}
|
||||
case ED25519:
|
||||
pk := k.publicKeyED25519()
|
||||
if pk != nil {
|
||||
if ed25519.Verify(pk, hashed, sig) {
|
||||
return nil
|
||||
}
|
||||
return ErrSig
|
||||
}
|
||||
}
|
||||
return ErrKeyAlg
|
||||
}
|
||||
|
|
18
sig0_test.go
18
sig0_test.go
|
@ -12,7 +12,7 @@ func TestSIG0(t *testing.T) {
|
|||
}
|
||||
m := new(Msg)
|
||||
m.SetQuestion("example.org.", TypeSOA)
|
||||
for _, alg := range []uint8{ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256, RSASHA512} {
|
||||
for _, alg := range []uint8{ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256, RSASHA512, ED25519} {
|
||||
algstr := AlgorithmToString[alg]
|
||||
keyrr := new(KEY)
|
||||
keyrr.Hdr.Name = algstr + "."
|
||||
|
@ -21,7 +21,7 @@ func TestSIG0(t *testing.T) {
|
|||
keyrr.Algorithm = alg
|
||||
keysize := 512
|
||||
switch alg {
|
||||
case ECDSAP256SHA256:
|
||||
case ECDSAP256SHA256, ED25519:
|
||||
keysize = 256
|
||||
case ECDSAP384SHA384:
|
||||
keysize = 384
|
||||
|
@ -30,7 +30,7 @@ func TestSIG0(t *testing.T) {
|
|||
}
|
||||
pk, err := keyrr.Generate(keysize)
|
||||
if err != nil {
|
||||
t.Errorf("failed to generate key for “%s”: %v", algstr, err)
|
||||
t.Errorf("failed to generate key for %q: %v", algstr, err)
|
||||
continue
|
||||
}
|
||||
now := uint32(time.Now().Unix())
|
||||
|
@ -45,16 +45,16 @@ func TestSIG0(t *testing.T) {
|
|||
sigrr.SignerName = keyrr.Hdr.Name
|
||||
mb, err := sigrr.Sign(pk.(crypto.Signer), m)
|
||||
if err != nil {
|
||||
t.Errorf("failed to sign message using “%s”: %v", algstr, err)
|
||||
t.Errorf("failed to sign message using %q: %v", algstr, err)
|
||||
continue
|
||||
}
|
||||
m := new(Msg)
|
||||
if err := m.Unpack(mb); err != nil {
|
||||
t.Errorf("failed to unpack message signed using “%s”: %v", algstr, err)
|
||||
t.Errorf("failed to unpack message signed using %q: %v", algstr, err)
|
||||
continue
|
||||
}
|
||||
if len(m.Extra) != 1 {
|
||||
t.Errorf("missing SIG for message signed using “%s”", algstr)
|
||||
t.Errorf("missing SIG for message signed using %q", algstr)
|
||||
continue
|
||||
}
|
||||
var sigrrwire *SIG
|
||||
|
@ -71,20 +71,20 @@ func TestSIG0(t *testing.T) {
|
|||
id = "sigrrwire"
|
||||
}
|
||||
if err := rr.Verify(keyrr, mb); err != nil {
|
||||
t.Errorf("failed to verify “%s” signed SIG(%s): %v", algstr, id, err)
|
||||
t.Errorf("failed to verify %q signed SIG(%s): %v", algstr, id, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
mb[13]++
|
||||
if err := sigrr.Verify(keyrr, mb); err == nil {
|
||||
t.Errorf("verify succeeded on an altered message using “%s”", algstr)
|
||||
t.Errorf("verify succeeded on an altered message using %q", algstr)
|
||||
continue
|
||||
}
|
||||
sigrr.Expiration = 2
|
||||
sigrr.Inception = 1
|
||||
mb, _ = sigrr.Sign(pk.(crypto.Signer), m)
|
||||
if err := sigrr.Verify(keyrr, mb); err == nil {
|
||||
t.Errorf("verify succeeded on an expired message using “%s”", algstr)
|
||||
t.Errorf("verify succeeded on an expired message using %q", algstr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
344
svcb.go
344
svcb.go
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
@ -13,16 +14,18 @@ import (
|
|||
// SVCBKey is the type of the keys used in the SVCB RR.
|
||||
type SVCBKey uint16
|
||||
|
||||
// Keys defined in draft-ietf-dnsop-svcb-https-01 Section 12.3.2.
|
||||
// Keys defined in draft-ietf-dnsop-svcb-https-08 Section 14.3.2.
|
||||
const (
|
||||
SVCB_MANDATORY SVCBKey = 0
|
||||
SVCB_ALPN SVCBKey = 1
|
||||
SVCB_NO_DEFAULT_ALPN SVCBKey = 2
|
||||
SVCB_PORT SVCBKey = 3
|
||||
SVCB_IPV4HINT SVCBKey = 4
|
||||
SVCB_ECHCONFIG SVCBKey = 5
|
||||
SVCB_IPV6HINT SVCBKey = 6
|
||||
svcb_RESERVED SVCBKey = 65535
|
||||
SVCB_MANDATORY SVCBKey = iota
|
||||
SVCB_ALPN
|
||||
SVCB_NO_DEFAULT_ALPN
|
||||
SVCB_PORT
|
||||
SVCB_IPV4HINT
|
||||
SVCB_ECHCONFIG
|
||||
SVCB_IPV6HINT
|
||||
SVCB_DOHPATH // draft-ietf-add-svcb-dns-02 Section 9
|
||||
|
||||
svcb_RESERVED SVCBKey = 65535
|
||||
)
|
||||
|
||||
var svcbKeyToStringMap = map[SVCBKey]string{
|
||||
|
@ -31,8 +34,9 @@ var svcbKeyToStringMap = map[SVCBKey]string{
|
|||
SVCB_NO_DEFAULT_ALPN: "no-default-alpn",
|
||||
SVCB_PORT: "port",
|
||||
SVCB_IPV4HINT: "ipv4hint",
|
||||
SVCB_ECHCONFIG: "echconfig",
|
||||
SVCB_ECHCONFIG: "ech",
|
||||
SVCB_IPV6HINT: "ipv6hint",
|
||||
SVCB_DOHPATH: "dohpath",
|
||||
}
|
||||
|
||||
var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap)
|
||||
|
@ -167,10 +171,14 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
|
|||
}
|
||||
l, _ = c.Next()
|
||||
}
|
||||
|
||||
// "In AliasMode, records SHOULD NOT include any SvcParams, and recipients MUST
|
||||
// ignore any SvcParams that are present."
|
||||
// However, we don't check rr.Priority == 0 && len(xs) > 0 here
|
||||
// It is the responsibility of the user of the library to check this.
|
||||
// This is to encourage the fixing of the source of this error.
|
||||
|
||||
rr.Value = xs
|
||||
if rr.Priority == 0 && len(xs) > 0 {
|
||||
return &ParseError{l.token, "SVCB aliasform can't have values", l}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -191,6 +199,8 @@ func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
|
|||
return new(SVCBECHConfig)
|
||||
case SVCB_IPV6HINT:
|
||||
return new(SVCBIPv6Hint)
|
||||
case SVCB_DOHPATH:
|
||||
return new(SVCBDoHPath)
|
||||
case svcb_RESERVED:
|
||||
return nil
|
||||
default:
|
||||
|
@ -200,16 +210,24 @@ func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
|
|||
}
|
||||
}
|
||||
|
||||
// SVCB RR. See RFC xxxx (https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-01).
|
||||
// SVCB RR. See RFC xxxx (https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-08).
|
||||
//
|
||||
// NOTE: The HTTPS/SVCB RFCs are in the draft stage.
|
||||
// The API, including constants and types related to SVCBKeyValues, may
|
||||
// change in future versions in accordance with the latest drafts.
|
||||
type SVCB struct {
|
||||
Hdr RR_Header
|
||||
Priority uint16
|
||||
Priority uint16 // If zero, Value must be empty or discarded by the user of this library
|
||||
Target string `dns:"domain-name"`
|
||||
Value []SVCBKeyValue `dns:"pairs"` // Value must be empty if Priority is zero.
|
||||
Value []SVCBKeyValue `dns:"pairs"`
|
||||
}
|
||||
|
||||
// HTTPS RR. Everything valid for SVCB applies to HTTPS as well.
|
||||
// Except that the HTTPS record is intended for use with the HTTP and HTTPS protocols.
|
||||
//
|
||||
// NOTE: The HTTPS/SVCB RFCs are in the draft stage.
|
||||
// The API, including constants and types related to SVCBKeyValues, may
|
||||
// change in future versions in accordance with the latest drafts.
|
||||
type HTTPS struct {
|
||||
SVCB
|
||||
}
|
||||
|
@ -235,15 +253,29 @@ type SVCBKeyValue interface {
|
|||
}
|
||||
|
||||
// SVCBMandatory pair adds to required keys that must be interpreted for the RR
|
||||
// to be functional.
|
||||
// to be functional. If ignored, the whole RRSet must be ignored.
|
||||
// "port" and "no-default-alpn" are mandatory by default if present,
|
||||
// so they shouldn't be included here.
|
||||
//
|
||||
// It is incumbent upon the user of this library to reject the RRSet if
|
||||
// or avoid constructing such an RRSet that:
|
||||
// - "mandatory" is included as one of the keys of mandatory
|
||||
// - no key is listed multiple times in mandatory
|
||||
// - all keys listed in mandatory are present
|
||||
// - escape sequences are not used in mandatory
|
||||
// - mandatory, when present, lists at least one key
|
||||
//
|
||||
// Basic use pattern for creating a mandatory option:
|
||||
//
|
||||
// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
|
||||
// e := new(dns.SVCBMandatory)
|
||||
// e.Code = []uint16{65403}
|
||||
// e.Code = []uint16{dns.SVCB_ALPN}
|
||||
// s.Value = append(s.Value, e)
|
||||
// t := new(dns.SVCBAlpn)
|
||||
// t.Alpn = []string{"xmpp-client"}
|
||||
// s.Value = append(s.Value, t)
|
||||
type SVCBMandatory struct {
|
||||
Code []SVCBKey // Must not include mandatory
|
||||
Code []SVCBKey
|
||||
}
|
||||
|
||||
func (*SVCBMandatory) Key() SVCBKey { return SVCB_MANDATORY }
|
||||
|
@ -302,7 +334,8 @@ func (s *SVCBMandatory) copy() SVCBKeyValue {
|
|||
}
|
||||
|
||||
// SVCBAlpn pair is used to list supported connection protocols.
|
||||
// Protocol ids can be found at:
|
||||
// The user of this library must ensure that at least one protocol is listed when alpn is present.
|
||||
// Protocol IDs can be found at:
|
||||
// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
|
||||
// Basic use pattern for creating an alpn option:
|
||||
//
|
||||
|
@ -310,13 +343,57 @@ func (s *SVCBMandatory) copy() SVCBKeyValue {
|
|||
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||
// e := new(dns.SVCBAlpn)
|
||||
// e.Alpn = []string{"h2", "http/1.1"}
|
||||
// h.Value = append(o.Value, e)
|
||||
// h.Value = append(h.Value, e)
|
||||
type SVCBAlpn struct {
|
||||
Alpn []string
|
||||
}
|
||||
|
||||
func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN }
|
||||
func (s *SVCBAlpn) String() string { return strings.Join(s.Alpn, ",") }
|
||||
func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN }
|
||||
|
||||
func (s *SVCBAlpn) String() string {
|
||||
// An ALPN value is a comma-separated list of values, each of which can be
|
||||
// an arbitrary binary value. In order to allow parsing, the comma and
|
||||
// backslash characters are themselves escaped.
|
||||
//
|
||||
// However, this escaping is done in addition to the normal escaping which
|
||||
// happens in zone files, meaning that these values must be
|
||||
// double-escaped. This looks terrible, so if you see a never-ending
|
||||
// sequence of backslash in a zone file this may be why.
|
||||
//
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-08#appendix-A.1
|
||||
var str strings.Builder
|
||||
for i, alpn := range s.Alpn {
|
||||
// 4*len(alpn) is the worst case where we escape every character in the alpn as \123, plus 1 byte for the ',' separating the alpn from others
|
||||
str.Grow(4*len(alpn) + 1)
|
||||
if i > 0 {
|
||||
str.WriteByte(',')
|
||||
}
|
||||
for j := 0; j < len(alpn); j++ {
|
||||
e := alpn[j]
|
||||
if ' ' > e || e > '~' {
|
||||
str.WriteString(escapeByte(e))
|
||||
continue
|
||||
}
|
||||
switch e {
|
||||
// We escape a few characters which may confuse humans or parsers.
|
||||
case '"', ';', ' ':
|
||||
str.WriteByte('\\')
|
||||
str.WriteByte(e)
|
||||
// The comma and backslash characters themselves must be
|
||||
// doubly-escaped. We use `\\` for the first backslash and
|
||||
// the escaped numeric value for the other value. We especially
|
||||
// don't want a comma in the output.
|
||||
case ',':
|
||||
str.WriteString(`\\\044`)
|
||||
case '\\':
|
||||
str.WriteString(`\\\092`)
|
||||
default:
|
||||
str.WriteByte(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
|
||||
func (s *SVCBAlpn) pack() ([]byte, error) {
|
||||
// Liberally estimate the size of an alpn as 10 octets
|
||||
|
@ -351,7 +428,47 @@ func (s *SVCBAlpn) unpack(b []byte) error {
|
|||
}
|
||||
|
||||
func (s *SVCBAlpn) parse(b string) error {
|
||||
s.Alpn = strings.Split(b, ",")
|
||||
if len(b) == 0 {
|
||||
s.Alpn = []string{}
|
||||
return nil
|
||||
}
|
||||
|
||||
alpn := []string{}
|
||||
a := []byte{}
|
||||
for p := 0; p < len(b); {
|
||||
c, q := nextByte(b, p)
|
||||
if q == 0 {
|
||||
return errors.New("dns: svcbalpn: unterminated escape")
|
||||
}
|
||||
p += q
|
||||
// If we find a comma, we have finished reading an alpn.
|
||||
if c == ',' {
|
||||
if len(a) == 0 {
|
||||
return errors.New("dns: svcbalpn: empty protocol identifier")
|
||||
}
|
||||
alpn = append(alpn, string(a))
|
||||
a = []byte{}
|
||||
continue
|
||||
}
|
||||
// If it's a backslash, we need to handle a comma-separated list.
|
||||
if c == '\\' {
|
||||
dc, dq := nextByte(b, p)
|
||||
if dq == 0 {
|
||||
return errors.New("dns: svcbalpn: unterminated escape decoding comma-separated list")
|
||||
}
|
||||
if dc != '\\' && dc != ',' {
|
||||
return errors.New("dns: svcbalpn: bad escaped character decoding comma-separated list")
|
||||
}
|
||||
p += dq
|
||||
c = dc
|
||||
}
|
||||
a = append(a, c)
|
||||
}
|
||||
// Add the final alpn.
|
||||
if len(a) == 0 {
|
||||
return errors.New("dns: svcbalpn: last protocol identifier empty")
|
||||
}
|
||||
s.Alpn = append(alpn, string(a))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -370,9 +487,13 @@ func (s *SVCBAlpn) copy() SVCBKeyValue {
|
|||
}
|
||||
|
||||
// SVCBNoDefaultAlpn pair signifies no support for default connection protocols.
|
||||
// Should be used in conjunction with alpn.
|
||||
// Basic use pattern for creating a no-default-alpn option:
|
||||
//
|
||||
// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
|
||||
// t := new(dns.SVCBAlpn)
|
||||
// t.Alpn = []string{"xmpp-client"}
|
||||
// s.Value = append(s.Value, t)
|
||||
// e := new(dns.SVCBNoDefaultAlpn)
|
||||
// s.Value = append(s.Value, e)
|
||||
type SVCBNoDefaultAlpn struct{}
|
||||
|
@ -385,14 +506,14 @@ func (*SVCBNoDefaultAlpn) len() int { return 0 }
|
|||
|
||||
func (*SVCBNoDefaultAlpn) unpack(b []byte) error {
|
||||
if len(b) != 0 {
|
||||
return errors.New("dns: svcbnodefaultalpn: no_default_alpn must have no value")
|
||||
return errors.New("dns: svcbnodefaultalpn: no-default-alpn must have no value")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*SVCBNoDefaultAlpn) parse(b string) error {
|
||||
if b != "" {
|
||||
return errors.New("dns: svcbnodefaultalpn: no_default_alpn must have no value")
|
||||
return errors.New("dns: svcbnodefaultalpn: no-default-alpn must have no value")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -442,15 +563,15 @@ func (s *SVCBPort) parse(b string) error {
|
|||
// to the hinted IP address may be terminated and a new connection may be opened.
|
||||
// Basic use pattern for creating an ipv4hint option:
|
||||
//
|
||||
// h := new(dns.HTTPS)
|
||||
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||
// e := new(dns.SVCBIPv4Hint)
|
||||
// e.Hint = []net.IP{net.IPv4(1,1,1,1).To4()}
|
||||
// h := new(dns.HTTPS)
|
||||
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||
// e := new(dns.SVCBIPv4Hint)
|
||||
// e.Hint = []net.IP{net.IPv4(1,1,1,1).To4()}
|
||||
//
|
||||
// Or
|
||||
// Or
|
||||
//
|
||||
// e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()}
|
||||
// h.Value = append(h.Value, e)
|
||||
// e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()}
|
||||
// h.Value = append(h.Value, e)
|
||||
type SVCBIPv4Hint struct {
|
||||
Hint []net.IP
|
||||
}
|
||||
|
@ -523,7 +644,7 @@ func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
|
|||
}
|
||||
|
||||
// SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx].
|
||||
// Basic use pattern for creating an echconfig option:
|
||||
// Basic use pattern for creating an ech option:
|
||||
//
|
||||
// h := new(dns.HTTPS)
|
||||
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||
|
@ -531,7 +652,7 @@ func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
|
|||
// e.ECH = []byte{0xfe, 0x08, ...}
|
||||
// h.Value = append(h.Value, e)
|
||||
type SVCBECHConfig struct {
|
||||
ECH []byte
|
||||
ECH []byte // Specifically ECHConfigList including the redundant length prefix
|
||||
}
|
||||
|
||||
func (*SVCBECHConfig) Key() SVCBKey { return SVCB_ECHCONFIG }
|
||||
|
@ -555,7 +676,7 @@ func (s *SVCBECHConfig) unpack(b []byte) error {
|
|||
func (s *SVCBECHConfig) parse(b string) error {
|
||||
x, err := fromBase64([]byte(b))
|
||||
if err != nil {
|
||||
return errors.New("dns: svcbechconfig: bad base64 echconfig")
|
||||
return errors.New("dns: svcbech: bad base64 ech")
|
||||
}
|
||||
s.ECH = x
|
||||
return nil
|
||||
|
@ -618,9 +739,6 @@ func (s *SVCBIPv6Hint) String() string {
|
|||
}
|
||||
|
||||
func (s *SVCBIPv6Hint) parse(b string) error {
|
||||
if strings.Contains(b, ".") {
|
||||
return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4")
|
||||
}
|
||||
str := strings.Split(b, ",")
|
||||
dst := make([]net.IP, len(str))
|
||||
for i, e := range str {
|
||||
|
@ -628,6 +746,9 @@ func (s *SVCBIPv6Hint) parse(b string) error {
|
|||
if ip == nil {
|
||||
return errors.New("dns: svcbipv6hint: bad ip")
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4-mapped-ipv6")
|
||||
}
|
||||
dst[i] = ip
|
||||
}
|
||||
s.Hint = dst
|
||||
|
@ -645,6 +766,54 @@ func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
|
|||
}
|
||||
}
|
||||
|
||||
// SVCBDoHPath pair is used to indicate the URI template that the
|
||||
// clients may use to construct a DNS over HTTPS URI.
|
||||
//
|
||||
// See RFC xxxx (https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02)
|
||||
// and RFC yyyy (https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-06).
|
||||
//
|
||||
// A basic example of using the dohpath option together with the alpn
|
||||
// option to indicate support for DNS over HTTPS on a certain path:
|
||||
//
|
||||
// s := new(dns.SVCB)
|
||||
// s.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}
|
||||
// e := new(dns.SVCBAlpn)
|
||||
// e.Alpn = []string{"h2", "h3"}
|
||||
// p := new(dns.SVCBDoHPath)
|
||||
// p.Template = "/dns-query{?dns}"
|
||||
// s.Value = append(s.Value, e, p)
|
||||
//
|
||||
// The parsing currently doesn't validate that Template is a valid
|
||||
// RFC 6570 URI template.
|
||||
type SVCBDoHPath struct {
|
||||
Template string
|
||||
}
|
||||
|
||||
func (*SVCBDoHPath) Key() SVCBKey { return SVCB_DOHPATH }
|
||||
func (s *SVCBDoHPath) String() string { return svcbParamToStr([]byte(s.Template)) }
|
||||
func (s *SVCBDoHPath) len() int { return len(s.Template) }
|
||||
func (s *SVCBDoHPath) pack() ([]byte, error) { return []byte(s.Template), nil }
|
||||
|
||||
func (s *SVCBDoHPath) unpack(b []byte) error {
|
||||
s.Template = string(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBDoHPath) parse(b string) error {
|
||||
template, err := svcbParseParam(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dns: svcbdohpath: %w", err)
|
||||
}
|
||||
s.Template = string(template)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBDoHPath) copy() SVCBKeyValue {
|
||||
return &SVCBDoHPath{
|
||||
Template: s.Template,
|
||||
}
|
||||
}
|
||||
|
||||
// SVCBLocal pair is intended for experimental/private use. The key is recommended
|
||||
// to be in the range [SVCB_PRIVATE_LOWER, SVCB_PRIVATE_UPPER].
|
||||
// Basic use pattern for creating a keyNNNNN option:
|
||||
|
@ -661,6 +830,7 @@ type SVCBLocal struct {
|
|||
}
|
||||
|
||||
func (s *SVCBLocal) Key() SVCBKey { return s.KeyCode }
|
||||
func (s *SVCBLocal) String() string { return svcbParamToStr(s.Data) }
|
||||
func (s *SVCBLocal) pack() ([]byte, error) { return append([]byte(nil), s.Data...), nil }
|
||||
func (s *SVCBLocal) len() int { return len(s.Data) }
|
||||
|
||||
|
@ -669,50 +839,10 @@ func (s *SVCBLocal) unpack(b []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBLocal) String() string {
|
||||
var str strings.Builder
|
||||
str.Grow(4 * len(s.Data))
|
||||
for _, e := range s.Data {
|
||||
if ' ' <= e && e <= '~' {
|
||||
switch e {
|
||||
case '"', ';', ' ', '\\':
|
||||
str.WriteByte('\\')
|
||||
str.WriteByte(e)
|
||||
default:
|
||||
str.WriteByte(e)
|
||||
}
|
||||
} else {
|
||||
str.WriteString(escapeByte(e))
|
||||
}
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
|
||||
func (s *SVCBLocal) parse(b string) error {
|
||||
data := make([]byte, 0, len(b))
|
||||
for i := 0; i < len(b); {
|
||||
if b[i] != '\\' {
|
||||
data = append(data, b[i])
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if i+1 == len(b) {
|
||||
return errors.New("dns: svcblocal: svcb private/experimental key escape unterminated")
|
||||
}
|
||||
if isDigit(b[i+1]) {
|
||||
if i+3 < len(b) && isDigit(b[i+2]) && isDigit(b[i+3]) {
|
||||
a, err := strconv.ParseUint(b[i+1:i+4], 10, 8)
|
||||
if err == nil {
|
||||
i += 4
|
||||
data = append(data, byte(a))
|
||||
continue
|
||||
}
|
||||
}
|
||||
return errors.New("dns: svcblocal: svcb private/experimental key bad escaped octet")
|
||||
} else {
|
||||
data = append(data, b[i+1])
|
||||
i += 2
|
||||
}
|
||||
data, err := svcbParseParam(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dns: svcblocal: svcb private/experimental key %w", err)
|
||||
}
|
||||
s.Data = data
|
||||
return nil
|
||||
|
@ -753,3 +883,53 @@ func areSVCBPairArraysEqual(a []SVCBKeyValue, b []SVCBKeyValue) bool {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// svcbParamStr converts the value of an SVCB parameter into a DNS presentation-format string.
|
||||
func svcbParamToStr(s []byte) string {
|
||||
var str strings.Builder
|
||||
str.Grow(4 * len(s))
|
||||
for _, e := range s {
|
||||
if ' ' <= e && e <= '~' {
|
||||
switch e {
|
||||
case '"', ';', ' ', '\\':
|
||||
str.WriteByte('\\')
|
||||
str.WriteByte(e)
|
||||
default:
|
||||
str.WriteByte(e)
|
||||
}
|
||||
} else {
|
||||
str.WriteString(escapeByte(e))
|
||||
}
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
|
||||
// svcbParseParam parses a DNS presentation-format string into an SVCB parameter value.
|
||||
func svcbParseParam(b string) ([]byte, error) {
|
||||
data := make([]byte, 0, len(b))
|
||||
for i := 0; i < len(b); {
|
||||
if b[i] != '\\' {
|
||||
data = append(data, b[i])
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if i+1 == len(b) {
|
||||
return nil, errors.New("escape unterminated")
|
||||
}
|
||||
if isDigit(b[i+1]) {
|
||||
if i+3 < len(b) && isDigit(b[i+2]) && isDigit(b[i+3]) {
|
||||
a, err := strconv.ParseUint(b[i+1:i+4], 10, 8)
|
||||
if err == nil {
|
||||
i += 4
|
||||
data = append(data, byte(a))
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil, errors.New("bad escaped octet")
|
||||
} else {
|
||||
data = append(data, b[i+1])
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
|
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`},
|
||||
{`no-default-alpn`, ``},
|
||||
{`ipv6hint`, `1::4:4:4:4,1::3:3:3:3`},
|
||||
{`echconfig`, `YUdWc2JHOD0=`},
|
||||
{`ech`, `YUdWc2JHOD0=`},
|
||||
{`dohpath`, `/dns-query{?dns}`},
|
||||
{`key65000`, `4\ 3`},
|
||||
{`key65001`, `\"\ `},
|
||||
{`key65002`, ``},
|
||||
|
@ -94,6 +95,48 @@ func TestDecodeBadSVCB(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPresentationSVCBAlpn(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
"h2": "h2",
|
||||
"http": "http",
|
||||
"\xfa": `\250`,
|
||||
"some\"other,chars": `some\"other\\\044chars`,
|
||||
}
|
||||
for input, want := range tests {
|
||||
e := new(SVCBAlpn)
|
||||
e.Alpn = []string{input}
|
||||
if e.String() != want {
|
||||
t.Errorf("improper conversion with String(), wanted %v got %v", want, e.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSVCBAlpn(t *testing.T) {
|
||||
tests := map[string][]string{
|
||||
`. 1 IN SVCB 10 one.test. alpn=h2`: {"h2"},
|
||||
`. 2 IN SVCB 20 two.test. alpn=h2,h3-19`: {"h2", "h3-19"},
|
||||
`. 3 IN SVCB 30 three.test. alpn="f\\\\oo\\,bar,h2"`: {`f\oo,bar`, "h2"},
|
||||
`. 4 IN SVCB 40 four.test. alpn="part1,part2,part3\\,part4\\\\"`: {"part1", "part2", `part3,part4\`},
|
||||
`. 5 IN SVCB 50 five.test. alpn=part1\,\p\a\r\t2\044part3\092,part4\092\\`: {"part1", "part2", `part3,part4\`},
|
||||
}
|
||||
for s, v := range tests {
|
||||
rr, err := NewRR(s)
|
||||
if err != nil {
|
||||
t.Error("failed to parse RR: ", err)
|
||||
continue
|
||||
}
|
||||
alpn := rr.(*SVCB).Value[0].(*SVCBAlpn).Alpn
|
||||
if len(v) != len(alpn) {
|
||||
t.Fatalf("parsing alpn failed, wanted %v got %v", v, alpn)
|
||||
}
|
||||
for i := range v {
|
||||
if v[i] != alpn[i] {
|
||||
t.Fatalf("parsing alpn failed, wanted %v got %v", v, alpn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareSVCB(t *testing.T) {
|
||||
val1 := []SVCBKeyValue{
|
||||
&SVCBPort{
|
||||
|
|
1
tools.go
1
tools.go
|
@ -1,3 +1,4 @@
|
|||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
// We include our tool dependencies for `go generate` here to ensure they're
|
||||
|
|
44
tsig.go
44
tsig.go
|
@ -74,6 +74,24 @@ func (key tsigHMACProvider) Verify(msg []byte, t *TSIG) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type tsigSecretProvider map[string]string
|
||||
|
||||
func (ts tsigSecretProvider) Generate(msg []byte, t *TSIG) ([]byte, error) {
|
||||
key, ok := ts[t.Hdr.Name]
|
||||
if !ok {
|
||||
return nil, ErrSecret
|
||||
}
|
||||
return tsigHMACProvider(key).Generate(msg, t)
|
||||
}
|
||||
|
||||
func (ts tsigSecretProvider) Verify(msg []byte, t *TSIG) error {
|
||||
key, ok := ts[t.Hdr.Name]
|
||||
if !ok {
|
||||
return ErrSecret
|
||||
}
|
||||
return tsigHMACProvider(key).Verify(msg, t)
|
||||
}
|
||||
|
||||
// TSIG is the RR the holds the transaction signature of a message.
|
||||
// See RFC 2845 and RFC 4635.
|
||||
type TSIG struct {
|
||||
|
@ -140,18 +158,17 @@ type timerWireFmt struct {
|
|||
}
|
||||
|
||||
// TsigGenerate fills out the TSIG record attached to the message.
|
||||
// The message should contain
|
||||
// a "stub" TSIG RR with the algorithm, key name (owner name of the RR),
|
||||
// time fudge (defaults to 300 seconds) and the current time
|
||||
// The TSIG MAC is saved in that Tsig RR.
|
||||
// When TsigGenerate is called for the first time requestMAC is set to the empty string and
|
||||
// timersOnly is false.
|
||||
// If something goes wrong an error is returned, otherwise it is nil.
|
||||
// The message should contain a "stub" TSIG RR with the algorithm, key name
|
||||
// (owner name of the RR), time fudge (defaults to 300 seconds) and the current
|
||||
// time The TSIG MAC is saved in that Tsig RR. When TsigGenerate is called for
|
||||
// the first time requestMAC should be set to the empty string and timersOnly to
|
||||
// false.
|
||||
func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) {
|
||||
return tsigGenerateProvider(m, tsigHMACProvider(secret), requestMAC, timersOnly)
|
||||
return TsigGenerateWithProvider(m, tsigHMACProvider(secret), requestMAC, timersOnly)
|
||||
}
|
||||
|
||||
func tsigGenerateProvider(m *Msg, provider TsigProvider, requestMAC string, timersOnly bool) ([]byte, string, error) {
|
||||
// TsigGenerateWithProvider is similar to TsigGenerate, but allows for a custom TsigProvider.
|
||||
func TsigGenerateWithProvider(m *Msg, provider TsigProvider, requestMAC string, timersOnly bool) ([]byte, string, error) {
|
||||
if m.IsTsig() == nil {
|
||||
panic("dns: TSIG not last RR in additional")
|
||||
}
|
||||
|
@ -198,14 +215,15 @@ func tsigGenerateProvider(m *Msg, provider TsigProvider, requestMAC string, time
|
|||
return mbuf, t.MAC, nil
|
||||
}
|
||||
|
||||
// TsigVerify verifies the TSIG on a message.
|
||||
// If the signature does not validate err contains the
|
||||
// error, otherwise it is nil.
|
||||
// TsigVerify verifies the TSIG on a message. If the signature does not
|
||||
// validate the returned error contains the cause. If the signature is OK, the
|
||||
// error is nil.
|
||||
func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
|
||||
return tsigVerify(msg, tsigHMACProvider(secret), requestMAC, timersOnly, uint64(time.Now().Unix()))
|
||||
}
|
||||
|
||||
func tsigVerifyProvider(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool) error {
|
||||
// TsigVerifyWithProvider is similar to TsigVerify, but allows for a custom TsigProvider.
|
||||
func TsigVerifyWithProvider(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool) error {
|
||||
return tsigVerify(msg, provider, requestMAC, timersOnly, uint64(time.Now().Unix()))
|
||||
}
|
||||
|
||||
|
|
|
@ -354,7 +354,7 @@ func TestTsigGenerateProvider(t *testing.T) {
|
|||
Extra: []RR{&tsig},
|
||||
}
|
||||
|
||||
_, mac, err := tsigGenerateProvider(req, new(testProvider), "", false)
|
||||
_, mac, err := TsigGenerateWithProvider(req, new(testProvider), "", false)
|
||||
if err != table.err {
|
||||
t.Fatalf("error doesn't match: expected '%s' but got '%s'", table.err, err)
|
||||
}
|
||||
|
@ -397,7 +397,7 @@ func TestTsigVerifyProvider(t *testing.T) {
|
|||
}
|
||||
|
||||
provider := &testProvider{true}
|
||||
msgData, _, err := tsigGenerateProvider(req, provider, "", false)
|
||||
msgData, _, err := TsigGenerateWithProvider(req, provider, "", false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//+build ignore
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
// types_generate.go is meant to run with go generate. It will use
|
||||
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build linux && !appengine
|
||||
// +build linux,!appengine
|
||||
|
||||
package dns
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package dns
|
||||
|
|
|
@ -92,7 +92,7 @@ func TestRemoveRRset(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPreReqAndRemovals(t *testing.T) {
|
||||
// Build a list of multiple prereqs and then somes removes followed by an insert.
|
||||
// Build a list of multiple prereqs and then some removes followed by an insert.
|
||||
// We should be able to add multiple prereqs and updates.
|
||||
m := new(Msg)
|
||||
m.SetUpdate("example.org.")
|
||||
|
|
|
@ -3,7 +3,7 @@ package dns
|
|||
import "fmt"
|
||||
|
||||
// Version is current version of this library.
|
||||
var Version = v{1, 1, 44}
|
||||
var Version = v{1, 1, 50}
|
||||
|
||||
// v holds the version of this library.
|
||||
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
|
||||
ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds
|
||||
WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds
|
||||
TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
|
||||
TsigSecret map[string]string // Secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
|
||||
tsigTimersOnly bool
|
||||
}
|
||||
|
||||
// Think we need to away to stop the transfer
|
||||
func (t *Transfer) tsigProvider() TsigProvider {
|
||||
if t.TsigProvider != nil {
|
||||
return t.TsigProvider
|
||||
}
|
||||
if t.TsigSecret != nil {
|
||||
return tsigSecretProvider(t.TsigSecret)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Think we need to away to stop the transfer
|
||||
|
||||
// In performs an incoming transfer with the server in a.
|
||||
// If you would like to set the source IP, or some other attribute
|
||||
|
@ -33,7 +44,6 @@ type Transfer struct {
|
|||
// dnscon := &dns.Conn{Conn:con}
|
||||
// transfer = &dns.Transfer{Conn: dnscon}
|
||||
// channel, err := transfer.In(message, master)
|
||||
//
|
||||
func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
|
||||
switch q.Question[0].Qtype {
|
||||
case TypeAXFR, TypeIXFR:
|
||||
|
@ -224,12 +234,9 @@ func (t *Transfer) ReadMsg() (*Msg, error) {
|
|||
if err := m.Unpack(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
|
||||
if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
|
||||
return m, ErrSecret
|
||||
}
|
||||
if ts, tp := m.IsTsig(), t.tsigProvider(); ts != nil && tp != nil {
|
||||
// Need to work on the original message p, as that was used to calculate the tsig.
|
||||
err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
|
||||
err = TsigVerifyWithProvider(p, tp, t.tsigRequestMAC, t.tsigTimersOnly)
|
||||
t.tsigRequestMAC = ts.MAC
|
||||
}
|
||||
return m, err
|
||||
|
@ -238,11 +245,8 @@ func (t *Transfer) ReadMsg() (*Msg, error) {
|
|||
// WriteMsg writes a message through the transfer connection t.
|
||||
func (t *Transfer) WriteMsg(m *Msg) (err error) {
|
||||
var out []byte
|
||||
if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
|
||||
if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
|
||||
return ErrSecret
|
||||
}
|
||||
out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
|
||||
if ts, tp := m.IsTsig(), t.tsigProvider(); ts != nil && tp != nil {
|
||||
out, t.tsigRequestMAC, err = TsigGenerateWithProvider(m, tp, t.tsigRequestMAC, t.tsigTimersOnly)
|
||||
} else {
|
||||
out, err = m.Pack()
|
||||
}
|
||||
|
|
56
xfr_test.go
56
xfr_test.go
|
@ -1,6 +1,9 @@
|
|||
package dns
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
tsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
|
||||
|
@ -127,3 +130,54 @@ func axfrTestingSuite(t *testing.T, addrstr string) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func axfrTestingSuiteWithCustomTsig(t *testing.T, addrstr string, provider TsigProvider) {
|
||||
tr := new(Transfer)
|
||||
m := new(Msg)
|
||||
var err error
|
||||
tr.Conn, err = Dial("tcp", addrstr)
|
||||
if err != nil {
|
||||
t.Fatal("failed to dial", err)
|
||||
}
|
||||
tr.TsigProvider = provider
|
||||
m.SetAxfr("miek.nl.")
|
||||
m.SetTsig("axfr.", HmacSHA256, 300, time.Now().Unix())
|
||||
|
||||
c, err := tr.In(m, addrstr)
|
||||
if err != nil {
|
||||
t.Fatal("failed to zone transfer in", err)
|
||||
}
|
||||
|
||||
var records []RR
|
||||
for msg := range c {
|
||||
if msg.Error != nil {
|
||||
t.Fatal(msg.Error)
|
||||
}
|
||||
records = append(records, msg.RR...)
|
||||
}
|
||||
|
||||
if len(records) != len(xfrTestData) {
|
||||
t.Fatalf("bad axfr: expected %v, got %v", records, xfrTestData)
|
||||
}
|
||||
|
||||
for i, rr := range records {
|
||||
if !IsDuplicate(rr, xfrTestData[i]) {
|
||||
t.Errorf("bad axfr: expected %v, got %v", records, xfrTestData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomTsigProvider(t *testing.T) {
|
||||
HandleFunc("miek.nl.", SingleEnvelopeXfrServer)
|
||||
defer HandleRemove("miek.nl.")
|
||||
|
||||
s, addrstr, _, err := RunLocalTCPServer(":0", func(srv *Server) {
|
||||
srv.TsigProvider = tsigSecretProvider(tsigSecret)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %s", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
axfrTestingSuiteWithCustomTsig(t, addrstr, tsigSecretProvider(tsigSecret))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue