Compare commits
473 Commits
Author | SHA1 | Date |
---|---|---|
|
d8fbd0a755 | |
|
f8a185d39e | |
|
0089167cae | |
|
fe20d5d323 | |
|
41a7730f43 | |
|
4bd038eb76 | |
|
caa3fe0583 | |
|
3b7e0b9bdd | |
|
8c643eba82 | |
|
16b12df562 | |
|
4822b271aa | |
|
b3dfea0715 | |
|
69924a02cf | |
|
ff611cdc4b | |
|
eb4745b695 | |
|
7413c83334 | |
|
5521648610 | |
|
bfcbf0fd23 | |
|
feda877277 | |
|
0d2c95b99c | |
|
c760d3c7f1 | |
|
656b7409ac | |
|
08c2616301 | |
|
dedee46bd4 | |
|
49c1b2e20f | |
|
045ac4ec6c | |
|
57e2e627a6 | |
|
2f577ca35d | |
|
d70eb7b9e1 | |
|
d48e92a0e6 | |
|
05140a3136 | |
|
af1ebf55eb | |
|
84af068d46 | |
|
33e64002b6 | |
|
51afb90ed3 | |
|
0544c8bb11 | |
|
af5144a5ca | |
|
32b1ed5f32 | |
|
294c41a1d8 | |
|
ba44371638 | |
|
f4af58267c | |
|
3a58872b63 | |
|
3b8982ccc6 | |
|
7318b01e11 | |
|
1630ffe2ca | |
|
4e8fe099f4 | |
|
df84acab71 | |
|
ab67aa6423 | |
|
af0c865ab3 | |
|
c23d8b3ce0 | |
|
996478ee91 | |
|
bd70190c4d | |
|
595ee5aa98 | |
|
ce48a4b9ef | |
|
9922549621 | |
|
d2b5d38d4f | |
|
9f8f2e3a3c | |
|
21ccaf84aa | |
|
cce7f43db4 | |
|
2a9acc8d83 | |
|
c99ea652e3 | |
|
88913150f0 | |
|
83b388a80c | |
|
40060b4a4b | |
|
ad89e5bc70 | |
|
797f1f2953 | |
|
a614451ab3 | |
|
e5407eb800 | |
|
40ce7c7df7 | |
|
b694ab3d93 | |
|
4fdbc51bbd | |
|
db96610e5e | |
|
375601dc88 | |
|
883641f4a9 | |
|
2f14d104f3 | |
|
9884b9f446 | |
|
2543d8bb2d | |
|
c08efdcc07 | |
|
4ec3e54a9e | |
|
4495f1939f | |
|
67bd57debd | |
|
7d5e1ea350 | |
|
ee8fef6743 | |
|
35023fab5c | |
|
2fd5af9f92 | |
|
e6df8867af | |
|
ba2d042a57 | |
|
a1362108be | |
|
a25c26b5ed | |
|
13238cb6ad | |
|
f9dc403cff | |
|
731b191cab | |
|
59aea23afe | |
|
23c4faca9d | |
|
428cef3187 | |
|
91eca25c30 | |
|
9732cfa6b8 | |
|
1ba9487b18 | |
|
3b41a31342 | |
|
fa528cceb7 | |
|
0e1c4e69dd | |
|
a3ad44419a | |
|
93945c2844 | |
|
db53c847ca | |
|
be51022368 | |
|
6d41f43022 | |
|
04c41addaf | |
|
276b51d84b | |
|
a433fbede4 | |
|
61a22d0ee6 | |
|
95dddd3867 | |
|
3b0ffe413f | |
|
10e0aeedbe | |
|
ef286f8f39 | |
|
5579226123 | |
|
08cf611c2b | |
|
eaab7b7e85 | |
|
0972db6834 | |
|
cec9156531 | |
|
68df4402de | |
|
7a5f1127f7 | |
|
efdec21496 | |
|
034f791cf8 | |
|
9df839b2b4 | |
|
da812eed45 | |
|
81df27db17 | |
|
86044e4e05 | |
|
34cecfe1b4 | |
|
de1def76d8 | |
|
f3da20bc00 | |
|
a7a0eafd7a | |
|
9093928550 | |
|
50b4756e47 | |
|
54ab126a04 | |
|
f17e6c7171 | |
|
978b9a827a | |
|
064ba4b789 | |
|
203ad2480b | |
|
0ffcea3295 | |
|
b7da9d95e0 | |
|
b28dcc1849 | |
|
8f63c2d20c | |
|
b7703d0fa0 | |
|
1fc9fa1db0 | |
|
d128d10d17 | |
|
5bfe94bb6e | |
|
67373879ce | |
|
2c9b7cfbaa | |
|
fd9c7eb788 | |
|
923fc6bc72 | |
|
f515aa579d | |
|
524a80c35d | |
|
438e446f5c | |
|
40ecd66164 | |
|
1d3a971542 | |
|
f0dca1ef05 | |
|
418631f446 | |
|
9dcf47a409 | |
|
7963800469 | |
|
6c0c4e6581 | |
|
c9b62b4215 | |
|
e636c10380 | |
|
ba5b1f0bae | |
|
eda228adcf | |
|
711e0fd90d | |
|
bfd8601222 | |
|
b3cafcb268 | |
|
a72e5ceb18 | |
|
bd4ba36771 | |
|
aae7df65e6 | |
|
8ebf2e419d | |
|
6d0449f981 | |
|
0788ed5f4e | |
|
730ff1f016 | |
|
78ecb5db60 | |
|
a98e771ba5 | |
|
22cda6dc4f | |
|
9b7437f11d | |
|
4d4363a5dc | |
|
4334efe802 | |
|
40eab7a196 | |
|
997f079b75 | |
|
58a17b97a1 | |
|
1e224ff5de | |
|
1cd342c79a | |
|
041b4bc010 | |
|
76b57d0384 | |
|
557870346a | |
|
046ae4ead6 | |
|
8ebfd8abbb | |
|
93f749db12 | |
|
1208fbdde0 | |
|
bb79ca102d | |
|
513f7ec990 | |
|
9a6f1f2dc9 | |
|
e393768b85 | |
|
9578caeab0 | |
|
b733ad8671 | |
|
ba5bfd0295 | |
|
ab6ac402ba | |
|
2acbc9eff3 | |
|
dcb849b337 | |
|
c674456565 | |
|
5825da9f4d | |
|
b13675009d | |
|
7f2bf8764a | |
|
d89f1e3d4b | |
|
ee62c8b086 | |
|
9cfcfb2209 | |
|
25cacca8ca | |
|
cbc52d2408 | |
|
8a56deec68 | |
|
d16ecb693e | |
|
1545072057 | |
|
fbd426fefa | |
|
a2c73fb86d | |
|
ccd41ffaf8 | |
|
77cba59d63 | |
|
9c315c51c3 | |
|
087e486609 | |
|
0930b62a13 | |
|
d49318b5a0 | |
|
37f455fa04 | |
|
2f1ea90356 | |
|
77c7d907b4 | |
|
59b8e6b3db | |
|
92185d1e17 | |
|
56c04f1fec | |
|
8aa92d4e02 | |
|
357af3038a | |
|
cfee849963 | |
|
73601d4aed | |
|
0460860e89 | |
|
19b4ba9c16 | |
|
d1c1f95f67 | |
|
d051b464e9 | |
|
d8ff986484 | |
|
bc7d5a495c | |
|
1f99ca2fa4 | |
|
d49c86087e | |
|
cc8cd02140 | |
|
834f456fff | |
|
337216f9a7 | |
|
1a5555c783 | |
|
53b8a87e14 | |
|
eef2495fa3 | |
|
035891ab61 | |
|
284bad20d8 | |
|
487e4636d5 | |
|
e838e1e3ce | |
|
72df20724e | |
|
092d7745b4 | |
|
12ac8fb6e8 | |
|
39e689aa4a | |
|
164b22ef9a | |
|
a288d199dc | |
|
8fc2e5773b | |
|
896800ef1d | |
|
2df14c5c9f | |
|
56be65265e | |
|
5c2ec9f7e4 | |
|
5beb962416 | |
|
c5493ad28d | |
|
db3d0ce13b | |
|
44a8c5f8ba | |
|
29b9bf368b | |
|
513c1ff221 | |
|
57ca5ae8f4 | |
|
34be74deeb | |
|
09499bd07f | |
|
b9e1e7529b | |
|
e8b24e80da | |
|
5b818eed53 | |
|
ace4e60848 | |
|
b955100a79 | |
|
813bd39114 | |
|
1048d2bf96 | |
|
57b81e0614 | |
|
17dcb39074 | |
|
f9fcf1448b | |
|
bfd648e102 | |
|
2533d75276 | |
|
ac5c421c48 | |
|
67bd05e2f4 | |
|
5913ad55e9 | |
|
b0835fab5e | |
|
56516cf4de | |
|
5f5f2380fc | |
|
450ab7d57f | |
|
870a59089c | |
|
65b2aa0a63 | |
|
7586a3cbe8 | |
|
ff7d445081 | |
|
470f08e191 | |
|
5c9c0e7818 | |
|
6b6e08b48c | |
|
8d24af5fb5 | |
|
6ade5b5fff | |
|
778aa4f83d | |
|
2c039114d2 | |
|
c0747f060e | |
|
b1bf6f1b9b | |
|
ed726faad6 | |
|
71bbe52b67 | |
|
c2889aea81 | |
|
2f8cf50b32 | |
|
1afd10068a | |
|
4058ac87fa | |
|
fa589750ad | |
|
56118562d7 | |
|
d27f0d3482 | |
|
07ae768ab1 | |
|
c1ad186588 | |
|
a220737569 | |
|
1c92765836 | |
|
f92da6fc6e | |
|
fef7963e99 | |
|
ab67d69d9b | |
|
6aa28be819 | |
|
091d66a39f | |
|
db37038897 | |
|
74dbfccc11 | |
|
2c18e7259a | |
|
6bf402f3c4 | |
|
64a73613cd | |
|
d193d08243 | |
|
32d8e33ba2 | |
|
c567cfc2bb | |
|
7ae05cdcf8 | |
|
34d23c00e1 | |
|
a7e7488e1d | |
|
03d7306558 | |
|
c03bc41f33 | |
|
e2f69345fd | |
|
11fb61cb84 | |
|
8eab0120c4 | |
|
e969cef252 | |
|
1ff265a784 | |
|
8f269a6b16 | |
|
30d0133e57 | |
|
f522504216 | |
|
07ed56b1d6 | |
|
149f3c884f | |
|
7f4b3bb806 | |
|
6aa05940d5 | |
|
896cef4ce4 | |
|
5547fd63a0 | |
|
260b5b401d | |
|
e5bc3b14fb | |
|
9358e95aef | |
|
926752f160 | |
|
03053758d4 | |
|
4c43711692 | |
|
36a30d2e58 | |
|
8995ae83e3 | |
|
ecef32b31b | |
|
3534784466 | |
|
7bef528091 | |
|
bf8065a091 | |
|
0125cf9d0c | |
|
77d26d8088 | |
|
c12f225763 | |
|
8e6e188a87 | |
|
3b3a5b7c6a | |
|
8d08c56229 | |
|
ab9dd29c1d | |
|
2eeda8aabc | |
|
1c9c9bf4c9 | |
|
043a442757 | |
|
7064f7248f | |
|
ec3443f85d | |
|
6ae357d393 | |
|
915ca3d5ff | |
|
274da7d3ef | |
|
17c1bc6792 | |
|
4a9ca7e98d | |
|
d74956db7b | |
|
dad917d8de | |
|
a3f088363b | |
|
e6cede5dc8 | |
|
39265ac07f | |
|
ac339476d7 | |
|
c0283a2028 | |
|
7eca355503 | |
|
0d29b283ac | |
|
36ffedf7d0 | |
|
7ca2be95a9 | |
|
008c8ca764 | |
|
c10ce5142a | |
|
ba6747e8a9 | |
|
cef340ab2c | |
|
26223ef466 | |
|
edac0c6ab3 | |
|
068c7e7c81 | |
|
45e481ce44 | |
|
7482521355 | |
|
7f61c6631b | |
|
ead9678cbc | |
|
60d113313c | |
|
ab16005053 | |
|
f195b71879 | |
|
94dab88533 | |
|
5debfeec63 | |
|
f4db2ca6ed | |
|
501e858f67 | |
|
833bf76c28 | |
|
426ea785a9 | |
|
b0dc93d276 | |
|
e875a31a5c | |
|
8f0a42efa0 | |
|
bfbb1715af | |
|
7da8f0db5c | |
|
bf6da3a5bd | |
|
3ce7efeace | |
|
9a34f54f7a | |
|
4bda1db839 | |
|
28216bf382 | |
|
c9b812d1d9 | |
|
208cd1e89e | |
|
21d95e19e6 | |
|
4a8fde8d2a | |
|
b559d43c31 | |
|
1e845a5b06 | |
|
8004f28488 | |
|
3e6e47bc11 | |
|
e7c3f513a1 | |
|
ed07089f3b | |
|
df46691620 | |
|
f90eb8fb45 | |
|
4c681ac41f | |
|
c9cd01bc14 | |
|
0a83f30697 | |
|
5a2b9fab83 | |
|
da0e668c16 | |
|
d8bd04e7e1 | |
|
8ccae88257 | |
|
350cd086d1 | |
|
0947afec0a | |
|
0f8c7717de | |
|
e57bf427e6 | |
|
64746df23b | |
|
3745b9737d | |
|
09649115c1 | |
|
621df0907e | |
|
77d95a53d0 | |
|
1f2aa4c780 | |
|
a93f3e4f6b | |
|
eac804ceef | |
|
98a1ef4565 | |
|
9c76f9827e | |
|
01d59357d4 | |
|
3f2548fb60 | |
|
800934f8d4 | |
|
46f728d51b | |
|
83c435cc65 | |
|
7fdfb0141b | |
|
dfb943b13e | |
|
d174bbf0a5 | |
|
22cb769f47 | |
|
5b169d1842 | |
|
906238edc6 | |
|
4d25966dce | |
|
5364553f1e | |
|
43913f2f4f | |
|
862243b3b1 | |
|
f5ac34d755 | |
|
9cef7a0d3d | |
|
5ec25f2a50 | |
|
ddd8477be2 | |
|
69d25e845f | |
|
e2db8456df | |
|
dcdbddd810 | |
|
ac8cd7878c |
|
@ -0,0 +1,32 @@
|
|||
name: "Code scanning - action"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, ]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: '0 23 * * 5'
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
|
@ -0,0 +1,25 @@
|
|||
name: Go
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build and Test
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go: [ 1.19.x, 1.20.x ]
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
20
.travis.yml
20
.travis.yml
|
@ -1,20 +0,0 @@
|
|||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.9.x
|
||||
- tip
|
||||
|
||||
env:
|
||||
- TESTS="-race -v -bench=. -coverprofile=coverage.txt -covermode=atomic"
|
||||
- TESTS="-race -v ./..."
|
||||
|
||||
before_install:
|
||||
# don't use the miekg/dns when testing forks
|
||||
- mkdir -p $GOPATH/src/github.com/miekg
|
||||
- ln -s $TRAVIS_BUILD_DIR $GOPATH/src/github.com/miekg/ || true
|
||||
|
||||
script:
|
||||
- go test $TESTS
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
|
@ -0,0 +1 @@
|
|||
* @miekg @tmthrgd
|
|
@ -1,21 +0,0 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["ed25519","ed25519/internal/edwards25519"]
|
||||
revision = "b080dc9a8c480b08e698fb1219160d598526310f"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["bpf","internal/iana","internal/socket","ipv4","ipv6"]
|
||||
revision = "894f8ed5849b15b810ae41e9590a0d05395bba27"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "c4abc38abaeeeeb9be92455c9c02cae32841122b8982aaa067ef25bb8e86ff9d"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
26
Gopkg.toml
26
Gopkg.toml
|
@ -1,26 +0,0 @@
|
|||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
49
LICENSE
49
LICENSE
|
@ -1,32 +1,29 @@
|
|||
Extensions of the original work are copyright (c) 2011 Miek Gieben
|
||||
BSD 3-Clause License
|
||||
|
||||
As this is fork of the official Go code the same license applies:
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2009, The Go Authors. Extensions copyright (c) 2011, Miek Gieben.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Makefile for releasing.
|
||||
#
|
||||
# The release is controlled from version.go. The version found there is
|
||||
# used to tag the git repo, we're not building any artifects so there is nothing
|
||||
# used to tag the git repo, we're not building any artifacts so there is nothing
|
||||
# to upload to github.
|
||||
#
|
||||
# * Up the version in version.go
|
||||
|
@ -49,3 +49,4 @@ commit:
|
|||
push:
|
||||
@echo Pushing release $(VERSION) to master
|
||||
git push --tags
|
||||
git push
|
||||
|
|
95
README.md
95
README.md
|
@ -7,10 +7,10 @@
|
|||
|
||||
> Less is more.
|
||||
|
||||
Complete and usable DNS library. All widely used Resource Records are supported, including the
|
||||
DNSSEC types. It follows a lean and mean philosophy. If there is stuff you should know as a DNS
|
||||
programmer there isn't a convenience function for it. Server side and client side programming is
|
||||
supported, i.e. you can build servers and resolvers with it.
|
||||
Complete and usable DNS library. All Resource Records are supported, including the DNSSEC types.
|
||||
It follows a lean and mean philosophy. If there is stuff you should know as a DNS programmer there
|
||||
isn't a convenience function for it. Server side and client side programming is supported, i.e. you
|
||||
can build servers and resolvers with it.
|
||||
|
||||
We try to keep the "master" branch as sane as possible and at the bleeding edge of standards,
|
||||
avoiding breaking changes wherever reasonable. We support the last two versions of Go.
|
||||
|
@ -26,8 +26,8 @@ avoiding breaking changes wherever reasonable. We support the last two versions
|
|||
A not-so-up-to-date-list-that-may-be-actually-current:
|
||||
|
||||
* https://github.com/coredns/coredns
|
||||
* https://cloudflare.com
|
||||
* https://github.com/abh/geodns
|
||||
* https://github.com/baidu/bfe
|
||||
* http://www.statdns.com/
|
||||
* http://www.dnsinspect.com/
|
||||
* https://github.com/chuangbo/jianbing-dictionary-dns
|
||||
|
@ -41,12 +41,9 @@ A not-so-up-to-date-list-that-may-be-actually-current:
|
|||
* https://github.com/StalkR/dns-reverse-proxy
|
||||
* https://github.com/tianon/rawdns
|
||||
* https://mesosphere.github.io/mesos-dns/
|
||||
* https://pulse.turbobytes.com/
|
||||
* https://play.google.com/store/apps/details?id=com.turbobytes.dig
|
||||
* https://github.com/fcambus/statzone
|
||||
* https://github.com/benschw/dns-clb-go
|
||||
* https://github.com/corny/dnscheck for http://public-dns.info/
|
||||
* https://namesmith.io
|
||||
* https://github.com/corny/dnscheck for <http://public-dns.info/>
|
||||
* https://github.com/miekg/unbound
|
||||
* https://github.com/miekg/exdns
|
||||
* https://dnslookup.org
|
||||
|
@ -55,49 +52,69 @@ A not-so-up-to-date-list-that-may-be-actually-current:
|
|||
* https://github.com/mehrdadrad/mylg
|
||||
* https://github.com/bamarni/dockness
|
||||
* https://github.com/fffaraz/microdns
|
||||
* http://kelda.io
|
||||
* https://github.com/ipdcode/hades (JD.COM)
|
||||
* https://github.com/ipdcode/hades <https://jd.com>
|
||||
* https://github.com/StackExchange/dnscontrol/
|
||||
* https://www.dnsperf.com/
|
||||
* https://dnssectest.net/
|
||||
* https://dns.apebits.com
|
||||
* https://github.com/oif/apex
|
||||
* https://github.com/jedisct1/dnscrypt-proxy
|
||||
* https://github.com/jedisct1/rpdns
|
||||
* https://github.com/xor-gate/sshfp
|
||||
* https://github.com/rs/dnstrace
|
||||
* https://blitiri.com.ar/p/dnss ([github mirror](https://github.com/albertito/dnss))
|
||||
* https://render.com
|
||||
* https://github.com/peterzen/goresolver
|
||||
* https://github.com/folbricht/routedns
|
||||
* https://domainr.com/
|
||||
* https://zonedb.org/
|
||||
* https://router7.org/
|
||||
* https://github.com/fortio/dnsping
|
||||
* https://github.com/Luzilla/dnsbl_exporter
|
||||
* https://github.com/bodgit/tsig
|
||||
* https://github.com/v2fly/v2ray-core (test only)
|
||||
* https://kuma.io/
|
||||
* https://www.misaka.io/services/dns
|
||||
* https://ping.sx/dig
|
||||
* https://fleetdeck.io/
|
||||
* https://github.com/markdingo/autoreverse
|
||||
* https://github.com/slackhq/nebula
|
||||
* https://github.com/dnschecktool/dow-proxy
|
||||
* https://dnscheck.tools/
|
||||
|
||||
|
||||
Send pull request if you want to be listed here.
|
||||
|
||||
# Features
|
||||
|
||||
* UDP/TCP queries, IPv4 and IPv6;
|
||||
* RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE (for all record types) are supported;
|
||||
* Fast:
|
||||
* Reply speed around ~ 80K qps (faster hardware results in more qps);
|
||||
* Parsing RRs ~ 100K RR/s, that's 5M records in about 50 seconds;
|
||||
* Server side programming (mimicking the net/http package);
|
||||
* Client side programming;
|
||||
* DNSSEC: signing, validating and key generation for DSA, RSA, ECDSA and Ed25519;
|
||||
* EDNS0, NSID, Cookies;
|
||||
* AXFR/IXFR;
|
||||
* TSIG, SIG(0);
|
||||
* DNS over TLS: optional encrypted connection between client and server;
|
||||
* DNS name compression;
|
||||
* Depends only on the standard library.
|
||||
* UDP/TCP queries, IPv4 and IPv6
|
||||
* RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE (for all record types) are supported
|
||||
* Fast
|
||||
* Server side programming (mimicking the net/http package)
|
||||
* Client side programming
|
||||
* DNSSEC: signing, validating and key generation for DSA, RSA, ECDSA and Ed25519
|
||||
* EDNS0, NSID, Cookies
|
||||
* AXFR/IXFR
|
||||
* TSIG, SIG(0)
|
||||
* DNS over TLS (DoT): encrypted connection between client and server over TCP
|
||||
* DNS name compression
|
||||
|
||||
Have fun!
|
||||
|
||||
Miek Gieben - 2010-2012 - <miek@miek.nl>
|
||||
DNS Authors 2012-
|
||||
|
||||
# Building
|
||||
|
||||
Building is done with the `go` tool. If you have setup your GOPATH correctly, the following should
|
||||
work:
|
||||
This library uses Go modules and uses semantic versioning. Building is done with the `go` tool, so
|
||||
the following should work:
|
||||
|
||||
go get github.com/miekg/dns
|
||||
go build github.com/miekg/dns
|
||||
|
||||
## Examples
|
||||
|
||||
A short "how to use the API" is at the beginning of doc.go (this also will show
|
||||
when you call `godoc github.com/miekg/dns`).
|
||||
A short "how to use the API" is at the beginning of doc.go (this also will show when you call `godoc
|
||||
github.com/miekg/dns`).
|
||||
|
||||
Example programs can be found in the `github.com/miekg/exdns` repository.
|
||||
|
||||
|
@ -121,6 +138,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
|
|||
* 2915 - NAPTR record
|
||||
* 2929 - DNS IANA Considerations
|
||||
* 3110 - RSASHA1 DNS keys
|
||||
* 3123 - APL record
|
||||
* 3225 - DO bit (DNSSEC OK)
|
||||
* 340{1,2,3} - NAPTR record
|
||||
* 3445 - Limiting the scope of (DNS)KEY
|
||||
|
@ -147,6 +165,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
|
|||
* 6844 - CAA record
|
||||
* 6891 - EDNS0 update
|
||||
* 6895 - DNS IANA considerations
|
||||
* 6944 - DNSSEC DNSKEY Algorithm Status
|
||||
* 6975 - Algorithm Understanding in DNSSEC
|
||||
* 7043 - EUI48/EUI64 records
|
||||
* 7314 - DNS (EDNS) EXPIRE Option
|
||||
|
@ -155,12 +174,16 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
|
|||
* 7553 - URI record
|
||||
* 7858 - DNS over TLS: Initiation and Performance Considerations
|
||||
* 7871 - EDNS0 Client Subnet
|
||||
* 7873 - Domain Name System (DNS) Cookies (draft-ietf-dnsop-cookies)
|
||||
* 7873 - Domain Name System (DNS) Cookies
|
||||
* 8080 - EdDSA for DNSSEC
|
||||
* 8499 - DNS Terminology
|
||||
* 8659 - DNS Certification Authority Authorization (CAA) Resource Record
|
||||
* 8914 - Extended DNS Errors
|
||||
* 8976 - Message Digest for DNS Zones (ZONEMD RR)
|
||||
|
||||
## Loosely based upon
|
||||
## Loosely Based Upon
|
||||
|
||||
* `ldns`
|
||||
* `NSD`
|
||||
* `Net::DNS`
|
||||
* `GRONG`
|
||||
* ldns - <https://nlnetlabs.nl/projects/ldns/about/>
|
||||
* NSD - <https://nlnetlabs.nl/projects/nsd/about/>
|
||||
* Net::DNS - <http://www.net-dns.org/>
|
||||
* GRONG - <https://github.com/bortzmeyer/grong>
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package dns
|
||||
|
||||
// MsgAcceptFunc is used early in the server code to accept or reject a message with RcodeFormatError.
|
||||
// It returns a MsgAcceptAction to indicate what should happen with the message.
|
||||
type MsgAcceptFunc func(dh Header) MsgAcceptAction
|
||||
|
||||
// DefaultMsgAcceptFunc checks the request and will reject if:
|
||||
//
|
||||
// * isn't a request (don't respond in that case)
|
||||
//
|
||||
// * opcode isn't OpcodeQuery or OpcodeNotify
|
||||
//
|
||||
// * Zero bit isn't zero
|
||||
//
|
||||
// * does not have exactly 1 question in the question section
|
||||
//
|
||||
// * has more than 1 RR in the Answer section
|
||||
//
|
||||
// * has more than 0 RRs in the Authority section
|
||||
//
|
||||
// * has more than 2 RRs in the Additional section
|
||||
var DefaultMsgAcceptFunc MsgAcceptFunc = defaultMsgAcceptFunc
|
||||
|
||||
// MsgAcceptAction represents the action to be taken.
|
||||
type MsgAcceptAction int
|
||||
|
||||
// Allowed returned values from a MsgAcceptFunc.
|
||||
const (
|
||||
MsgAccept MsgAcceptAction = iota // Accept the message
|
||||
MsgReject // Reject the message with a RcodeFormatError
|
||||
MsgIgnore // Ignore the error and send nothing back.
|
||||
MsgRejectNotImplemented // Reject the message with a RcodeNotImplemented
|
||||
)
|
||||
|
||||
func defaultMsgAcceptFunc(dh Header) MsgAcceptAction {
|
||||
if isResponse := dh.Bits&_QR != 0; isResponse {
|
||||
return MsgIgnore
|
||||
}
|
||||
|
||||
// Don't allow dynamic updates, because then the sections can contain a whole bunch of RRs.
|
||||
opcode := int(dh.Bits>>11) & 0xF
|
||||
if opcode != OpcodeQuery && opcode != OpcodeNotify {
|
||||
return MsgRejectNotImplemented
|
||||
}
|
||||
|
||||
if dh.Qdcount != 1 {
|
||||
return MsgReject
|
||||
}
|
||||
// NOTIFY requests can have a SOA in the ANSWER section. See RFC 1996 Section 3.7 and 3.11.
|
||||
if dh.Ancount > 1 {
|
||||
return MsgReject
|
||||
}
|
||||
// IXFR request could have one SOA RR in the NS section. See RFC 1995, section 3.
|
||||
if dh.Nscount > 1 {
|
||||
return MsgReject
|
||||
}
|
||||
if dh.Arcount > 2 {
|
||||
return MsgReject
|
||||
}
|
||||
return MsgAccept
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAcceptNotify(t *testing.T) {
|
||||
HandleFunc("example.org.", handleNotify)
|
||||
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
m := new(Msg)
|
||||
m.SetNotify("example.org.")
|
||||
// Set a SOA hint in the answer section, this is allowed according to RFC 1996.
|
||||
soa, _ := NewRR("example.org. IN SOA sns.dns.icann.org. noc.dns.icann.org. 2018112827 7200 3600 1209600 3600")
|
||||
m.Answer = []RR{soa}
|
||||
|
||||
c := new(Client)
|
||||
resp, _, err := c.Exchange(m, addrstr)
|
||||
if err != nil {
|
||||
t.Errorf("failed to exchange: %v", err)
|
||||
}
|
||||
if resp.Rcode != RcodeSuccess {
|
||||
t.Errorf("expected %s, got %s", RcodeToString[RcodeSuccess], RcodeToString[resp.Rcode])
|
||||
}
|
||||
}
|
||||
|
||||
func handleNotify(w ResponseWriter, req *Msg) {
|
||||
m := new(Msg)
|
||||
m.SetReply(req)
|
||||
w.WriteMsg(m)
|
||||
}
|
377
client.go
377
client.go
|
@ -3,29 +3,50 @@ package dns
|
|||
// A client implementation.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const dnsTimeout time.Duration = 2 * time.Second
|
||||
const tcpIdleTimeout time.Duration = 8 * time.Second
|
||||
const (
|
||||
dnsTimeout time.Duration = 2 * time.Second
|
||||
tcpIdleTimeout time.Duration = 8 * time.Second
|
||||
)
|
||||
|
||||
func isPacketConn(c net.Conn) bool {
|
||||
if _, ok := c.(net.PacketConn); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if ua, ok := c.LocalAddr().(*net.UnixAddr); ok {
|
||||
return ua.Net == "unixgram" || ua.Net == "unixpacket"
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// A Conn represents a connection to a DNS server.
|
||||
type Conn struct {
|
||||
net.Conn // a net.Conn holding the connection
|
||||
UDPSize uint16 // minimum receive buffer for UDP messages
|
||||
TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
|
||||
rtt time.Duration
|
||||
t time.Time
|
||||
TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
|
||||
tsigRequestMAC string
|
||||
}
|
||||
|
||||
func (co *Conn) tsigProvider() TsigProvider {
|
||||
if co.TsigProvider != nil {
|
||||
return co.TsigProvider
|
||||
}
|
||||
// tsigSecretProvider will return ErrSecret if co.TsigSecret is nil.
|
||||
return tsigSecretProvider(co.TsigSecret)
|
||||
}
|
||||
|
||||
// A Client defines parameters for a DNS client.
|
||||
type Client struct {
|
||||
Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP)
|
||||
|
@ -34,12 +55,13 @@ type Client struct {
|
|||
Dialer *net.Dialer // a net.Dialer used to set local address, timeouts and more
|
||||
// Timeout is a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout,
|
||||
// WriteTimeout when non-zero. Can be overridden with net.Dialer.Timeout (see Client.ExchangeWithDialer and
|
||||
// Client.Dialer) or context.Context.Deadline (see the deprecated ExchangeContext)
|
||||
// Client.Dialer) or context.Context.Deadline (see ExchangeContext)
|
||||
Timeout time.Duration
|
||||
DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds, or net.Dialer.Timeout if expiring earlier - overridden by Timeout when that value is non-zero
|
||||
ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
|
||||
WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
|
||||
TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
|
||||
TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
|
||||
SingleInflight bool // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass
|
||||
group singleflight
|
||||
}
|
||||
|
@ -80,43 +102,47 @@ func (c *Client) writeTimeout() time.Duration {
|
|||
|
||||
// Dial connects to the address on the named network.
|
||||
func (c *Client) Dial(address string) (conn *Conn, err error) {
|
||||
return c.DialContext(context.Background(), address)
|
||||
}
|
||||
|
||||
// DialContext connects to the address on the named network, with a context.Context.
|
||||
// For TLS over TCP (DoT) the context isn't used yet. This will be enabled when Go 1.18 is released.
|
||||
func (c *Client) DialContext(ctx context.Context, address string) (conn *Conn, err error) {
|
||||
// create a new dialer with the appropriate timeout
|
||||
var d net.Dialer
|
||||
if c.Dialer == nil {
|
||||
d = net.Dialer{}
|
||||
d = net.Dialer{Timeout: c.getTimeoutForRequest(c.dialTimeout())}
|
||||
} else {
|
||||
d = net.Dialer(*c.Dialer)
|
||||
d = *c.Dialer
|
||||
}
|
||||
d.Timeout = c.getTimeoutForRequest(c.writeTimeout())
|
||||
|
||||
network := "udp"
|
||||
useTLS := false
|
||||
|
||||
switch c.Net {
|
||||
case "tcp-tls":
|
||||
network = "tcp"
|
||||
useTLS = true
|
||||
case "tcp4-tls":
|
||||
network = "tcp4"
|
||||
useTLS = true
|
||||
case "tcp6-tls":
|
||||
network = "tcp6"
|
||||
useTLS = true
|
||||
default:
|
||||
if c.Net != "" {
|
||||
network = c.Net
|
||||
}
|
||||
network := c.Net
|
||||
if network == "" {
|
||||
network = "udp"
|
||||
}
|
||||
|
||||
useTLS := strings.HasPrefix(network, "tcp") && strings.HasSuffix(network, "-tls")
|
||||
|
||||
conn = new(Conn)
|
||||
if useTLS {
|
||||
network = strings.TrimSuffix(network, "-tls")
|
||||
|
||||
// TODO(miekg): Enable after Go 1.18 is released, to be able to support two prev. releases.
|
||||
/*
|
||||
tlsDialer := tls.Dialer{
|
||||
NetDialer: &d,
|
||||
Config: c.TLSConfig,
|
||||
}
|
||||
conn.Conn, err = tlsDialer.DialContext(ctx, network, address)
|
||||
*/
|
||||
conn.Conn, err = tls.DialWithDialer(&d, network, address, c.TLSConfig)
|
||||
} else {
|
||||
conn.Conn, err = d.Dial(network, address)
|
||||
conn.Conn, err = d.DialContext(ctx, network, address)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.UDPSize = c.UDPSize
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
|
@ -135,37 +161,55 @@ func (c *Client) Dial(address string) (conn *Conn, err error) {
|
|||
// To specify a local address or a timeout, the caller has to set the `Client.Dialer`
|
||||
// attribute appropriately
|
||||
func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, err error) {
|
||||
if !c.SingleInflight {
|
||||
return c.exchange(m, address)
|
||||
}
|
||||
|
||||
t := "nop"
|
||||
if t1, ok := TypeToString[m.Question[0].Qtype]; ok {
|
||||
t = t1
|
||||
}
|
||||
cl := "nop"
|
||||
if cl1, ok := ClassToString[m.Question[0].Qclass]; ok {
|
||||
cl = cl1
|
||||
}
|
||||
r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) {
|
||||
return c.exchange(m, address)
|
||||
})
|
||||
if r != nil && shared {
|
||||
r = r.Copy()
|
||||
}
|
||||
return r, rtt, err
|
||||
}
|
||||
|
||||
func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
|
||||
var co *Conn
|
||||
|
||||
co, err = c.Dial(a)
|
||||
co, err := c.Dial(address)
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer co.Close()
|
||||
return c.ExchangeWithConn(m, co)
|
||||
}
|
||||
|
||||
// ExchangeWithConn has the same behavior as Exchange, just with a predetermined connection
|
||||
// that will be used instead of creating a new one.
|
||||
// Usage pattern with a *dns.Client:
|
||||
//
|
||||
// c := new(dns.Client)
|
||||
// // connection management logic goes here
|
||||
//
|
||||
// conn := c.Dial(address)
|
||||
// in, rtt, err := c.ExchangeWithConn(message, conn)
|
||||
//
|
||||
// This allows users of the library to implement their own connection management,
|
||||
// as opposed to Exchange, which will always use new connections and incur the added overhead
|
||||
// that entails when using "tcp" and especially "tcp-tls" clients.
|
||||
//
|
||||
// When the singleflight is set for this client the context is _not_ forwarded to the (shared) exchange, to
|
||||
// prevent one cancellation from canceling all outstanding requests.
|
||||
func (c *Client) ExchangeWithConn(m *Msg, conn *Conn) (r *Msg, rtt time.Duration, err error) {
|
||||
return c.exchangeWithConnContext(context.Background(), m, conn)
|
||||
}
|
||||
|
||||
func (c *Client) exchangeWithConnContext(ctx context.Context, m *Msg, conn *Conn) (r *Msg, rtt time.Duration, err error) {
|
||||
if !c.SingleInflight {
|
||||
return c.exchangeContext(ctx, m, conn)
|
||||
}
|
||||
|
||||
q := m.Question[0]
|
||||
key := fmt.Sprintf("%s:%d:%d", q.Name, q.Qtype, q.Qclass)
|
||||
r, rtt, err, shared := c.group.Do(key, func() (*Msg, time.Duration, error) {
|
||||
// When we're doing singleflight we don't want one context cancellation, cancel _all_ outstanding queries.
|
||||
// Hence we ignore the context and use Background().
|
||||
return c.exchangeContext(context.Background(), m, conn)
|
||||
})
|
||||
if r != nil && shared {
|
||||
r = r.Copy()
|
||||
}
|
||||
|
||||
return r, rtt, err
|
||||
}
|
||||
|
||||
func (c *Client) exchangeContext(ctx context.Context, m *Msg, co *Conn) (r *Msg, rtt time.Duration, err error) {
|
||||
opt := m.IsEdns0()
|
||||
// If EDNS0 is used use that for size.
|
||||
if opt != nil && opt.UDPSize() >= MinMsgSize {
|
||||
|
@ -176,19 +220,44 @@ func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err erro
|
|||
co.UDPSize = c.UDPSize
|
||||
}
|
||||
|
||||
co.TsigSecret = c.TsigSecret
|
||||
// write with the appropriate write timeout
|
||||
co.SetWriteDeadline(time.Now().Add(c.getTimeoutForRequest(c.writeTimeout())))
|
||||
t := time.Now()
|
||||
writeDeadline := t.Add(c.getTimeoutForRequest(c.writeTimeout()))
|
||||
readDeadline := t.Add(c.getTimeoutForRequest(c.readTimeout()))
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
if deadline.Before(writeDeadline) {
|
||||
writeDeadline = deadline
|
||||
}
|
||||
if deadline.Before(readDeadline) {
|
||||
readDeadline = deadline
|
||||
}
|
||||
}
|
||||
co.SetWriteDeadline(writeDeadline)
|
||||
co.SetReadDeadline(readDeadline)
|
||||
|
||||
co.TsigSecret, co.TsigProvider = c.TsigSecret, c.TsigProvider
|
||||
|
||||
if err = co.WriteMsg(m); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
co.SetReadDeadline(time.Now().Add(c.getTimeoutForRequest(c.readTimeout())))
|
||||
r, err = co.ReadMsg()
|
||||
if err == nil && r.Id != m.Id {
|
||||
err = ErrId
|
||||
if isPacketConn(co.Conn) {
|
||||
for {
|
||||
r, err = co.ReadMsg()
|
||||
// Ignore replies with mismatched IDs because they might be
|
||||
// responses to earlier queries that timed out.
|
||||
if err != nil || r.Id == m.Id {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r, err = co.ReadMsg()
|
||||
if err == nil && r.Id != m.Id {
|
||||
err = ErrId
|
||||
}
|
||||
}
|
||||
return r, co.rtt, err
|
||||
rtt = time.Since(t)
|
||||
return r, rtt, err
|
||||
}
|
||||
|
||||
// ReadMsg reads a message from the connection co.
|
||||
|
@ -210,11 +279,8 @@ func (co *Conn) ReadMsg() (*Msg, error) {
|
|||
return m, err
|
||||
}
|
||||
if t := m.IsTsig(); t != nil {
|
||||
if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
|
||||
return m, ErrSecret
|
||||
}
|
||||
// Need to work on the original message p, as that was used to calculate the tsig.
|
||||
err = TsigVerify(p, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false)
|
||||
err = TsigVerifyWithProvider(p, co.tsigProvider(), co.tsigRequestMAC, false)
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
@ -229,26 +295,21 @@ func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
|
|||
err error
|
||||
)
|
||||
|
||||
switch t := co.Conn.(type) {
|
||||
case *net.TCPConn, *tls.Conn:
|
||||
r := t.(io.Reader)
|
||||
|
||||
// First two bytes specify the length of the entire message.
|
||||
l, err := tcpMsgLen(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = make([]byte, l)
|
||||
n, err = tcpRead(r, p)
|
||||
co.rtt = time.Since(co.t)
|
||||
default:
|
||||
if isPacketConn(co.Conn) {
|
||||
if co.UDPSize > MinMsgSize {
|
||||
p = make([]byte, co.UDPSize)
|
||||
} else {
|
||||
p = make([]byte, MinMsgSize)
|
||||
}
|
||||
n, err = co.Read(p)
|
||||
co.rtt = time.Since(co.t)
|
||||
} else {
|
||||
var length uint16
|
||||
if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p = make([]byte, length)
|
||||
n, err = io.ReadFull(co.Conn, p)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -268,78 +329,26 @@ func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
|
|||
return p, err
|
||||
}
|
||||
|
||||
// tcpMsgLen is a helper func to read first two bytes of stream as uint16 packet length.
|
||||
func tcpMsgLen(t io.Reader) (int, error) {
|
||||
p := []byte{0, 0}
|
||||
n, err := t.Read(p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// As seen with my local router/switch, returns 1 byte on the above read,
|
||||
// resulting a a ShortRead. Just write it out (instead of loop) and read the
|
||||
// other byte.
|
||||
if n == 1 {
|
||||
n1, err := t.Read(p[1:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n += n1
|
||||
}
|
||||
|
||||
if n != 2 {
|
||||
return 0, ErrShortRead
|
||||
}
|
||||
l := binary.BigEndian.Uint16(p)
|
||||
if l == 0 {
|
||||
return 0, ErrShortRead
|
||||
}
|
||||
return int(l), nil
|
||||
}
|
||||
|
||||
// tcpRead calls TCPConn.Read enough times to fill allocated buffer.
|
||||
func tcpRead(t io.Reader, p []byte) (int, error) {
|
||||
n, err := t.Read(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
for n < len(p) {
|
||||
j, err := t.Read(p[n:])
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
n += j
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Read implements the net.Conn read method.
|
||||
func (co *Conn) Read(p []byte) (n int, err error) {
|
||||
if co.Conn == nil {
|
||||
return 0, ErrConnEmpty
|
||||
}
|
||||
if len(p) < 2 {
|
||||
|
||||
if isPacketConn(co.Conn) {
|
||||
// UDP connection
|
||||
return co.Conn.Read(p)
|
||||
}
|
||||
|
||||
var length uint16
|
||||
if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if int(length) > len(p) {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
switch t := co.Conn.(type) {
|
||||
case *net.TCPConn, *tls.Conn:
|
||||
r := t.(io.Reader)
|
||||
|
||||
l, err := tcpMsgLen(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if l > len(p) {
|
||||
return int(l), io.ErrShortBuffer
|
||||
}
|
||||
return tcpRead(r, p[:l])
|
||||
}
|
||||
// UDP connection
|
||||
n, err = co.Conn.Read(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, err
|
||||
return io.ReadFull(co.Conn, p[:length])
|
||||
}
|
||||
|
||||
// WriteMsg sends a message through the connection co.
|
||||
|
@ -348,47 +357,32 @@ func (co *Conn) Read(p []byte) (n int, err error) {
|
|||
func (co *Conn) WriteMsg(m *Msg) (err error) {
|
||||
var out []byte
|
||||
if t := m.IsTsig(); t != nil {
|
||||
mac := ""
|
||||
if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
|
||||
return ErrSecret
|
||||
}
|
||||
out, mac, err = TsigGenerate(m, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false)
|
||||
// Set for the next read, although only used in zone transfers
|
||||
co.tsigRequestMAC = mac
|
||||
// Set tsigRequestMAC for the next read, although only used in zone transfers.
|
||||
out, co.tsigRequestMAC, err = TsigGenerateWithProvider(m, co.tsigProvider(), co.tsigRequestMAC, false)
|
||||
} else {
|
||||
out, err = m.Pack()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
co.t = time.Now()
|
||||
if _, err = co.Write(out); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
_, err = co.Write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
// Write implements the net.Conn Write method.
|
||||
func (co *Conn) Write(p []byte) (n int, err error) {
|
||||
switch t := co.Conn.(type) {
|
||||
case *net.TCPConn, *tls.Conn:
|
||||
w := t.(io.Writer)
|
||||
|
||||
lp := len(p)
|
||||
if lp < 2 {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
if lp > MaxMsgSize {
|
||||
return 0, &Error{err: "message too large"}
|
||||
}
|
||||
l := make([]byte, 2, lp+2)
|
||||
binary.BigEndian.PutUint16(l, uint16(lp))
|
||||
p = append(l, p...)
|
||||
n, err := io.Copy(w, bytes.NewReader(p))
|
||||
return int(n), err
|
||||
func (co *Conn) Write(p []byte) (int, error) {
|
||||
if len(p) > MaxMsgSize {
|
||||
return 0, &Error{err: "message too large"}
|
||||
}
|
||||
n, err = co.Conn.Write(p)
|
||||
return n, err
|
||||
|
||||
if isPacketConn(co.Conn) {
|
||||
return co.Conn.Write(p)
|
||||
}
|
||||
|
||||
msg := make([]byte, 2+len(p))
|
||||
binary.BigEndian.PutUint16(msg, uint16(len(p)))
|
||||
copy(msg[2:], p)
|
||||
return co.Conn.Write(msg)
|
||||
}
|
||||
|
||||
// Return the appropriate timeout for a specific request
|
||||
|
@ -424,20 +418,19 @@ func Dial(network, address string) (conn *Conn, err error) {
|
|||
func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) {
|
||||
client := Client{Net: "udp"}
|
||||
r, _, err = client.ExchangeContext(ctx, m, a)
|
||||
// ignorint rtt to leave the original ExchangeContext API unchanged, but
|
||||
// ignoring rtt to leave the original ExchangeContext API unchanged, but
|
||||
// this function will go away
|
||||
return r, err
|
||||
}
|
||||
|
||||
// ExchangeConn performs a synchronous query. It sends the message m via the connection
|
||||
// c and waits for a reply. The connection c is not closed by ExchangeConn.
|
||||
// This function is going away, but can easily be mimicked:
|
||||
// Deprecated: This function is going away, but can easily be mimicked:
|
||||
//
|
||||
// co := &dns.Conn{Conn: c} // c is your net.Conn
|
||||
// co.WriteMsg(m)
|
||||
// in, _ := co.ReadMsg()
|
||||
// co.Close()
|
||||
//
|
||||
func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) {
|
||||
println("dns: ExchangeConn: this function is deprecated")
|
||||
co := new(Conn)
|
||||
|
@ -455,11 +448,7 @@ func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) {
|
|||
// DialTimeout acts like Dial but takes a timeout.
|
||||
func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) {
|
||||
client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}}
|
||||
conn, err = client.Dial(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
return client.Dial(address)
|
||||
}
|
||||
|
||||
// DialWithTLS connects to the address on the named network with TLS.
|
||||
|
@ -468,12 +457,7 @@ func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, er
|
|||
network += "-tls"
|
||||
}
|
||||
client := Client{Net: network, TLSConfig: tlsConfig}
|
||||
conn, err = client.Dial(address)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
return client.Dial(address)
|
||||
}
|
||||
|
||||
// DialTimeoutWithTLS acts like DialWithTLS but takes a timeout.
|
||||
|
@ -482,25 +466,18 @@ func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout
|
|||
network += "-tls"
|
||||
}
|
||||
client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}, TLSConfig: tlsConfig}
|
||||
conn, err = client.Dial(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
return client.Dial(address)
|
||||
}
|
||||
|
||||
// ExchangeContext acts like Exchange, but honors the deadline on the provided
|
||||
// context, if present. If there is both a context deadline and a configured
|
||||
// timeout on the client, the earliest of the two takes effect.
|
||||
func (c *Client) ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
|
||||
var timeout time.Duration
|
||||
if deadline, ok := ctx.Deadline(); !ok {
|
||||
timeout = 0
|
||||
} else {
|
||||
timeout = deadline.Sub(time.Now())
|
||||
conn, err := c.DialContext(ctx, a)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
// not passing the context to the underlying calls, as the API does not support
|
||||
// context. For timeouts you should set up Client.Dialer and call Client.Exchange.
|
||||
c.Dialer = &net.Dialer{Timeout: timeout}
|
||||
return c.Exchange(m, a)
|
||||
defer conn.Close()
|
||||
|
||||
return c.exchangeWithConnContext(ctx, m, conn)
|
||||
}
|
||||
|
|
341
client_test.go
341
client_test.go
|
@ -3,20 +3,116 @@ package dns
|
|||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIsPacketConn(t *testing.T) {
|
||||
// UDP
|
||||
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
c, err := net.Dial("udp", addrstr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
if !isPacketConn(c) {
|
||||
t.Error("UDP connection should be a packet conn")
|
||||
}
|
||||
if !isPacketConn(struct{ *net.UDPConn }{c.(*net.UDPConn)}) {
|
||||
t.Error("UDP connection (wrapped type) should be a packet conn")
|
||||
}
|
||||
|
||||
// TCP
|
||||
s, addrstr, _, err = RunLocalTCPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
c, err = net.Dial("tcp", addrstr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
if isPacketConn(c) {
|
||||
t.Error("TCP connection should not be a packet conn")
|
||||
}
|
||||
if isPacketConn(struct{ *net.TCPConn }{c.(*net.TCPConn)}) {
|
||||
t.Error("TCP connection (wrapped type) should not be a packet conn")
|
||||
}
|
||||
|
||||
// Unix datagram
|
||||
s, addrstr, _, err = RunLocalUnixGramServer(filepath.Join(t.TempDir(), "unixgram.sock"))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
c, err = net.Dial("unixgram", addrstr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
if !isPacketConn(c) {
|
||||
t.Error("Unix datagram connection should be a packet conn")
|
||||
}
|
||||
if !isPacketConn(struct{ *net.UnixConn }{c.(*net.UnixConn)}) {
|
||||
t.Error("Unix datagram connection (wrapped type) should be a packet conn")
|
||||
}
|
||||
|
||||
// Unix Seqpacket
|
||||
shutChan, addrstr, err := RunLocalUnixSeqPacketServer(filepath.Join(t.TempDir(), "unixpacket.sock"))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
shutChan <- &struct{}{}
|
||||
}()
|
||||
c, err = net.Dial("unixpacket", addrstr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
if !isPacketConn(c) {
|
||||
t.Error("Unix datagram connection should be a packet conn")
|
||||
}
|
||||
if !isPacketConn(struct{ *net.UnixConn }{c.(*net.UnixConn)}) {
|
||||
t.Error("Unix datagram connection (wrapped type) should be a packet conn")
|
||||
}
|
||||
|
||||
// Unix stream
|
||||
s, addrstr, _, err = RunLocalUnixServer(filepath.Join(t.TempDir(), "unixstream.sock"))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
c, err = net.Dial("unix", addrstr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
if isPacketConn(c) {
|
||||
t.Error("Unix stream connection should not be a packet conn")
|
||||
}
|
||||
if isPacketConn(struct{ *net.UnixConn }{c.(*net.UnixConn)}) {
|
||||
t.Error("Unix stream connection (wrapped type) should not be a packet conn")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialUDP(t *testing.T) {
|
||||
HandleFunc("miek.nl.", HelloServer)
|
||||
defer HandleRemove("miek.nl.")
|
||||
|
||||
s, addrstr, err := RunLocalUDPServer(":0")
|
||||
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
|
@ -39,7 +135,7 @@ func TestClientSync(t *testing.T) {
|
|||
HandleFunc("miek.nl.", HelloServer)
|
||||
defer HandleRemove("miek.nl.")
|
||||
|
||||
s, addrstr, err := RunLocalUDPServer(":0")
|
||||
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
|
@ -73,7 +169,7 @@ func TestClientLocalAddress(t *testing.T) {
|
|||
HandleFunc("miek.nl.", HelloServerEchoAddrPort)
|
||||
defer HandleRemove("miek.nl.")
|
||||
|
||||
s, addrstr, err := RunLocalUDPServer(":0")
|
||||
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
|
@ -93,7 +189,7 @@ func TestClientLocalAddress(t *testing.T) {
|
|||
t.Errorf("failed to get an valid answer\n%v", r)
|
||||
}
|
||||
if len(r.Extra) != 1 {
|
||||
t.Errorf("failed to get additional answers\n%v", r)
|
||||
t.Fatalf("failed to get additional answers\n%v", r)
|
||||
}
|
||||
txt := r.Extra[0].(*TXT)
|
||||
if txt == nil {
|
||||
|
@ -117,7 +213,7 @@ func TestClientTLSSyncV4(t *testing.T) {
|
|||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
|
||||
s, addrstr, err := RunLocalTLSServer(":0", &config)
|
||||
s, addrstr, _, err := RunLocalTLSServer(":0", &config)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
|
@ -163,11 +259,40 @@ func TestClientTLSSyncV4(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func isNetworkTimeout(err error) bool {
|
||||
// TODO: when Go 1.14 support is dropped, do this: https://golang.org/doc/go1.15#net
|
||||
var netError net.Error
|
||||
return errors.As(err, &netError) && netError.Timeout()
|
||||
}
|
||||
|
||||
func TestClientSyncBadID(t *testing.T) {
|
||||
HandleFunc("miek.nl.", HelloServerBadID)
|
||||
defer HandleRemove("miek.nl.")
|
||||
|
||||
s, addrstr, err := RunLocalUDPServer(":0")
|
||||
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
m := new(Msg)
|
||||
m.SetQuestion("miek.nl.", TypeSOA)
|
||||
|
||||
// Test with client.Exchange, the plain Exchange function is just a wrapper, so
|
||||
// we don't need to test that separately.
|
||||
c := &Client{
|
||||
Timeout: 10 * time.Millisecond,
|
||||
}
|
||||
if _, _, err := c.Exchange(m, addrstr); err == nil || !isNetworkTimeout(err) {
|
||||
t.Errorf("query did not time out")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientSyncBadThenGoodID(t *testing.T) {
|
||||
HandleFunc("miek.nl.", HelloServerBadThenGoodID)
|
||||
defer HandleRemove("miek.nl.")
|
||||
|
||||
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
|
@ -177,11 +302,32 @@ func TestClientSyncBadID(t *testing.T) {
|
|||
m.SetQuestion("miek.nl.", TypeSOA)
|
||||
|
||||
c := new(Client)
|
||||
if _, _, err := c.Exchange(m, addrstr); err != ErrId {
|
||||
t.Errorf("did not find a bad Id")
|
||||
r, _, err := c.Exchange(m, addrstr)
|
||||
if err != nil {
|
||||
t.Errorf("failed to exchange: %v", err)
|
||||
}
|
||||
// And now with plain Exchange().
|
||||
if _, err := Exchange(m, addrstr); err != ErrId {
|
||||
if r.Id != m.Id {
|
||||
t.Errorf("failed to get response with expected Id")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientSyncTCPBadID(t *testing.T) {
|
||||
HandleFunc("miek.nl.", HelloServerBadID)
|
||||
defer HandleRemove("miek.nl.")
|
||||
|
||||
s, addrstr, _, err := RunLocalTCPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
m := new(Msg)
|
||||
m.SetQuestion("miek.nl.", TypeSOA)
|
||||
|
||||
c := &Client{
|
||||
Net: "tcp",
|
||||
}
|
||||
if _, _, err := c.Exchange(m, addrstr); err != ErrId {
|
||||
t.Errorf("did not find a bad Id")
|
||||
}
|
||||
}
|
||||
|
@ -190,7 +336,7 @@ func TestClientEDNS0(t *testing.T) {
|
|||
HandleFunc("miek.nl.", HelloServer)
|
||||
defer HandleRemove("miek.nl.")
|
||||
|
||||
s, addrstr, err := RunLocalUDPServer(":0")
|
||||
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
|
@ -237,7 +383,7 @@ func TestClientEDNS0Local(t *testing.T) {
|
|||
HandleFunc("miek.nl.", handler)
|
||||
defer HandleRemove("miek.nl.")
|
||||
|
||||
s, addrstr, err := RunLocalUDPServer(":0")
|
||||
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %s", err)
|
||||
}
|
||||
|
@ -287,7 +433,7 @@ func TestClientConn(t *testing.T) {
|
|||
defer HandleRemove("miek.nl.")
|
||||
|
||||
// This uses TCP just to make it slightly different than TestClientSync
|
||||
s, addrstr, err := RunLocalTCPServer(":0")
|
||||
s, addrstr, _, err := RunLocalTCPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
|
@ -336,6 +482,24 @@ func TestClientConn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClientConnWriteSinglePacket(t *testing.T) {
|
||||
c := &countingConn{}
|
||||
conn := Conn{
|
||||
Conn: c,
|
||||
}
|
||||
m := new(Msg)
|
||||
m.SetQuestion("miek.nl.", TypeTXT)
|
||||
err := conn.WriteMsg(m)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write: %v", err)
|
||||
}
|
||||
|
||||
if c.writes != 1 {
|
||||
t.Fatalf("incorrect number of Write calls")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncatedMsg(t *testing.T) {
|
||||
m := new(Msg)
|
||||
m.SetQuestion("miek.nl.", TypeSRV)
|
||||
|
@ -373,12 +537,12 @@ func TestTruncatedMsg(t *testing.T) {
|
|||
m.Truncated = true
|
||||
buf, err = m.Pack()
|
||||
if err != nil {
|
||||
t.Errorf("failed to pack truncated: %v", err)
|
||||
t.Errorf("failed to pack truncated message: %v", err)
|
||||
}
|
||||
|
||||
r = new(Msg)
|
||||
if err = r.Unpack(buf); err != nil && err != ErrTruncated {
|
||||
t.Errorf("unable to unpack truncated message: %v", err)
|
||||
if err = r.Unpack(buf); err != nil {
|
||||
t.Errorf("failed to unpack truncated message: %v", err)
|
||||
}
|
||||
if !r.Truncated {
|
||||
t.Errorf("truncated message wasn't unpacked as truncated")
|
||||
|
@ -407,9 +571,10 @@ func TestTruncatedMsg(t *testing.T) {
|
|||
buf1 = buf[:len(buf)-off]
|
||||
|
||||
r = new(Msg)
|
||||
if err = r.Unpack(buf1); err != nil && err != ErrTruncated {
|
||||
t.Errorf("unable to unpack cutoff message: %v", err)
|
||||
if err = r.Unpack(buf1); err == nil {
|
||||
t.Error("cutoff message should have failed to unpack")
|
||||
}
|
||||
// r's header might be still usable.
|
||||
if !r.Truncated {
|
||||
t.Error("truncated cutoff message wasn't unpacked as truncated")
|
||||
}
|
||||
|
@ -438,8 +603,8 @@ func TestTruncatedMsg(t *testing.T) {
|
|||
buf1 = buf[:len(buf)-off]
|
||||
|
||||
r = new(Msg)
|
||||
if err = r.Unpack(buf1); err != nil && err != ErrTruncated {
|
||||
t.Errorf("unable to unpack cutoff message: %v", err)
|
||||
if err = r.Unpack(buf1); err == nil {
|
||||
t.Error("cutoff message should have failed to unpack")
|
||||
}
|
||||
if !r.Truncated {
|
||||
t.Error("truncated cutoff message wasn't unpacked as truncated")
|
||||
|
@ -454,8 +619,8 @@ func TestTruncatedMsg(t *testing.T) {
|
|||
|
||||
r = new(Msg)
|
||||
err = r.Unpack(buf1)
|
||||
if err == nil || err == ErrTruncated {
|
||||
t.Errorf("error should not be ErrTruncated from question cutoff unpack: %v", err)
|
||||
if err == nil {
|
||||
t.Errorf("error should be nil after question cutoff unpack: %v", err)
|
||||
}
|
||||
|
||||
// Finally, if we only have the header, we don't return an error.
|
||||
|
@ -484,49 +649,34 @@ func TestTimeout(t *testing.T) {
|
|||
m := new(Msg)
|
||||
m.SetQuestion("miek.nl.", TypeTXT)
|
||||
|
||||
// Use a channel + timeout to ensure we don't get stuck if the
|
||||
// Client Timeout is not working properly
|
||||
done := make(chan struct{}, 2)
|
||||
runTest := func(name string, exchange func(m *Msg, addr string, timeout time.Duration) (*Msg, time.Duration, error)) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
start := time.Now()
|
||||
|
||||
timeout := time.Millisecond
|
||||
allowable := timeout + (10 * time.Millisecond)
|
||||
abortAfter := timeout + (100 * time.Millisecond)
|
||||
timeout := time.Millisecond
|
||||
allowable := timeout + 10*time.Millisecond
|
||||
|
||||
start := time.Now()
|
||||
_, _, err := exchange(m, addrstr, timeout)
|
||||
if err == nil {
|
||||
t.Errorf("no timeout using Client.%s", name)
|
||||
}
|
||||
|
||||
go func() {
|
||||
length := time.Since(start)
|
||||
if length > allowable {
|
||||
t.Errorf("exchange took longer %v than specified Timeout %v", length, allowable)
|
||||
}
|
||||
})
|
||||
}
|
||||
runTest("Exchange", func(m *Msg, addr string, timeout time.Duration) (*Msg, time.Duration, error) {
|
||||
c := &Client{Timeout: timeout}
|
||||
_, _, err := c.Exchange(m, addrstr)
|
||||
if err == nil {
|
||||
t.Error("no timeout using Client.Exchange")
|
||||
}
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
return c.Exchange(m, addr)
|
||||
})
|
||||
runTest("ExchangeContext", func(m *Msg, addr string, timeout time.Duration) (*Msg, time.Duration, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
c := &Client{}
|
||||
_, _, err := c.ExchangeContext(ctx, m, addrstr)
|
||||
if err == nil {
|
||||
t.Error("no timeout using Client.ExchangeContext")
|
||||
}
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
// Wait for both the Exchange and ExchangeContext tests to be done.
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(abortAfter):
|
||||
}
|
||||
}
|
||||
|
||||
length := time.Since(start)
|
||||
|
||||
if length > allowable {
|
||||
t.Errorf("exchange took longer %v than specified Timeout %v", length, allowable)
|
||||
}
|
||||
return new(Client).ExchangeContext(ctx, m, addrstr)
|
||||
})
|
||||
}
|
||||
|
||||
// Check that responses from deduplicated requests aren't shared between callers
|
||||
|
@ -535,23 +685,20 @@ func TestConcurrentExchanges(t *testing.T) {
|
|||
cases[0] = new(Msg)
|
||||
cases[1] = new(Msg)
|
||||
cases[1].Truncated = true
|
||||
for _, m := range cases {
|
||||
block := make(chan struct{})
|
||||
waiting := make(chan struct{})
|
||||
|
||||
for _, m := range cases {
|
||||
mm := m // redeclare m so as not to trip the race detector
|
||||
handler := func(w ResponseWriter, req *Msg) {
|
||||
r := m.Copy()
|
||||
r := mm.Copy()
|
||||
r.SetReply(req)
|
||||
|
||||
waiting <- struct{}{}
|
||||
<-block
|
||||
w.WriteMsg(r)
|
||||
}
|
||||
|
||||
HandleFunc("miek.nl.", handler)
|
||||
defer HandleRemove("miek.nl.")
|
||||
|
||||
s, addrstr, err := RunLocalUDPServer(":0")
|
||||
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %s", err)
|
||||
}
|
||||
|
@ -559,32 +706,58 @@ func TestConcurrentExchanges(t *testing.T) {
|
|||
|
||||
m := new(Msg)
|
||||
m.SetQuestion("miek.nl.", TypeSRV)
|
||||
|
||||
c := &Client{
|
||||
SingleInflight: true,
|
||||
}
|
||||
r := make([]*Msg, 2)
|
||||
// Force this client to always return the same request,
|
||||
// even though we're querying sequentially. Running the
|
||||
// Exchange calls below concurrently can fail due to
|
||||
// goroutine scheduling, but this simulates the same
|
||||
// outcome.
|
||||
c.group.dontDeleteForTesting = true
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(r))
|
||||
for i := 0; i < len(r); i++ {
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
r[i], _, _ = c.Exchange(m.Copy(), addrstr)
|
||||
if r[i] == nil {
|
||||
t.Errorf("response %d is nil", i)
|
||||
}
|
||||
}(i)
|
||||
r := make([]*Msg, 2)
|
||||
for i := range r {
|
||||
r[i], _, _ = c.Exchange(m.Copy(), addrstr)
|
||||
if r[i] == nil {
|
||||
t.Errorf("response %d is nil", i)
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-waiting:
|
||||
case <-time.After(time.Second):
|
||||
t.FailNow()
|
||||
}
|
||||
close(block)
|
||||
wg.Wait()
|
||||
|
||||
if r[0] == r[1] {
|
||||
t.Errorf("got same response, expected non-shared responses")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExchangeWithConn(t *testing.T) {
|
||||
HandleFunc("miek.nl.", HelloServer)
|
||||
defer HandleRemove("miek.nl.")
|
||||
|
||||
s, addrstr, _, err := RunLocalUDPServer(":0")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to run test server: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
m := new(Msg)
|
||||
m.SetQuestion("miek.nl.", TypeSOA)
|
||||
|
||||
c := new(Client)
|
||||
conn, err := c.Dial(addrstr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
|
||||
r, _, err := c.ExchangeWithConn(m, conn)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to exchange: %v", err)
|
||||
}
|
||||
if r == nil {
|
||||
t.Fatal("response is nil")
|
||||
}
|
||||
if r.Rcode != RcodeSuccess {
|
||||
t.Errorf("failed to get an valid answer\n%v", r)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,14 +68,10 @@ func ClientConfigFromReader(resolvconf io.Reader) (*ClientConfig, error) {
|
|||
}
|
||||
|
||||
case "search": // set search path to given servers
|
||||
c.Search = make([]string, len(f)-1)
|
||||
for i := 0; i < len(c.Search); i++ {
|
||||
c.Search[i] = f[i+1]
|
||||
}
|
||||
c.Search = append([]string(nil), f[1:]...)
|
||||
|
||||
case "options": // magic options
|
||||
for i := 1; i < len(f); i++ {
|
||||
s := f[i]
|
||||
for _, s := range f[1:] {
|
||||
switch {
|
||||
case len(s) >= 6 && s[:6] == "ndots:":
|
||||
n, _ := strconv.Atoi(s[6:])
|
||||
|
@ -91,7 +87,7 @@ func ClientConfigFromReader(resolvconf io.Reader) (*ClientConfig, error) {
|
|||
n = 1
|
||||
}
|
||||
c.Timeout = n
|
||||
case len(s) >= 8 && s[:9] == "attempts:":
|
||||
case len(s) >= 9 && s[:9] == "attempts:":
|
||||
n, _ := strconv.Atoi(s[9:])
|
||||
if n < 1 {
|
||||
n = 1
|
||||
|
|
|
@ -59,7 +59,35 @@ func TestNdots(t *testing.T) {
|
|||
t.Errorf("Ndots not properly parsed: (Expected: %d / Was: %d)", ndotsVariants[data], cc.Ndots)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientConfigFromReaderAttempts(t *testing.T) {
|
||||
testCases := []struct {
|
||||
data string
|
||||
expected int
|
||||
}{
|
||||
{data: "options attempts:0", expected: 1},
|
||||
{data: "options attempts:1", expected: 1},
|
||||
{data: "options attempts:15", expected: 15},
|
||||
{data: "options attempts:16", expected: 16},
|
||||
{data: "options attempts:-1", expected: 1},
|
||||
{data: "options attempt:", expected: 2},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(strings.Replace(test.data, ":", " ", -1), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cc, err := ClientConfigFromReader(strings.NewReader(test.data))
|
||||
if err != nil {
|
||||
t.Errorf("error parsing resolv.conf: %v", err)
|
||||
}
|
||||
if cc.Attempts != test.expected {
|
||||
t.Errorf("A attempts not properly parsed: (Expected: %d / Was: %d)", test.expected, cc.Attempts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFromFile(t *testing.T) {
|
||||
|
|
|
@ -1,188 +0,0 @@
|
|||
//+build ignore
|
||||
|
||||
// compression_generate.go is meant to run with go generate. It will use
|
||||
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||
// it will look to see if there are (compressible) names, if so it will add that
|
||||
// type to compressionLenHelperType and comressionLenSearchType which "fake" the
|
||||
// compression so that Len() is fast.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"go/importer"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var packageHdr = `
|
||||
// Code generated by "go run compress_generate.go"; DO NOT EDIT.
|
||||
|
||||
package dns
|
||||
|
||||
`
|
||||
|
||||
// getTypeStruct will take a type and the package scope, and return the
|
||||
// (innermost) struct if the type is considered a RR type (currently defined as
|
||||
// those structs beginning with a RR_Header, could be redefined as implementing
|
||||
// the RR interface). The bool return value indicates if embedded structs were
|
||||
// resolved.
|
||||
func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
|
||||
st, ok := t.Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
||||
return st, false
|
||||
}
|
||||
if st.Field(0).Anonymous() {
|
||||
st, _ := getTypeStruct(st.Field(0).Type(), scope)
|
||||
return st, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Import and type-check the package
|
||||
pkg, err := importer.Default().Import("github.com/miekg/dns")
|
||||
fatalIfErr(err)
|
||||
scope := pkg.Scope()
|
||||
|
||||
var domainTypes []string // Types that have a domain name in them (either compressible or not).
|
||||
var cdomainTypes []string // Types that have a compressible domain name in them (subset of domainType)
|
||||
Names:
|
||||
for _, name := range scope.Names() {
|
||||
o := scope.Lookup(name)
|
||||
if o == nil || !o.Exported() {
|
||||
continue
|
||||
}
|
||||
st, _ := getTypeStruct(o.Type(), scope)
|
||||
if st == nil {
|
||||
continue
|
||||
}
|
||||
if name == "PrivateRR" {
|
||||
continue
|
||||
}
|
||||
|
||||
if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" {
|
||||
log.Fatalf("Constant Type%s does not exist.", o.Name())
|
||||
}
|
||||
|
||||
for i := 1; i < st.NumFields(); i++ {
|
||||
if _, ok := st.Field(i).Type().(*types.Slice); ok {
|
||||
if st.Tag(i) == `dns:"domain-name"` {
|
||||
domainTypes = append(domainTypes, o.Name())
|
||||
continue Names
|
||||
}
|
||||
if st.Tag(i) == `dns:"cdomain-name"` {
|
||||
cdomainTypes = append(cdomainTypes, o.Name())
|
||||
domainTypes = append(domainTypes, o.Name())
|
||||
continue Names
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case st.Tag(i) == `dns:"domain-name"`:
|
||||
domainTypes = append(domainTypes, o.Name())
|
||||
continue Names
|
||||
case st.Tag(i) == `dns:"cdomain-name"`:
|
||||
cdomainTypes = append(cdomainTypes, o.Name())
|
||||
domainTypes = append(domainTypes, o.Name())
|
||||
continue Names
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
b.WriteString(packageHdr)
|
||||
|
||||
// compressionLenHelperType - all types that have domain-name/cdomain-name can be used for compressing names
|
||||
|
||||
fmt.Fprint(b, "func compressionLenHelperType(c map[string]int, r RR) {\n")
|
||||
fmt.Fprint(b, "switch x := r.(type) {\n")
|
||||
for _, name := range domainTypes {
|
||||
o := scope.Lookup(name)
|
||||
st, _ := getTypeStruct(o.Type(), scope)
|
||||
|
||||
fmt.Fprintf(b, "case *%s:\n", name)
|
||||
for i := 1; i < st.NumFields(); i++ {
|
||||
out := func(s string) { fmt.Fprintf(b, "compressionLenHelper(c, x.%s)\n", st.Field(i).Name()) }
|
||||
|
||||
if _, ok := st.Field(i).Type().(*types.Slice); ok {
|
||||
switch st.Tag(i) {
|
||||
case `dns:"domain-name"`:
|
||||
fallthrough
|
||||
case `dns:"cdomain-name"`:
|
||||
// For HIP we need to slice over the elements in this slice.
|
||||
fmt.Fprintf(b, `for i := range x.%s {
|
||||
compressionLenHelper(c, x.%s[i])
|
||||
}
|
||||
`, st.Field(i).Name(), st.Field(i).Name())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case st.Tag(i) == `dns:"cdomain-name"`:
|
||||
fallthrough
|
||||
case st.Tag(i) == `dns:"domain-name"`:
|
||||
out(st.Field(i).Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(b, "}\n}\n\n")
|
||||
|
||||
// compressionLenSearchType - search cdomain-tags types for compressible names.
|
||||
|
||||
fmt.Fprint(b, "func compressionLenSearchType(c map[string]int, r RR) (int, bool) {\n")
|
||||
fmt.Fprint(b, "switch x := r.(type) {\n")
|
||||
for _, name := range cdomainTypes {
|
||||
o := scope.Lookup(name)
|
||||
st, _ := getTypeStruct(o.Type(), scope)
|
||||
|
||||
fmt.Fprintf(b, "case *%s:\n", name)
|
||||
j := 1
|
||||
for i := 1; i < st.NumFields(); i++ {
|
||||
out := func(s string, j int) {
|
||||
fmt.Fprintf(b, "k%d, ok%d := compressionLenSearch(c, x.%s)\n", j, j, st.Field(i).Name())
|
||||
}
|
||||
|
||||
// There are no slice types with names that can be compressed.
|
||||
|
||||
switch {
|
||||
case st.Tag(i) == `dns:"cdomain-name"`:
|
||||
out(st.Field(i).Name(), j)
|
||||
j++
|
||||
}
|
||||
}
|
||||
k := "k1"
|
||||
ok := "ok1"
|
||||
for i := 2; i < j; i++ {
|
||||
k += fmt.Sprintf(" + k%d", i)
|
||||
ok += fmt.Sprintf(" && ok%d", i)
|
||||
}
|
||||
fmt.Fprintf(b, "return %s, %s\n", k, ok)
|
||||
}
|
||||
fmt.Fprintln(b, "}\nreturn 0, false\n}\n\n")
|
||||
|
||||
// gofmt
|
||||
res, err := format.Source(b.Bytes())
|
||||
if err != nil {
|
||||
b.WriteTo(os.Stderr)
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
f, err := os.Create("zcompress.go")
|
||||
fatalIfErr(err)
|
||||
defer f.Close()
|
||||
f.Write(res)
|
||||
}
|
||||
|
||||
func fatalIfErr(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
147
defaults.go
147
defaults.go
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const hexDigit = "0123456789abcdef"
|
||||
|
@ -104,7 +105,7 @@ func (dns *Msg) SetAxfr(z string) *Msg {
|
|||
|
||||
// SetTsig appends a TSIG RR to the message.
|
||||
// This is only a skeleton TSIG RR that is added as the last RR in the
|
||||
// additional section. The Tsig is calculated when the message is being send.
|
||||
// additional section. The TSIG is calculated when the message is being send.
|
||||
func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg {
|
||||
t := new(TSIG)
|
||||
t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0}
|
||||
|
@ -145,10 +146,9 @@ func (dns *Msg) IsTsig() *TSIG {
|
|||
// record in the additional section will do. It returns the OPT record
|
||||
// found or nil.
|
||||
func (dns *Msg) IsEdns0() *OPT {
|
||||
// EDNS0 is at the end of the additional section, start there.
|
||||
// We might want to change this to *only* look at the last two
|
||||
// records. So we see TSIG and/or OPT - this a slightly bigger
|
||||
// change though.
|
||||
// RFC 6891, Section 6.1.1 allows the OPT record to appear
|
||||
// anywhere in the additional record section, but it's usually at
|
||||
// the end so start there.
|
||||
for i := len(dns.Extra) - 1; i >= 0; i-- {
|
||||
if dns.Extra[i].Header().Rrtype == TypeOPT {
|
||||
return dns.Extra[i].(*OPT)
|
||||
|
@ -157,17 +157,98 @@ func (dns *Msg) IsEdns0() *OPT {
|
|||
return nil
|
||||
}
|
||||
|
||||
// popEdns0 is like IsEdns0, but it removes the record from the message.
|
||||
func (dns *Msg) popEdns0() *OPT {
|
||||
// RFC 6891, Section 6.1.1 allows the OPT record to appear
|
||||
// anywhere in the additional record section, but it's usually at
|
||||
// the end so start there.
|
||||
for i := len(dns.Extra) - 1; i >= 0; i-- {
|
||||
if dns.Extra[i].Header().Rrtype == TypeOPT {
|
||||
opt := dns.Extra[i].(*OPT)
|
||||
dns.Extra = append(dns.Extra[:i], dns.Extra[i+1:]...)
|
||||
return opt
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsDomainName checks if s is a valid domain name, it returns the number of
|
||||
// labels and true, when a domain name is valid. Note that non fully qualified
|
||||
// domain name is considered valid, in this case the last label is counted in
|
||||
// the number of labels. When false is returned the number of labels is not
|
||||
// defined. Also note that this function is extremely liberal; almost any
|
||||
// string is a valid domain name as the DNS is 8 bit protocol. It checks if each
|
||||
// label fits in 63 characters, but there is no length check for the entire
|
||||
// string s. I.e. a domain name longer than 255 characters is considered valid.
|
||||
// label fits in 63 characters and that the entire name will fit into the 255
|
||||
// octet wire format limit.
|
||||
func IsDomainName(s string) (labels int, ok bool) {
|
||||
_, labels, err := packDomainName(s, nil, 0, nil, false)
|
||||
return labels, err == nil
|
||||
// XXX: The logic in this function was copied from packDomainName and
|
||||
// should be kept in sync with that function.
|
||||
|
||||
const lenmsg = 256
|
||||
|
||||
if len(s) == 0 { // Ok, for instance when dealing with update RR without any rdata.
|
||||
return 0, false
|
||||
}
|
||||
|
||||
s = Fqdn(s)
|
||||
|
||||
// Each dot ends a segment of the name. Except for escaped dots (\.), which
|
||||
// are normal dots.
|
||||
|
||||
var (
|
||||
off int
|
||||
begin int
|
||||
wasDot bool
|
||||
)
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '\\':
|
||||
if off+1 > lenmsg {
|
||||
return labels, false
|
||||
}
|
||||
|
||||
// check for \DDD
|
||||
if i+3 < len(s) && isDigit(s[i+1]) && isDigit(s[i+2]) && isDigit(s[i+3]) {
|
||||
i += 3
|
||||
begin += 3
|
||||
} else {
|
||||
i++
|
||||
begin++
|
||||
}
|
||||
|
||||
wasDot = false
|
||||
case '.':
|
||||
if i == 0 && len(s) > 1 {
|
||||
// leading dots are not legal except for the root zone
|
||||
return labels, false
|
||||
}
|
||||
|
||||
if wasDot {
|
||||
// two dots back to back is not legal
|
||||
return labels, false
|
||||
}
|
||||
wasDot = true
|
||||
|
||||
labelLen := i - begin
|
||||
if labelLen >= 1<<6 { // top two bits of length must be clear
|
||||
return labels, false
|
||||
}
|
||||
|
||||
// off can already (we're in a loop) be bigger than lenmsg
|
||||
// this happens when a name isn't fully qualified
|
||||
off += 1 + labelLen
|
||||
if off > lenmsg {
|
||||
return labels, false
|
||||
}
|
||||
|
||||
labels++
|
||||
begin = i + 1
|
||||
default:
|
||||
wasDot = false
|
||||
}
|
||||
}
|
||||
|
||||
return labels, true
|
||||
}
|
||||
|
||||
// IsSubDomain checks if child is indeed a child of the parent. If child and parent
|
||||
|
@ -181,7 +262,7 @@ func IsSubDomain(parent, child string) bool {
|
|||
// The checking is performed on the binary payload.
|
||||
func IsMsg(buf []byte) error {
|
||||
// Header
|
||||
if len(buf) < 12 {
|
||||
if len(buf) < headerSize {
|
||||
return errors.New("dns: bad message header")
|
||||
}
|
||||
// Header: Opcode
|
||||
|
@ -191,11 +272,18 @@ func IsMsg(buf []byte) error {
|
|||
|
||||
// IsFqdn checks if a domain name is fully qualified.
|
||||
func IsFqdn(s string) bool {
|
||||
l := len(s)
|
||||
if l == 0 {
|
||||
s2 := strings.TrimSuffix(s, ".")
|
||||
if s == s2 {
|
||||
return false
|
||||
}
|
||||
return s[l-1] == '.'
|
||||
|
||||
i := strings.LastIndexFunc(s2, func(r rune) bool {
|
||||
return r != '\\'
|
||||
})
|
||||
|
||||
// Test whether we have an even number of escape sequences before
|
||||
// the dot or none.
|
||||
return (len(s2)-i)%2 != 0
|
||||
}
|
||||
|
||||
// IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181.
|
||||
|
@ -234,6 +322,12 @@ func Fqdn(s string) string {
|
|||
return s + "."
|
||||
}
|
||||
|
||||
// CanonicalName returns the domain name in canonical form. A name in canonical
|
||||
// form is lowercase and fully qualified. See Section 6.2 in RFC 4034.
|
||||
func CanonicalName(s string) string {
|
||||
return strings.ToLower(Fqdn(s))
|
||||
}
|
||||
|
||||
// Copied from the official Go code.
|
||||
|
||||
// ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
|
||||
|
@ -244,19 +338,23 @@ func ReverseAddr(addr string) (arpa string, err error) {
|
|||
if ip == nil {
|
||||
return "", &Error{err: "unrecognized address: " + addr}
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
return strconv.Itoa(int(ip[15])) + "." + strconv.Itoa(int(ip[14])) + "." + strconv.Itoa(int(ip[13])) + "." +
|
||||
strconv.Itoa(int(ip[12])) + ".in-addr.arpa.", nil
|
||||
if v4 := ip.To4(); v4 != nil {
|
||||
buf := make([]byte, 0, net.IPv4len*4+len("in-addr.arpa."))
|
||||
// Add it, in reverse, to the buffer
|
||||
for i := len(v4) - 1; i >= 0; i-- {
|
||||
buf = strconv.AppendInt(buf, int64(v4[i]), 10)
|
||||
buf = append(buf, '.')
|
||||
}
|
||||
// Append "in-addr.arpa." and return (buf already has the final .)
|
||||
buf = append(buf, "in-addr.arpa."...)
|
||||
return string(buf), nil
|
||||
}
|
||||
// Must be IPv6
|
||||
buf := make([]byte, 0, len(ip)*4+len("ip6.arpa."))
|
||||
buf := make([]byte, 0, net.IPv6len*4+len("ip6.arpa."))
|
||||
// Add it, in reverse, to the buffer
|
||||
for i := len(ip) - 1; i >= 0; i-- {
|
||||
v := ip[i]
|
||||
buf = append(buf, hexDigit[v&0xF])
|
||||
buf = append(buf, '.')
|
||||
buf = append(buf, hexDigit[v>>4])
|
||||
buf = append(buf, '.')
|
||||
buf = append(buf, hexDigit[v&0xF], '.', hexDigit[v>>4], '.')
|
||||
}
|
||||
// Append "ip6.arpa." and return (buf already has the final .)
|
||||
buf = append(buf, "ip6.arpa."...)
|
||||
|
@ -273,8 +371,11 @@ func (t Type) String() string {
|
|||
|
||||
// String returns the string representation for the class c.
|
||||
func (c Class) String() string {
|
||||
if c1, ok := ClassToString[uint16(c)]; ok {
|
||||
return c1
|
||||
if s, ok := ClassToString[uint16(c)]; ok {
|
||||
// Only emit mnemonics when they are unambiguous, specially ANY is in both.
|
||||
if _, ok := StringToType[s]; !ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return "CLASS" + strconv.Itoa(int(c))
|
||||
}
|
||||
|
|
99
dns.go
99
dns.go
|
@ -1,6 +1,9 @@
|
|||
package dns
|
||||
|
||||
import "strconv"
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits.
|
||||
|
@ -34,10 +37,30 @@ type RR interface {
|
|||
|
||||
// copy returns a copy of the RR
|
||||
copy() RR
|
||||
// len returns the length (in octets) of the uncompressed RR in wire format.
|
||||
len() int
|
||||
// pack packs an RR into wire format.
|
||||
pack([]byte, int, map[string]int, bool) (int, error)
|
||||
|
||||
// len returns the length (in octets) of the compressed or uncompressed RR in wire format.
|
||||
//
|
||||
// If compression is nil, the uncompressed size will be returned, otherwise the compressed
|
||||
// size will be returned and domain names will be added to the map for future compression.
|
||||
len(off int, compression map[string]struct{}) int
|
||||
|
||||
// pack packs the records RDATA into wire format. The header will
|
||||
// already have been packed into msg.
|
||||
pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error)
|
||||
|
||||
// unpack unpacks an RR from wire format.
|
||||
//
|
||||
// This will only be called on a new and empty RR type with only the header populated. It
|
||||
// will only be called if the record's RDATA is non-empty.
|
||||
unpack(msg []byte, off int) (off1 int, err error)
|
||||
|
||||
// parse parses an RR from zone file format.
|
||||
//
|
||||
// This will only be called on a new and empty RR type with only the header populated.
|
||||
parse(c *zlexer, origin string) *ParseError
|
||||
|
||||
// isDuplicate returns whether the two RRs are duplicates.
|
||||
isDuplicate(r2 RR) bool
|
||||
}
|
||||
|
||||
// RR_Header is the header all DNS resource records share.
|
||||
|
@ -55,16 +78,6 @@ func (h *RR_Header) Header() *RR_Header { return h }
|
|||
// Just to implement the RR interface.
|
||||
func (h *RR_Header) copy() RR { return nil }
|
||||
|
||||
func (h *RR_Header) copyHeader() *RR_Header {
|
||||
r := new(RR_Header)
|
||||
r.Name = h.Name
|
||||
r.Rrtype = h.Rrtype
|
||||
r.Class = h.Class
|
||||
r.Ttl = h.Ttl
|
||||
r.Rdlength = h.Rdlength
|
||||
return r
|
||||
}
|
||||
|
||||
func (h *RR_Header) String() string {
|
||||
var s string
|
||||
|
||||
|
@ -80,28 +93,66 @@ func (h *RR_Header) String() string {
|
|||
return s
|
||||
}
|
||||
|
||||
func (h *RR_Header) len() int {
|
||||
l := len(h.Name) + 1
|
||||
func (h *RR_Header) len(off int, compression map[string]struct{}) int {
|
||||
l := domainNameLen(h.Name, off, compression, true)
|
||||
l += 10 // rrtype(2) + class(2) + ttl(4) + rdlength(2)
|
||||
return l
|
||||
}
|
||||
|
||||
func (h *RR_Header) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
||||
// RR_Header has no RDATA to pack.
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func (h *RR_Header) unpack(msg []byte, off int) (int, error) {
|
||||
panic("dns: internal error: unpack should never be called on RR_Header")
|
||||
}
|
||||
|
||||
func (h *RR_Header) parse(c *zlexer, origin string) *ParseError {
|
||||
panic("dns: internal error: parse should never be called on RR_Header")
|
||||
}
|
||||
|
||||
// ToRFC3597 converts a known RR to the unknown RR representation from RFC 3597.
|
||||
func (rr *RFC3597) ToRFC3597(r RR) error {
|
||||
buf := make([]byte, r.len()*2)
|
||||
off, err := PackRR(r, buf, 0, nil, false)
|
||||
buf := make([]byte, Len(r))
|
||||
headerEnd, off, err := packRR(r, buf, 0, compressionMap{}, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf = buf[:off]
|
||||
if int(r.Header().Rdlength) > off {
|
||||
return ErrBuf
|
||||
|
||||
*rr = RFC3597{Hdr: *r.Header()}
|
||||
rr.Hdr.Rdlength = uint16(off - headerEnd)
|
||||
|
||||
if noRdata(rr.Hdr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
rfc3597, _, err := unpackRFC3597(*r.Header(), buf, off-int(r.Header().Rdlength))
|
||||
_, err = rr.unpack(buf, headerEnd)
|
||||
return err
|
||||
}
|
||||
|
||||
// fromRFC3597 converts an unknown RR representation from RFC 3597 to the known RR type.
|
||||
func (rr *RFC3597) fromRFC3597(r RR) error {
|
||||
hdr := r.Header()
|
||||
*hdr = rr.Hdr
|
||||
|
||||
// Can't overflow uint16 as the length of Rdata is validated in (*RFC3597).parse.
|
||||
// We can only get here when rr was constructed with that method.
|
||||
hdr.Rdlength = uint16(hex.DecodedLen(len(rr.Rdata)))
|
||||
|
||||
if noRdata(*hdr) {
|
||||
// Dynamic update.
|
||||
return nil
|
||||
}
|
||||
|
||||
// rr.pack requires an extra allocation and a copy so we just decode Rdata
|
||||
// manually, it's simpler anyway.
|
||||
msg, err := hex.DecodeString(rr.Rdata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*rr = *rfc3597.(*RFC3597)
|
||||
return nil
|
||||
|
||||
_, err = r.unpack(msg, 0)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
@ -63,6 +64,50 @@ func BenchmarkMsgLengthPack(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkMsgLengthMassive(b *testing.B) {
|
||||
makeMsg := func(question string, ans, ns, e []RR) *Msg {
|
||||
msg := new(Msg)
|
||||
msg.SetQuestion(Fqdn(question), TypeANY)
|
||||
msg.Answer = append(msg.Answer, ans...)
|
||||
msg.Ns = append(msg.Ns, ns...)
|
||||
msg.Extra = append(msg.Extra, e...)
|
||||
msg.Compress = true
|
||||
return msg
|
||||
}
|
||||
const name1 = "12345678901234567890123456789012345.12345678.123."
|
||||
rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
|
||||
answer := []RR{rrMx, rrMx}
|
||||
for i := 0; i < 128; i++ {
|
||||
rrA := testRR(fmt.Sprintf("example%03d.something%03delse.org. 2311 IN A 127.0.0.1", i/32, i%32))
|
||||
answer = append(answer, rrA)
|
||||
}
|
||||
answer = append(answer, rrMx, rrMx)
|
||||
msg := makeMsg(name1, answer, nil, nil)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
msg.Len()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMsgLengthOnlyQuestion(b *testing.B) {
|
||||
msg := new(Msg)
|
||||
msg.SetQuestion(Fqdn("12345678901234567890123456789012345.12345678.123."), TypeANY)
|
||||
msg.Compress = true
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
msg.Len()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMsgLengthEscapedName(b *testing.B) {
|
||||
msg := new(Msg)
|
||||
msg.SetQuestion(`\1\2\3\4\5\6\7\8\9\0\1\2\3\4\5\6\7\8\9\0\1\2\3\4\5\6\7\8\9\0\1\2\3\4\5.\1\2\3\4\5\6\7\8.\1\2\3.`, TypeANY)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
msg.Len()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPackDomainName(b *testing.B) {
|
||||
name1 := "12345678901234567890123456789012345.12345678.123."
|
||||
buf := make([]byte, len(name1)+1)
|
||||
|
@ -92,6 +137,36 @@ func BenchmarkUnpackDomainNameUnprintable(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnpackDomainNameLongest(b *testing.B) {
|
||||
buf := make([]byte, len(longestDomain)+1)
|
||||
n, err := PackDomainName(longestDomain, buf, 0, nil, false)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if n != maxDomainNameWireOctets {
|
||||
b.Fatalf("name wrong size in wire format, expected %d, got %d", maxDomainNameWireOctets, n)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, _ = UnpackDomainName(buf, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnpackDomainNameLongestUnprintable(b *testing.B) {
|
||||
buf := make([]byte, len(longestUnprintableDomain)+1)
|
||||
n, err := PackDomainName(longestUnprintableDomain, buf, 0, nil, false)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if n != maxDomainNameWireOctets {
|
||||
b.Fatalf("name wrong size in wire format, expected %d, got %d", maxDomainNameWireOctets, n)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, _ = UnpackDomainName(buf, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCopy(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
m := new(Msg)
|
||||
|
@ -112,7 +187,7 @@ func BenchmarkCopy(b *testing.B) {
|
|||
func BenchmarkPackA(b *testing.B) {
|
||||
a := &A{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, A: net.IPv4(127, 0, 0, 1)}
|
||||
|
||||
buf := make([]byte, a.len())
|
||||
buf := make([]byte, Len(a))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
@ -123,7 +198,7 @@ func BenchmarkPackA(b *testing.B) {
|
|||
func BenchmarkUnpackA(b *testing.B) {
|
||||
a := &A{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, A: net.IPv4(127, 0, 0, 1)}
|
||||
|
||||
buf := make([]byte, a.len())
|
||||
buf := make([]byte, Len(a))
|
||||
PackRR(a, buf, 0, nil, false)
|
||||
a = nil
|
||||
b.ReportAllocs()
|
||||
|
@ -136,7 +211,7 @@ func BenchmarkUnpackA(b *testing.B) {
|
|||
func BenchmarkPackMX(b *testing.B) {
|
||||
m := &MX{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, Mx: "mx.miek.nl."}
|
||||
|
||||
buf := make([]byte, m.len())
|
||||
buf := make([]byte, Len(m))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
@ -147,7 +222,7 @@ func BenchmarkPackMX(b *testing.B) {
|
|||
func BenchmarkUnpackMX(b *testing.B) {
|
||||
m := &MX{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, Mx: "mx.miek.nl."}
|
||||
|
||||
buf := make([]byte, m.len())
|
||||
buf := make([]byte, Len(m))
|
||||
PackRR(m, buf, 0, nil, false)
|
||||
m = nil
|
||||
b.ReportAllocs()
|
||||
|
@ -158,9 +233,9 @@ func BenchmarkUnpackMX(b *testing.B) {
|
|||
}
|
||||
|
||||
func BenchmarkPackAAAAA(b *testing.B) {
|
||||
aaaa := testRR(". IN A ::1")
|
||||
aaaa := testRR(". IN AAAA ::1")
|
||||
|
||||
buf := make([]byte, aaaa.len())
|
||||
buf := make([]byte, Len(aaaa))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
@ -169,9 +244,9 @@ func BenchmarkPackAAAAA(b *testing.B) {
|
|||
}
|
||||
|
||||
func BenchmarkUnpackAAAA(b *testing.B) {
|
||||
aaaa := testRR(". IN A ::1")
|
||||
aaaa := testRR(". IN AAAA ::1")
|
||||
|
||||
buf := make([]byte, aaaa.len())
|
||||
buf := make([]byte, Len(aaaa))
|
||||
PackRR(aaaa, buf, 0, nil, false)
|
||||
aaaa = nil
|
||||
b.ReportAllocs()
|
||||
|
@ -202,6 +277,45 @@ func BenchmarkPackMsg(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkPackMsgMassive(b *testing.B) {
|
||||
makeMsg := func(question string, ans, ns, e []RR) *Msg {
|
||||
msg := new(Msg)
|
||||
msg.SetQuestion(Fqdn(question), TypeANY)
|
||||
msg.Answer = append(msg.Answer, ans...)
|
||||
msg.Ns = append(msg.Ns, ns...)
|
||||
msg.Extra = append(msg.Extra, e...)
|
||||
msg.Compress = true
|
||||
return msg
|
||||
}
|
||||
const name1 = "12345678901234567890123456789012345.12345678.123."
|
||||
rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
|
||||
answer := []RR{rrMx, rrMx}
|
||||
for i := 0; i < 128; i++ {
|
||||
rrA := testRR(fmt.Sprintf("example%03d.something%03delse.org. 2311 IN A 127.0.0.1", i/32, i%32))
|
||||
answer = append(answer, rrA)
|
||||
}
|
||||
answer = append(answer, rrMx, rrMx)
|
||||
msg := makeMsg(name1, answer, nil, nil)
|
||||
buf := make([]byte, 512)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = msg.PackBuffer(buf)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPackMsgOnlyQuestion(b *testing.B) {
|
||||
msg := new(Msg)
|
||||
msg.SetQuestion(Fqdn("12345678901234567890123456789012345.12345678.123."), TypeANY)
|
||||
msg.Compress = true
|
||||
buf := make([]byte, 512)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = msg.PackBuffer(buf)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnpackMsg(b *testing.B) {
|
||||
makeMsg := func(question string, ans, ns, e []RR) *Msg {
|
||||
msg := new(Msg)
|
||||
|
@ -228,3 +342,29 @@ func BenchmarkIdGeneration(b *testing.B) {
|
|||
_ = id()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReverseAddr(b *testing.B) {
|
||||
b.Run("IP4", func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
addr, err := ReverseAddr("192.0.2.1")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if expect := "1.2.0.192.in-addr.arpa."; addr != expect {
|
||||
b.Fatalf("invalid reverse address, expected %q, got %q", expect, addr)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("IP6", func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
addr, err := ReverseAddr("2001:db8::68")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if expect := "8.6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."; addr != expect {
|
||||
b.Fatalf("invalid reverse address, expected %q, got %q", expect, addr)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
170
dns_test.go
170
dns_test.go
|
@ -10,8 +10,7 @@ import (
|
|||
func TestPackUnpack(t *testing.T) {
|
||||
out := new(Msg)
|
||||
out.Answer = make([]RR, 1)
|
||||
key := new(DNSKEY)
|
||||
key = &DNSKEY{Flags: 257, Protocol: 3, Algorithm: RSASHA1}
|
||||
key := &DNSKEY{Flags: 257, Protocol: 3, Algorithm: RSASHA1}
|
||||
key.Hdr = RR_Header{Name: "miek.nl.", Rrtype: TypeDNSKEY, Class: ClassINET, Ttl: 3600}
|
||||
key.PublicKey = "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ"
|
||||
|
||||
|
@ -25,8 +24,7 @@ func TestPackUnpack(t *testing.T) {
|
|||
t.Error("failed to unpack msg with DNSKEY")
|
||||
}
|
||||
|
||||
sig := new(RRSIG)
|
||||
sig = &RRSIG{TypeCovered: TypeDNSKEY, Algorithm: RSASHA1, Labels: 2,
|
||||
sig := &RRSIG{TypeCovered: TypeDNSKEY, Algorithm: RSASHA1, Labels: 2,
|
||||
OrigTtl: 3600, Expiration: 4000, Inception: 4000, KeyTag: 34641, SignerName: "miek.nl.",
|
||||
Signature: "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ"}
|
||||
sig.Hdr = RR_Header{Name: "miek.nl.", Rrtype: TypeRRSIG, Class: ClassINET, Ttl: 3600}
|
||||
|
@ -134,160 +132,14 @@ func TestPackNAPTR(t *testing.T) {
|
|||
`apple.com. IN NAPTR 50 50 "se" "SIPS+D2T" "" _sips._tcp.apple.com.`,
|
||||
} {
|
||||
rr := testRR(n)
|
||||
msg := make([]byte, rr.len())
|
||||
msg := make([]byte, Len(rr))
|
||||
if off, err := PackRR(rr, msg, 0, nil, false); err != nil {
|
||||
t.Errorf("packing failed: %v", err)
|
||||
t.Errorf("length %d, need more than %d", rr.len(), off)
|
||||
t.Errorf("length %d, need more than %d", Len(rr), off)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompressLength(t *testing.T) {
|
||||
m := new(Msg)
|
||||
m.SetQuestion("miek.nl", TypeMX)
|
||||
ul := m.Len()
|
||||
m.Compress = true
|
||||
if ul != m.Len() {
|
||||
t.Fatalf("should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
// Does the predicted length match final packed length?
|
||||
func TestMsgCompressLength(t *testing.T) {
|
||||
makeMsg := func(question string, ans, ns, e []RR) *Msg {
|
||||
msg := new(Msg)
|
||||
msg.SetQuestion(Fqdn(question), TypeANY)
|
||||
msg.Answer = append(msg.Answer, ans...)
|
||||
msg.Ns = append(msg.Ns, ns...)
|
||||
msg.Extra = append(msg.Extra, e...)
|
||||
msg.Compress = true
|
||||
return msg
|
||||
}
|
||||
|
||||
name1 := "12345678901234567890123456789012345.12345678.123."
|
||||
rrA := testRR(name1 + " 3600 IN A 192.0.2.1")
|
||||
rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
|
||||
tests := []*Msg{
|
||||
makeMsg(name1, []RR{rrA}, nil, nil),
|
||||
makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)}
|
||||
|
||||
for _, msg := range tests {
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted < len(buf) {
|
||||
t.Errorf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d",
|
||||
msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgLength(t *testing.T) {
|
||||
makeMsg := func(question string, ans, ns, e []RR) *Msg {
|
||||
msg := new(Msg)
|
||||
msg.SetQuestion(Fqdn(question), TypeANY)
|
||||
msg.Answer = append(msg.Answer, ans...)
|
||||
msg.Ns = append(msg.Ns, ns...)
|
||||
msg.Extra = append(msg.Extra, e...)
|
||||
return msg
|
||||
}
|
||||
|
||||
name1 := "12345678901234567890123456789012345.12345678.123."
|
||||
rrA := testRR(name1 + " 3600 IN A 192.0.2.1")
|
||||
rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
|
||||
tests := []*Msg{
|
||||
makeMsg(name1, []RR{rrA}, nil, nil),
|
||||
makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)}
|
||||
|
||||
for _, msg := range tests {
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted < len(buf) {
|
||||
t.Errorf("predicted length is wrong: predicted %s (len=%d), actual %d",
|
||||
msg.Question[0].Name, predicted, len(buf))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgLength2(t *testing.T) {
|
||||
// Serialized replies
|
||||
var testMessages = []string{
|
||||
// google.com. IN A?
|
||||
"064e81800001000b0004000506676f6f676c6503636f6d0000010001c00c00010001000000050004adc22986c00c00010001000000050004adc22987c00c00010001000000050004adc22988c00c00010001000000050004adc22989c00c00010001000000050004adc2298ec00c00010001000000050004adc22980c00c00010001000000050004adc22981c00c00010001000000050004adc22982c00c00010001000000050004adc22983c00c00010001000000050004adc22984c00c00010001000000050004adc22985c00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc0d800010001000000050004d8ef200ac0ea00010001000000050004d8ef220ac0fc00010001000000050004d8ef240ac10e00010001000000050004d8ef260a0000290500000000050000",
|
||||
// amazon.com. IN A? (reply has no EDNS0 record)
|
||||
// TODO(miek): this one is off-by-one, need to find out why
|
||||
//"6de1818000010004000a000806616d617a6f6e03636f6d0000010001c00c000100010000000500044815c2d4c00c000100010000000500044815d7e8c00c00010001000000050004b02062a6c00c00010001000000050004cdfbf236c00c000200010000000500140570646e733408756c747261646e73036f726700c00c000200010000000500150570646e733508756c747261646e7304696e666f00c00c000200010000000500160570646e733608756c747261646e7302636f02756b00c00c00020001000000050014036e7331037033310664796e656374036e657400c00c00020001000000050006036e7332c0cfc00c00020001000000050006036e7333c0cfc00c00020001000000050006036e7334c0cfc00c000200010000000500110570646e733108756c747261646e73c0dac00c000200010000000500080570646e7332c127c00c000200010000000500080570646e7333c06ec0cb00010001000000050004d04e461fc0eb00010001000000050004cc0dfa1fc0fd00010001000000050004d04e471fc10f00010001000000050004cc0dfb1fc12100010001000000050004cc4a6c01c121001c000100000005001020010502f3ff00000000000000000001c13e00010001000000050004cc4a6d01c13e001c0001000000050010261000a1101400000000000000000001",
|
||||
// yahoo.com. IN A?
|
||||
"fc2d81800001000300070008057961686f6f03636f6d0000010001c00c00010001000000050004628afd6dc00c00010001000000050004628bb718c00c00010001000000050004cebe242dc00c00020001000000050006036e7336c00cc00c00020001000000050006036e7338c00cc00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc00c00020001000000050006036e7335c00cc07b0001000100000005000444b48310c08d00010001000000050004448eff10c09f00010001000000050004cb54dd35c0b100010001000000050004628a0b9dc0c30001000100000005000477a0f77cc05700010001000000050004ca2bdfaac06900010001000000050004caa568160000290500000000050000",
|
||||
// microsoft.com. IN A?
|
||||
"f4368180000100020005000b096d6963726f736f667403636f6d0000010001c00c0001000100000005000440040b25c00c0001000100000005000441373ac9c00c0002000100000005000e036e7331046d736674036e657400c00c00020001000000050006036e7332c04fc00c00020001000000050006036e7333c04fc00c00020001000000050006036e7334c04fc00c00020001000000050006036e7335c04fc04b000100010000000500044137253ec04b001c00010000000500102a010111200500000000000000010001c0650001000100000005000440043badc065001c00010000000500102a010111200600060000000000010001c07700010001000000050004d5c7b435c077001c00010000000500102a010111202000000000000000010001c08900010001000000050004cf2e4bfec089001c00010000000500102404f800200300000000000000010001c09b000100010000000500044137e28cc09b001c00010000000500102a010111200f000100000000000100010000290500000000050000",
|
||||
// google.com. IN MX?
|
||||
"724b8180000100050004000b06676f6f676c6503636f6d00000f0001c00c000f000100000005000c000a056173706d78016cc00cc00c000f0001000000050009001404616c7431c02ac00c000f0001000000050009001e04616c7432c02ac00c000f0001000000050009002804616c7433c02ac00c000f0001000000050009003204616c7434c02ac00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc00c00020001000000050006036e7331c00cc02a00010001000000050004adc2421bc02a001c00010000000500102a00145040080c01000000000000001bc04200010001000000050004adc2461bc05700010001000000050004adc2451bc06c000100010000000500044a7d8f1bc081000100010000000500044a7d191bc0ca00010001000000050004d8ef200ac09400010001000000050004d8ef220ac0a600010001000000050004d8ef240ac0b800010001000000050004d8ef260a0000290500000000050000",
|
||||
// reddit.com. IN A?
|
||||
"12b98180000100080000000c0672656464697403636f6d0000020001c00c0002000100000005000f046175733204616b616d036e657400c00c000200010000000500070475736534c02dc00c000200010000000500070475737733c02dc00c000200010000000500070475737735c02dc00c00020001000000050008056173696131c02dc00c00020001000000050008056173696139c02dc00c00020001000000050008056e73312d31c02dc00c0002000100000005000a076e73312d313935c02dc02800010001000000050004c30a242ec04300010001000000050004451f1d39c05600010001000000050004451f3bc7c0690001000100000005000460073240c07c000100010000000500046007fb81c090000100010000000500047c283484c090001c00010000000500102a0226f0006700000000000000000064c0a400010001000000050004c16c5b01c0a4001c000100000005001026001401000200000000000000000001c0b800010001000000050004c16c5bc3c0b8001c0001000000050010260014010002000000000000000000c30000290500000000050000",
|
||||
}
|
||||
|
||||
for i, hexData := range testMessages {
|
||||
// we won't fail the decoding of the hex
|
||||
input, _ := hex.DecodeString(hexData)
|
||||
|
||||
m := new(Msg)
|
||||
m.Unpack(input)
|
||||
m.Compress = true
|
||||
lenComp := m.Len()
|
||||
b, _ := m.Pack()
|
||||
pacComp := len(b)
|
||||
m.Compress = false
|
||||
lenUnComp := m.Len()
|
||||
b, _ = m.Pack()
|
||||
pacUnComp := len(b)
|
||||
if pacComp+1 != lenComp {
|
||||
t.Errorf("msg.Len(compressed)=%d actual=%d for test %d", lenComp, pacComp, i)
|
||||
}
|
||||
if pacUnComp+1 != lenUnComp {
|
||||
t.Errorf("msg.Len(uncompressed)=%d actual=%d for test %d", lenUnComp, pacUnComp, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgLengthCompressionMalformed(t *testing.T) {
|
||||
// SOA with empty hostmaster, which is illegal
|
||||
soa := &SOA{Hdr: RR_Header{Name: ".", Rrtype: TypeSOA, Class: ClassINET, Ttl: 12345},
|
||||
Ns: ".",
|
||||
Mbox: "",
|
||||
Serial: 0,
|
||||
Refresh: 28800,
|
||||
Retry: 7200,
|
||||
Expire: 604800,
|
||||
Minttl: 60}
|
||||
m := new(Msg)
|
||||
m.Compress = true
|
||||
m.Ns = []RR{soa}
|
||||
m.Len() // Should not crash.
|
||||
}
|
||||
|
||||
func TestMsgCompressLength2(t *testing.T) {
|
||||
msg := new(Msg)
|
||||
msg.Compress = true
|
||||
msg.SetQuestion(Fqdn("bliep."), TypeANY)
|
||||
msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "blaat.", Rrtype: 0x21, Class: 0x1, Ttl: 0x3c}, Port: 0x4c57, Target: "foo.bar."})
|
||||
msg.Extra = append(msg.Extra, &A{Hdr: RR_Header{Name: "foo.bar.", Rrtype: 0x1, Class: 0x1, Ttl: 0x3c}, A: net.IP{0xac, 0x11, 0x0, 0x3}})
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted != len(buf) {
|
||||
t.Errorf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d",
|
||||
msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToRFC3597(t *testing.T) {
|
||||
a := testRR("miek.nl. IN A 10.0.1.1")
|
||||
x := new(RFC3597)
|
||||
|
@ -427,8 +279,8 @@ func TestTKEY(t *testing.T) {
|
|||
t.Fatal("Unable to decode TKEY")
|
||||
}
|
||||
// Make sure we get back the same length
|
||||
if rr.len() != len(tkeyBytes) {
|
||||
t.Fatalf("Lengths don't match %d != %d", rr.len(), len(tkeyBytes))
|
||||
if Len(rr) != len(tkeyBytes) {
|
||||
t.Fatalf("Lengths don't match %d != %d", Len(rr), len(tkeyBytes))
|
||||
}
|
||||
// make space for it with some fudge room
|
||||
msg := make([]byte, tkeyLen+1000)
|
||||
|
@ -439,10 +291,10 @@ func TestTKEY(t *testing.T) {
|
|||
if offset != len(tkeyBytes) {
|
||||
t.Fatalf("mismatched TKEY RR size %d != %d", len(tkeyBytes), offset)
|
||||
}
|
||||
if bytes.Compare(tkeyBytes, msg[0:offset]) != 0 {
|
||||
if !bytes.Equal(tkeyBytes, msg[0:offset]) {
|
||||
t.Fatal("mismatched TKEY data after rewriting bytes")
|
||||
}
|
||||
t.Logf("got TKEY of: " + rr.String())
|
||||
|
||||
// Now add some bytes to this and make sure we can encode OtherData properly
|
||||
tkey := rr.(*TKEY)
|
||||
tkey.OtherData = "abcd"
|
||||
|
@ -451,16 +303,14 @@ func TestTKEY(t *testing.T) {
|
|||
if packErr != nil {
|
||||
t.Fatal("unable to pack TKEY RR after modification", packErr)
|
||||
}
|
||||
if offset != (len(tkeyBytes) + 2) {
|
||||
if offset != len(tkeyBytes)+2 {
|
||||
t.Fatalf("mismatched TKEY RR size %d != %d", offset, len(tkeyBytes)+2)
|
||||
}
|
||||
t.Logf("modified to TKEY of: " + rr.String())
|
||||
|
||||
// Make sure we can parse our string output
|
||||
tkey.Hdr.Class = ClassINET // https://github.com/miekg/dns/issues/577
|
||||
newRR, newError := NewRR(tkey.String())
|
||||
_, newError := NewRR(tkey.String())
|
||||
if newError != nil {
|
||||
t.Fatalf("unable to parse TKEY string: %s", newError)
|
||||
}
|
||||
t.Log("got reparsed TKEY of newRR: " + newRR.String())
|
||||
}
|
||||
|
|
269
dnssec.go
269
dnssec.go
|
@ -3,15 +3,14 @@ package dns
|
|||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
_ "crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
_ "crypto/sha1"
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha512"
|
||||
_ "crypto/sha1" // need its init function
|
||||
_ "crypto/sha256" // need its init function
|
||||
_ "crypto/sha512" // need its init function
|
||||
"encoding/asn1"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
|
@ -19,8 +18,6 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
// DNSSEC encryption algorithm codes.
|
||||
|
@ -67,19 +64,20 @@ var AlgorithmToString = map[uint8]string{
|
|||
PRIVATEOID: "PRIVATEOID",
|
||||
}
|
||||
|
||||
// StringToAlgorithm is the reverse of AlgorithmToString.
|
||||
var StringToAlgorithm = reverseInt8(AlgorithmToString)
|
||||
|
||||
// AlgorithmToHash is a map of algorithm crypto hash IDs to crypto.Hash's.
|
||||
// For newer algorithm that do their own hashing (i.e. ED25519) the returned value
|
||||
// is 0, implying no (external) hashing should occur. The non-exported identityHash is then
|
||||
// used.
|
||||
var AlgorithmToHash = map[uint8]crypto.Hash{
|
||||
RSAMD5: crypto.MD5, // Deprecated in RFC 6725
|
||||
DSA: crypto.SHA1,
|
||||
RSASHA1: crypto.SHA1,
|
||||
RSASHA1NSEC3SHA1: crypto.SHA1,
|
||||
RSASHA256: crypto.SHA256,
|
||||
ECDSAP256SHA256: crypto.SHA256,
|
||||
ECDSAP384SHA384: crypto.SHA384,
|
||||
RSASHA512: crypto.SHA512,
|
||||
ED25519: crypto.Hash(0),
|
||||
ED25519: 0,
|
||||
}
|
||||
|
||||
// DNSSEC hashing algorithm codes.
|
||||
|
@ -101,9 +99,6 @@ var HashToString = map[uint8]string{
|
|||
SHA512: "SHA512",
|
||||
}
|
||||
|
||||
// StringToHash is a map of names to hash IDs.
|
||||
var StringToHash = reverseInt8(HashToString)
|
||||
|
||||
// DNSKEY flag values.
|
||||
const (
|
||||
SEP = 1
|
||||
|
@ -145,12 +140,12 @@ func (k *DNSKEY) KeyTag() uint16 {
|
|||
var keytag int
|
||||
switch k.Algorithm {
|
||||
case RSAMD5:
|
||||
// Look at the bottom two bytes of the modules, which the last
|
||||
// item in the pubkey. We could do this faster by looking directly
|
||||
// at the base64 values. But I'm lazy.
|
||||
// This algorithm has been deprecated, but keep this key-tag calculation.
|
||||
// Look at the bottom two bytes of the modules, which the last item in the pubkey.
|
||||
// See https://www.rfc-editor.org/errata/eid193 .
|
||||
modulus, _ := fromBase64([]byte(k.PublicKey))
|
||||
if len(modulus) > 1 {
|
||||
x := binary.BigEndian.Uint16(modulus[len(modulus)-2:])
|
||||
x := binary.BigEndian.Uint16(modulus[len(modulus)-3:])
|
||||
keytag = int(x)
|
||||
}
|
||||
default:
|
||||
|
@ -172,7 +167,7 @@ func (k *DNSKEY) KeyTag() uint16 {
|
|||
keytag += int(v) << 8
|
||||
}
|
||||
}
|
||||
keytag += (keytag >> 16) & 0xFFFF
|
||||
keytag += keytag >> 16 & 0xFFFF
|
||||
keytag &= 0xFFFF
|
||||
}
|
||||
return uint16(keytag)
|
||||
|
@ -205,7 +200,7 @@ func (k *DNSKEY) ToDS(h uint8) *DS {
|
|||
wire = wire[:n]
|
||||
|
||||
owner := make([]byte, 255)
|
||||
off, err1 := PackDomainName(strings.ToLower(k.Hdr.Name), owner, 0, nil, false)
|
||||
off, err1 := PackDomainName(CanonicalName(k.Hdr.Name), owner, 0, nil, false)
|
||||
if err1 != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -239,7 +234,7 @@ func (k *DNSKEY) ToDS(h uint8) *DS {
|
|||
// ToCDNSKEY converts a DNSKEY record to a CDNSKEY record.
|
||||
func (k *DNSKEY) ToCDNSKEY() *CDNSKEY {
|
||||
c := &CDNSKEY{DNSKEY: *k}
|
||||
c.Hdr = *k.Hdr.copyHeader()
|
||||
c.Hdr = k.Hdr
|
||||
c.Hdr.Rrtype = TypeCDNSKEY
|
||||
return c
|
||||
}
|
||||
|
@ -247,7 +242,7 @@ func (k *DNSKEY) ToCDNSKEY() *CDNSKEY {
|
|||
// ToCDS converts a DS record to a CDS record.
|
||||
func (d *DS) ToCDS() *CDS {
|
||||
c := &CDS{DS: *d}
|
||||
c.Hdr = *d.Hdr.copyHeader()
|
||||
c.Hdr = d.Hdr
|
||||
c.Hdr.Rrtype = TypeCDS
|
||||
return c
|
||||
}
|
||||
|
@ -267,16 +262,17 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
|
|||
return ErrKey
|
||||
}
|
||||
|
||||
h0 := rrset[0].Header()
|
||||
rr.Hdr.Rrtype = TypeRRSIG
|
||||
rr.Hdr.Name = rrset[0].Header().Name
|
||||
rr.Hdr.Class = rrset[0].Header().Class
|
||||
rr.Hdr.Name = h0.Name
|
||||
rr.Hdr.Class = h0.Class
|
||||
if rr.OrigTtl == 0 { // If set don't override
|
||||
rr.OrigTtl = rrset[0].Header().Ttl
|
||||
rr.OrigTtl = h0.Ttl
|
||||
}
|
||||
rr.TypeCovered = rrset[0].Header().Rrtype
|
||||
rr.Labels = uint8(CountLabel(rrset[0].Header().Name))
|
||||
rr.TypeCovered = h0.Rrtype
|
||||
rr.Labels = uint8(CountLabel(h0.Name))
|
||||
|
||||
if strings.HasPrefix(rrset[0].Header().Name, "*") {
|
||||
if strings.HasPrefix(h0.Name, "*") {
|
||||
rr.Labels-- // wildcard, remove from label count
|
||||
}
|
||||
|
||||
|
@ -289,7 +285,7 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
|
|||
sigwire.Inception = rr.Inception
|
||||
sigwire.KeyTag = rr.KeyTag
|
||||
// For signing, lowercase this name
|
||||
sigwire.SignerName = strings.ToLower(rr.SignerName)
|
||||
sigwire.SignerName = CanonicalName(rr.SignerName)
|
||||
|
||||
// Create the desired binary blob
|
||||
signdata := make([]byte, DefaultMsgSize)
|
||||
|
@ -303,39 +299,27 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
|
|||
return err
|
||||
}
|
||||
|
||||
hash, ok := AlgorithmToHash[rr.Algorithm]
|
||||
if !ok {
|
||||
return ErrAlg
|
||||
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch rr.Algorithm {
|
||||
case ED25519:
|
||||
// ed25519 signs the raw message and performs hashing internally.
|
||||
// All other supported signature schemes operate over the pre-hashed
|
||||
// message, and thus ed25519 must be handled separately here.
|
||||
//
|
||||
// The raw message is passed directly into sign and crypto.Hash(0) is
|
||||
// used to signal to the crypto.Signer that the data has not been hashed.
|
||||
signature, err := sign(k, append(signdata, wire...), crypto.Hash(0), rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rr.Signature = toBase64(signature)
|
||||
case RSAMD5, DSA, DSANSEC3SHA1:
|
||||
// See RFC 6944.
|
||||
return ErrAlg
|
||||
default:
|
||||
h := hash.New()
|
||||
h.Write(signdata)
|
||||
h.Write(wire)
|
||||
|
||||
signature, err := sign(k, h.Sum(nil), hash, rr.Algorithm)
|
||||
signature, err := sign(k, h.Sum(nil), cryptohash, rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rr.Signature = toBase64(signature)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte, error) {
|
||||
|
@ -345,9 +329,8 @@ func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte,
|
|||
}
|
||||
|
||||
switch alg {
|
||||
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
|
||||
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, ED25519:
|
||||
return signature, nil
|
||||
|
||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||
ecdsaSignature := &struct {
|
||||
R, S *big.Int
|
||||
|
@ -367,25 +350,16 @@ func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte,
|
|||
signature := intToBytes(ecdsaSignature.R, intlen)
|
||||
signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...)
|
||||
return signature, nil
|
||||
|
||||
// There is no defined interface for what a DSA backed crypto.Signer returns
|
||||
case DSA, DSANSEC3SHA1:
|
||||
// t := divRoundUp(divRoundUp(p.PublicKey.Y.BitLen(), 8)-64, 8)
|
||||
// signature := []byte{byte(t)}
|
||||
// signature = append(signature, intToBytes(r1, 20)...)
|
||||
// signature = append(signature, intToBytes(s1, 20)...)
|
||||
// rr.Signature = signature
|
||||
|
||||
case ED25519:
|
||||
return signature, nil
|
||||
default:
|
||||
return nil, ErrAlg
|
||||
}
|
||||
|
||||
return nil, ErrAlg
|
||||
}
|
||||
|
||||
// Verify validates an RRSet with the signature and key. This is only the
|
||||
// cryptographic test, the signature validity period must be checked separately.
|
||||
// This function copies the rdata of some RRs (to lowercase domain names) for the validation to work.
|
||||
// It also checks that the Zone Key bit (RFC 4034 2.1.1) is set on the DNSKEY
|
||||
// and that the Protocol field is set to 3 (RFC 4034 2.1.2).
|
||||
func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
||||
// First the easy checks
|
||||
if !IsRRset(rrset) {
|
||||
|
@ -400,20 +374,23 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
|||
if rr.Algorithm != k.Algorithm {
|
||||
return ErrKey
|
||||
}
|
||||
if strings.ToLower(rr.SignerName) != strings.ToLower(k.Hdr.Name) {
|
||||
if !strings.EqualFold(rr.SignerName, k.Hdr.Name) {
|
||||
return ErrKey
|
||||
}
|
||||
if k.Protocol != 3 {
|
||||
return ErrKey
|
||||
}
|
||||
// RFC 4034 2.1.1 If bit 7 has value 0, then the DNSKEY record holds some
|
||||
// other type of DNS public key and MUST NOT be used to verify RRSIGs that
|
||||
// cover RRsets.
|
||||
if k.Flags&ZONE == 0 {
|
||||
return ErrKey
|
||||
}
|
||||
|
||||
// IsRRset checked that we have at least one RR and that the RRs in
|
||||
// the set have consistent type, class, and name. Also check that type and
|
||||
// class matches the RRSIG record.
|
||||
if rrset[0].Header().Class != rr.Hdr.Class {
|
||||
return ErrRRset
|
||||
}
|
||||
if rrset[0].Header().Rrtype != rr.TypeCovered {
|
||||
if h0 := rrset[0].Header(); h0.Class != rr.Hdr.Class || h0.Rrtype != rr.TypeCovered {
|
||||
return ErrRRset
|
||||
}
|
||||
|
||||
|
@ -427,7 +404,7 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
|||
sigwire.Expiration = rr.Expiration
|
||||
sigwire.Inception = rr.Inception
|
||||
sigwire.KeyTag = rr.KeyTag
|
||||
sigwire.SignerName = strings.ToLower(rr.SignerName)
|
||||
sigwire.SignerName = CanonicalName(rr.SignerName)
|
||||
// Create the desired binary blob
|
||||
signeddata := make([]byte, DefaultMsgSize)
|
||||
n, err := packSigWire(sigwire, signeddata)
|
||||
|
@ -446,23 +423,22 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
|||
// remove the domain name and assume its ours?
|
||||
}
|
||||
|
||||
hash, ok := AlgorithmToHash[rr.Algorithm]
|
||||
if !ok {
|
||||
return ErrAlg
|
||||
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch rr.Algorithm {
|
||||
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, RSAMD5:
|
||||
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
|
||||
// TODO(mg): this can be done quicker, ie. cache the pubkey data somewhere??
|
||||
pubkey := k.publicKeyRSA() // Get the key
|
||||
if pubkey == nil {
|
||||
return ErrKey
|
||||
}
|
||||
|
||||
h := hash.New()
|
||||
h.Write(signeddata)
|
||||
h.Write(wire)
|
||||
return rsa.VerifyPKCS1v15(pubkey, hash, h.Sum(nil), sigbuf)
|
||||
return rsa.VerifyPKCS1v15(pubkey, cryptohash, h.Sum(nil), sigbuf)
|
||||
|
||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||
pubkey := k.publicKeyECDSA()
|
||||
|
@ -474,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) {
|
||||
|
@ -511,12 +486,12 @@ func (rr *RRSIG) ValidityPeriod(t time.Time) bool {
|
|||
}
|
||||
modi := (int64(rr.Inception) - utc) / year68
|
||||
mode := (int64(rr.Expiration) - utc) / year68
|
||||
ti := int64(rr.Inception) + (modi * year68)
|
||||
te := int64(rr.Expiration) + (mode * year68)
|
||||
ti := int64(rr.Inception) + modi*year68
|
||||
te := int64(rr.Expiration) + mode*year68
|
||||
return ti <= utc && utc <= te
|
||||
}
|
||||
|
||||
// Return the signatures base64 encodedig sigdata as a byte slice.
|
||||
// Return the signatures base64 encoding sigdata as a byte slice.
|
||||
func (rr *RRSIG) sigBuf() []byte {
|
||||
sigbuf, err := fromBase64([]byte(rr.Signature))
|
||||
if err != nil {
|
||||
|
@ -532,6 +507,11 @@ func (k *DNSKEY) publicKeyRSA() *rsa.PublicKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
if len(keybuf) < 1+1+64 {
|
||||
// Exponent must be at least 1 byte and modulus at least 64
|
||||
return nil
|
||||
}
|
||||
|
||||
// RFC 2537/3110, section 2. RSA Public KEY Resource Records
|
||||
// Length is in the 0th byte, unless its zero, then it
|
||||
// it in bytes 1 and 2 and its a 16 bit number
|
||||
|
@ -541,25 +521,35 @@ func (k *DNSKEY) publicKeyRSA() *rsa.PublicKey {
|
|||
explen = uint16(keybuf[1])<<8 | uint16(keybuf[2])
|
||||
keyoff = 3
|
||||
}
|
||||
pubkey := new(rsa.PublicKey)
|
||||
|
||||
pubkey.N = big.NewInt(0)
|
||||
shift := uint64((explen - 1) * 8)
|
||||
expo := uint64(0)
|
||||
for i := int(explen - 1); i > 0; i-- {
|
||||
expo += uint64(keybuf[keyoff+i]) << shift
|
||||
shift -= 8
|
||||
}
|
||||
// Remainder
|
||||
expo += uint64(keybuf[keyoff])
|
||||
if expo > (2<<31)+1 {
|
||||
// Larger expo than supported.
|
||||
// println("dns: F5 primes (or larger) are not supported")
|
||||
if explen > 4 || explen == 0 || keybuf[keyoff] == 0 {
|
||||
// Exponent larger than supported by the crypto package,
|
||||
// empty, or contains prohibited leading zero.
|
||||
return nil
|
||||
}
|
||||
pubkey.E = int(expo)
|
||||
|
||||
pubkey.N.SetBytes(keybuf[keyoff+int(explen):])
|
||||
modoff := keyoff + int(explen)
|
||||
modlen := len(keybuf) - modoff
|
||||
if modlen < 64 || modlen > 512 || keybuf[modoff] == 0 {
|
||||
// Modulus is too small, large, or contains prohibited leading zero.
|
||||
return nil
|
||||
}
|
||||
|
||||
pubkey := new(rsa.PublicKey)
|
||||
|
||||
var expo uint64
|
||||
// The exponent of length explen is between keyoff and modoff.
|
||||
for _, v := range keybuf[keyoff:modoff] {
|
||||
expo <<= 8
|
||||
expo |= uint64(v)
|
||||
}
|
||||
if expo > 1<<31-1 {
|
||||
// Larger exponent than supported by the crypto package.
|
||||
return nil
|
||||
}
|
||||
|
||||
pubkey.E = int(expo)
|
||||
pubkey.N = new(big.Int).SetBytes(keybuf[modoff:])
|
||||
return pubkey
|
||||
}
|
||||
|
||||
|
@ -584,34 +574,8 @@ func (k *DNSKEY) publicKeyECDSA() *ecdsa.PublicKey {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
pubkey.X = big.NewInt(0)
|
||||
pubkey.X.SetBytes(keybuf[:len(keybuf)/2])
|
||||
pubkey.Y = big.NewInt(0)
|
||||
pubkey.Y.SetBytes(keybuf[len(keybuf)/2:])
|
||||
return pubkey
|
||||
}
|
||||
|
||||
func (k *DNSKEY) publicKeyDSA() *dsa.PublicKey {
|
||||
keybuf, err := fromBase64([]byte(k.PublicKey))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if len(keybuf) < 22 {
|
||||
return nil
|
||||
}
|
||||
t, keybuf := int(keybuf[0]), keybuf[1:]
|
||||
size := 64 + t*8
|
||||
q, keybuf := keybuf[:20], keybuf[20:]
|
||||
if len(keybuf) != 3*size {
|
||||
return nil
|
||||
}
|
||||
p, keybuf := keybuf[:size], keybuf[size:]
|
||||
g, y := keybuf[:size], keybuf[size:]
|
||||
pubkey := new(dsa.PublicKey)
|
||||
pubkey.Parameters.Q = big.NewInt(0).SetBytes(q)
|
||||
pubkey.Parameters.P = big.NewInt(0).SetBytes(p)
|
||||
pubkey.Parameters.G = big.NewInt(0).SetBytes(g)
|
||||
pubkey.Y = big.NewInt(0).SetBytes(y)
|
||||
pubkey.X = new(big.Int).SetBytes(keybuf[:len(keybuf)/2])
|
||||
pubkey.Y = new(big.Int).SetBytes(keybuf[len(keybuf)/2:])
|
||||
return pubkey
|
||||
}
|
||||
|
||||
|
@ -641,15 +605,16 @@ func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) {
|
|||
wires := make(wireSlice, len(rrset))
|
||||
for i, r := range rrset {
|
||||
r1 := r.copy()
|
||||
r1.Header().Ttl = s.OrigTtl
|
||||
labels := SplitDomainName(r1.Header().Name)
|
||||
h := r1.Header()
|
||||
h.Ttl = s.OrigTtl
|
||||
labels := SplitDomainName(h.Name)
|
||||
// 6.2. Canonical RR Form. (4) - wildcards
|
||||
if len(labels) > int(s.Labels) {
|
||||
// Wildcard
|
||||
r1.Header().Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "."
|
||||
h.Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "."
|
||||
}
|
||||
// RFC 4034: 6.2. Canonical RR Form. (2) - domain name to lowercase
|
||||
r1.Header().Name = strings.ToLower(r1.Header().Name)
|
||||
h.Name = CanonicalName(h.Name)
|
||||
// 6.2. Canonical RR Form. (3) - domain rdata to lowercase.
|
||||
// NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
|
||||
// HINFO, MINFO, MX, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
|
||||
|
@ -662,52 +627,52 @@ func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) {
|
|||
// conversion.
|
||||
switch x := r1.(type) {
|
||||
case *NS:
|
||||
x.Ns = strings.ToLower(x.Ns)
|
||||
x.Ns = CanonicalName(x.Ns)
|
||||
case *MD:
|
||||
x.Md = strings.ToLower(x.Md)
|
||||
x.Md = CanonicalName(x.Md)
|
||||
case *MF:
|
||||
x.Mf = strings.ToLower(x.Mf)
|
||||
x.Mf = CanonicalName(x.Mf)
|
||||
case *CNAME:
|
||||
x.Target = strings.ToLower(x.Target)
|
||||
x.Target = CanonicalName(x.Target)
|
||||
case *SOA:
|
||||
x.Ns = strings.ToLower(x.Ns)
|
||||
x.Mbox = strings.ToLower(x.Mbox)
|
||||
x.Ns = CanonicalName(x.Ns)
|
||||
x.Mbox = CanonicalName(x.Mbox)
|
||||
case *MB:
|
||||
x.Mb = strings.ToLower(x.Mb)
|
||||
x.Mb = CanonicalName(x.Mb)
|
||||
case *MG:
|
||||
x.Mg = strings.ToLower(x.Mg)
|
||||
x.Mg = CanonicalName(x.Mg)
|
||||
case *MR:
|
||||
x.Mr = strings.ToLower(x.Mr)
|
||||
x.Mr = CanonicalName(x.Mr)
|
||||
case *PTR:
|
||||
x.Ptr = strings.ToLower(x.Ptr)
|
||||
x.Ptr = CanonicalName(x.Ptr)
|
||||
case *MINFO:
|
||||
x.Rmail = strings.ToLower(x.Rmail)
|
||||
x.Email = strings.ToLower(x.Email)
|
||||
x.Rmail = CanonicalName(x.Rmail)
|
||||
x.Email = CanonicalName(x.Email)
|
||||
case *MX:
|
||||
x.Mx = strings.ToLower(x.Mx)
|
||||
x.Mx = CanonicalName(x.Mx)
|
||||
case *RP:
|
||||
x.Mbox = strings.ToLower(x.Mbox)
|
||||
x.Txt = strings.ToLower(x.Txt)
|
||||
x.Mbox = CanonicalName(x.Mbox)
|
||||
x.Txt = CanonicalName(x.Txt)
|
||||
case *AFSDB:
|
||||
x.Hostname = strings.ToLower(x.Hostname)
|
||||
x.Hostname = CanonicalName(x.Hostname)
|
||||
case *RT:
|
||||
x.Host = strings.ToLower(x.Host)
|
||||
x.Host = CanonicalName(x.Host)
|
||||
case *SIG:
|
||||
x.SignerName = strings.ToLower(x.SignerName)
|
||||
x.SignerName = CanonicalName(x.SignerName)
|
||||
case *PX:
|
||||
x.Map822 = strings.ToLower(x.Map822)
|
||||
x.Mapx400 = strings.ToLower(x.Mapx400)
|
||||
x.Map822 = CanonicalName(x.Map822)
|
||||
x.Mapx400 = CanonicalName(x.Mapx400)
|
||||
case *NAPTR:
|
||||
x.Replacement = strings.ToLower(x.Replacement)
|
||||
x.Replacement = CanonicalName(x.Replacement)
|
||||
case *KX:
|
||||
x.Exchanger = strings.ToLower(x.Exchanger)
|
||||
x.Exchanger = CanonicalName(x.Exchanger)
|
||||
case *SRV:
|
||||
x.Target = strings.ToLower(x.Target)
|
||||
x.Target = CanonicalName(x.Target)
|
||||
case *DNAME:
|
||||
x.Target = strings.ToLower(x.Target)
|
||||
x.Target = CanonicalName(x.Target)
|
||||
}
|
||||
// 6.2. Canonical RR Form. (5) - origTTL
|
||||
wire := make([]byte, r1.len()+1) // +1 to be safe(r)
|
||||
wire := make([]byte, Len(r1)+1) // +1 to be safe(r)
|
||||
off, err1 := PackRR(r1, wire, 0, nil, false)
|
||||
if err1 != nil {
|
||||
return nil, err1
|
||||
|
|
|
@ -2,14 +2,12 @@ package dns
|
|||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"math/big"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
// Generate generates a DNSKEY of the given bit size.
|
||||
|
@ -20,11 +18,7 @@ import (
|
|||
// bits should be set to the size of the algorithm.
|
||||
func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) {
|
||||
switch k.Algorithm {
|
||||
case DSA, DSANSEC3SHA1:
|
||||
if bits != 1024 {
|
||||
return nil, ErrKeySize
|
||||
}
|
||||
case RSAMD5, RSASHA1, RSASHA256, RSASHA1NSEC3SHA1:
|
||||
case RSASHA1, RSASHA256, RSASHA1NSEC3SHA1:
|
||||
if bits < 512 || bits > 4096 {
|
||||
return nil, ErrKeySize
|
||||
}
|
||||
|
@ -44,23 +38,12 @@ func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) {
|
|||
if bits != 256 {
|
||||
return nil, ErrKeySize
|
||||
}
|
||||
default:
|
||||
return nil, ErrAlg
|
||||
}
|
||||
|
||||
switch k.Algorithm {
|
||||
case DSA, DSANSEC3SHA1:
|
||||
params := new(dsa.Parameters)
|
||||
if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
priv := new(dsa.PrivateKey)
|
||||
priv.PublicKey.Parameters = *params
|
||||
err := dsa.GenerateKey(priv, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k.setPublicKeyDSA(params.Q, params.P, params.G, priv.PublicKey.Y)
|
||||
return priv, nil
|
||||
case RSAMD5, RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1:
|
||||
case RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1:
|
||||
priv, err := rsa.GenerateKey(rand.Reader, bits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -120,16 +103,6 @@ func (k *DNSKEY) setPublicKeyECDSA(_X, _Y *big.Int) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// Set the public key for DSA
|
||||
func (k *DNSKEY) setPublicKeyDSA(_Q, _P, _G, _Y *big.Int) bool {
|
||||
if _Q == nil || _P == nil || _G == nil || _Y == nil {
|
||||
return false
|
||||
}
|
||||
buf := dsaToBuf(_Q, _P, _G, _Y)
|
||||
k.PublicKey = toBase64(buf)
|
||||
return true
|
||||
}
|
||||
|
||||
// Set the public key for Ed25519
|
||||
func (k *DNSKEY) setPublicKeyED25519(_K ed25519.PublicKey) bool {
|
||||
if _K == nil {
|
||||
|
@ -164,15 +137,3 @@ func curveToBuf(_X, _Y *big.Int, intlen int) []byte {
|
|||
buf = append(buf, intToBytes(_Y, intlen)...)
|
||||
return buf
|
||||
}
|
||||
|
||||
// Set the public key for X and Y for Curve. The two
|
||||
// values are just concatenated.
|
||||
func dsaToBuf(_Q, _P, _G, _Y *big.Int) []byte {
|
||||
t := divRoundUp(divRoundUp(_G.BitLen(), 8)-64, 8)
|
||||
buf := []byte{byte(t)}
|
||||
buf = append(buf, intToBytes(_Q, 20)...)
|
||||
buf = append(buf, intToBytes(_P, 64+t*8)...)
|
||||
buf = append(buf, intToBytes(_G, 64+t*8)...)
|
||||
buf = append(buf, intToBytes(_Y, 64+t*8)...)
|
||||
return buf
|
||||
}
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"bufio"
|
||||
"crypto"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"io"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
// NewPrivateKey returns a PrivateKey by parsing the string s.
|
||||
|
@ -44,26 +42,7 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
|
|||
return nil, ErrPrivKey
|
||||
}
|
||||
switch uint8(algo) {
|
||||
case DSA:
|
||||
priv, err := readPrivateKeyDSA(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pub := k.publicKeyDSA()
|
||||
if pub == nil {
|
||||
return nil, ErrKey
|
||||
}
|
||||
priv.PublicKey = *pub
|
||||
return priv, nil
|
||||
case RSAMD5:
|
||||
fallthrough
|
||||
case RSASHA1:
|
||||
fallthrough
|
||||
case RSASHA1NSEC3SHA1:
|
||||
fallthrough
|
||||
case RSASHA256:
|
||||
fallthrough
|
||||
case RSASHA512:
|
||||
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
|
||||
priv, err := readPrivateKeyRSA(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -74,11 +53,7 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
|
|||
}
|
||||
priv.PublicKey = *pub
|
||||
return priv, nil
|
||||
case ECCGOST:
|
||||
return nil, ErrPrivKey
|
||||
case ECDSAP256SHA256:
|
||||
fallthrough
|
||||
case ECDSAP384SHA384:
|
||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||
priv, err := readPrivateKeyECDSA(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -92,7 +67,7 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
|
|||
case ED25519:
|
||||
return readPrivateKeyED25519(m)
|
||||
default:
|
||||
return nil, ErrPrivKey
|
||||
return nil, ErrAlg
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,21 +84,16 @@ func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) {
|
|||
}
|
||||
switch k {
|
||||
case "modulus":
|
||||
p.PublicKey.N = big.NewInt(0)
|
||||
p.PublicKey.N.SetBytes(v1)
|
||||
p.PublicKey.N = new(big.Int).SetBytes(v1)
|
||||
case "publicexponent":
|
||||
i := big.NewInt(0)
|
||||
i.SetBytes(v1)
|
||||
i := new(big.Int).SetBytes(v1)
|
||||
p.PublicKey.E = int(i.Int64()) // int64 should be large enough
|
||||
case "privateexponent":
|
||||
p.D = big.NewInt(0)
|
||||
p.D.SetBytes(v1)
|
||||
p.D = new(big.Int).SetBytes(v1)
|
||||
case "prime1":
|
||||
p.Primes[0] = big.NewInt(0)
|
||||
p.Primes[0].SetBytes(v1)
|
||||
p.Primes[0] = new(big.Int).SetBytes(v1)
|
||||
case "prime2":
|
||||
p.Primes[1] = big.NewInt(0)
|
||||
p.Primes[1].SetBytes(v1)
|
||||
p.Primes[1] = new(big.Int).SetBytes(v1)
|
||||
}
|
||||
case "exponent1", "exponent2", "coefficient":
|
||||
// not used in Go (yet)
|
||||
|
@ -134,27 +104,9 @@ func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) {
|
|||
return p, nil
|
||||
}
|
||||
|
||||
func readPrivateKeyDSA(m map[string]string) (*dsa.PrivateKey, error) {
|
||||
p := new(dsa.PrivateKey)
|
||||
p.X = big.NewInt(0)
|
||||
for k, v := range m {
|
||||
switch k {
|
||||
case "private_value(x)":
|
||||
v1, err := fromBase64([]byte(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.X.SetBytes(v1)
|
||||
case "created", "publish", "activate":
|
||||
/* not used in Go (yet) */
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func readPrivateKeyECDSA(m map[string]string) (*ecdsa.PrivateKey, error) {
|
||||
p := new(ecdsa.PrivateKey)
|
||||
p.D = big.NewInt(0)
|
||||
p.D = new(big.Int)
|
||||
// TODO: validate that the required flags are present
|
||||
for k, v := range m {
|
||||
switch k {
|
||||
|
@ -181,22 +133,10 @@ func readPrivateKeyED25519(m map[string]string) (ed25519.PrivateKey, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(p1) != 32 {
|
||||
if len(p1) != ed25519.SeedSize {
|
||||
return nil, ErrPrivKey
|
||||
}
|
||||
// RFC 8080 and Golang's x/crypto/ed25519 differ as to how the
|
||||
// private keys are represented. RFC 8080 specifies that private
|
||||
// keys be stored solely as the seed value (p1 above) while the
|
||||
// ed25519 package represents them as the seed value concatenated
|
||||
// to the public key, which is derived from the seed value.
|
||||
//
|
||||
// ed25519.GenerateKey reads exactly 32 bytes from the passed in
|
||||
// io.Reader and uses them as the seed. It also derives the
|
||||
// public key and produces a compatible private key.
|
||||
_, p, err = ed25519.GenerateKey(bytes.NewReader(p1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = ed25519.NewKeyFromSeed(p1)
|
||||
case "created", "publish", "activate":
|
||||
/* not used in Go (yet) */
|
||||
}
|
||||
|
@ -207,23 +147,12 @@ func readPrivateKeyED25519(m map[string]string) (ed25519.PrivateKey, error) {
|
|||
// parseKey reads a private key from r. It returns a map[string]string,
|
||||
// with the key-value pairs, or an error when the file is not correct.
|
||||
func parseKey(r io.Reader, file string) (map[string]string, error) {
|
||||
s, cancel := scanInit(r)
|
||||
m := make(map[string]string)
|
||||
c := make(chan lex)
|
||||
k := ""
|
||||
defer func() {
|
||||
cancel()
|
||||
// zlexer can send up to two tokens, the next one and possibly 1 remainders.
|
||||
// Do a non-blocking read.
|
||||
_, ok := <-c
|
||||
_, ok = <-c
|
||||
if !ok {
|
||||
// too bad
|
||||
}
|
||||
}()
|
||||
// Start the lexer
|
||||
go klexer(s, c)
|
||||
for l := range c {
|
||||
var k string
|
||||
|
||||
c := newKLexer(r)
|
||||
|
||||
for l, ok := c.Next(); ok; l, ok = c.Next() {
|
||||
// It should alternate
|
||||
switch l.value {
|
||||
case zKey:
|
||||
|
@ -232,41 +161,111 @@ func parseKey(r io.Reader, file string) (map[string]string, error) {
|
|||
if k == "" {
|
||||
return nil, &ParseError{file, "no private key seen", l}
|
||||
}
|
||||
//println("Setting", strings.ToLower(k), "to", l.token, "b")
|
||||
|
||||
m[strings.ToLower(k)] = l.token
|
||||
k = ""
|
||||
}
|
||||
}
|
||||
|
||||
// Surface any read errors from r.
|
||||
if err := c.Err(); err != nil {
|
||||
return nil, &ParseError{file: file, err: err.Error()}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// klexer scans the sourcefile and returns tokens on the channel c.
|
||||
func klexer(s *scan, c chan lex) {
|
||||
var l lex
|
||||
str := "" // Hold the current read text
|
||||
commt := false
|
||||
key := true
|
||||
x, err := s.tokenText()
|
||||
defer close(c)
|
||||
for err == nil {
|
||||
l.column = s.position.Column
|
||||
l.line = s.position.Line
|
||||
type klexer struct {
|
||||
br io.ByteReader
|
||||
|
||||
readErr error
|
||||
|
||||
line int
|
||||
column int
|
||||
|
||||
key bool
|
||||
|
||||
eol bool // end-of-line
|
||||
}
|
||||
|
||||
func newKLexer(r io.Reader) *klexer {
|
||||
br, ok := r.(io.ByteReader)
|
||||
if !ok {
|
||||
br = bufio.NewReaderSize(r, 1024)
|
||||
}
|
||||
|
||||
return &klexer{
|
||||
br: br,
|
||||
|
||||
line: 1,
|
||||
|
||||
key: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (kl *klexer) Err() error {
|
||||
if kl.readErr == io.EOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
return kl.readErr
|
||||
}
|
||||
|
||||
// readByte returns the next byte from the input
|
||||
func (kl *klexer) readByte() (byte, bool) {
|
||||
if kl.readErr != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
c, err := kl.br.ReadByte()
|
||||
if err != nil {
|
||||
kl.readErr = err
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// delay the newline handling until the next token is delivered,
|
||||
// fixes off-by-one errors when reporting a parse error.
|
||||
if kl.eol {
|
||||
kl.line++
|
||||
kl.column = 0
|
||||
kl.eol = false
|
||||
}
|
||||
|
||||
if c == '\n' {
|
||||
kl.eol = true
|
||||
} else {
|
||||
kl.column++
|
||||
}
|
||||
|
||||
return c, true
|
||||
}
|
||||
|
||||
func (kl *klexer) Next() (lex, bool) {
|
||||
var (
|
||||
l lex
|
||||
|
||||
str strings.Builder
|
||||
|
||||
commt bool
|
||||
)
|
||||
|
||||
for x, ok := kl.readByte(); ok; x, ok = kl.readByte() {
|
||||
l.line, l.column = kl.line, kl.column
|
||||
|
||||
switch x {
|
||||
case ':':
|
||||
if commt {
|
||||
if commt || !kl.key {
|
||||
break
|
||||
}
|
||||
l.token = str
|
||||
if key {
|
||||
l.value = zKey
|
||||
c <- l
|
||||
// Next token is a space, eat it
|
||||
s.tokenText()
|
||||
key = false
|
||||
str = ""
|
||||
} else {
|
||||
l.value = zValue
|
||||
}
|
||||
|
||||
kl.key = false
|
||||
|
||||
// Next token is a space, eat it
|
||||
kl.readByte()
|
||||
|
||||
l.value = zKey
|
||||
l.token = str.String()
|
||||
return l, true
|
||||
case ';':
|
||||
commt = true
|
||||
case '\n':
|
||||
|
@ -274,24 +273,37 @@ func klexer(s *scan, c chan lex) {
|
|||
// Reset a comment
|
||||
commt = false
|
||||
}
|
||||
|
||||
if kl.key && str.Len() == 0 {
|
||||
// ignore empty lines
|
||||
break
|
||||
}
|
||||
|
||||
kl.key = true
|
||||
|
||||
l.value = zValue
|
||||
l.token = str
|
||||
c <- l
|
||||
str = ""
|
||||
commt = false
|
||||
key = true
|
||||
l.token = str.String()
|
||||
return l, true
|
||||
default:
|
||||
if commt {
|
||||
break
|
||||
}
|
||||
str += string(x)
|
||||
|
||||
str.WriteByte(x)
|
||||
}
|
||||
x, err = s.tokenText()
|
||||
}
|
||||
if len(str) > 0 {
|
||||
|
||||
if kl.readErr != nil && kl.readErr != io.EOF {
|
||||
// Don't return any tokens after a read error occurs.
|
||||
return lex{value: zEOF}, false
|
||||
}
|
||||
|
||||
if str.Len() > 0 {
|
||||
// Send remainder
|
||||
l.token = str
|
||||
l.value = zValue
|
||||
c <- l
|
||||
l.token = str.String()
|
||||
return l, true
|
||||
}
|
||||
|
||||
return lex{value: zEOF}, false
|
||||
}
|
||||
|
|
|
@ -2,21 +2,21 @@ package dns
|
|||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
const format = "Private-key-format: v1.3\n"
|
||||
|
||||
var bigIntOne = big.NewInt(1)
|
||||
|
||||
// PrivateKeyString converts a PrivateKey to a string. This string has the same
|
||||
// format as the private-key-file of BIND9 (Private-key-format: v1.3).
|
||||
// It needs some info from the key (the algorithm), so its a method of the DNSKEY
|
||||
// It supports rsa.PrivateKey, ecdsa.PrivateKey and dsa.PrivateKey
|
||||
// It needs some info from the key (the algorithm), so its a method of the DNSKEY.
|
||||
// It supports *rsa.PrivateKey, *ecdsa.PrivateKey and ed25519.PrivateKey.
|
||||
func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string {
|
||||
algorithm := strconv.Itoa(int(r.Algorithm))
|
||||
algorithm += " (" + AlgorithmToString[r.Algorithm] + ")"
|
||||
|
@ -31,12 +31,11 @@ func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string {
|
|||
prime2 := toBase64(p.Primes[1].Bytes())
|
||||
// Calculate Exponent1/2 and Coefficient as per: http://en.wikipedia.org/wiki/RSA#Using_the_Chinese_remainder_algorithm
|
||||
// and from: http://code.google.com/p/go/issues/detail?id=987
|
||||
one := big.NewInt(1)
|
||||
p1 := big.NewInt(0).Sub(p.Primes[0], one)
|
||||
q1 := big.NewInt(0).Sub(p.Primes[1], one)
|
||||
exp1 := big.NewInt(0).Mod(p.D, p1)
|
||||
exp2 := big.NewInt(0).Mod(p.D, q1)
|
||||
coeff := big.NewInt(0).ModInverse(p.Primes[1], p.Primes[0])
|
||||
p1 := new(big.Int).Sub(p.Primes[0], bigIntOne)
|
||||
q1 := new(big.Int).Sub(p.Primes[1], bigIntOne)
|
||||
exp1 := new(big.Int).Mod(p.D, p1)
|
||||
exp2 := new(big.Int).Mod(p.D, q1)
|
||||
coeff := new(big.Int).ModInverse(p.Primes[1], p.Primes[0])
|
||||
|
||||
exponent1 := toBase64(exp1.Bytes())
|
||||
exponent2 := toBase64(exp2.Bytes())
|
||||
|
@ -66,23 +65,8 @@ func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string {
|
|||
"Algorithm: " + algorithm + "\n" +
|
||||
"PrivateKey: " + private + "\n"
|
||||
|
||||
case *dsa.PrivateKey:
|
||||
T := divRoundUp(divRoundUp(p.PublicKey.Parameters.G.BitLen(), 8)-64, 8)
|
||||
prime := toBase64(intToBytes(p.PublicKey.Parameters.P, 64+T*8))
|
||||
subprime := toBase64(intToBytes(p.PublicKey.Parameters.Q, 20))
|
||||
base := toBase64(intToBytes(p.PublicKey.Parameters.G, 64+T*8))
|
||||
priv := toBase64(intToBytes(p.X, 20))
|
||||
pub := toBase64(intToBytes(p.PublicKey.Y, 64+T*8))
|
||||
return format +
|
||||
"Algorithm: " + algorithm + "\n" +
|
||||
"Prime(p): " + prime + "\n" +
|
||||
"Subprime(q): " + subprime + "\n" +
|
||||
"Base(g): " + base + "\n" +
|
||||
"Private_value(x): " + priv + "\n" +
|
||||
"Public_value(y): " + pub + "\n"
|
||||
|
||||
case ed25519.PrivateKey:
|
||||
private := toBase64(p[:32])
|
||||
private := toBase64(p.Seed())
|
||||
return format +
|
||||
"Algorithm: " + algorithm + "\n" +
|
||||
"PrivateKey: " + private + "\n"
|
||||
|
|
|
@ -3,27 +3,14 @@ package dns
|
|||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
func getKey() *DNSKEY {
|
||||
key := new(DNSKEY)
|
||||
key.Hdr.Name = "miek.nl."
|
||||
key.Hdr.Class = ClassINET
|
||||
key.Hdr.Ttl = 14400
|
||||
key.Flags = 256
|
||||
key.Protocol = 3
|
||||
key.Algorithm = RSASHA256
|
||||
key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz"
|
||||
return key
|
||||
}
|
||||
|
||||
func getSoa() *SOA {
|
||||
soa := new(SOA)
|
||||
soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0}
|
||||
|
@ -180,7 +167,7 @@ func Test65534(t *testing.T) {
|
|||
key.Flags = 256
|
||||
key.Protocol = 3
|
||||
key.Algorithm = RSASHA256
|
||||
privkey, _ := key.Generate(1024)
|
||||
privkey, _ := key.Generate(512)
|
||||
|
||||
sig := new(RRSIG)
|
||||
sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0}
|
||||
|
@ -263,7 +250,7 @@ func TestKeyRSA(t *testing.T) {
|
|||
key.Flags = 256
|
||||
key.Protocol = 3
|
||||
key.Algorithm = RSASHA256
|
||||
priv, _ := key.Generate(2048)
|
||||
priv, _ := key.Generate(512)
|
||||
|
||||
soa := new(SOA)
|
||||
soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0}
|
||||
|
@ -337,7 +324,7 @@ Activate: 20110302104537`
|
|||
}
|
||||
switch priv := p.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
if 65537 != priv.PublicKey.E {
|
||||
if priv.PublicKey.E != 65537 {
|
||||
t.Error("exponenent should be 65537")
|
||||
}
|
||||
default:
|
||||
|
@ -839,3 +826,45 @@ func TestInvalidRRSet(t *testing.T) {
|
|||
t.Fatal("Verification did not return ErrRRset with inconsistent records")
|
||||
}
|
||||
}
|
||||
|
||||
// Issue #688 - RSA exponent unpacked in reverse
|
||||
func TestRsaExponentUnpack(t *testing.T) {
|
||||
zskRrDnskey, _ := NewRR("isc.org. 7200 IN DNSKEY 256 3 5 AwEAAcdkaRUlsRD4gcF63PpPJJ1E6kOIb3yn/UHptVsPEQtEbgJ2y20O eix4unpwoQkz+bIAd2rrOU/95wgV530x0/qqKwBLWoGkxdcnNcvVT4hl 3SOTZy1VjwkAfyayHPU8VisXqJGbB3KWevBZlb6AtrXzFu8AHuBeeAAe /fOgreCh")
|
||||
kskRrDnskey, _ := NewRR("isc.org. 7200 IN DNSKEY 257 3 5 BEAAAAOhHQDBrhQbtphgq2wQUpEQ5t4DtUHxoMVFu2hWLDMvoOMRXjGr hhCeFvAZih7yJHf8ZGfW6hd38hXG/xylYCO6Krpbdojwx8YMXLA5/kA+ u50WIL8ZR1R6KTbsYVMf/Qx5RiNbPClw+vT+U8eXEJmO20jIS1ULgqy3 47cBB1zMnnz/4LJpA0da9CbKj3A254T515sNIMcwsB8/2+2E63/zZrQz Bkj0BrN/9Bexjpiks3jRhZatEsXn3dTy47R09Uix5WcJt+xzqZ7+ysyL KOOedS39Z7SDmsn2eA0FKtQpwA6LXeG2w+jxmw3oA8lVUgEf/rzeC/bB yBNsO70aEFTd")
|
||||
kskRrRrsig, _ := NewRR("isc.org. 7200 IN RRSIG DNSKEY 5 2 7200 20180627230244 20180528230244 12892 isc.org. ebKBlhYi1hPGTdPg6zSwvprOIkoFMs+WIhMSjoYW6/K5CS9lDDFdK4cu TgXJRT3etrltTuJiFe2HRpp+7t5cKLy+CeJZVzqrCz200MoHiFuLI9yI DJQGaS5YYCiFbw5+jUGU6aUhZ7Y5/YufeqATkRZzdrKwgK+zri8LPw9T WLoVJPAOW7GR0dgxl9WKmO7Fzi9P8BZR3NuwLV7329X94j+4zyswaw7q e5vif0ybzFveODLsEi/E0a2rTXc4QzzyM0fSVxRkVQyQ7ifIPP4ohnnT d5qpPUbE8xxBzTdWR/TaKADC5aCFkppG9lVAq5CPfClii2949X5RYzy1 rxhuSA==")
|
||||
zskRrRrsig, _ := NewRR("isc.org. 7200 IN RRSIG DNSKEY 5 2 7200 20180627230244 20180528230244 19923 isc.org. RgCfzUeq4RJPGoe9RRB6cWf6d/Du+tHK5SxI5QL1waA3O5qVtQKFkY1C dq/yyVjwzfjD9F62TObujOaktv8X80ZMcNPmgHbvK1xOqelMBWv5hxj3 xRe+QQObLZ5NPfHFsphQKXvwgO5Sjk8py2B2iCr3BHCZ8S38oIfuSrQx sn8=")
|
||||
|
||||
zsk, ksk := zskRrDnskey.(*DNSKEY), kskRrDnskey.(*DNSKEY)
|
||||
zskSig, kskSig := zskRrRrsig.(*RRSIG), kskRrRrsig.(*RRSIG)
|
||||
|
||||
if e := zskSig.Verify(zsk, []RR{zsk, ksk}); e != nil {
|
||||
t.Fatalf("cannot verify RRSIG with keytag [%d]. Cause [%s]", zsk.KeyTag(), e.Error())
|
||||
}
|
||||
|
||||
if e := kskSig.Verify(ksk, []RR{zsk, ksk}); e != nil {
|
||||
t.Fatalf("cannot verify RRSIG with keytag [%d]. Cause [%s]", ksk.KeyTag(), e.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseKeyReadError(t *testing.T) {
|
||||
m, err := parseKey(errReader{}, "")
|
||||
if err == nil || !strings.Contains(err.Error(), errTestReadError.Error()) {
|
||||
t.Errorf("expected error to contain %q, but got %v", errTestReadError, err)
|
||||
}
|
||||
if m != nil {
|
||||
t.Errorf("expected a nil map, but got %v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSAMD5KeyTag(t *testing.T) {
|
||||
rr1, _ := NewRR("test. IN DNSKEY 257 3 1 AwEAAcntNdoMnY8pvyPcpDTAaiqHyAhf53XUBANq166won/fjBFvmuzhTuP5r4el/pV0tzEBL73zpoU48BqF66uiL+qRijXCySJiaBUvLNll5rpwuduAOoVpmwOmkC4fV6izHOAx/Uy8c+pYP0YR8+1P7GuTFxgnMmt9sUGtoe+la0X/ ;{id = 27461 (ksk), size = 1024b}")
|
||||
rr2, _ := NewRR("test. IN DNSKEY 257 3 1 AwEAAf0bKO/m45ylk5BlSLmQHQRBLx1m/ZUXvyPFB387bJXxnTk6so3ub97L1RQ+8bOoiRh3Qm5EaYihjco7J8b/W5WbS3tVsE79nY584RfTKT2zcZ9AoFP2XLChXxPIf/6l0H9n6sH0aBjsG8vabEIp8e06INM3CXVPiMRPPeGNa0Ub ;{id = 27461 (ksk), size = 1024b}")
|
||||
|
||||
exp := uint16(27461)
|
||||
if x := rr1.(*DNSKEY).KeyTag(); x != exp {
|
||||
t.Errorf("expected %d, got %d, as keytag for rr1", exp, x)
|
||||
}
|
||||
if x := rr2.(*DNSKEY).KeyTag(); x != exp { // yes, same key tag
|
||||
t.Errorf("expected %d, got %d, as keytag for rr2", exp, x)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,10 +30,10 @@ func AddOrigin(s, origin string) string {
|
|||
if dns.IsFqdn(s) {
|
||||
return s // s is already a FQDN, no need to mess with it.
|
||||
}
|
||||
if len(origin) == 0 {
|
||||
if origin == "" {
|
||||
return s // Nothing to append.
|
||||
}
|
||||
if s == "@" || len(s) == 0 {
|
||||
if s == "@" || s == "" {
|
||||
return origin // Expand apex.
|
||||
}
|
||||
if origin == "." {
|
||||
|
@ -50,7 +50,7 @@ func TrimDomainName(s, origin string) string {
|
|||
// If the return value ends in a ".", the domain was not the suffix.
|
||||
// origin can end in "." or not. Either way the results should be the same.
|
||||
|
||||
if len(s) == 0 {
|
||||
if s == "" {
|
||||
return "@"
|
||||
}
|
||||
// Someone is using TrimDomainName(s, ".") to remove a dot if it exists.
|
||||
|
|
|
@ -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 }{
|
||||
{"", "@"},
|
||||
{".", "."},
|
||||
|
|
226
doc.go
226
doc.go
|
@ -1,62 +1,61 @@
|
|||
/*
|
||||
Package dns implements a full featured interface to the Domain Name System.
|
||||
Server- and client-side programming is supported.
|
||||
The package allows complete control over what is sent out to the DNS. The package
|
||||
API follows the less-is-more principle, by presenting a small, clean interface.
|
||||
Both server- and client-side programming is supported. The package allows
|
||||
complete control over what is sent out to the DNS. The API follows the
|
||||
less-is-more principle, by presenting a small, clean interface.
|
||||
|
||||
The package dns supports (asynchronous) querying/replying, incoming/outgoing zone transfers,
|
||||
It supports (asynchronous) querying/replying, incoming/outgoing zone transfers,
|
||||
TSIG, EDNS0, dynamic updates, notifies and DNSSEC validation/signing.
|
||||
Note that domain names MUST be fully qualified, before sending them, unqualified
|
||||
|
||||
Note that domain names MUST be fully qualified before sending them, unqualified
|
||||
names in a message will result in a packing failure.
|
||||
|
||||
Resource records are native types. They are not stored in wire format.
|
||||
Basic usage pattern for creating a new resource record:
|
||||
Resource records are native types. They are not stored in wire format. Basic
|
||||
usage pattern for creating a new resource record:
|
||||
|
||||
r := new(dns.MX)
|
||||
r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX,
|
||||
Class: dns.ClassINET, Ttl: 3600}
|
||||
r.Preference = 10
|
||||
r.Mx = "mx.miek.nl."
|
||||
r := new(dns.MX)
|
||||
r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 3600}
|
||||
r.Preference = 10
|
||||
r.Mx = "mx.miek.nl."
|
||||
|
||||
Or directly from a string:
|
||||
|
||||
mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.")
|
||||
mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.")
|
||||
|
||||
Or when the default origin (.) and TTL (3600) and class (IN) suit you:
|
||||
|
||||
mx, err := dns.NewRR("miek.nl MX 10 mx.miek.nl")
|
||||
mx, err := dns.NewRR("miek.nl MX 10 mx.miek.nl")
|
||||
|
||||
Or even:
|
||||
|
||||
mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek")
|
||||
mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek")
|
||||
|
||||
In the DNS messages are exchanged, these messages contain resource
|
||||
records (sets). Use pattern for creating a message:
|
||||
In the DNS messages are exchanged, these messages contain resource records
|
||||
(sets). Use pattern for creating a message:
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||
|
||||
Or when not certain if the domain name is fully qualified:
|
||||
|
||||
m.SetQuestion(dns.Fqdn("miek.nl"), dns.TypeMX)
|
||||
|
||||
The message m is now a message with the question section set to ask
|
||||
the MX records for the miek.nl. zone.
|
||||
The message m is now a message with the question section set to ask the MX
|
||||
records for the miek.nl. zone.
|
||||
|
||||
The following is slightly more verbose, but more flexible:
|
||||
|
||||
m1 := new(dns.Msg)
|
||||
m1.Id = dns.Id()
|
||||
m1.RecursionDesired = true
|
||||
m1.Question = make([]dns.Question, 1)
|
||||
m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET}
|
||||
m1 := new(dns.Msg)
|
||||
m1.Id = dns.Id()
|
||||
m1.RecursionDesired = true
|
||||
m1.Question = make([]dns.Question, 1)
|
||||
m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET}
|
||||
|
||||
After creating a message it can be sent.
|
||||
Basic use pattern for synchronous querying the DNS at a
|
||||
server configured on 127.0.0.1 and port 53:
|
||||
After creating a message it can be sent. Basic use pattern for synchronous
|
||||
querying the DNS at a server configured on 127.0.0.1 and port 53:
|
||||
|
||||
c := new(dns.Client)
|
||||
in, rtt, err := c.Exchange(m1, "127.0.0.1:53")
|
||||
c := new(dns.Client)
|
||||
in, rtt, err := c.Exchange(m1, "127.0.0.1:53")
|
||||
|
||||
Suppressing multiple outstanding queries (with the same question, type and
|
||||
class) is as easy as setting:
|
||||
|
@ -73,18 +72,18 @@ and port to use for the connection:
|
|||
Port: 12345,
|
||||
Zone: "",
|
||||
}
|
||||
d := net.Dialer{
|
||||
c.Dialer = &net.Dialer{
|
||||
Timeout: 200 * time.Millisecond,
|
||||
LocalAddr: &laddr,
|
||||
}
|
||||
in, rtt, err := c.ExchangeWithDialer(&d, m1, "8.8.8.8:53")
|
||||
in, rtt, err := c.Exchange(m1, "8.8.8.8:53")
|
||||
|
||||
If these "advanced" features are not needed, a simple UDP query can be sent,
|
||||
with:
|
||||
|
||||
in, err := dns.Exchange(m1, "127.0.0.1:53")
|
||||
|
||||
When this functions returns you will get dns message. A dns message consists
|
||||
When this functions returns you will get DNS message. A DNS message consists
|
||||
out of four sections.
|
||||
The question section: in.Question, the answer section: in.Answer,
|
||||
the authority section: in.Ns and the additional section: in.Extra.
|
||||
|
@ -97,72 +96,70 @@ the Answer section:
|
|||
// do something with t.Txt
|
||||
}
|
||||
|
||||
Domain Name and TXT Character String Representations
|
||||
# Domain Name and TXT Character String Representations
|
||||
|
||||
Both domain names and TXT character strings are converted to presentation
|
||||
form both when unpacked and when converted to strings.
|
||||
Both domain names and TXT character strings are converted to presentation form
|
||||
both when unpacked and when converted to strings.
|
||||
|
||||
For TXT character strings, tabs, carriage returns and line feeds will be
|
||||
converted to \t, \r and \n respectively. Back slashes and quotations marks
|
||||
will be escaped. Bytes below 32 and above 127 will be converted to \DDD
|
||||
form.
|
||||
converted to \t, \r and \n respectively. Back slashes and quotations marks will
|
||||
be escaped. Bytes below 32 and above 127 will be converted to \DDD form.
|
||||
|
||||
For domain names, in addition to the above rules brackets, periods,
|
||||
spaces, semicolons and the at symbol are escaped.
|
||||
For domain names, in addition to the above rules brackets, periods, spaces,
|
||||
semicolons and the at symbol are escaped.
|
||||
|
||||
DNSSEC
|
||||
# DNSSEC
|
||||
|
||||
DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It
|
||||
uses public key cryptography to sign resource records. The
|
||||
public keys are stored in DNSKEY records and the signatures in RRSIG records.
|
||||
DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It uses
|
||||
public key cryptography to sign resource records. The public keys are stored in
|
||||
DNSKEY records and the signatures in RRSIG records.
|
||||
|
||||
Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK) bit
|
||||
to a request.
|
||||
Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK)
|
||||
bit to a request.
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetEdns0(4096, true)
|
||||
m := new(dns.Msg)
|
||||
m.SetEdns0(4096, true)
|
||||
|
||||
Signature generation, signature verification and key generation are all supported.
|
||||
|
||||
DYNAMIC UPDATES
|
||||
# DYNAMIC UPDATES
|
||||
|
||||
Dynamic updates reuses the DNS message format, but renames three of
|
||||
the sections. Question is Zone, Answer is Prerequisite, Authority is
|
||||
Update, only the Additional is not renamed. See RFC 2136 for the gory details.
|
||||
Dynamic updates reuses the DNS message format, but renames three of the
|
||||
sections. Question is Zone, Answer is Prerequisite, Authority is Update, only
|
||||
the Additional is not renamed. See RFC 2136 for the gory details.
|
||||
|
||||
You can set a rather complex set of rules for the existence of absence of
|
||||
certain resource records or names in a zone to specify if resource records
|
||||
should be added or removed. The table from RFC 2136 supplemented with the Go
|
||||
DNS function shows which functions exist to specify the prerequisites.
|
||||
|
||||
3.2.4 - Table Of Metavalues Used In Prerequisite Section
|
||||
3.2.4 - Table Of Metavalues Used In Prerequisite Section
|
||||
|
||||
CLASS TYPE RDATA Meaning Function
|
||||
--------------------------------------------------------------
|
||||
ANY ANY empty Name is in use dns.NameUsed
|
||||
ANY rrset empty RRset exists (value indep) dns.RRsetUsed
|
||||
NONE ANY empty Name is not in use dns.NameNotUsed
|
||||
NONE rrset empty RRset does not exist dns.RRsetNotUsed
|
||||
zone rrset rr RRset exists (value dep) dns.Used
|
||||
CLASS TYPE RDATA Meaning Function
|
||||
--------------------------------------------------------------
|
||||
ANY ANY empty Name is in use dns.NameUsed
|
||||
ANY rrset empty RRset exists (value indep) dns.RRsetUsed
|
||||
NONE ANY empty Name is not in use dns.NameNotUsed
|
||||
NONE rrset empty RRset does not exist dns.RRsetNotUsed
|
||||
zone rrset rr RRset exists (value dep) dns.Used
|
||||
|
||||
The prerequisite section can also be left empty.
|
||||
If you have decided on the prerequisites you can tell what RRs should
|
||||
be added or deleted. The next table shows the options you have and
|
||||
what functions to call.
|
||||
The prerequisite section can also be left empty. If you have decided on the
|
||||
prerequisites you can tell what RRs should be added or deleted. The next table
|
||||
shows the options you have and what functions to call.
|
||||
|
||||
3.4.2.6 - Table Of Metavalues Used In Update Section
|
||||
3.4.2.6 - Table Of Metavalues Used In Update Section
|
||||
|
||||
CLASS TYPE RDATA Meaning Function
|
||||
---------------------------------------------------------------
|
||||
ANY ANY empty Delete all RRsets from name dns.RemoveName
|
||||
ANY rrset empty Delete an RRset dns.RemoveRRset
|
||||
NONE rrset rr Delete an RR from RRset dns.Remove
|
||||
zone rrset rr Add to an RRset dns.Insert
|
||||
CLASS TYPE RDATA Meaning Function
|
||||
---------------------------------------------------------------
|
||||
ANY ANY empty Delete all RRsets from name dns.RemoveName
|
||||
ANY rrset empty Delete an RRset dns.RemoveRRset
|
||||
NONE rrset rr Delete an RR from RRset dns.Remove
|
||||
zone rrset rr Add to an RRset dns.Insert
|
||||
|
||||
TRANSACTION SIGNATURE
|
||||
# TRANSACTION SIGNATURE
|
||||
|
||||
An TSIG or transaction signature adds a HMAC TSIG record to each message sent.
|
||||
The supported algorithms include: HmacMD5, HmacSHA1, HmacSHA256 and HmacSHA512.
|
||||
The supported algorithms include: HmacSHA1, HmacSHA256 and HmacSHA512.
|
||||
|
||||
Basic use pattern when querying with a TSIG name "axfr." (note that these key names
|
||||
must be fully qualified - as they are domain names) and the base64 secret
|
||||
|
@ -177,25 +174,49 @@ changes to the RRset after calling SetTsig() the signature will be incorrect.
|
|||
c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||
m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
|
||||
m.SetTsig("axfr.", dns.HmacSHA256, 300, time.Now().Unix())
|
||||
...
|
||||
// When sending the TSIG RR is calculated and filled in before sending
|
||||
|
||||
When requesting an zone transfer (almost all TSIG usage is when requesting zone transfers), with
|
||||
TSIG, this is the basic use pattern. In this example we request an AXFR for
|
||||
miek.nl. with TSIG key named "axfr." and secret "so6ZGir4GPAqINNh9U5c3A=="
|
||||
and using the server 176.58.119.54:
|
||||
When requesting an zone transfer (almost all TSIG usage is when requesting zone
|
||||
transfers), with TSIG, this is the basic use pattern. In this example we
|
||||
request an AXFR for miek.nl. with TSIG key named "axfr." and secret
|
||||
"so6ZGir4GPAqINNh9U5c3A==" and using the server 176.58.119.54:
|
||||
|
||||
t := new(dns.Transfer)
|
||||
m := new(dns.Msg)
|
||||
t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
|
||||
m.SetAxfr("miek.nl.")
|
||||
m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
|
||||
m.SetTsig("axfr.", dns.HmacSHA256, 300, time.Now().Unix())
|
||||
c, err := t.In(m, "176.58.119.54:53")
|
||||
for r := range c { ... }
|
||||
|
||||
You can now read the records from the transfer as they come in. Each envelope is checked with TSIG.
|
||||
If something is not correct an error is returned.
|
||||
You can now read the records from the transfer as they come in. Each envelope
|
||||
is checked with TSIG. If something is not correct an error is returned.
|
||||
|
||||
A custom TSIG implementation can be used. This requires additional code to
|
||||
perform any session establishment and signature generation/verification. The
|
||||
client must be configured with an implementation of the TsigProvider interface:
|
||||
|
||||
type Provider struct{}
|
||||
|
||||
func (*Provider) Generate(msg []byte, tsig *dns.TSIG) ([]byte, error) {
|
||||
// Use tsig.Hdr.Name and tsig.Algorithm in your code to
|
||||
// generate the MAC using msg as the payload.
|
||||
}
|
||||
|
||||
func (*Provider) Verify(msg []byte, tsig *dns.TSIG) error {
|
||||
// Use tsig.Hdr.Name and tsig.Algorithm in your code to verify
|
||||
// that msg matches the value in tsig.MAC.
|
||||
}
|
||||
|
||||
c := new(dns.Client)
|
||||
c.TsigProvider = new(Provider)
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||
m.SetTsig(keyname, dns.HmacSHA256, 300, time.Now().Unix())
|
||||
...
|
||||
// TSIG RR is calculated by calling your Generate method
|
||||
|
||||
Basic use pattern validating and replying to a message that has TSIG set.
|
||||
|
||||
|
@ -210,38 +231,38 @@ Basic use pattern validating and replying to a message that has TSIG set.
|
|||
if r.IsTsig() != nil {
|
||||
if w.TsigStatus() == nil {
|
||||
// *Msg r has an TSIG record and it was validated
|
||||
m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
|
||||
m.SetTsig("axfr.", dns.HmacSHA256, 300, time.Now().Unix())
|
||||
} else {
|
||||
// *Msg r has an TSIG records and it was not valided
|
||||
// *Msg r has an TSIG records and it was not validated
|
||||
}
|
||||
}
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
PRIVATE RRS
|
||||
# PRIVATE RRS
|
||||
|
||||
RFC 6895 sets aside a range of type codes for private use. This range
|
||||
is 65,280 - 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these
|
||||
RFC 6895 sets aside a range of type codes for private use. This range is 65,280
|
||||
- 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these
|
||||
can be used, before requesting an official type code from IANA.
|
||||
|
||||
see http://miek.nl/2014/September/21/idn-and-private-rr-in-go-dns/ for more
|
||||
See https://miek.nl/2014/september/21/idn-and-private-rr-in-go-dns/ for more
|
||||
information.
|
||||
|
||||
EDNS0
|
||||
# EDNS0
|
||||
|
||||
EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated
|
||||
by RFC 6891. It defines an new RR type, the OPT RR, which is then completely
|
||||
EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated by
|
||||
RFC 6891. It defines a new RR type, the OPT RR, which is then completely
|
||||
abused.
|
||||
|
||||
Basic use pattern for creating an (empty) OPT RR:
|
||||
|
||||
o := new(dns.OPT)
|
||||
o.Hdr.Name = "." // MUST be the root zone, per definition.
|
||||
o.Hdr.Rrtype = dns.TypeOPT
|
||||
|
||||
The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891)
|
||||
interfaces. Currently only a few have been standardized: EDNS0_NSID
|
||||
(RFC 5001) and EDNS0_SUBNET (draft-vandergaast-edns-client-subnet-02). Note
|
||||
that these options may be combined in an OPT RR.
|
||||
The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891) interfaces.
|
||||
Currently only a few have been standardized: EDNS0_NSID (RFC 5001) and
|
||||
EDNS0_SUBNET (RFC 7871). Note that these options may be combined in an OPT RR.
|
||||
Basic use pattern for a server to check if (and which) options are set:
|
||||
|
||||
// o is a dns.OPT
|
||||
|
@ -258,14 +279,13 @@ SIG(0)
|
|||
|
||||
From RFC 2931:
|
||||
|
||||
SIG(0) provides protection for DNS transactions and requests ....
|
||||
... protection for glue records, DNS requests, protection for message headers
|
||||
on requests and responses, and protection of the overall integrity of a response.
|
||||
SIG(0) provides protection for DNS transactions and requests ....
|
||||
... protection for glue records, DNS requests, protection for message headers
|
||||
on requests and responses, and protection of the overall integrity of a response.
|
||||
|
||||
It works like TSIG, except that SIG(0) uses public key cryptography, instead of the shared
|
||||
secret approach in TSIG.
|
||||
Supported algorithms: DSA, ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256 and
|
||||
RSASHA512.
|
||||
It works like TSIG, except that SIG(0) uses public key cryptography, instead of
|
||||
the shared secret approach in TSIG. Supported algorithms: ECDSAP256SHA256,
|
||||
ECDSAP384SHA384, RSASHA1, RSASHA256 and RSASHA512.
|
||||
|
||||
Signing subsequent messages in multi-message sessions is not implemented.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package dns
|
||||
|
||||
//go:generate go run duplicate_generate.go
|
||||
|
||||
// IsDuplicate checks of r1 and r2 are duplicates of each other, excluding the TTL.
|
||||
// So this means the header data is equal *and* the RDATA is the same. Returns true
|
||||
// if so, otherwise false. It's a protocol violation to have identical RRs in a message.
|
||||
func IsDuplicate(r1, r2 RR) bool {
|
||||
// Check whether the record header is identical.
|
||||
if !r1.Header().isDuplicate(r2.Header()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check whether the RDATA is identical.
|
||||
return r1.isDuplicate(r2)
|
||||
}
|
||||
|
||||
func (r1 *RR_Header) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*RR_Header)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if r1.Class != r2.Class {
|
||||
return false
|
||||
}
|
||||
if r1.Rrtype != r2.Rrtype {
|
||||
return false
|
||||
}
|
||||
if !isDuplicateName(r1.Name, r2.Name) {
|
||||
return false
|
||||
}
|
||||
// ignore TTL
|
||||
return true
|
||||
}
|
||||
|
||||
// isDuplicateName checks if the domain names s1 and s2 are equal.
|
||||
func isDuplicateName(s1, s2 string) bool { return equal(s1, s2) }
|
|
@ -0,0 +1,174 @@
|
|||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
// types_generate.go is meant to run with go generate. It will use
|
||||
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||
// it will generate conversion tables (TypeToRR and TypeToString) and banal
|
||||
// methods (len, Header, copy) based on the struct tags. The generated source is
|
||||
// written to ztypes.go, and is meant to be checked into git.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
var packageHdr = `
|
||||
// Code generated by "go run duplicate_generate.go"; DO NOT EDIT.
|
||||
|
||||
package dns
|
||||
|
||||
`
|
||||
|
||||
func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
|
||||
st, ok := t.Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if st.NumFields() == 0 {
|
||||
return nil, false
|
||||
}
|
||||
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
||||
return st, false
|
||||
}
|
||||
if st.Field(0).Anonymous() {
|
||||
st, _ := getTypeStruct(st.Field(0).Type(), scope)
|
||||
return st, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// loadModule retrieves package description for a given module.
|
||||
func loadModule(name string) (*types.Package, error) {
|
||||
conf := packages.Config{Mode: packages.NeedTypes | packages.NeedTypesInfo}
|
||||
pkgs, err := packages.Load(&conf, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pkgs[0].Types, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Import and type-check the package
|
||||
pkg, err := loadModule("github.com/miekg/dns")
|
||||
fatalIfErr(err)
|
||||
scope := pkg.Scope()
|
||||
|
||||
// Collect actual types (*X)
|
||||
var namedTypes []string
|
||||
for _, name := range scope.Names() {
|
||||
o := scope.Lookup(name)
|
||||
if o == nil || !o.Exported() {
|
||||
continue
|
||||
}
|
||||
|
||||
if st, _ := getTypeStruct(o.Type(), scope); st == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if name == "PrivateRR" || name == "OPT" {
|
||||
continue
|
||||
}
|
||||
|
||||
namedTypes = append(namedTypes, o.Name())
|
||||
}
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
b.WriteString(packageHdr)
|
||||
|
||||
// Generate the duplicate check for each type.
|
||||
fmt.Fprint(b, "// isDuplicate() functions\n\n")
|
||||
for _, name := range namedTypes {
|
||||
|
||||
o := scope.Lookup(name)
|
||||
st, _ := getTypeStruct(o.Type(), scope)
|
||||
fmt.Fprintf(b, "func (r1 *%s) isDuplicate(_r2 RR) bool {\n", name)
|
||||
fmt.Fprintf(b, "r2, ok := _r2.(*%s)\n", name)
|
||||
fmt.Fprint(b, "if !ok { return false }\n")
|
||||
fmt.Fprint(b, "_ = r2\n")
|
||||
for i := 1; i < st.NumFields(); i++ {
|
||||
field := st.Field(i).Name()
|
||||
o2 := func(s string) { fmt.Fprintf(b, s+"\n", field, field) }
|
||||
o3 := func(s string) { fmt.Fprintf(b, s+"\n", field, field, field) }
|
||||
|
||||
// For some reason, a and aaaa don't pop up as *types.Slice here (mostly like because the are
|
||||
// *indirectly* defined as a slice in the net package).
|
||||
if _, ok := st.Field(i).Type().(*types.Slice); ok {
|
||||
o2("if len(r1.%s) != len(r2.%s) {\nreturn false\n}")
|
||||
|
||||
if st.Tag(i) == `dns:"cdomain-name"` || st.Tag(i) == `dns:"domain-name"` {
|
||||
o3(`for i := 0; i < len(r1.%s); i++ {
|
||||
if !isDuplicateName(r1.%s[i], r2.%s[i]) {
|
||||
return false
|
||||
}
|
||||
}`)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if st.Tag(i) == `dns:"apl"` {
|
||||
o3(`for i := 0; i < len(r1.%s); i++ {
|
||||
if !r1.%s[i].equals(&r2.%s[i]) {
|
||||
return false
|
||||
}
|
||||
}`)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if st.Tag(i) == `dns:"pairs"` {
|
||||
o2(`if !areSVCBPairArraysEqual(r1.%s, r2.%s) {
|
||||
return false
|
||||
}`)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
o3(`for i := 0; i < len(r1.%s); i++ {
|
||||
if r1.%s[i] != r2.%s[i] {
|
||||
return false
|
||||
}
|
||||
}`)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
switch st.Tag(i) {
|
||||
case `dns:"-"`:
|
||||
// ignored
|
||||
case `dns:"a"`, `dns:"aaaa"`:
|
||||
o2("if !r1.%s.Equal(r2.%s) {\nreturn false\n}")
|
||||
case `dns:"cdomain-name"`, `dns:"domain-name"`:
|
||||
o2("if !isDuplicateName(r1.%s, r2.%s) {\nreturn false\n}")
|
||||
default:
|
||||
o2("if r1.%s != r2.%s {\nreturn false\n}")
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(b, "return true\n}\n\n")
|
||||
}
|
||||
|
||||
// gofmt
|
||||
res, err := format.Source(b.Bytes())
|
||||
if err != nil {
|
||||
b.WriteTo(os.Stderr)
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// write result
|
||||
f, err := os.Create("zduplicate.go")
|
||||
fatalIfErr(err)
|
||||
defer f.Close()
|
||||
f.Write(res)
|
||||
}
|
||||
|
||||
func fatalIfErr(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package dns
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDuplicateA(t *testing.T) {
|
||||
a1, _ := NewRR("www.example.org. 2700 IN A 127.0.0.1")
|
||||
a2, _ := NewRR("www.example.org. IN A 127.0.0.1")
|
||||
if !IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
|
||||
}
|
||||
|
||||
a2, _ = NewRR("www.example.org. IN A 127.0.0.2")
|
||||
if IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateTXT(t *testing.T) {
|
||||
a1, _ := NewRR("www.example.org. IN TXT \"aa\"")
|
||||
a2, _ := NewRR("www.example.org. IN TXT \"aa\"")
|
||||
|
||||
if !IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
|
||||
}
|
||||
|
||||
a2, _ = NewRR("www.example.org. IN TXT \"aa\" \"bb\"")
|
||||
if IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
|
||||
}
|
||||
|
||||
a1, _ = NewRR("www.example.org. IN TXT \"aa\" \"bc\"")
|
||||
if IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateSVCB(t *testing.T) {
|
||||
a1, _ := NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1::3:3:3:3 key65300=\254\032\030\000\ \043,\;`)
|
||||
a2, _ := NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1:0::3:3:3:3 key65300="\254\ \030\000 +\,;"`)
|
||||
|
||||
if !IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
|
||||
}
|
||||
|
||||
a2, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1::3:3:3:3 key65300="\255\ \030\000 +\,;"`)
|
||||
|
||||
if IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
|
||||
}
|
||||
|
||||
a1, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1::3:3:3:3`)
|
||||
|
||||
if IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
|
||||
}
|
||||
|
||||
a2, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv4hint=1.1.1.1`)
|
||||
|
||||
if IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
|
||||
}
|
||||
|
||||
a1, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv4hint=1.1.1.1,1.0.2.1`)
|
||||
|
||||
if IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateOwner(t *testing.T) {
|
||||
a1, _ := NewRR("www.example.org. IN A 127.0.0.1")
|
||||
a2, _ := NewRR("www.example.org. IN A 127.0.0.1")
|
||||
if !IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
|
||||
}
|
||||
|
||||
a2, _ = NewRR("WWw.exaMPle.org. IN A 127.0.0.2")
|
||||
if IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateDomain(t *testing.T) {
|
||||
a1, _ := NewRR("www.example.org. IN CNAME example.org.")
|
||||
a2, _ := NewRR("www.example.org. IN CNAME example.org.")
|
||||
if !IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
|
||||
}
|
||||
|
||||
a2, _ = NewRR("www.example.org. IN CNAME exAMPLe.oRG.")
|
||||
if !IsDuplicate(a1, a2) {
|
||||
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateWrongRrtype(t *testing.T) {
|
||||
// Test that IsDuplicate won't panic for a record that's lying about
|
||||
// it's Rrtype.
|
||||
|
||||
r1 := &A{Hdr: RR_Header{Rrtype: TypeA}}
|
||||
r2 := &AAAA{Hdr: RR_Header{Rrtype: TypeA}}
|
||||
if IsDuplicate(r1, r2) {
|
||||
t.Errorf("expected %s/%s not to be duplicates, but got true", r1.String(), r2.String())
|
||||
}
|
||||
|
||||
r3 := &AAAA{Hdr: RR_Header{Rrtype: TypeA}}
|
||||
r4 := &A{Hdr: RR_Header{Rrtype: TypeA}}
|
||||
if IsDuplicate(r3, r4) {
|
||||
t.Errorf("expected %s/%s not to be duplicates, but got true", r3.String(), r4.String())
|
||||
}
|
||||
|
||||
r5 := &AAAA{Hdr: RR_Header{Rrtype: TypeA}}
|
||||
r6 := &AAAA{Hdr: RR_Header{Rrtype: TypeA}}
|
||||
if !IsDuplicate(r5, r6) {
|
||||
t.Errorf("expected %s/%s to be duplicates, but got false", r5.String(), r6.String())
|
||||
}
|
||||
}
|
385
edns.go
385
edns.go
|
@ -14,6 +14,7 @@ const (
|
|||
EDNS0LLQ = 0x1 // long lived queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01
|
||||
EDNS0UL = 0x2 // update lease draft: http://files.dns-sd.org/draft-sekar-dns-ul.txt
|
||||
EDNS0NSID = 0x3 // nsid (See RFC 5001)
|
||||
EDNS0ESU = 0x4 // ENUM Source-URI draft: https://datatracker.ietf.org/doc/html/draft-kaplan-enum-source-uri-00
|
||||
EDNS0DAU = 0x5 // DNSSEC Algorithm Understood
|
||||
EDNS0DHU = 0x6 // DS Hash Understood
|
||||
EDNS0N3U = 0x7 // NSEC3 Hash Understood
|
||||
|
@ -22,11 +23,49 @@ const (
|
|||
EDNS0COOKIE = 0xa // EDNS0 Cookie
|
||||
EDNS0TCPKEEPALIVE = 0xb // EDNS0 tcp keep alive (See RFC 7828)
|
||||
EDNS0PADDING = 0xc // EDNS0 padding (See RFC 7830)
|
||||
EDNS0EDE = 0xf // EDNS0 extended DNS errors (See RFC 8914)
|
||||
EDNS0LOCALSTART = 0xFDE9 // Beginning of range reserved for local/experimental use (See RFC 6891)
|
||||
EDNS0LOCALEND = 0xFFFE // End of range reserved for local/experimental use (See RFC 6891)
|
||||
_DO = 1 << 15 // DNSSEC OK
|
||||
)
|
||||
|
||||
// makeDataOpt is used to unpack the EDNS0 option(s) from a message.
|
||||
func makeDataOpt(code uint16) EDNS0 {
|
||||
// All the EDNS0.* constants above need to be in this switch.
|
||||
switch code {
|
||||
case EDNS0LLQ:
|
||||
return new(EDNS0_LLQ)
|
||||
case EDNS0UL:
|
||||
return new(EDNS0_UL)
|
||||
case EDNS0NSID:
|
||||
return new(EDNS0_NSID)
|
||||
case EDNS0DAU:
|
||||
return new(EDNS0_DAU)
|
||||
case EDNS0DHU:
|
||||
return new(EDNS0_DHU)
|
||||
case EDNS0N3U:
|
||||
return new(EDNS0_N3U)
|
||||
case EDNS0SUBNET:
|
||||
return new(EDNS0_SUBNET)
|
||||
case EDNS0EXPIRE:
|
||||
return new(EDNS0_EXPIRE)
|
||||
case EDNS0COOKIE:
|
||||
return new(EDNS0_COOKIE)
|
||||
case EDNS0TCPKEEPALIVE:
|
||||
return new(EDNS0_TCP_KEEPALIVE)
|
||||
case EDNS0PADDING:
|
||||
return new(EDNS0_PADDING)
|
||||
case EDNS0EDE:
|
||||
return new(EDNS0_EDE)
|
||||
case EDNS0ESU:
|
||||
return &EDNS0_ESU{Code: EDNS0ESU}
|
||||
default:
|
||||
e := new(EDNS0_LOCAL)
|
||||
e.Code = code
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
// OPT is the EDNS0 RR appended to messages to convey extra (meta) information.
|
||||
// See RFC 6891.
|
||||
type OPT struct {
|
||||
|
@ -39,7 +78,10 @@ func (rr *OPT) String() string {
|
|||
if rr.Do() {
|
||||
s += "flags: do; "
|
||||
} else {
|
||||
s += "flags: ; "
|
||||
s += "flags:; "
|
||||
}
|
||||
if rr.Hdr.Ttl&0x7FFF != 0 {
|
||||
s += fmt.Sprintf("MBZ: 0x%04x, ", rr.Hdr.Ttl&0x7FFF)
|
||||
}
|
||||
s += "udp: " + strconv.Itoa(int(rr.UDPSize()))
|
||||
|
||||
|
@ -59,6 +101,10 @@ func (rr *OPT) String() string {
|
|||
s += "\n; SUBNET: " + o.String()
|
||||
case *EDNS0_COOKIE:
|
||||
s += "\n; COOKIE: " + o.String()
|
||||
case *EDNS0_EXPIRE:
|
||||
s += "\n; EXPIRE: " + o.String()
|
||||
case *EDNS0_TCP_KEEPALIVE:
|
||||
s += "\n; KEEPALIVE: " + o.String()
|
||||
case *EDNS0_UL:
|
||||
s += "\n; UPDATE LEASE: " + o.String()
|
||||
case *EDNS0_LLQ:
|
||||
|
@ -73,41 +119,53 @@ func (rr *OPT) String() string {
|
|||
s += "\n; LOCAL OPT: " + o.String()
|
||||
case *EDNS0_PADDING:
|
||||
s += "\n; PADDING: " + o.String()
|
||||
case *EDNS0_EDE:
|
||||
s += "\n; EDE: " + o.String()
|
||||
case *EDNS0_ESU:
|
||||
s += "\n; ESU: " + o.String()
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (rr *OPT) len() int {
|
||||
l := rr.Hdr.len()
|
||||
for i := 0; i < len(rr.Option); i++ {
|
||||
func (rr *OPT) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
for _, o := range rr.Option {
|
||||
l += 4 // Account for 2-byte option code and 2-byte option length.
|
||||
lo, _ := rr.Option[i].pack()
|
||||
lo, _ := o.pack()
|
||||
l += len(lo)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (*OPT) parse(c *zlexer, origin string) *ParseError {
|
||||
return &ParseError{err: "OPT records do not have a presentation format"}
|
||||
}
|
||||
|
||||
func (rr *OPT) isDuplicate(r2 RR) bool { return false }
|
||||
|
||||
// return the old value -> delete SetVersion?
|
||||
|
||||
// Version returns the EDNS version used. Only zero is defined.
|
||||
func (rr *OPT) Version() uint8 {
|
||||
return uint8((rr.Hdr.Ttl & 0x00FF0000) >> 16)
|
||||
return uint8(rr.Hdr.Ttl & 0x00FF0000 >> 16)
|
||||
}
|
||||
|
||||
// SetVersion sets the version of EDNS. This is usually zero.
|
||||
func (rr *OPT) SetVersion(v uint8) {
|
||||
rr.Hdr.Ttl = rr.Hdr.Ttl&0xFF00FFFF | (uint32(v) << 16)
|
||||
rr.Hdr.Ttl = rr.Hdr.Ttl&0xFF00FFFF | uint32(v)<<16
|
||||
}
|
||||
|
||||
// ExtendedRcode returns the EDNS extended RCODE field (the upper 8 bits of the TTL).
|
||||
func (rr *OPT) ExtendedRcode() int {
|
||||
return int((rr.Hdr.Ttl & 0xFF000000) >> 24)
|
||||
return int(rr.Hdr.Ttl&0xFF000000>>24) << 4
|
||||
}
|
||||
|
||||
// SetExtendedRcode sets the EDNS extended RCODE field.
|
||||
func (rr *OPT) SetExtendedRcode(v uint8) {
|
||||
rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | (uint32(v) << 24)
|
||||
//
|
||||
// If the RCODE is not an extended RCODE, will reset the extended RCODE field to 0.
|
||||
func (rr *OPT) SetExtendedRcode(v uint16) {
|
||||
rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | uint32(v>>4)<<24
|
||||
}
|
||||
|
||||
// UDPSize returns the UDP buffer size.
|
||||
|
@ -140,6 +198,16 @@ func (rr *OPT) SetDo(do ...bool) {
|
|||
}
|
||||
}
|
||||
|
||||
// Z returns the Z part of the OPT RR as a uint16 with only the 15 least significant bits used.
|
||||
func (rr *OPT) Z() uint16 {
|
||||
return uint16(rr.Hdr.Ttl & 0x7FFF)
|
||||
}
|
||||
|
||||
// SetZ sets the Z part of the OPT RR, note only the 15 least significant bits of z are used.
|
||||
func (rr *OPT) SetZ(z uint16) {
|
||||
rr.Hdr.Ttl = rr.Hdr.Ttl&^0x7FFF | uint32(z&0x7FFF)
|
||||
}
|
||||
|
||||
// EDNS0 defines an EDNS0 Option. An OPT RR can have multiple options appended to it.
|
||||
type EDNS0 interface {
|
||||
// Option returns the option code for the option.
|
||||
|
@ -151,6 +219,8 @@ type EDNS0 interface {
|
|||
unpack([]byte) error
|
||||
// String returns the string representation of the option.
|
||||
String() string
|
||||
// copy returns a deep-copy of the option.
|
||||
copy() EDNS0
|
||||
}
|
||||
|
||||
// EDNS0_NSID option is used to retrieve a nameserver
|
||||
|
@ -181,7 +251,8 @@ func (e *EDNS0_NSID) pack() ([]byte, error) {
|
|||
// Option implements the EDNS0 interface.
|
||||
func (e *EDNS0_NSID) Option() uint16 { return EDNS0NSID } // Option returns the option code.
|
||||
func (e *EDNS0_NSID) unpack(b []byte) error { e.Nsid = hex.EncodeToString(b); return nil }
|
||||
func (e *EDNS0_NSID) String() string { return string(e.Nsid) }
|
||||
func (e *EDNS0_NSID) String() string { return e.Nsid }
|
||||
func (e *EDNS0_NSID) copy() EDNS0 { return &EDNS0_NSID{e.Code, e.Nsid} }
|
||||
|
||||
// EDNS0_SUBNET is the subnet option that is used to give the remote nameserver
|
||||
// an idea of where the client lives. See RFC 7871. It can then give back a different
|
||||
|
@ -192,7 +263,7 @@ func (e *EDNS0_NSID) String() string { return string(e.Nsid) }
|
|||
// o.Hdr.Name = "."
|
||||
// o.Hdr.Rrtype = dns.TypeOPT
|
||||
// e := new(dns.EDNS0_SUBNET)
|
||||
// e.Code = dns.EDNS0SUBNET
|
||||
// e.Code = dns.EDNS0SUBNET // by default this is filled in through unpacking OPT packets (unpackDataOpt)
|
||||
// e.Family = 1 // 1 for IPv4 source address, 2 for IPv6
|
||||
// e.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6
|
||||
// e.SourceScope = 0
|
||||
|
@ -271,22 +342,16 @@ func (e *EDNS0_SUBNET) unpack(b []byte) error {
|
|||
if e.SourceNetmask > net.IPv4len*8 || e.SourceScope > net.IPv4len*8 {
|
||||
return errors.New("dns: bad netmask")
|
||||
}
|
||||
addr := make([]byte, net.IPv4len)
|
||||
for i := 0; i < net.IPv4len && 4+i < len(b); i++ {
|
||||
addr[i] = b[4+i]
|
||||
}
|
||||
e.Address = net.IPv4(addr[0], addr[1], addr[2], addr[3])
|
||||
addr := make(net.IP, net.IPv4len)
|
||||
copy(addr, b[4:])
|
||||
e.Address = addr.To16()
|
||||
case 2:
|
||||
if e.SourceNetmask > net.IPv6len*8 || e.SourceScope > net.IPv6len*8 {
|
||||
return errors.New("dns: bad netmask")
|
||||
}
|
||||
addr := make([]byte, net.IPv6len)
|
||||
for i := 0; i < net.IPv6len && 4+i < len(b); i++ {
|
||||
addr[i] = b[4+i]
|
||||
}
|
||||
e.Address = net.IP{addr[0], addr[1], addr[2], addr[3], addr[4],
|
||||
addr[5], addr[6], addr[7], addr[8], addr[9], addr[10],
|
||||
addr[11], addr[12], addr[13], addr[14], addr[15]}
|
||||
addr := make(net.IP, net.IPv6len)
|
||||
copy(addr, b[4:])
|
||||
e.Address = addr
|
||||
default:
|
||||
return errors.New("dns: bad address family")
|
||||
}
|
||||
|
@ -305,6 +370,16 @@ func (e *EDNS0_SUBNET) String() (s string) {
|
|||
return
|
||||
}
|
||||
|
||||
func (e *EDNS0_SUBNET) copy() EDNS0 {
|
||||
return &EDNS0_SUBNET{
|
||||
e.Code,
|
||||
e.Family,
|
||||
e.SourceNetmask,
|
||||
e.SourceScope,
|
||||
e.Address,
|
||||
}
|
||||
}
|
||||
|
||||
// The EDNS0_COOKIE option is used to add a DNS Cookie to a message.
|
||||
//
|
||||
// o := new(dns.OPT)
|
||||
|
@ -340,11 +415,12 @@ func (e *EDNS0_COOKIE) pack() ([]byte, error) {
|
|||
func (e *EDNS0_COOKIE) Option() uint16 { return EDNS0COOKIE }
|
||||
func (e *EDNS0_COOKIE) unpack(b []byte) error { e.Cookie = hex.EncodeToString(b); return nil }
|
||||
func (e *EDNS0_COOKIE) String() string { return e.Cookie }
|
||||
func (e *EDNS0_COOKIE) copy() EDNS0 { return &EDNS0_COOKIE{e.Code, e.Cookie} }
|
||||
|
||||
// The EDNS0_UL (Update Lease) (draft RFC) option is used to tell the server to set
|
||||
// an expiration on an update RR. This is helpful for clients that cannot clean
|
||||
// up after themselves. This is a draft RFC and more information can be found at
|
||||
// http://files.dns-sd.org/draft-sekar-dns-ul.txt
|
||||
// https://tools.ietf.org/html/draft-sekar-dns-ul-02
|
||||
//
|
||||
// o := new(dns.OPT)
|
||||
// o.Hdr.Name = "."
|
||||
|
@ -354,23 +430,36 @@ func (e *EDNS0_COOKIE) String() string { return e.Cookie }
|
|||
// e.Lease = 120 // in seconds
|
||||
// o.Option = append(o.Option, e)
|
||||
type EDNS0_UL struct {
|
||||
Code uint16 // Always EDNS0UL
|
||||
Lease uint32
|
||||
Code uint16 // Always EDNS0UL
|
||||
Lease uint32
|
||||
KeyLease uint32
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
func (e *EDNS0_UL) Option() uint16 { return EDNS0UL }
|
||||
func (e *EDNS0_UL) String() string { return strconv.FormatUint(uint64(e.Lease), 10) }
|
||||
func (e *EDNS0_UL) String() string { return fmt.Sprintf("%d %d", e.Lease, e.KeyLease) }
|
||||
func (e *EDNS0_UL) copy() EDNS0 { return &EDNS0_UL{e.Code, e.Lease, e.KeyLease} }
|
||||
|
||||
// Copied: http://golang.org/src/pkg/net/dnsmsg.go
|
||||
func (e *EDNS0_UL) pack() ([]byte, error) {
|
||||
b := make([]byte, 4)
|
||||
var b []byte
|
||||
if e.KeyLease == 0 {
|
||||
b = make([]byte, 4)
|
||||
} else {
|
||||
b = make([]byte, 8)
|
||||
binary.BigEndian.PutUint32(b[4:], e.KeyLease)
|
||||
}
|
||||
binary.BigEndian.PutUint32(b, e.Lease)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_UL) unpack(b []byte) error {
|
||||
if len(b) < 4 {
|
||||
switch len(b) {
|
||||
case 4:
|
||||
e.KeyLease = 0
|
||||
case 8:
|
||||
e.KeyLease = binary.BigEndian.Uint32(b[4:])
|
||||
default:
|
||||
return ErrBuf
|
||||
}
|
||||
e.Lease = binary.BigEndian.Uint32(b)
|
||||
|
@ -415,12 +504,15 @@ func (e *EDNS0_LLQ) unpack(b []byte) error {
|
|||
|
||||
func (e *EDNS0_LLQ) String() string {
|
||||
s := strconv.FormatUint(uint64(e.Version), 10) + " " + strconv.FormatUint(uint64(e.Opcode), 10) +
|
||||
" " + strconv.FormatUint(uint64(e.Error), 10) + " " + strconv.FormatUint(uint64(e.Id), 10) +
|
||||
" " + strconv.FormatUint(uint64(e.Error), 10) + " " + strconv.FormatUint(e.Id, 10) +
|
||||
" " + strconv.FormatUint(uint64(e.LeaseLife), 10)
|
||||
return s
|
||||
}
|
||||
func (e *EDNS0_LLQ) copy() EDNS0 {
|
||||
return &EDNS0_LLQ{e.Code, e.Version, e.Opcode, e.Error, e.Id, e.LeaseLife}
|
||||
}
|
||||
|
||||
// EDNS0_DUA implements the EDNS0 "DNSSEC Algorithm Understood" option. See RFC 6975.
|
||||
// EDNS0_DAU implements the EDNS0 "DNSSEC Algorithm Understood" option. See RFC 6975.
|
||||
type EDNS0_DAU struct {
|
||||
Code uint16 // Always EDNS0DAU
|
||||
AlgCode []uint8
|
||||
|
@ -433,15 +525,16 @@ func (e *EDNS0_DAU) unpack(b []byte) error { e.AlgCode = b; return nil }
|
|||
|
||||
func (e *EDNS0_DAU) String() string {
|
||||
s := ""
|
||||
for i := 0; i < len(e.AlgCode); i++ {
|
||||
if a, ok := AlgorithmToString[e.AlgCode[i]]; ok {
|
||||
for _, alg := range e.AlgCode {
|
||||
if a, ok := AlgorithmToString[alg]; ok {
|
||||
s += " " + a
|
||||
} else {
|
||||
s += " " + strconv.Itoa(int(e.AlgCode[i]))
|
||||
s += " " + strconv.Itoa(int(alg))
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
func (e *EDNS0_DAU) copy() EDNS0 { return &EDNS0_DAU{e.Code, e.AlgCode} }
|
||||
|
||||
// EDNS0_DHU implements the EDNS0 "DS Hash Understood" option. See RFC 6975.
|
||||
type EDNS0_DHU struct {
|
||||
|
@ -456,15 +549,16 @@ func (e *EDNS0_DHU) unpack(b []byte) error { e.AlgCode = b; return nil }
|
|||
|
||||
func (e *EDNS0_DHU) String() string {
|
||||
s := ""
|
||||
for i := 0; i < len(e.AlgCode); i++ {
|
||||
if a, ok := HashToString[e.AlgCode[i]]; ok {
|
||||
for _, alg := range e.AlgCode {
|
||||
if a, ok := HashToString[alg]; ok {
|
||||
s += " " + a
|
||||
} else {
|
||||
s += " " + strconv.Itoa(int(e.AlgCode[i]))
|
||||
s += " " + strconv.Itoa(int(alg))
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
func (e *EDNS0_DHU) copy() EDNS0 { return &EDNS0_DHU{e.Code, e.AlgCode} }
|
||||
|
||||
// EDNS0_N3U implements the EDNS0 "NSEC3 Hash Understood" option. See RFC 6975.
|
||||
type EDNS0_N3U struct {
|
||||
|
@ -480,43 +574,58 @@ func (e *EDNS0_N3U) unpack(b []byte) error { e.AlgCode = b; return nil }
|
|||
func (e *EDNS0_N3U) String() string {
|
||||
// Re-use the hash map
|
||||
s := ""
|
||||
for i := 0; i < len(e.AlgCode); i++ {
|
||||
if a, ok := HashToString[e.AlgCode[i]]; ok {
|
||||
for _, alg := range e.AlgCode {
|
||||
if a, ok := HashToString[alg]; ok {
|
||||
s += " " + a
|
||||
} else {
|
||||
s += " " + strconv.Itoa(int(e.AlgCode[i]))
|
||||
s += " " + strconv.Itoa(int(alg))
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} }
|
||||
|
||||
// EDNS0_EXPIRE implementes the EDNS0 option as described in RFC 7314.
|
||||
// EDNS0_EXPIRE implements the EDNS0 option as described in RFC 7314.
|
||||
type EDNS0_EXPIRE struct {
|
||||
Code uint16 // Always EDNS0EXPIRE
|
||||
Expire uint32
|
||||
Empty bool // Empty is used to signal an empty Expire option in a backwards compatible way, it's not used on the wire.
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE }
|
||||
func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) }
|
||||
func (e *EDNS0_EXPIRE) copy() EDNS0 { return &EDNS0_EXPIRE{e.Code, e.Expire, e.Empty} }
|
||||
|
||||
func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
|
||||
if e.Empty {
|
||||
return []byte{}, nil
|
||||
}
|
||||
b := make([]byte, 4)
|
||||
b[0] = byte(e.Expire >> 24)
|
||||
b[1] = byte(e.Expire >> 16)
|
||||
b[2] = byte(e.Expire >> 8)
|
||||
b[3] = byte(e.Expire)
|
||||
binary.BigEndian.PutUint32(b, e.Expire)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_EXPIRE) unpack(b []byte) error {
|
||||
if len(b) == 0 {
|
||||
// zero-length EXPIRE query, see RFC 7314 Section 2
|
||||
e.Empty = true
|
||||
return nil
|
||||
}
|
||||
if len(b) < 4 {
|
||||
return ErrBuf
|
||||
}
|
||||
e.Expire = binary.BigEndian.Uint32(b)
|
||||
e.Empty = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_EXPIRE) String() (s string) {
|
||||
if e.Empty {
|
||||
return ""
|
||||
}
|
||||
return strconv.FormatUint(uint64(e.Expire), 10)
|
||||
}
|
||||
|
||||
// The EDNS0_LOCAL option is used for local/experimental purposes. The option
|
||||
// code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND]
|
||||
// (RFC6891), although any unassigned code can actually be used. The content of
|
||||
|
@ -540,6 +649,11 @@ func (e *EDNS0_LOCAL) Option() uint16 { return e.Code }
|
|||
func (e *EDNS0_LOCAL) String() string {
|
||||
return strconv.FormatInt(int64(e.Code), 10) + ":0x" + hex.EncodeToString(e.Data)
|
||||
}
|
||||
func (e *EDNS0_LOCAL) copy() EDNS0 {
|
||||
b := make([]byte, len(e.Data))
|
||||
copy(b, e.Data)
|
||||
return &EDNS0_LOCAL{e.Code, b}
|
||||
}
|
||||
|
||||
func (e *EDNS0_LOCAL) pack() ([]byte, error) {
|
||||
b := make([]byte, len(e.Data))
|
||||
|
@ -562,57 +676,53 @@ func (e *EDNS0_LOCAL) unpack(b []byte) error {
|
|||
// EDNS0_TCP_KEEPALIVE is an EDNS0 option that instructs the server to keep
|
||||
// the TCP connection alive. See RFC 7828.
|
||||
type EDNS0_TCP_KEEPALIVE struct {
|
||||
Code uint16 // Always EDNSTCPKEEPALIVE
|
||||
Length uint16 // the value 0 if the TIMEOUT is omitted, the value 2 if it is present;
|
||||
Timeout uint16 // an idle timeout value for the TCP connection, specified in units of 100 milliseconds, encoded in network byte order.
|
||||
Code uint16 // Always EDNSTCPKEEPALIVE
|
||||
|
||||
// Timeout is an idle timeout value for the TCP connection, specified in
|
||||
// units of 100 milliseconds, encoded in network byte order. If set to 0,
|
||||
// pack will return a nil slice.
|
||||
Timeout uint16
|
||||
|
||||
// Length is the option's length.
|
||||
// Deprecated: this field is deprecated and is always equal to 0.
|
||||
Length uint16
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
func (e *EDNS0_TCP_KEEPALIVE) Option() uint16 { return EDNS0TCPKEEPALIVE }
|
||||
|
||||
func (e *EDNS0_TCP_KEEPALIVE) pack() ([]byte, error) {
|
||||
if e.Timeout != 0 && e.Length != 2 {
|
||||
return nil, errors.New("dns: timeout specified but length is not 2")
|
||||
if e.Timeout > 0 {
|
||||
b := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(b, e.Timeout)
|
||||
return b, nil
|
||||
}
|
||||
if e.Timeout == 0 && e.Length != 0 {
|
||||
return nil, errors.New("dns: timeout not specified but length is not 0")
|
||||
}
|
||||
b := make([]byte, 4+e.Length)
|
||||
binary.BigEndian.PutUint16(b[0:], e.Code)
|
||||
binary.BigEndian.PutUint16(b[2:], e.Length)
|
||||
if e.Length == 2 {
|
||||
binary.BigEndian.PutUint16(b[4:], e.Timeout)
|
||||
}
|
||||
return b, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_TCP_KEEPALIVE) unpack(b []byte) error {
|
||||
if len(b) < 4 {
|
||||
return ErrBuf
|
||||
}
|
||||
e.Length = binary.BigEndian.Uint16(b[2:4])
|
||||
if e.Length != 0 && e.Length != 2 {
|
||||
return errors.New("dns: length mismatch, want 0/2 but got " + strconv.FormatUint(uint64(e.Length), 10))
|
||||
}
|
||||
if e.Length == 2 {
|
||||
if len(b) < 6 {
|
||||
return ErrBuf
|
||||
}
|
||||
e.Timeout = binary.BigEndian.Uint16(b[4:6])
|
||||
switch len(b) {
|
||||
case 0:
|
||||
case 2:
|
||||
e.Timeout = binary.BigEndian.Uint16(b)
|
||||
default:
|
||||
return fmt.Errorf("dns: length mismatch, want 0/2 but got %d", len(b))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_TCP_KEEPALIVE) String() (s string) {
|
||||
s = "use tcp keep-alive"
|
||||
if e.Length == 0 {
|
||||
func (e *EDNS0_TCP_KEEPALIVE) String() string {
|
||||
s := "use tcp keep-alive"
|
||||
if e.Timeout == 0 {
|
||||
s += ", timeout omitted"
|
||||
} else {
|
||||
s += fmt.Sprintf(", timeout %dms", e.Timeout*100)
|
||||
}
|
||||
return
|
||||
return s
|
||||
}
|
||||
|
||||
func (e *EDNS0_TCP_KEEPALIVE) copy() EDNS0 { return &EDNS0_TCP_KEEPALIVE{e.Code, e.Timeout, e.Length} }
|
||||
|
||||
// EDNS0_PADDING option is used to add padding to a request/response. The default
|
||||
// value of padding SHOULD be 0x0 but other values MAY be used, for instance if
|
||||
// compression is applied before encryption which may break signatures.
|
||||
|
@ -625,3 +735,122 @@ func (e *EDNS0_PADDING) Option() uint16 { return EDNS0PADDING }
|
|||
func (e *EDNS0_PADDING) pack() ([]byte, error) { return e.Padding, nil }
|
||||
func (e *EDNS0_PADDING) unpack(b []byte) error { e.Padding = b; return nil }
|
||||
func (e *EDNS0_PADDING) String() string { return fmt.Sprintf("%0X", e.Padding) }
|
||||
func (e *EDNS0_PADDING) copy() EDNS0 {
|
||||
b := make([]byte, len(e.Padding))
|
||||
copy(b, e.Padding)
|
||||
return &EDNS0_PADDING{b}
|
||||
}
|
||||
|
||||
// Extended DNS Error Codes (RFC 8914).
|
||||
const (
|
||||
ExtendedErrorCodeOther uint16 = iota
|
||||
ExtendedErrorCodeUnsupportedDNSKEYAlgorithm
|
||||
ExtendedErrorCodeUnsupportedDSDigestType
|
||||
ExtendedErrorCodeStaleAnswer
|
||||
ExtendedErrorCodeForgedAnswer
|
||||
ExtendedErrorCodeDNSSECIndeterminate
|
||||
ExtendedErrorCodeDNSBogus
|
||||
ExtendedErrorCodeSignatureExpired
|
||||
ExtendedErrorCodeSignatureNotYetValid
|
||||
ExtendedErrorCodeDNSKEYMissing
|
||||
ExtendedErrorCodeRRSIGsMissing
|
||||
ExtendedErrorCodeNoZoneKeyBitSet
|
||||
ExtendedErrorCodeNSECMissing
|
||||
ExtendedErrorCodeCachedError
|
||||
ExtendedErrorCodeNotReady
|
||||
ExtendedErrorCodeBlocked
|
||||
ExtendedErrorCodeCensored
|
||||
ExtendedErrorCodeFiltered
|
||||
ExtendedErrorCodeProhibited
|
||||
ExtendedErrorCodeStaleNXDOMAINAnswer
|
||||
ExtendedErrorCodeNotAuthoritative
|
||||
ExtendedErrorCodeNotSupported
|
||||
ExtendedErrorCodeNoReachableAuthority
|
||||
ExtendedErrorCodeNetworkError
|
||||
ExtendedErrorCodeInvalidData
|
||||
)
|
||||
|
||||
// ExtendedErrorCodeToString maps extended error info codes to a human readable
|
||||
// description.
|
||||
var ExtendedErrorCodeToString = map[uint16]string{
|
||||
ExtendedErrorCodeOther: "Other",
|
||||
ExtendedErrorCodeUnsupportedDNSKEYAlgorithm: "Unsupported DNSKEY Algorithm",
|
||||
ExtendedErrorCodeUnsupportedDSDigestType: "Unsupported DS Digest Type",
|
||||
ExtendedErrorCodeStaleAnswer: "Stale Answer",
|
||||
ExtendedErrorCodeForgedAnswer: "Forged Answer",
|
||||
ExtendedErrorCodeDNSSECIndeterminate: "DNSSEC Indeterminate",
|
||||
ExtendedErrorCodeDNSBogus: "DNSSEC Bogus",
|
||||
ExtendedErrorCodeSignatureExpired: "Signature Expired",
|
||||
ExtendedErrorCodeSignatureNotYetValid: "Signature Not Yet Valid",
|
||||
ExtendedErrorCodeDNSKEYMissing: "DNSKEY Missing",
|
||||
ExtendedErrorCodeRRSIGsMissing: "RRSIGs Missing",
|
||||
ExtendedErrorCodeNoZoneKeyBitSet: "No Zone Key Bit Set",
|
||||
ExtendedErrorCodeNSECMissing: "NSEC Missing",
|
||||
ExtendedErrorCodeCachedError: "Cached Error",
|
||||
ExtendedErrorCodeNotReady: "Not Ready",
|
||||
ExtendedErrorCodeBlocked: "Blocked",
|
||||
ExtendedErrorCodeCensored: "Censored",
|
||||
ExtendedErrorCodeFiltered: "Filtered",
|
||||
ExtendedErrorCodeProhibited: "Prohibited",
|
||||
ExtendedErrorCodeStaleNXDOMAINAnswer: "Stale NXDOMAIN Answer",
|
||||
ExtendedErrorCodeNotAuthoritative: "Not Authoritative",
|
||||
ExtendedErrorCodeNotSupported: "Not Supported",
|
||||
ExtendedErrorCodeNoReachableAuthority: "No Reachable Authority",
|
||||
ExtendedErrorCodeNetworkError: "Network Error",
|
||||
ExtendedErrorCodeInvalidData: "Invalid Data",
|
||||
}
|
||||
|
||||
// StringToExtendedErrorCode is a map from human readable descriptions to
|
||||
// extended error info codes.
|
||||
var StringToExtendedErrorCode = reverseInt16(ExtendedErrorCodeToString)
|
||||
|
||||
// EDNS0_EDE option is used to return additional information about the cause of
|
||||
// DNS errors.
|
||||
type EDNS0_EDE struct {
|
||||
InfoCode uint16
|
||||
ExtraText string
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
func (e *EDNS0_EDE) Option() uint16 { return EDNS0EDE }
|
||||
func (e *EDNS0_EDE) copy() EDNS0 { return &EDNS0_EDE{e.InfoCode, e.ExtraText} }
|
||||
|
||||
func (e *EDNS0_EDE) String() string {
|
||||
info := strconv.FormatUint(uint64(e.InfoCode), 10)
|
||||
if s, ok := ExtendedErrorCodeToString[e.InfoCode]; ok {
|
||||
info += fmt.Sprintf(" (%s)", s)
|
||||
}
|
||||
return fmt.Sprintf("%s: (%s)", info, e.ExtraText)
|
||||
}
|
||||
|
||||
func (e *EDNS0_EDE) pack() ([]byte, error) {
|
||||
b := make([]byte, 2+len(e.ExtraText))
|
||||
binary.BigEndian.PutUint16(b[0:], e.InfoCode)
|
||||
copy(b[2:], []byte(e.ExtraText))
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_EDE) unpack(b []byte) error {
|
||||
if len(b) < 2 {
|
||||
return ErrBuf
|
||||
}
|
||||
e.InfoCode = binary.BigEndian.Uint16(b[0:])
|
||||
e.ExtraText = string(b[2:])
|
||||
return nil
|
||||
}
|
||||
|
||||
// The EDNS0_ESU option for ENUM Source-URI Extension
|
||||
type EDNS0_ESU struct {
|
||||
Code uint16
|
||||
Uri string
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
func (e *EDNS0_ESU) Option() uint16 { return EDNS0ESU }
|
||||
func (e *EDNS0_ESU) String() string { return e.Uri }
|
||||
func (e *EDNS0_ESU) copy() EDNS0 { return &EDNS0_ESU{e.Code, e.Uri} }
|
||||
func (e *EDNS0_ESU) pack() ([]byte, error) { return []byte(e.Uri), nil }
|
||||
func (e *EDNS0_ESU) unpack(b []byte) error {
|
||||
e.Uri = string(b)
|
||||
return nil
|
||||
}
|
||||
|
|
217
edns_test.go
217
edns_test.go
|
@ -1,6 +1,10 @@
|
|||
package dns
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOPTTtl(t *testing.T) {
|
||||
e := &OPT{}
|
||||
|
@ -62,7 +66,214 @@ func TestOPTTtl(t *testing.T) {
|
|||
}
|
||||
|
||||
e.SetExtendedRcode(42)
|
||||
if e.ExtendedRcode() != 42 {
|
||||
t.Errorf("set 42, expected %d, got %d", 42, e.ExtendedRcode())
|
||||
// ExtendedRcode has the last 4 bits set to 0.
|
||||
if e.ExtendedRcode() != 42&0xFFFFFFF0 {
|
||||
t.Errorf("set 42, expected %d, got %d", 42&0xFFFFFFF0, e.ExtendedRcode())
|
||||
}
|
||||
|
||||
// This will reset the 8 upper bits of the extended rcode
|
||||
e.SetExtendedRcode(RcodeNotAuth)
|
||||
if e.ExtendedRcode() != 0 {
|
||||
t.Errorf("Setting a non-extended rcode is expected to set extended rcode to 0, got: %d", e.ExtendedRcode())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEDNS0_SUBNETUnpack(t *testing.T) {
|
||||
for _, ip := range []net.IP{
|
||||
net.IPv4(0xde, 0xad, 0xbe, 0xef),
|
||||
net.ParseIP("192.0.2.1"),
|
||||
net.ParseIP("2001:db8::68"),
|
||||
} {
|
||||
var s1 EDNS0_SUBNET
|
||||
s1.Address = ip
|
||||
|
||||
if ip.To4() == nil {
|
||||
s1.Family = 2
|
||||
s1.SourceNetmask = net.IPv6len * 8
|
||||
} else {
|
||||
s1.Family = 1
|
||||
s1.SourceNetmask = net.IPv4len * 8
|
||||
}
|
||||
|
||||
b, err := s1.pack()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to pack: %v", err)
|
||||
}
|
||||
|
||||
var s2 EDNS0_SUBNET
|
||||
if err := s2.unpack(b); err != nil {
|
||||
t.Fatalf("failed to unpack: %v", err)
|
||||
}
|
||||
|
||||
if !ip.Equal(s2.Address) {
|
||||
t.Errorf("address different after unpacking; expected %s, got %s", ip, s2.Address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEDNS0_UL(t *testing.T) {
|
||||
cases := []struct {
|
||||
l uint32
|
||||
kl uint32
|
||||
}{
|
||||
{0x01234567, 0},
|
||||
{0x76543210, 0xFEDCBA98},
|
||||
}
|
||||
for _, c := range cases {
|
||||
expect := EDNS0_UL{EDNS0UL, c.l, c.kl}
|
||||
b, err := expect.pack()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to pack: %v", err)
|
||||
}
|
||||
actual := EDNS0_UL{EDNS0UL, ^uint32(0), ^uint32(0)}
|
||||
if err := actual.unpack(b); err != nil {
|
||||
t.Fatalf("failed to unpack: %v", err)
|
||||
}
|
||||
if expect != actual {
|
||||
t.Errorf("unpacked option is different; expected %v, got %v", expect, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestZ(t *testing.T) {
|
||||
e := &OPT{}
|
||||
e.Hdr.Name = "."
|
||||
e.Hdr.Rrtype = TypeOPT
|
||||
e.SetVersion(8)
|
||||
e.SetDo()
|
||||
if e.Z() != 0 {
|
||||
t.Errorf("expected Z of 0, got %d", e.Z())
|
||||
}
|
||||
e.SetZ(5)
|
||||
if e.Z() != 5 {
|
||||
t.Errorf("expected Z of 5, got %d", e.Z())
|
||||
}
|
||||
e.SetZ(0xFFFF)
|
||||
if e.Z() != 0x7FFF {
|
||||
t.Errorf("expected Z of 0x7FFFF, got %d", e.Z())
|
||||
}
|
||||
if e.Version() != 8 {
|
||||
t.Errorf("expected version to still be 8, got %d", e.Version())
|
||||
}
|
||||
if !e.Do() {
|
||||
t.Error("expected DO to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEDNS0_ESU(t *testing.T) {
|
||||
p := []byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x29, 0x04,
|
||||
0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||
0x04, 0x00, 0x24, 0x73, 0x69, 0x70, 0x3A, 0x2B,
|
||||
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
0x39, 0x40, 0x74, 0x65, 0x73, 0x74, 0x2E, 0x63,
|
||||
0x6F, 0x6D, 0x3B, 0x75, 0x73, 0x65, 0x72, 0x3D,
|
||||
0x63, 0x67, 0x72, 0x61, 0x74, 0x65, 0x73,
|
||||
}
|
||||
|
||||
m := new(Msg)
|
||||
if err := m.Unpack(p); err != nil {
|
||||
t.Fatalf("failed to unpack: %v", err)
|
||||
}
|
||||
opt := m.IsEdns0()
|
||||
if opt == nil {
|
||||
t.Fatalf("expected edns0 option")
|
||||
}
|
||||
if len(opt.Option) != 1 {
|
||||
t.Fatalf("expected only one option: %v", opt.Option)
|
||||
}
|
||||
edns0 := opt.Option[0]
|
||||
esu, ok := edns0.(*EDNS0_ESU)
|
||||
if !ok {
|
||||
t.Fatalf("expected option of type EDNS0_ESU, got %t", edns0)
|
||||
}
|
||||
expect := "sip:+123456789@test.com;user=cgrates"
|
||||
if esu.Uri != expect {
|
||||
t.Errorf("unpacked option is different; expected %v, got %v", expect, esu.Uri)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEDNS0_TCP_KEEPALIVE_unpack(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
b []byte
|
||||
expected uint16
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
b: []byte{},
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "timeout 1",
|
||||
b: []byte{0, 1},
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
b: []byte{0, 1, 3},
|
||||
expectedErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
e := &EDNS0_TCP_KEEPALIVE{}
|
||||
err := e.unpack(tc.b)
|
||||
if err != nil && !tc.expectedErr {
|
||||
t.Error("failed to unpack, expected no error")
|
||||
}
|
||||
if err == nil && tc.expectedErr {
|
||||
t.Error("unpacked, but expected an error")
|
||||
}
|
||||
if e.Timeout != tc.expected {
|
||||
t.Errorf("invalid timeout, actual: %d, expected: %d", e.Timeout, tc.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEDNS0_TCP_KEEPALIVE_pack(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
edns *EDNS0_TCP_KEEPALIVE
|
||||
expected []byte
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
edns: &EDNS0_TCP_KEEPALIVE{
|
||||
Code: EDNS0TCPKEEPALIVE,
|
||||
Timeout: 0,
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "timeout 1",
|
||||
edns: &EDNS0_TCP_KEEPALIVE{
|
||||
Code: EDNS0TCPKEEPALIVE,
|
||||
Timeout: 1,
|
||||
},
|
||||
expected: []byte{0, 1},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
b, err := tc.edns.pack()
|
||||
if err != nil {
|
||||
t.Error("expected no error")
|
||||
}
|
||||
|
||||
if tc.expected == nil && b != nil {
|
||||
t.Errorf("invalid result, expected nil")
|
||||
}
|
||||
|
||||
res := bytes.Compare(b, tc.expected)
|
||||
if res != 0 {
|
||||
t.Errorf("invalid result, expected: %v, actual: %v", tc.expected, b)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -20,7 +20,7 @@ func Field(r RR, i int) string {
|
|||
return ""
|
||||
}
|
||||
d := reflect.ValueOf(r).Elem().Field(i)
|
||||
switch k := d.Kind(); k {
|
||||
switch d.Kind() {
|
||||
case reflect.String:
|
||||
return d.String()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
|
@ -31,6 +31,9 @@ func Field(r RR, i int) string {
|
|||
switch reflect.ValueOf(r).Elem().Type().Field(i).Tag {
|
||||
case `dns:"a"`:
|
||||
// TODO(miek): Hmm store this as 16 bytes
|
||||
if d.Len() < net.IPv4len {
|
||||
return ""
|
||||
}
|
||||
if d.Len() < net.IPv6len {
|
||||
return net.IPv4(byte(d.Index(0).Uint()),
|
||||
byte(d.Index(1).Uint()),
|
||||
|
@ -42,6 +45,9 @@ func Field(r RR, i int) string {
|
|||
byte(d.Index(14).Uint()),
|
||||
byte(d.Index(15).Uint())).String()
|
||||
case `dns:"aaaa"`:
|
||||
if d.Len() < net.IPv6len {
|
||||
return ""
|
||||
}
|
||||
return net.IP{
|
||||
byte(d.Index(0).Uint()),
|
||||
byte(d.Index(1).Uint()),
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFieldEmptyAOrAAAAData(t *testing.T) {
|
||||
res := Field(new(A), 1)
|
||||
if res != "" {
|
||||
t.Errorf("expected empty string but got %v", res)
|
||||
}
|
||||
res = Field(new(AAAA), 1)
|
||||
if res != "" {
|
||||
t.Errorf("expected empty string but got %v", res)
|
||||
}
|
||||
}
|
12
fuzz.go
12
fuzz.go
|
@ -1,7 +1,10 @@
|
|||
//go:build fuzz
|
||||
// +build fuzz
|
||||
|
||||
package dns
|
||||
|
||||
import "strings"
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
msg := new(Msg)
|
||||
|
||||
|
@ -16,7 +19,14 @@ func Fuzz(data []byte) int {
|
|||
}
|
||||
|
||||
func FuzzNewRR(data []byte) int {
|
||||
if _, err := NewRR(string(data)); err != nil {
|
||||
str := string(data)
|
||||
// Do not fuzz lines that include the $INCLUDE keyword and hint the fuzzer
|
||||
// at avoiding them.
|
||||
// See GH#1025 for context.
|
||||
if strings.Contains(strings.ToUpper(str), "$INCLUDE") {
|
||||
return -1
|
||||
}
|
||||
if _, err := NewRR(str); err != nil {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestPackDataOpt tests generated using fuzz.go and with a message pack
|
||||
// containing the following bytes:
|
||||
// "0000\x00\x00000000\x00\x00/00000" +
|
||||
// "0\x00\v\x00#\b00000000\x00\x00)000" +
|
||||
// "000\x00\x1c00\x00\x0000\x00\x01000\x00\x00\x00\b" +
|
||||
// "\x00\v\x00\x02\x0000000000"
|
||||
// That bytes sequence created the overflow error.
|
||||
func TestPackDataOpt(t *testing.T) {
|
||||
type args struct {
|
||||
option []EDNS0
|
||||
msg []byte
|
||||
off int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "overflow",
|
||||
args: args{
|
||||
option: []EDNS0{
|
||||
&EDNS0_LOCAL{Code: 0x3030, Data: []uint8{}},
|
||||
&EDNS0_LOCAL{Code: 0x3030, Data: []uint8{0x30}},
|
||||
&EDNS0_LOCAL{Code: 0x3030, Data: []uint8{}},
|
||||
&EDNS0_SUBNET{
|
||||
Code: 0x0, Family: 0x2,
|
||||
SourceNetmask: 0x0, SourceScope: 0x30,
|
||||
Address: net.IP{0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
|
||||
},
|
||||
msg: []byte{
|
||||
0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x2,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x30,
|
||||
0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x0b, 0x00,
|
||||
0x23, 0x08, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||
0x30, 0x30, 0x00, 0x00, 0x29, 0x30, 0x30, 0x30,
|
||||
0x30, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00,
|
||||
0x00, 0x30, 0x30, 0x00, 0x01, 0x30, 0x00, 0x00,
|
||||
0x00,
|
||||
},
|
||||
off: 54,
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "dns: overflow packing opt",
|
||||
want: 57,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := packDataOpt(tt.args.option, tt.args.msg, tt.args.off)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("packDataOpt() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if err != nil && tt.wantErrMsg != err.Error() {
|
||||
t.Errorf("packDataOpt() error msg = %v, wantErrMsg %v", err.Error(), tt.wantErrMsg)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("packDataOpt() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCrashNSEC tests generated using fuzz.go and with a message pack
|
||||
// containing the following bytes:
|
||||
// "0000\x00\x00000000\x00\x00/00000" +
|
||||
// "0\x00\v\x00#\b00000\x00\x00\x00\x00\x00\x1a000" +
|
||||
// "000\x00\x00\x00\x00\x1a000000\x00\x00\x00\x00\x1a0" +
|
||||
// "00000\x00\v00\a0000000\x00"
|
||||
// That byte sequence, when Unpack() and subsequent Pack() created a
|
||||
// panic: runtime error: slice bounds out of range
|
||||
// which was attributed to the fact that NSEC RR length computation was different (and smaller)
|
||||
// then when within packDataNsec.
|
||||
func TestCrashNSEC(t *testing.T) {
|
||||
compression := make(map[string]struct{})
|
||||
nsec := &NSEC{
|
||||
Hdr: RR_Header{
|
||||
Name: ".",
|
||||
Rrtype: 0x2f,
|
||||
Class: 0x3030,
|
||||
Ttl: 0x30303030,
|
||||
Rdlength: 0xb,
|
||||
},
|
||||
NextDomain: ".",
|
||||
TypeBitMap: []uint16{
|
||||
0x2302, 0x2303, 0x230a, 0x230b,
|
||||
0x2312, 0x2313, 0x231a, 0x231b,
|
||||
0x2322, 0x2323,
|
||||
},
|
||||
}
|
||||
expectedLength := 19
|
||||
l := nsec.len(0, compression)
|
||||
if l != expectedLength {
|
||||
t.Fatalf("expected length of %d, got %d", expectedLength, l)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCrashNSEC3 tests generated using fuzz.go and with a message pack
|
||||
// containing the following bytes:
|
||||
// "0000\x00\x00000000\x00\x00200000" +
|
||||
// "0\x00\v0000\x00\x00#\x0300\x00\x00\x00\x1a000" +
|
||||
// "000\x00\v00\x0200\x00\x03000\x00"
|
||||
// That byte sequence, when Unpack() and subsequent Pack() created a
|
||||
// panic: runtime error: slice bounds out of range
|
||||
// which was attributed to the fact that NSEC3 RR length computation was
|
||||
// different (and smaller) then within NSEC3.pack (which relies on
|
||||
// packDataNsec).
|
||||
func TestCrashNSEC3(t *testing.T) {
|
||||
compression := make(map[string]struct{})
|
||||
nsec3 := &NSEC3{
|
||||
Hdr: RR_Header{
|
||||
Name: ".",
|
||||
Rrtype: 0x32,
|
||||
Class: 0x3030,
|
||||
Ttl: 0x30303030,
|
||||
Rdlength: 0xb,
|
||||
},
|
||||
Hash: 0x30,
|
||||
Flags: 0x30,
|
||||
Iterations: 0x3030,
|
||||
SaltLength: 0x0,
|
||||
Salt: "",
|
||||
HashLength: 0x0,
|
||||
NextDomain: ".",
|
||||
TypeBitMap: []uint16{
|
||||
0x2302, 0x2303, 0x230a, 0x230b,
|
||||
},
|
||||
}
|
||||
expectedLength := 24
|
||||
l := nsec3.len(0, compression)
|
||||
if l != expectedLength {
|
||||
t.Fatalf("expected length of %d, got %d", expectedLength, l)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewRRCommentLengthCrasherString test inputs to NewRR that generated crashes.
|
||||
func TestNewRRCommentLengthCrasherString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
err string
|
||||
}{
|
||||
|
||||
{
|
||||
"HINFO1", " HINFO ;;;;;;;;;;;;;" +
|
||||
";;;;;;;;\x00\x19;;;;;;;;;;" +
|
||||
";\u007f;;;;;;;;;;;;;;;;;;" +
|
||||
";;}mP_Qq_3sJ_1_84X_5" +
|
||||
"45iW_3K4p8J8_v9_LT3_" +
|
||||
"6_0l_3D4VT3xq6N_3K__" +
|
||||
"_U_xX2m;;;;;;(;;;;;;" +
|
||||
";;;;;;;;;;;;;;;\x1d;;;;" +
|
||||
";;;;;;-0x804dBDe8ba " +
|
||||
"\t \t\tr HINFO \" \t\t\tve" +
|
||||
"k1xH11e__P6_dk1_51bo" +
|
||||
"g8gJK1V_O_v84_Bw4_1_" +
|
||||
"72jQ3_0J3V_S5iYn4h5X" +
|
||||
"R_2n___51J nN_ \t\tm " +
|
||||
"aa_XO4_5\t \t\t \t\tg6b" +
|
||||
"p_KI_1_YWc_K8c2b___A" +
|
||||
"e_Y1m__4Y_R_avy6t08x" +
|
||||
"b5Cp9_7uS_yLa\t\t\t d " +
|
||||
"EKe1Q83vS___ a \t\t " +
|
||||
"\tmP_Qq_3sJ_1_84X_545" +
|
||||
"iW_3K4p8J8_v9_LT3_6_" +
|
||||
"0l_3D4VT3xq6N_3K___U" +
|
||||
"_xX2\"\" \t \t_fL Ogl5" +
|
||||
"_09i_9__3O7C__QMAG2U" +
|
||||
"35IO8RRU6aJ9_6_57_6_" +
|
||||
"b05BMoX5I__4833_____" +
|
||||
"yfD_2_OPs__sqzM_pqQi" +
|
||||
"_\t\t \tN__GuY4_Trath_0" +
|
||||
"yy___cAK_a__0J0q5 L_" +
|
||||
"p63Fzdva_Lb_29V7_R__" +
|
||||
"Go_H2_8m_4__FJM5B_Y5" +
|
||||
"Slw_ghp_55l_X2_Pnt6Y" +
|
||||
"_Wd_hM7jRZ_\t\t \tm \t" +
|
||||
" \t\ta md rK \x00 7_\"sr " +
|
||||
"- sg o -0x804dBDe8b" +
|
||||
"a \t \t\tN_W6J3PBS_W__C" +
|
||||
"yJu__k6F_jY0INI_LC27" +
|
||||
"7x14b_1b___Y8f_K_3y_" +
|
||||
"0055yaP_LKu_72g_T_32" +
|
||||
"iBk1Zm_o 9i1P44_S0_" +
|
||||
"_4AXUpo2__H55tL_g78_" +
|
||||
"8V_8l0yg6bp_KI_1_YWc" +
|
||||
"_K8c2b \t \tmaa_XO4_5" +
|
||||
"rg6bp_KI_1_YWc_K8c2b" +
|
||||
" _C20w i_4 \t\t u_k d" +
|
||||
" rKsg09099 \"\"2335779" +
|
||||
"05047986112651e025 \t" +
|
||||
" \t\tN_W6J3PBS_W__CyJu" +
|
||||
"__k6F_jY0INI_LC277x1" +
|
||||
"4b_1b___Y8f_K_3y_005" +
|
||||
"5yaP_LKu_72g_T_32iBk" +
|
||||
"1Zm_o 9i1P44_S0__4A" +
|
||||
"XUpo2__H55tL_g78_8V_" +
|
||||
"8l0y_9K9_C__6af__wj_" +
|
||||
"UbSYy_ge29S_s_Qe259q" +
|
||||
"_kGod \t\t\t\t :0xb1AF1F" +
|
||||
"b71D2ACeaB3FEce2ssg " +
|
||||
"o dr-0x804dBDe8ba \t " +
|
||||
"\t\t$ Y5 _BzOc6S_Lk0K" +
|
||||
"y43j1TzV__9367tbX56_" +
|
||||
"6B3__q6_v8_4_0_t_2q_" +
|
||||
"nJ2gV3j9_tkOrx_H__a}" +
|
||||
"mT 0g6bp_KI_1_YWc_K8" +
|
||||
"c2b\t_ a\t \t54KM8f9_63" +
|
||||
"zJ2Q_c1_C_Zf4ICF4m0q" +
|
||||
"_RVm_3Zh4vr7yI_H2 a" +
|
||||
" m 0yq__TiqA_FQBv_SS" +
|
||||
"_Hm_8T8__M8F2_53TTo_" +
|
||||
"k_o2__u_W6Vr__524q9l" +
|
||||
"9CQsC_kOU___g_94 \"" +
|
||||
" ~a_j_16_6iUSu_96V1W" +
|
||||
"5r01j____gn157__8_LO" +
|
||||
"0y_08Jr6OR__WF8__JK_" +
|
||||
"N_wx_k_CGB_SjJ9R74i_" +
|
||||
"7_1t_6 m NULLNULLNUL" +
|
||||
"L \t \t\t\t drK\t\x00 7_\"\" 5" +
|
||||
"_5_y732S43__D_8U9FX2" +
|
||||
"27_k\t\tg6bp_KI_1_YWc_" +
|
||||
"K8c2b_J_wx8yw1CMw27j" +
|
||||
"___f_a8uw_ Er9gB_L2 " +
|
||||
"\t\t \t\t\tm aa_XO4_5 Y_" +
|
||||
" I_T7762_zlMi_n8_FjH" +
|
||||
"vy62p__M4S_8__r092af" +
|
||||
"P_T_vhp6__SA_jVF13c5" +
|
||||
"2__8J48K__S4YcjoY91X" +
|
||||
"_iNf06 am aa_XO4_5\t" +
|
||||
" d _ am_SYY4G__2h4QL" +
|
||||
"iUIDd \t\t \tXXp__KFjR" +
|
||||
"V__JU3o\"\" d \t_Iks_ " +
|
||||
"aa_XO4_5<g6bp_KI_1_Y" +
|
||||
"Wc_K8c2b _BzOc6S_Lk0" +
|
||||
"Ky43j1TzV__9367tbX56" +
|
||||
"_6B3__q6_v8_4_0_t_2q" +
|
||||
"_nJ2gV3j9_tkOrx_H__ " +
|
||||
"a\t_Iks_ \\ ma 0_58_r1" +
|
||||
"y8jib_FaV_C_e \t \td\"\"" +
|
||||
" ^Dy_0 \t\t \t ;;;;;;;" +
|
||||
";;;;;;;;;;;",
|
||||
`dns: bad HINFO Fields: "comment length insufficient for parsing" at line: 1:1951`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := NewRR(tc.in)
|
||||
if err == nil {
|
||||
t.Errorf("Expecting error for crasher line %s", tc.in)
|
||||
}
|
||||
if tc.err != err.Error() {
|
||||
t.Errorf("Expecting error %s, got %s", tc.err, err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
332
generate.go
332
generate.go
|
@ -2,8 +2,8 @@ package dns
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
@ -18,142 +18,230 @@ import (
|
|||
// * rhs (rdata)
|
||||
// But we are lazy here, only the range is parsed *all* occurrences
|
||||
// of $ after that are interpreted.
|
||||
// Any error are returned as a string value, the empty string signals
|
||||
// "no error".
|
||||
func generate(l lex, c chan lex, t chan *Token, o string) string {
|
||||
step := 1
|
||||
if i := strings.IndexAny(l.token, "/"); i != -1 {
|
||||
if i+1 == len(l.token) {
|
||||
return "bad step in $GENERATE range"
|
||||
func (zp *ZoneParser) generate(l lex) (RR, bool) {
|
||||
token := l.token
|
||||
step := int64(1)
|
||||
if i := strings.IndexByte(token, '/'); i >= 0 {
|
||||
if i+1 == len(token) {
|
||||
return zp.setParseError("bad step in $GENERATE range", l)
|
||||
}
|
||||
if s, err := strconv.Atoi(l.token[i+1:]); err == nil {
|
||||
if s < 0 {
|
||||
return "bad step in $GENERATE range"
|
||||
}
|
||||
step = s
|
||||
} else {
|
||||
return "bad step in $GENERATE range"
|
||||
|
||||
s, err := strconv.ParseInt(token[i+1:], 10, 64)
|
||||
if err != nil || s <= 0 {
|
||||
return zp.setParseError("bad step in $GENERATE range", l)
|
||||
}
|
||||
l.token = l.token[:i]
|
||||
|
||||
step = s
|
||||
token = token[:i]
|
||||
}
|
||||
sx := strings.SplitN(l.token, "-", 2)
|
||||
|
||||
sx := strings.SplitN(token, "-", 2)
|
||||
if len(sx) != 2 {
|
||||
return "bad start-stop in $GENERATE range"
|
||||
}
|
||||
start, err := strconv.Atoi(sx[0])
|
||||
if err != nil {
|
||||
return "bad start in $GENERATE range"
|
||||
}
|
||||
end, err := strconv.Atoi(sx[1])
|
||||
if err != nil {
|
||||
return "bad stop in $GENERATE range"
|
||||
}
|
||||
if end < 0 || start < 0 || end < start {
|
||||
return "bad range in $GENERATE range"
|
||||
return zp.setParseError("bad start-stop in $GENERATE range", l)
|
||||
}
|
||||
|
||||
start, err := strconv.ParseInt(sx[0], 10, 64)
|
||||
if err != nil {
|
||||
return zp.setParseError("bad start in $GENERATE range", l)
|
||||
}
|
||||
|
||||
end, err := strconv.ParseInt(sx[1], 10, 64)
|
||||
if err != nil {
|
||||
return zp.setParseError("bad stop in $GENERATE range", l)
|
||||
}
|
||||
if end < 0 || start < 0 || end < start || (end-start)/step > 65535 {
|
||||
return zp.setParseError("bad range in $GENERATE range", l)
|
||||
}
|
||||
|
||||
// _BLANK
|
||||
l, ok := zp.c.Next()
|
||||
if !ok || l.value != zBlank {
|
||||
return zp.setParseError("garbage after $GENERATE range", l)
|
||||
}
|
||||
|
||||
<-c // _BLANK
|
||||
// Create a complete new string, which we then parse again.
|
||||
s := ""
|
||||
BuildRR:
|
||||
l = <-c
|
||||
if l.value != zNewline && l.value != zEOF {
|
||||
s += l.token
|
||||
goto BuildRR
|
||||
}
|
||||
for i := start; i <= end; i += step {
|
||||
var (
|
||||
escape bool
|
||||
dom bytes.Buffer
|
||||
mod string
|
||||
err error
|
||||
offset int
|
||||
)
|
||||
var s string
|
||||
for l, ok := zp.c.Next(); ok; l, ok = zp.c.Next() {
|
||||
if l.err {
|
||||
return zp.setParseError("bad data in $GENERATE directive", l)
|
||||
}
|
||||
if l.value == zNewline {
|
||||
break
|
||||
}
|
||||
|
||||
for j := 0; j < len(s); j++ { // No 'range' because we need to jump around
|
||||
switch s[j] {
|
||||
case '\\':
|
||||
if escape {
|
||||
dom.WriteByte('\\')
|
||||
escape = false
|
||||
continue
|
||||
}
|
||||
escape = true
|
||||
case '$':
|
||||
mod = "%d"
|
||||
offset = 0
|
||||
if escape {
|
||||
dom.WriteByte('$')
|
||||
escape = false
|
||||
continue
|
||||
}
|
||||
escape = false
|
||||
if j+1 >= len(s) { // End of the string
|
||||
dom.WriteString(fmt.Sprintf(mod, i+offset))
|
||||
continue
|
||||
} else {
|
||||
if s[j+1] == '$' {
|
||||
dom.WriteByte('$')
|
||||
j++
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Search for { and }
|
||||
if s[j+1] == '{' { // Modifier block
|
||||
sep := strings.Index(s[j+2:], "}")
|
||||
if sep == -1 {
|
||||
return "bad modifier in $GENERATE"
|
||||
}
|
||||
mod, offset, err = modToPrintf(s[j+2 : j+2+sep])
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
j += 2 + sep // Jump to it
|
||||
}
|
||||
dom.WriteString(fmt.Sprintf(mod, i+offset))
|
||||
default:
|
||||
if escape { // Pretty useless here
|
||||
escape = false
|
||||
continue
|
||||
}
|
||||
dom.WriteByte(s[j])
|
||||
}
|
||||
}
|
||||
// Re-parse the RR and send it on the current channel t
|
||||
rx, err := NewRR("$ORIGIN " + o + "\n" + dom.String())
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
t <- &Token{RR: rx}
|
||||
// Its more efficient to first built the rrlist and then parse it in
|
||||
// one go! But is this a problem?
|
||||
s += l.token
|
||||
}
|
||||
|
||||
r := &generateReader{
|
||||
s: s,
|
||||
|
||||
cur: start,
|
||||
start: start,
|
||||
end: end,
|
||||
step: step,
|
||||
|
||||
file: zp.file,
|
||||
lex: &l,
|
||||
}
|
||||
zp.sub = NewZoneParser(r, zp.origin, zp.file)
|
||||
zp.sub.includeDepth, zp.sub.includeAllowed = zp.includeDepth, zp.includeAllowed
|
||||
zp.sub.generateDisallowed = true
|
||||
zp.sub.SetDefaultTTL(defaultTtl)
|
||||
return zp.subNext()
|
||||
}
|
||||
|
||||
type generateReader struct {
|
||||
s string
|
||||
si int
|
||||
|
||||
cur int64
|
||||
start int64
|
||||
end int64
|
||||
step int64
|
||||
|
||||
mod bytes.Buffer
|
||||
|
||||
escape bool
|
||||
|
||||
eof bool
|
||||
|
||||
file string
|
||||
lex *lex
|
||||
}
|
||||
|
||||
func (r *generateReader) parseError(msg string, end int) *ParseError {
|
||||
r.eof = true // Make errors sticky.
|
||||
|
||||
l := *r.lex
|
||||
l.token = r.s[r.si-1 : end]
|
||||
l.column += r.si // l.column starts one zBLANK before r.s
|
||||
|
||||
return &ParseError{r.file, msg, l}
|
||||
}
|
||||
|
||||
func (r *generateReader) Read(p []byte) (int, error) {
|
||||
// NewZLexer, through NewZoneParser, should use ReadByte and
|
||||
// not end up here.
|
||||
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (r *generateReader) ReadByte() (byte, error) {
|
||||
if r.eof {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if r.mod.Len() > 0 {
|
||||
return r.mod.ReadByte()
|
||||
}
|
||||
|
||||
if r.si >= len(r.s) {
|
||||
r.si = 0
|
||||
r.cur += r.step
|
||||
|
||||
r.eof = r.cur > r.end || r.cur < 0
|
||||
return '\n', nil
|
||||
}
|
||||
|
||||
si := r.si
|
||||
r.si++
|
||||
|
||||
switch r.s[si] {
|
||||
case '\\':
|
||||
if r.escape {
|
||||
r.escape = false
|
||||
return '\\', nil
|
||||
}
|
||||
|
||||
r.escape = true
|
||||
return r.ReadByte()
|
||||
case '$':
|
||||
if r.escape {
|
||||
r.escape = false
|
||||
return '$', nil
|
||||
}
|
||||
|
||||
mod := "%d"
|
||||
|
||||
if si >= len(r.s)-1 {
|
||||
// End of the string
|
||||
fmt.Fprintf(&r.mod, mod, r.cur)
|
||||
return r.mod.ReadByte()
|
||||
}
|
||||
|
||||
if r.s[si+1] == '$' {
|
||||
r.si++
|
||||
return '$', nil
|
||||
}
|
||||
|
||||
var offset int64
|
||||
|
||||
// Search for { and }
|
||||
if r.s[si+1] == '{' {
|
||||
// Modifier block
|
||||
sep := strings.Index(r.s[si+2:], "}")
|
||||
if sep < 0 {
|
||||
return 0, r.parseError("bad modifier in $GENERATE", len(r.s))
|
||||
}
|
||||
|
||||
var errMsg string
|
||||
mod, offset, errMsg = modToPrintf(r.s[si+2 : si+2+sep])
|
||||
if errMsg != "" {
|
||||
return 0, r.parseError(errMsg, si+3+sep)
|
||||
}
|
||||
if r.start+offset < 0 || r.end+offset > 1<<31-1 {
|
||||
return 0, r.parseError("bad offset in $GENERATE", si+3+sep)
|
||||
}
|
||||
|
||||
r.si += 2 + sep // Jump to it
|
||||
}
|
||||
|
||||
fmt.Fprintf(&r.mod, mod, r.cur+offset)
|
||||
return r.mod.ReadByte()
|
||||
default:
|
||||
if r.escape { // Pretty useless here
|
||||
r.escape = false
|
||||
return r.ReadByte()
|
||||
}
|
||||
|
||||
return r.s[si], nil
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
|
||||
func modToPrintf(s string) (string, int, error) {
|
||||
xs := strings.SplitN(s, ",", 3)
|
||||
if len(xs) != 3 {
|
||||
return "", 0, errors.New("bad modifier in $GENERATE")
|
||||
func modToPrintf(s string) (string, int64, string) {
|
||||
// Modifier is { offset [ ,width [ ,base ] ] } - provide default
|
||||
// values for optional width and type, if necessary.
|
||||
var offStr, widthStr, base string
|
||||
switch xs := strings.Split(s, ","); len(xs) {
|
||||
case 1:
|
||||
offStr, widthStr, base = xs[0], "0", "d"
|
||||
case 2:
|
||||
offStr, widthStr, base = xs[0], xs[1], "d"
|
||||
case 3:
|
||||
offStr, widthStr, base = xs[0], xs[1], xs[2]
|
||||
default:
|
||||
return "", 0, "bad modifier in $GENERATE"
|
||||
}
|
||||
// xs[0] is offset, xs[1] is width, xs[2] is base
|
||||
if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" {
|
||||
return "", 0, errors.New("bad base in $GENERATE")
|
||||
|
||||
switch base {
|
||||
case "o", "d", "x", "X":
|
||||
default:
|
||||
return "", 0, "bad base in $GENERATE"
|
||||
}
|
||||
offset, err := strconv.Atoi(xs[0])
|
||||
if err != nil || offset > 255 {
|
||||
return "", 0, errors.New("bad offset in $GENERATE")
|
||||
|
||||
offset, err := strconv.ParseInt(offStr, 10, 64)
|
||||
if err != nil {
|
||||
return "", 0, "bad offset in $GENERATE"
|
||||
}
|
||||
width, err := strconv.Atoi(xs[1])
|
||||
if err != nil || width > 255 {
|
||||
return "", offset, errors.New("bad width in $GENERATE")
|
||||
|
||||
width, err := strconv.ParseInt(widthStr, 10, 64)
|
||||
if err != nil || width < 0 || width > 255 {
|
||||
return "", 0, "bad width in $GENERATE"
|
||||
}
|
||||
switch {
|
||||
case width < 0:
|
||||
return "", offset, errors.New("bad width in $GENERATE")
|
||||
case width == 0:
|
||||
return "%" + xs[1] + xs[2], offset, nil
|
||||
|
||||
if width == 0 {
|
||||
return "%" + base, offset, ""
|
||||
}
|
||||
return "%0" + xs[1] + xs[2], offset, nil
|
||||
|
||||
return "%0" + widthStr + base, offset, ""
|
||||
}
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateRangeGuard(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "dns")
|
||||
if err != nil {
|
||||
t.Fatalf("could not create tmpdir for test: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
for i := 0; i <= 1; i++ {
|
||||
path := filepath.Join(tmpdir, fmt.Sprintf("%04d.conf", i))
|
||||
data := []byte(fmt.Sprintf("dhcp-%04d A 10.0.0.%d", i, i))
|
||||
|
||||
if err := ioutil.WriteFile(path, data, 0644); err != nil {
|
||||
t.Fatalf("could not create tmpfile for test: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var tests = [...]struct {
|
||||
zone string
|
||||
fail bool
|
||||
}{
|
||||
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-1 dhcp-${0,4,d} A 10.0.0.$
|
||||
`, false},
|
||||
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-1 dhcp-${0,0,x} A 10.0.0.$
|
||||
`, false},
|
||||
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 128-129 dhcp-${-128,4,d} A 10.0.0.$
|
||||
`, false},
|
||||
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 128-129 dhcp-${-129,4,d} A 10.0.0.$
|
||||
`, true},
|
||||
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-2 dhcp-${2147483647,4,d} A 10.0.0.$
|
||||
`, true},
|
||||
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-1 dhcp-${2147483646,4,d} A 10.0.0.$
|
||||
`, false},
|
||||
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-1/step dhcp-${0,4,d} A 10.0.0.$
|
||||
`, true},
|
||||
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-1/ dhcp-${0,4,d} A 10.0.0.$
|
||||
`, true},
|
||||
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-10/2 dhcp-${0,4,d} A 10.0.0.$
|
||||
`, false},
|
||||
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-1/0 dhcp-${0,4,d} A 10.0.0.$
|
||||
`, true},
|
||||
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-1 $$INCLUDE ` + tmpdir + string(filepath.Separator) + `${0,4,d}.conf
|
||||
`, false},
|
||||
{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-1 dhcp-${0,4,d} A 10.0.0.$
|
||||
$GENERATE 0-2 dhcp-${0,4,d} A 10.1.0.$
|
||||
`, false},
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
z := NewZoneParser(strings.NewReader(tests[i].zone), "test.", "test")
|
||||
z.SetIncludeAllowed(true)
|
||||
|
||||
for _, ok := z.Next(); ok; _, ok = z.Next() {
|
||||
}
|
||||
|
||||
err := z.Err()
|
||||
if err != nil && !tests[i].fail {
|
||||
t.Errorf("expected \n\n%s\nto be parsed, but got %v", tests[i].zone, err)
|
||||
} else if err == nil && tests[i].fail {
|
||||
t.Errorf("expected \n\n%s\nto fail, but got no error", tests[i].zone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateIncludeDepth(t *testing.T) {
|
||||
tmpfile, err := ioutil.TempFile("", "dns")
|
||||
if err != nil {
|
||||
t.Fatalf("could not create tmpfile for test: %v", err)
|
||||
}
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
zone := `@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-1 $$INCLUDE ` + tmpfile.Name() + `
|
||||
`
|
||||
if _, err := tmpfile.WriteString(zone); err != nil {
|
||||
t.Fatalf("could not write to tmpfile for test: %v", err)
|
||||
}
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
t.Fatalf("could not close tmpfile for test: %v", err)
|
||||
}
|
||||
|
||||
zp := NewZoneParser(strings.NewReader(zone), ".", tmpfile.Name())
|
||||
zp.SetIncludeAllowed(true)
|
||||
|
||||
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
|
||||
}
|
||||
|
||||
const expected = "too deeply nested $INCLUDE"
|
||||
if err := zp.Err(); err == nil || !strings.Contains(err.Error(), expected) {
|
||||
t.Errorf("expected error to include %q, got %v", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateIncludeDisallowed(t *testing.T) {
|
||||
const zone = `@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-1 $$INCLUDE test.conf
|
||||
`
|
||||
zp := NewZoneParser(strings.NewReader(zone), ".", "")
|
||||
|
||||
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
|
||||
}
|
||||
|
||||
const expected = "$INCLUDE directive not allowed"
|
||||
if err := zp.Err(); err == nil || !strings.Contains(err.Error(), expected) {
|
||||
t.Errorf("expected error to include %q, got %v", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateSurfacesErrors(t *testing.T) {
|
||||
const zone = `@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-1 dhcp-${0,4,dd} A 10.0.0.$
|
||||
`
|
||||
zp := NewZoneParser(strings.NewReader(zone), ".", "test")
|
||||
|
||||
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
|
||||
}
|
||||
|
||||
const expected = `test: dns: bad base in $GENERATE: "${0,4,dd}" at line: 2:20`
|
||||
if err := zp.Err(); err == nil || err.Error() != expected {
|
||||
t.Errorf("expected specific error, wanted %q, got %v", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateSurfacesLexerErrors(t *testing.T) {
|
||||
const zone = `@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 0-1 dhcp-${0,4,d} A 10.0.0.$ )
|
||||
`
|
||||
zp := NewZoneParser(strings.NewReader(zone), ".", "test")
|
||||
|
||||
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
|
||||
}
|
||||
|
||||
const expected = `test: dns: bad data in $GENERATE directive: "extra closing brace" at line: 2:40`
|
||||
if err := zp.Err(); err == nil || err.Error() != expected {
|
||||
t.Errorf("expected specific error, wanted %q, got %v", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateModToPrintf(t *testing.T) {
|
||||
tests := []struct {
|
||||
mod string
|
||||
wantFmt string
|
||||
wantOffset int64
|
||||
wantErr bool
|
||||
}{
|
||||
{"0,0,d", "%d", 0, false},
|
||||
{"0,0", "%d", 0, false},
|
||||
{"0", "%d", 0, false},
|
||||
{"3,2,d", "%02d", 3, false},
|
||||
{"3,2", "%02d", 3, false},
|
||||
{"3", "%d", 3, false},
|
||||
{"0,0,o", "%o", 0, false},
|
||||
{"0,0,x", "%x", 0, false},
|
||||
{"0,0,X", "%X", 0, false},
|
||||
{"0,0,z", "", 0, true},
|
||||
{"0,0,0,d", "", 0, true},
|
||||
{"-100,0,d", "%d", -100, false},
|
||||
}
|
||||
for _, test := range tests {
|
||||
gotFmt, gotOffset, errMsg := modToPrintf(test.mod)
|
||||
switch {
|
||||
case errMsg != "" && !test.wantErr:
|
||||
t.Errorf("modToPrintf(%q) - expected empty-error, but got %v", test.mod, errMsg)
|
||||
case errMsg == "" && test.wantErr:
|
||||
t.Errorf("modToPrintf(%q) - expected error, but got empty-error", test.mod)
|
||||
case gotFmt != test.wantFmt:
|
||||
t.Errorf("modToPrintf(%q) - expected format %q, but got %q", test.mod, test.wantFmt, gotFmt)
|
||||
case gotOffset != test.wantOffset:
|
||||
t.Errorf("modToPrintf(%q) - expected offset %d, but got %d", test.mod, test.wantOffset, gotOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGenerate(b *testing.B) {
|
||||
const zone = `@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d )
|
||||
$GENERATE 32-158 dhcp-${-32,4,d} A 10.0.0.$
|
||||
`
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
zp := NewZoneParser(strings.NewReader(zone), ".", "")
|
||||
|
||||
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
|
||||
}
|
||||
|
||||
if err := zp.Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrasherString(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
err string
|
||||
}{
|
||||
{"$GENERATE 0-300103\"$$GENERATE 2-2", "bad range in $GENERATE"},
|
||||
{"$GENERATE 0-5414137360", "bad range in $GENERATE"},
|
||||
{"$GENERATE 11522-3668518066406258", "bad range in $GENERATE"},
|
||||
{"$GENERATE 0-200\"(;00000000000000\n$$GENERATE 0-0", "dns: garbage after $GENERATE range: \"\\\"\" at line: 1:16"},
|
||||
{"$GENERATE 6-2048 $$GENERATE 6-036160 $$$$ORIGIN \\$", `dns: nested $GENERATE directive not allowed: "6-036160" at line: 1:19`},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.in, func(t *testing.T) {
|
||||
_, err := NewRR(tc.in)
|
||||
if err == nil {
|
||||
t.Errorf("Expecting error for crasher line %s", tc.in)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.err) {
|
||||
t.Errorf("Expecting error %s, got %s", tc.err, err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
module github.com/miekg/dns
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2
|
||||
)
|
|
@ -0,0 +1,33 @@
|
|||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
@ -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
|
||||
}
|
|
@ -7,24 +7,6 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestTCPRtt(t *testing.T) {
|
||||
m := new(Msg)
|
||||
m.RecursionDesired = true
|
||||
m.SetQuestion("example.org.", TypeA)
|
||||
|
||||
c := &Client{}
|
||||
for _, proto := range []string{"udp", "tcp"} {
|
||||
c.Net = proto
|
||||
_, rtt, err := c.Exchange(m, "8.8.4.4:53")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rtt == 0 {
|
||||
t.Fatalf("expecting non zero rtt %s, got zero", c.Net)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNSEC3MissingSalt(t *testing.T) {
|
||||
rr := testRR("ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. NSEC3 1 1 12 aabbccdd K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H")
|
||||
m := new(Msg)
|
||||
|
|
79
labels.go
79
labels.go
|
@ -10,13 +10,13 @@ package dns
|
|||
// escaped dots (\.) for instance.
|
||||
// s must be a syntactically valid domain name, see IsDomainName.
|
||||
func SplitDomainName(s string) (labels []string) {
|
||||
if len(s) == 0 {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
fqdnEnd := 0 // offset of the final '.' or the length of the name
|
||||
idx := Split(s)
|
||||
begin := 0
|
||||
if s[len(s)-1] == '.' {
|
||||
if IsFqdn(s) {
|
||||
fqdnEnd = len(s) - 1
|
||||
} else {
|
||||
fqdnEnd = len(s)
|
||||
|
@ -28,16 +28,13 @@ func SplitDomainName(s string) (labels []string) {
|
|||
case 1:
|
||||
// no-op
|
||||
default:
|
||||
end := 0
|
||||
for i := 1; i < len(idx); i++ {
|
||||
end = idx[i]
|
||||
for _, end := range idx[1:] {
|
||||
labels = append(labels, s[begin:end-1])
|
||||
begin = end
|
||||
}
|
||||
}
|
||||
|
||||
labels = append(labels, s[begin:fqdnEnd])
|
||||
return labels
|
||||
return append(labels, s[begin:fqdnEnd])
|
||||
}
|
||||
|
||||
// CompareDomainName compares the names s1 and s2 and
|
||||
|
@ -86,7 +83,7 @@ func CompareDomainName(s1, s2 string) (n int) {
|
|||
return
|
||||
}
|
||||
|
||||
// CountLabel counts the the number of labels in the string s.
|
||||
// CountLabel counts the number of labels in the string s.
|
||||
// s must be a syntactically valid domain name.
|
||||
func CountLabel(s string) (labels int) {
|
||||
if s == "." {
|
||||
|
@ -125,24 +122,27 @@ func Split(s string) []int {
|
|||
}
|
||||
|
||||
// NextLabel returns the index of the start of the next label in the
|
||||
// string s starting at offset.
|
||||
// string s starting at offset. A negative offset will cause a panic.
|
||||
// The bool end is true when the end of the string has been reached.
|
||||
// Also see PrevLabel.
|
||||
func NextLabel(s string, offset int) (i int, end bool) {
|
||||
quote := false
|
||||
if s == "" {
|
||||
return 0, true
|
||||
}
|
||||
for i = offset; i < len(s)-1; i++ {
|
||||
switch s[i] {
|
||||
case '\\':
|
||||
quote = !quote
|
||||
default:
|
||||
quote = false
|
||||
case '.':
|
||||
if quote {
|
||||
quote = !quote
|
||||
continue
|
||||
}
|
||||
return i + 1, false
|
||||
if s[i] != '.' {
|
||||
continue
|
||||
}
|
||||
j := i - 1
|
||||
for j >= 0 && s[j] == '\\' {
|
||||
j--
|
||||
}
|
||||
|
||||
if (j-i)%2 == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
return i + 1, false
|
||||
}
|
||||
return i + 1, true
|
||||
}
|
||||
|
@ -152,17 +152,38 @@ func NextLabel(s string, offset int) (i int, end bool) {
|
|||
// The bool start is true when the start of the string has been overshot.
|
||||
// Also see NextLabel.
|
||||
func PrevLabel(s string, n int) (i int, start bool) {
|
||||
if s == "" {
|
||||
return 0, true
|
||||
}
|
||||
if n == 0 {
|
||||
return len(s), false
|
||||
}
|
||||
lab := Split(s)
|
||||
if lab == nil {
|
||||
return 0, true
|
||||
|
||||
l := len(s) - 1
|
||||
if s[l] == '.' {
|
||||
l--
|
||||
}
|
||||
if n > len(lab) {
|
||||
return 0, true
|
||||
|
||||
for ; l >= 0 && n > 0; l-- {
|
||||
if s[l] != '.' {
|
||||
continue
|
||||
}
|
||||
j := l - 1
|
||||
for j >= 0 && s[j] == '\\' {
|
||||
j--
|
||||
}
|
||||
|
||||
if (j-l)%2 == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
n--
|
||||
if n == 0 {
|
||||
return l + 1, false
|
||||
}
|
||||
}
|
||||
return lab[len(lab)-n], false
|
||||
|
||||
return 0, n > 1
|
||||
}
|
||||
|
||||
// equal compares a and b while ignoring case. It returns true when equal otherwise false.
|
||||
|
@ -178,10 +199,10 @@ func equal(a, b string) bool {
|
|||
ai := a[i]
|
||||
bi := b[i]
|
||||
if ai >= 'A' && ai <= 'Z' {
|
||||
ai |= ('a' - 'A')
|
||||
ai |= 'a' - 'A'
|
||||
}
|
||||
if bi >= 'A' && bi <= 'Z' {
|
||||
bi |= ('a' - 'A')
|
||||
bi |= 'a' - 'A'
|
||||
}
|
||||
if ai != bi {
|
||||
return false
|
||||
|
|
174
labels_test.go
174
labels_test.go
|
@ -40,16 +40,17 @@ func TestCompareDomainName(t *testing.T) {
|
|||
|
||||
func TestSplit(t *testing.T) {
|
||||
splitter := map[string]int{
|
||||
"www.miek.nl.": 3,
|
||||
"www.miek.nl": 3,
|
||||
"www..miek.nl": 4,
|
||||
`www\.miek.nl.`: 2,
|
||||
`www\\.miek.nl.`: 3,
|
||||
".": 0,
|
||||
"nl.": 1,
|
||||
"nl": 1,
|
||||
"com.": 1,
|
||||
".com.": 2,
|
||||
"www.miek.nl.": 3,
|
||||
"www.miek.nl": 3,
|
||||
"www..miek.nl": 4,
|
||||
`www\.miek.nl.`: 2,
|
||||
`www\\.miek.nl.`: 3,
|
||||
`www\\\.miek.nl.`: 2,
|
||||
".": 0,
|
||||
"nl.": 1,
|
||||
"nl": 1,
|
||||
"com.": 1,
|
||||
".com.": 2,
|
||||
}
|
||||
for s, i := range splitter {
|
||||
if x := len(Split(s)); x != i {
|
||||
|
@ -79,12 +80,32 @@ func TestSplit2(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNextLabel(t *testing.T) {
|
||||
type next struct {
|
||||
string
|
||||
int
|
||||
}
|
||||
nexts := map[next]int{
|
||||
{"", 1}: 0,
|
||||
{"www.miek.nl.", 0}: 4,
|
||||
{"www.miek.nl.", 4}: 9,
|
||||
{"www.miek.nl.", 9}: 12,
|
||||
}
|
||||
for s, i := range nexts {
|
||||
x, ok := NextLabel(s.string, s.int)
|
||||
if i != x {
|
||||
t.Errorf("label should be %d, got %d, %t: next %d, %s", i, x, ok, s.int, s.string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrevLabel(t *testing.T) {
|
||||
type prev struct {
|
||||
string
|
||||
int
|
||||
}
|
||||
prever := map[prev]int{
|
||||
{"", 1}: 0,
|
||||
{"www.miek.nl.", 0}: 12,
|
||||
{"www.miek.nl.", 1}: 9,
|
||||
{"www.miek.nl.", 2}: 4,
|
||||
|
@ -102,7 +123,7 @@ func TestPrevLabel(t *testing.T) {
|
|||
for s, i := range prever {
|
||||
x, ok := PrevLabel(s.string, s.int)
|
||||
if i != x {
|
||||
t.Errorf("label should be %d, got %d, %t: preving %d, %s", i, x, ok, s.int, s.string)
|
||||
t.Errorf("label should be %d, got %d, %t: previous %d, %s", i, x, ok, s.int, s.string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,13 +176,18 @@ func TestIsDomainName(t *testing.T) {
|
|||
lab int
|
||||
}
|
||||
names := map[string]*ret{
|
||||
"..": {false, 1},
|
||||
"@.": {true, 1},
|
||||
"www.example.com": {true, 3},
|
||||
"www.e%ample.com": {true, 3},
|
||||
"www.example.com.": {true, 3},
|
||||
"mi\\k.nl.": {true, 2},
|
||||
"mi\\k.nl": {true, 2},
|
||||
".": {true, 1},
|
||||
"..": {false, 0},
|
||||
"double-dot..test": {false, 1},
|
||||
".leading-dot.test": {false, 0},
|
||||
"@.": {true, 1},
|
||||
"www.example.com": {true, 3},
|
||||
"www.e%ample.com": {true, 3},
|
||||
"www.example.com.": {true, 3},
|
||||
"mi\\k.nl.": {true, 2},
|
||||
"mi\\k.nl": {true, 2},
|
||||
longestDomain: {true, 4},
|
||||
longestUnprintableDomain: {true, 4},
|
||||
}
|
||||
for d, ok := range names {
|
||||
l, k := IsDomainName(d)
|
||||
|
@ -172,6 +198,58 @@ func TestIsDomainName(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestIsFqdnEscaped(t *testing.T) {
|
||||
for s, expect := range map[string]bool{
|
||||
".": true,
|
||||
"\\.": false,
|
||||
"\\\\.": true,
|
||||
"\\\\\\.": false,
|
||||
"\\\\\\\\.": true,
|
||||
"a.": true,
|
||||
"a\\.": false,
|
||||
"a\\\\.": true,
|
||||
"a\\\\\\.": false,
|
||||
"ab.": true,
|
||||
"ab\\.": false,
|
||||
"ab\\\\.": true,
|
||||
"ab\\\\\\.": false,
|
||||
"..": true,
|
||||
".\\.": false,
|
||||
".\\\\.": true,
|
||||
".\\\\\\.": false,
|
||||
"example.org.": true,
|
||||
"example.org\\.": false,
|
||||
"example.org\\\\.": true,
|
||||
"example.org\\\\\\.": false,
|
||||
"example\\.org.": true,
|
||||
"example\\\\.org.": true,
|
||||
"example\\\\\\.org.": true,
|
||||
"\\example.org.": true,
|
||||
"\\\\example.org.": true,
|
||||
"\\\\\\example.org.": true,
|
||||
} {
|
||||
if got := IsFqdn(s); got != expect {
|
||||
t.Errorf("IsFqdn(%q) = %t, expected %t", s, got, expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalName(t *testing.T) {
|
||||
for s, expect := range map[string]string{
|
||||
"": ".",
|
||||
".": ".",
|
||||
"tld": "tld.",
|
||||
"tld.": "tld.",
|
||||
"example.test": "example.test.",
|
||||
"Lower.CASE.test.": "lower.case.test.",
|
||||
"*.Test": "*.test.",
|
||||
} {
|
||||
if got := CanonicalName(s); got != expect {
|
||||
t.Errorf("CanonicalName(%q) = %q, expected %q", s, got, expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSplitLabels(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Split("www.example.com.")
|
||||
|
@ -199,3 +277,63 @@ func BenchmarkIsSubDomain(b *testing.B) {
|
|||
IsSubDomain("miek.nl.", "aa.example.com.")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNextLabelSimple(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
NextLabel("www.example.com", 0)
|
||||
NextLabel("www.example.com", 5)
|
||||
NextLabel("www.example.com", 12)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrevLabelSimple(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
PrevLabel("www.example.com", 0)
|
||||
PrevLabel("www.example.com", 5)
|
||||
PrevLabel("www.example.com", 12)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNextLabelComplex(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
NextLabel(`www\.example.com`, 0)
|
||||
NextLabel(`www\\.example.com`, 0)
|
||||
NextLabel(`www\\\.example.com`, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrevLabelComplex(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
PrevLabel(`www\.example.com`, 10)
|
||||
PrevLabel(`www\\.example.com`, 10)
|
||||
PrevLabel(`www\\\.example.com`, 10)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNextLabelMixed(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
NextLabel("www.example.com", 0)
|
||||
NextLabel(`www\.example.com`, 0)
|
||||
NextLabel("www.example.com", 5)
|
||||
NextLabel(`www\\.example.com`, 0)
|
||||
NextLabel("www.example.com", 12)
|
||||
NextLabel(`www\\\.example.com`, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrevLabelMixed(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
PrevLabel("www.example.com", 0)
|
||||
PrevLabel(`www\.example.com`, 10)
|
||||
PrevLabel("www.example.com", 5)
|
||||
PrevLabel(`www\\.example.com`, 10)
|
||||
PrevLabel("www.example.com", 12)
|
||||
PrevLabel(`www\\\.example.com`, 10)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,517 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompressLength(t *testing.T) {
|
||||
m := new(Msg)
|
||||
m.SetQuestion("miek.nl.", TypeMX)
|
||||
ul := m.Len()
|
||||
m.Compress = true
|
||||
if ul != m.Len() {
|
||||
t.Fatalf("should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
// Does the predicted length match final packed length?
|
||||
func TestMsgCompressLength(t *testing.T) {
|
||||
makeMsg := func(question string, ans, ns, e []RR) *Msg {
|
||||
msg := new(Msg)
|
||||
msg.SetQuestion(Fqdn(question), TypeANY)
|
||||
msg.Answer = append(msg.Answer, ans...)
|
||||
msg.Ns = append(msg.Ns, ns...)
|
||||
msg.Extra = append(msg.Extra, e...)
|
||||
msg.Compress = true
|
||||
return msg
|
||||
}
|
||||
|
||||
name1 := "12345678901234567890123456789012345.12345678.123."
|
||||
rrA := testRR(name1 + " 3600 IN A 192.0.2.1")
|
||||
rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
|
||||
tests := []*Msg{
|
||||
makeMsg(name1, []RR{rrA}, nil, nil),
|
||||
makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)}
|
||||
|
||||
for _, msg := range tests {
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted < len(buf) {
|
||||
t.Errorf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d",
|
||||
msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgLength(t *testing.T) {
|
||||
makeMsg := func(question string, ans, ns, e []RR) *Msg {
|
||||
msg := new(Msg)
|
||||
msg.Compress = true
|
||||
msg.SetQuestion(Fqdn(question), TypeANY)
|
||||
msg.Answer = append(msg.Answer, ans...)
|
||||
msg.Ns = append(msg.Ns, ns...)
|
||||
msg.Extra = append(msg.Extra, e...)
|
||||
return msg
|
||||
}
|
||||
|
||||
name1 := "12345678901234567890123456789012345.12345678.123."
|
||||
rrA := testRR(name1 + " 3600 IN A 192.0.2.1")
|
||||
rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
|
||||
tests := []*Msg{
|
||||
makeMsg(name1, []RR{rrA}, nil, nil),
|
||||
makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)}
|
||||
|
||||
for _, msg := range tests {
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted < len(buf) {
|
||||
t.Errorf("predicted length is wrong: predicted %s (len=%d), actual %d",
|
||||
msg.Question[0].Name, predicted, len(buf))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompressionLenSearchInsert(t *testing.T) {
|
||||
c := make(map[string]struct{})
|
||||
compressionLenSearch(c, "example.com", 12)
|
||||
if _, ok := c["example.com"]; !ok {
|
||||
t.Errorf("bad example.com")
|
||||
}
|
||||
if _, ok := c["com"]; !ok {
|
||||
t.Errorf("bad com")
|
||||
}
|
||||
|
||||
// Test boundaries
|
||||
c = make(map[string]struct{})
|
||||
// foo label starts at 16379
|
||||
// com label starts at 16384
|
||||
compressionLenSearch(c, "foo.com", 16379)
|
||||
if _, ok := c["foo.com"]; !ok {
|
||||
t.Errorf("bad foo.com")
|
||||
}
|
||||
// com label is accessible
|
||||
if _, ok := c["com"]; !ok {
|
||||
t.Errorf("bad com")
|
||||
}
|
||||
|
||||
c = make(map[string]struct{})
|
||||
// foo label starts at 16379
|
||||
// com label starts at 16385 => outside range
|
||||
compressionLenSearch(c, "foo.com", 16380)
|
||||
if _, ok := c["foo.com"]; !ok {
|
||||
t.Errorf("bad foo.com")
|
||||
}
|
||||
// com label is NOT accessible
|
||||
if _, ok := c["com"]; ok {
|
||||
t.Errorf("bad com")
|
||||
}
|
||||
|
||||
c = make(map[string]struct{})
|
||||
compressionLenSearch(c, "example.com", 16375)
|
||||
if _, ok := c["example.com"]; !ok {
|
||||
t.Errorf("bad example.com")
|
||||
}
|
||||
// com starts AFTER 16384
|
||||
if _, ok := c["com"]; !ok {
|
||||
t.Errorf("bad com")
|
||||
}
|
||||
|
||||
c = make(map[string]struct{})
|
||||
compressionLenSearch(c, "example.com", 16376)
|
||||
if _, ok := c["example.com"]; !ok {
|
||||
t.Errorf("bad example.com")
|
||||
}
|
||||
// com starts AFTER 16384
|
||||
if _, ok := c["com"]; ok {
|
||||
t.Errorf("bad com")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompressionLenSearch(t *testing.T) {
|
||||
c := make(map[string]struct{})
|
||||
compressed, ok := compressionLenSearch(c, "a.b.org.", maxCompressionOffset)
|
||||
if compressed != 0 || ok {
|
||||
t.Errorf("Failed: compressed:=%d, ok:=%v", compressed, ok)
|
||||
}
|
||||
c["org."] = struct{}{}
|
||||
compressed, ok = compressionLenSearch(c, "a.b.org.", maxCompressionOffset)
|
||||
if compressed != 4 || !ok {
|
||||
t.Errorf("Failed: compressed:=%d, ok:=%v", compressed, ok)
|
||||
}
|
||||
c["b.org."] = struct{}{}
|
||||
compressed, ok = compressionLenSearch(c, "a.b.org.", maxCompressionOffset)
|
||||
if compressed != 2 || !ok {
|
||||
t.Errorf("Failed: compressed:=%d, ok:=%v", compressed, ok)
|
||||
}
|
||||
// Not found long compression
|
||||
c["x.b.org."] = struct{}{}
|
||||
compressed, ok = compressionLenSearch(c, "a.b.org.", maxCompressionOffset)
|
||||
if compressed != 2 || !ok {
|
||||
t.Errorf("Failed: compressed:=%d, ok:=%v", compressed, ok)
|
||||
}
|
||||
// Found long compression
|
||||
c["a.b.org."] = struct{}{}
|
||||
compressed, ok = compressionLenSearch(c, "a.b.org.", maxCompressionOffset)
|
||||
if compressed != 0 || !ok {
|
||||
t.Errorf("Failed: compressed:=%d, ok:=%v", compressed, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgLength2(t *testing.T) {
|
||||
// Serialized replies
|
||||
var testMessages = []string{
|
||||
// google.com. IN A?
|
||||
"064e81800001000b0004000506676f6f676c6503636f6d0000010001c00c00010001000000050004adc22986c00c00010001000000050004adc22987c00c00010001000000050004adc22988c00c00010001000000050004adc22989c00c00010001000000050004adc2298ec00c00010001000000050004adc22980c00c00010001000000050004adc22981c00c00010001000000050004adc22982c00c00010001000000050004adc22983c00c00010001000000050004adc22984c00c00010001000000050004adc22985c00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc0d800010001000000050004d8ef200ac0ea00010001000000050004d8ef220ac0fc00010001000000050004d8ef240ac10e00010001000000050004d8ef260a0000290500000000050000",
|
||||
// amazon.com. IN A? (reply has no EDNS0 record)
|
||||
"6de1818000010004000a000806616d617a6f6e03636f6d0000010001c00c000100010000000500044815c2d4c00c000100010000000500044815d7e8c00c00010001000000050004b02062a6c00c00010001000000050004cdfbf236c00c000200010000000500140570646e733408756c747261646e73036f726700c00c000200010000000500150570646e733508756c747261646e7304696e666f00c00c000200010000000500160570646e733608756c747261646e7302636f02756b00c00c00020001000000050014036e7331037033310664796e656374036e657400c00c00020001000000050006036e7332c0cfc00c00020001000000050006036e7333c0cfc00c00020001000000050006036e7334c0cfc00c000200010000000500110570646e733108756c747261646e73c0dac00c000200010000000500080570646e7332c127c00c000200010000000500080570646e7333c06ec0cb00010001000000050004d04e461fc0eb00010001000000050004cc0dfa1fc0fd00010001000000050004d04e471fc10f00010001000000050004cc0dfb1fc12100010001000000050004cc4a6c01c121001c000100000005001020010502f3ff00000000000000000001c13e00010001000000050004cc4a6d01c13e001c0001000000050010261000a1101400000000000000000001",
|
||||
// yahoo.com. IN A?
|
||||
"fc2d81800001000300070008057961686f6f03636f6d0000010001c00c00010001000000050004628afd6dc00c00010001000000050004628bb718c00c00010001000000050004cebe242dc00c00020001000000050006036e7336c00cc00c00020001000000050006036e7338c00cc00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc00c00020001000000050006036e7335c00cc07b0001000100000005000444b48310c08d00010001000000050004448eff10c09f00010001000000050004cb54dd35c0b100010001000000050004628a0b9dc0c30001000100000005000477a0f77cc05700010001000000050004ca2bdfaac06900010001000000050004caa568160000290500000000050000",
|
||||
// microsoft.com. IN A?
|
||||
"f4368180000100020005000b096d6963726f736f667403636f6d0000010001c00c0001000100000005000440040b25c00c0001000100000005000441373ac9c00c0002000100000005000e036e7331046d736674036e657400c00c00020001000000050006036e7332c04fc00c00020001000000050006036e7333c04fc00c00020001000000050006036e7334c04fc00c00020001000000050006036e7335c04fc04b000100010000000500044137253ec04b001c00010000000500102a010111200500000000000000010001c0650001000100000005000440043badc065001c00010000000500102a010111200600060000000000010001c07700010001000000050004d5c7b435c077001c00010000000500102a010111202000000000000000010001c08900010001000000050004cf2e4bfec089001c00010000000500102404f800200300000000000000010001c09b000100010000000500044137e28cc09b001c00010000000500102a010111200f000100000000000100010000290500000000050000",
|
||||
// google.com. IN MX?
|
||||
"724b8180000100050004000b06676f6f676c6503636f6d00000f0001c00c000f000100000005000c000a056173706d78016cc00cc00c000f0001000000050009001404616c7431c02ac00c000f0001000000050009001e04616c7432c02ac00c000f0001000000050009002804616c7433c02ac00c000f0001000000050009003204616c7434c02ac00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc00c00020001000000050006036e7331c00cc02a00010001000000050004adc2421bc02a001c00010000000500102a00145040080c01000000000000001bc04200010001000000050004adc2461bc05700010001000000050004adc2451bc06c000100010000000500044a7d8f1bc081000100010000000500044a7d191bc0ca00010001000000050004d8ef200ac09400010001000000050004d8ef220ac0a600010001000000050004d8ef240ac0b800010001000000050004d8ef260a0000290500000000050000",
|
||||
// reddit.com. IN A?
|
||||
"12b98180000100080000000c0672656464697403636f6d0000020001c00c0002000100000005000f046175733204616b616d036e657400c00c000200010000000500070475736534c02dc00c000200010000000500070475737733c02dc00c000200010000000500070475737735c02dc00c00020001000000050008056173696131c02dc00c00020001000000050008056173696139c02dc00c00020001000000050008056e73312d31c02dc00c0002000100000005000a076e73312d313935c02dc02800010001000000050004c30a242ec04300010001000000050004451f1d39c05600010001000000050004451f3bc7c0690001000100000005000460073240c07c000100010000000500046007fb81c090000100010000000500047c283484c090001c00010000000500102a0226f0006700000000000000000064c0a400010001000000050004c16c5b01c0a4001c000100000005001026001401000200000000000000000001c0b800010001000000050004c16c5bc3c0b8001c0001000000050010260014010002000000000000000000c30000290500000000050000",
|
||||
}
|
||||
|
||||
for i, hexData := range testMessages {
|
||||
// we won't fail the decoding of the hex
|
||||
input, _ := hex.DecodeString(hexData)
|
||||
|
||||
m := new(Msg)
|
||||
m.Unpack(input)
|
||||
m.Compress = true
|
||||
lenComp := m.Len()
|
||||
b, _ := m.Pack()
|
||||
pacComp := len(b)
|
||||
m.Compress = false
|
||||
lenUnComp := m.Len()
|
||||
b, _ = m.Pack()
|
||||
pacUnComp := len(b)
|
||||
if pacComp != lenComp {
|
||||
t.Errorf("msg.Len(compressed)=%d actual=%d for test %d", lenComp, pacComp, i)
|
||||
}
|
||||
if pacUnComp != lenUnComp {
|
||||
t.Errorf("msg.Len(uncompressed)=%d actual=%d for test %d", lenUnComp, pacUnComp, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgLengthCompressionMalformed(t *testing.T) {
|
||||
// SOA with empty hostmaster, which is illegal
|
||||
soa := &SOA{Hdr: RR_Header{Name: ".", Rrtype: TypeSOA, Class: ClassINET, Ttl: 12345},
|
||||
Ns: ".",
|
||||
Mbox: "",
|
||||
Serial: 0,
|
||||
Refresh: 28800,
|
||||
Retry: 7200,
|
||||
Expire: 604800,
|
||||
Minttl: 60}
|
||||
m := new(Msg)
|
||||
m.Compress = true
|
||||
m.Ns = []RR{soa}
|
||||
m.Len() // Should not crash.
|
||||
}
|
||||
|
||||
func TestMsgCompressLength2(t *testing.T) {
|
||||
msg := new(Msg)
|
||||
msg.Compress = true
|
||||
msg.SetQuestion(Fqdn("bliep."), TypeANY)
|
||||
msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "blaat.", Rrtype: 0x21, Class: 0x1, Ttl: 0x3c}, Port: 0x4c57, Target: "foo.bar."})
|
||||
msg.Extra = append(msg.Extra, &A{Hdr: RR_Header{Name: "foo.bar.", Rrtype: 0x1, Class: 0x1, Ttl: 0x3c}, A: net.IP{0xac, 0x11, 0x0, 0x3}})
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted != len(buf) {
|
||||
t.Errorf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d",
|
||||
msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgCompressLengthLargeRecords(t *testing.T) {
|
||||
msg := new(Msg)
|
||||
msg.Compress = true
|
||||
msg.SetQuestion("my.service.acme.", TypeSRV)
|
||||
j := 1
|
||||
for i := 0; i < 250; i++ {
|
||||
target := fmt.Sprintf("host-redis-1-%d.test.acme.com.node.dc1.consul.", i)
|
||||
msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
|
||||
msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: 1, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", j, i)})
|
||||
}
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted != len(buf) {
|
||||
t.Fatalf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d", msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func compressionMapsEqual(a map[string]struct{}, b map[string]int) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for k := range a {
|
||||
if _, ok := b[k]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func compressionMapsDifference(a map[string]struct{}, b map[string]int) string {
|
||||
var s strings.Builder
|
||||
|
||||
var c int
|
||||
fmt.Fprintf(&s, "length compression map (%d):", len(a))
|
||||
for k := range b {
|
||||
if _, ok := a[k]; !ok {
|
||||
if c > 0 {
|
||||
s.WriteString(",")
|
||||
}
|
||||
|
||||
fmt.Fprintf(&s, " missing %q", k)
|
||||
c++
|
||||
}
|
||||
}
|
||||
|
||||
c = 0
|
||||
fmt.Fprintf(&s, "\npack compression map (%d):", len(b))
|
||||
for k := range a {
|
||||
if _, ok := b[k]; !ok {
|
||||
if c > 0 {
|
||||
s.WriteString(",")
|
||||
}
|
||||
|
||||
fmt.Fprintf(&s, " missing %q", k)
|
||||
c++
|
||||
}
|
||||
}
|
||||
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func TestCompareCompressionMapsForANY(t *testing.T) {
|
||||
msg := new(Msg)
|
||||
msg.Compress = true
|
||||
msg.SetQuestion("a.service.acme.", TypeANY)
|
||||
// Be sure to have more than 14bits
|
||||
for i := 0; i < 2000; i++ {
|
||||
target := fmt.Sprintf("host.app-%d.x%d.test.acme.", i%250, i)
|
||||
msg.Answer = append(msg.Answer, &AAAA{Hdr: RR_Header{Name: target, Rrtype: TypeAAAA, Class: ClassINET, Ttl: 0x3c}, AAAA: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, byte(i / 255), byte(i % 255)}})
|
||||
msg.Answer = append(msg.Answer, &A{Hdr: RR_Header{Name: target, Rrtype: TypeA, Class: ClassINET, Ttl: 0x3c}, A: net.IP{127, 0, byte(i / 255), byte(i % 255)}})
|
||||
if msg.Len() > 16384 {
|
||||
break
|
||||
}
|
||||
}
|
||||
for labelSize := 0; labelSize < 63; labelSize++ {
|
||||
msg.SetQuestion(fmt.Sprintf("a%s.service.acme.", strings.Repeat("x", labelSize)), TypeANY)
|
||||
|
||||
compressionFake := make(map[string]struct{})
|
||||
lenFake := msgLenWithCompressionMap(msg, compressionFake)
|
||||
|
||||
compressionReal := make(map[string]int)
|
||||
buf, err := msg.packBufferWithCompressionMap(nil, compressionMap{ext: compressionReal}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if lenFake != len(buf) {
|
||||
t.Fatalf("padding= %d ; Predicted len := %d != real:= %d", labelSize, lenFake, len(buf))
|
||||
}
|
||||
if !compressionMapsEqual(compressionFake, compressionReal) {
|
||||
t.Fatalf("padding= %d ; Fake Compression Map != Real Compression Map\n%s", labelSize, compressionMapsDifference(compressionFake, compressionReal))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareCompressionMapsForSRV(t *testing.T) {
|
||||
msg := new(Msg)
|
||||
msg.Compress = true
|
||||
msg.SetQuestion("a.service.acme.", TypeSRV)
|
||||
// Be sure to have more than 14bits
|
||||
for i := 0; i < 2000; i++ {
|
||||
target := fmt.Sprintf("host.app-%d.x%d.test.acme.", i%250, i)
|
||||
msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: ClassINET, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
|
||||
msg.Extra = append(msg.Extra, &A{Hdr: RR_Header{Name: target, Rrtype: TypeA, Class: ClassINET, Ttl: 0x3c}, A: net.IP{127, 0, byte(i / 255), byte(i % 255)}})
|
||||
if msg.Len() > 16384 {
|
||||
break
|
||||
}
|
||||
}
|
||||
for labelSize := 0; labelSize < 63; labelSize++ {
|
||||
msg.SetQuestion(fmt.Sprintf("a%s.service.acme.", strings.Repeat("x", labelSize)), TypeAAAA)
|
||||
|
||||
compressionFake := make(map[string]struct{})
|
||||
lenFake := msgLenWithCompressionMap(msg, compressionFake)
|
||||
|
||||
compressionReal := make(map[string]int)
|
||||
buf, err := msg.packBufferWithCompressionMap(nil, compressionMap{ext: compressionReal}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if lenFake != len(buf) {
|
||||
t.Fatalf("padding= %d ; Predicted len := %d != real:= %d", labelSize, lenFake, len(buf))
|
||||
}
|
||||
if !compressionMapsEqual(compressionFake, compressionReal) {
|
||||
t.Fatalf("padding= %d ; Fake Compression Map != Real Compression Map\n%s", labelSize, compressionMapsDifference(compressionFake, compressionReal))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgCompressLengthLargeRecordsWithPaddingPermutation(t *testing.T) {
|
||||
msg := new(Msg)
|
||||
msg.Compress = true
|
||||
msg.SetQuestion("my.service.acme.", TypeSRV)
|
||||
|
||||
for i := 0; i < 250; i++ {
|
||||
target := fmt.Sprintf("host-redis-x-%d.test.acme.com.node.dc1.consul.", i)
|
||||
msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
|
||||
msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: ClassINET, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.x.%d.", i)})
|
||||
}
|
||||
for labelSize := 1; labelSize < 63; labelSize++ {
|
||||
msg.SetQuestion(fmt.Sprintf("my.%s.service.acme.", strings.Repeat("x", labelSize)), TypeSRV)
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted != len(buf) {
|
||||
t.Fatalf("padding= %d ; predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d", labelSize, msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgCompressLengthLargeRecordsAllValues(t *testing.T) {
|
||||
// we want to cross the 14 (16384) bit boundary here, so we build it up to just below and go slightly over.
|
||||
msg := new(Msg)
|
||||
msg.Compress = true
|
||||
msg.SetQuestion("redis.service.consul.", TypeSRV)
|
||||
for i := 0; i < 170; i++ {
|
||||
target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", i/256, i%256)
|
||||
msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
|
||||
msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: ClassINET, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)})
|
||||
}
|
||||
// msg.Len() == 15458
|
||||
// msg.Len() == 16470 at 180
|
||||
|
||||
for i := 170; i < 181; i++ {
|
||||
target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", i/256, i%256)
|
||||
msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
|
||||
msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: ClassINET, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)})
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted != len(buf) {
|
||||
t.Fatalf("predicted compressed length is wrong for %d records: predicted %s (len=%d) %d, actual %d", i, msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgCompressionMultipleQuestions(t *testing.T) {
|
||||
msg := new(Msg)
|
||||
msg.Compress = true
|
||||
msg.SetQuestion("www.example.org.", TypeA)
|
||||
msg.Question = append(msg.Question, Question{"other.example.org.", TypeA, ClassINET})
|
||||
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted != len(buf) {
|
||||
t.Fatalf("predicted compressed length is wrong: predicted %d, actual %d", predicted, len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgCompressMultipleCompressedNames(t *testing.T) {
|
||||
msg := new(Msg)
|
||||
msg.Compress = true
|
||||
msg.SetQuestion("www.example.com.", TypeSRV)
|
||||
msg.Answer = append(msg.Answer, &MINFO{
|
||||
Hdr: RR_Header{Name: "www.example.com.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c},
|
||||
Rmail: "mail.example.org.",
|
||||
Email: "mail.example.org.",
|
||||
})
|
||||
msg.Answer = append(msg.Answer, &SOA{
|
||||
Hdr: RR_Header{Name: "www.example.com.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c},
|
||||
Ns: "ns.example.net.",
|
||||
Mbox: "mail.example.net.",
|
||||
})
|
||||
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted != len(buf) {
|
||||
t.Fatalf("predicted compressed length is wrong: predicted %d, actual %d", predicted, len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgCompressLengthEscapingMatch(t *testing.T) {
|
||||
// Although slightly non-optimal, "example.org." and "ex\\097mple.org."
|
||||
// are not considered equal in the compression map, even though \097 is
|
||||
// a valid escaping of a. This test ensures that the Len code and the
|
||||
// Pack code don't disagree on this.
|
||||
|
||||
msg := new(Msg)
|
||||
msg.Compress = true
|
||||
msg.SetQuestion("www.example.org.", TypeA)
|
||||
msg.Answer = append(msg.Answer, &NS{Hdr: RR_Header{Name: "ex\\097mple.org.", Rrtype: TypeNS, Class: ClassINET}, Ns: "ns.example.org."})
|
||||
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted != len(buf) {
|
||||
t.Fatalf("predicted compressed length is wrong: predicted %d, actual %d", predicted, len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgLengthEscaped(t *testing.T) {
|
||||
msg := new(Msg)
|
||||
msg.SetQuestion(`\000\001\002.\003\004\005\006\007\008\009.\a\b\c.`, TypeA)
|
||||
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted != len(buf) {
|
||||
t.Fatalf("predicted compressed length is wrong: predicted %d, actual %d", predicted, len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgCompressLengthEscaped(t *testing.T) {
|
||||
msg := new(Msg)
|
||||
msg.Compress = true
|
||||
msg.SetQuestion("www.example.org.", TypeA)
|
||||
msg.Answer = append(msg.Answer, &NS{Hdr: RR_Header{Name: `\000\001\002.example.org.`, Rrtype: TypeNS, Class: ClassINET}, Ns: `ns.\e\x\a\m\p\l\e.org.`})
|
||||
msg.Answer = append(msg.Answer, &NS{Hdr: RR_Header{Name: `www.\e\x\a\m\p\l\e.org.`, Rrtype: TypeNS, Class: ClassINET}, Ns: "ns.example.org."})
|
||||
|
||||
predicted := msg.Len()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if predicted != len(buf) {
|
||||
t.Fatalf("predicted compressed length is wrong: predicted %d, actual %d", predicted, len(buf))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
//go:build !go1.11 || (!aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd)
|
||||
// +build !go1.11 !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd
|
||||
|
||||
package dns
|
||||
|
||||
import "net"
|
||||
|
||||
const supportsReusePort = false
|
||||
|
||||
func listenTCP(network, addr string, reuseport bool) (net.Listener, error) {
|
||||
if reuseport {
|
||||
// TODO(tmthrgd): return an error?
|
||||
}
|
||||
|
||||
return net.Listen(network, addr)
|
||||
}
|
||||
|
||||
func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) {
|
||||
if reuseport {
|
||||
// TODO(tmthrgd): return an error?
|
||||
}
|
||||
|
||||
return net.ListenPacket(network, addr)
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
//go:build go1.11 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd)
|
||||
// +build go1.11
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const supportsReusePort = true
|
||||
|
||||
func reuseportControl(network, address string, c syscall.RawConn) error {
|
||||
var opErr error
|
||||
err := c.Control(func(fd uintptr) {
|
||||
opErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return opErr
|
||||
}
|
||||
|
||||
func listenTCP(network, addr string, reuseport bool) (net.Listener, error) {
|
||||
var lc net.ListenConfig
|
||||
if reuseport {
|
||||
lc.Control = reuseportControl
|
||||
}
|
||||
|
||||
return lc.Listen(context.Background(), network, addr)
|
||||
}
|
||||
|
||||
func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) {
|
||||
var lc net.ListenConfig
|
||||
if reuseport {
|
||||
lc.Control = reuseportControl
|
||||
}
|
||||
|
||||
return lc.ListenPacket(context.Background(), network, addr)
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
//+build ignore
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
// msg_generate.go is meant to run with go generate. It will use
|
||||
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||
|
@ -10,11 +11,12 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"go/importer"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
var packageHdr = `
|
||||
|
@ -34,6 +36,9 @@ func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
|
|||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if st.NumFields() == 0 {
|
||||
return nil, false
|
||||
}
|
||||
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
||||
return st, false
|
||||
}
|
||||
|
@ -44,9 +49,19 @@ func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
|
|||
return nil, false
|
||||
}
|
||||
|
||||
// loadModule retrieves package description for a given module.
|
||||
func loadModule(name string) (*types.Package, error) {
|
||||
conf := packages.Config{Mode: packages.NeedTypes | packages.NeedTypesInfo}
|
||||
pkgs, err := packages.Load(&conf, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pkgs[0].Types, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Import and type-check the package
|
||||
pkg, err := importer.Default().Import("github.com/miekg/dns")
|
||||
pkg, err := loadModule("github.com/miekg/dns")
|
||||
fatalIfErr(err)
|
||||
scope := pkg.Scope()
|
||||
|
||||
|
@ -80,13 +95,7 @@ func main() {
|
|||
o := scope.Lookup(name)
|
||||
st, _ := getTypeStruct(o.Type(), scope)
|
||||
|
||||
fmt.Fprintf(b, "func (rr *%s) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) {\n", name)
|
||||
fmt.Fprint(b, `off, err := rr.Hdr.pack(msg, off, compression, compress)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
headerEnd := off
|
||||
`)
|
||||
fmt.Fprintf(b, "func (rr *%s) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {\n", name)
|
||||
for i := 1; i < st.NumFields(); i++ {
|
||||
o := func(s string) {
|
||||
fmt.Fprintf(b, s, st.Field(i).Name())
|
||||
|
@ -105,8 +114,12 @@ return off, err
|
|||
o("off, err = packDataOpt(rr.%s, msg, off)\n")
|
||||
case `dns:"nsec"`:
|
||||
o("off, err = packDataNsec(rr.%s, msg, off)\n")
|
||||
case `dns:"pairs"`:
|
||||
o("off, err = packDataSVCB(rr.%s, msg, off)\n")
|
||||
case `dns:"domain-name"`:
|
||||
o("off, err = packDataDomainNames(rr.%s, msg, off, compression, compress)\n")
|
||||
o("off, err = packDataDomainNames(rr.%s, msg, off, compression, false)\n")
|
||||
case `dns:"apl"`:
|
||||
o("off, err = packDataApl(rr.%s, msg, off)\n")
|
||||
default:
|
||||
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
||||
}
|
||||
|
@ -116,9 +129,9 @@ return off, err
|
|||
switch {
|
||||
case st.Tag(i) == `dns:"-"`: // ignored
|
||||
case st.Tag(i) == `dns:"cdomain-name"`:
|
||||
o("off, err = PackDomainName(rr.%s, msg, off, compression, compress)\n")
|
||||
o("off, err = packDomainName(rr.%s, msg, off, compression, compress)\n")
|
||||
case st.Tag(i) == `dns:"domain-name"`:
|
||||
o("off, err = PackDomainName(rr.%s, msg, off, compression, false)\n")
|
||||
o("off, err = packDomainName(rr.%s, msg, off, compression, false)\n")
|
||||
case st.Tag(i) == `dns:"a"`:
|
||||
o("off, err = packDataA(rr.%s, msg, off)\n")
|
||||
case st.Tag(i) == `dns:"aaaa"`:
|
||||
|
@ -154,7 +167,8 @@ if rr.%s != "-" {
|
|||
fallthrough
|
||||
case st.Tag(i) == `dns:"hex"`:
|
||||
o("off, err = packStringHex(rr.%s, msg, off)\n")
|
||||
|
||||
case st.Tag(i) == `dns:"any"`:
|
||||
o("off, err = packStringAny(rr.%s, msg, off)\n")
|
||||
case st.Tag(i) == `dns:"octet"`:
|
||||
o("off, err = packStringOctet(rr.%s, msg, off)\n")
|
||||
case st.Tag(i) == "":
|
||||
|
@ -176,8 +190,6 @@ if rr.%s != "-" {
|
|||
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
||||
}
|
||||
}
|
||||
// We have packed everything, only now we know the rdlength of this RR
|
||||
fmt.Fprintln(b, "rr.Header().Rdlength = uint16(off-headerEnd)")
|
||||
fmt.Fprintln(b, "return off, nil }\n")
|
||||
}
|
||||
|
||||
|
@ -186,14 +198,8 @@ if rr.%s != "-" {
|
|||
o := scope.Lookup(name)
|
||||
st, _ := getTypeStruct(o.Type(), scope)
|
||||
|
||||
fmt.Fprintf(b, "func unpack%s(h RR_Header, msg []byte, off int) (RR, int, error) {\n", name)
|
||||
fmt.Fprintf(b, "rr := new(%s)\n", name)
|
||||
fmt.Fprint(b, "rr.Hdr = h\n")
|
||||
fmt.Fprint(b, `if noRdata(h) {
|
||||
return rr, off, nil
|
||||
}
|
||||
var err error
|
||||
rdStart := off
|
||||
fmt.Fprintf(b, "func (rr *%s) unpack(msg []byte, off int) (off1 int, err error) {\n", name)
|
||||
fmt.Fprint(b, `rdStart := off
|
||||
_ = rdStart
|
||||
|
||||
`)
|
||||
|
@ -201,7 +207,7 @@ _ = rdStart
|
|||
o := func(s string) {
|
||||
fmt.Fprintf(b, s, st.Field(i).Name())
|
||||
fmt.Fprint(b, `if err != nil {
|
||||
return rr, off, err
|
||||
return off, err
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
@ -221,7 +227,7 @@ return rr, off, err
|
|||
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
||||
}
|
||||
fmt.Fprint(b, `if err != nil {
|
||||
return rr, off, err
|
||||
return off, err
|
||||
}
|
||||
`)
|
||||
continue
|
||||
|
@ -236,8 +242,12 @@ return rr, off, err
|
|||
o("rr.%s, off, err = unpackDataOpt(msg, off)\n")
|
||||
case `dns:"nsec"`:
|
||||
o("rr.%s, off, err = unpackDataNsec(msg, off)\n")
|
||||
case `dns:"pairs"`:
|
||||
o("rr.%s, off, err = unpackDataSVCB(msg, off)\n")
|
||||
case `dns:"domain-name"`:
|
||||
o("rr.%s, off, err = unpackDataDomainNames(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
|
||||
case `dns:"apl"`:
|
||||
o("rr.%s, off, err = unpackDataApl(msg, off)\n")
|
||||
default:
|
||||
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
||||
}
|
||||
|
@ -264,6 +274,8 @@ return rr, off, err
|
|||
o("rr.%s, off, err = unpackStringBase64(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
|
||||
case `dns:"hex"`:
|
||||
o("rr.%s, off, err = unpackStringHex(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
|
||||
case `dns:"any"`:
|
||||
o("rr.%s, off, err = unpackStringAny(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
|
||||
case `dns:"octet"`:
|
||||
o("rr.%s, off, err = unpackStringOctet(msg, off)\n")
|
||||
case "":
|
||||
|
@ -287,22 +299,13 @@ return rr, off, err
|
|||
// If we've hit len(msg) we return without error.
|
||||
if i < st.NumFields()-1 {
|
||||
fmt.Fprintf(b, `if off == len(msg) {
|
||||
return rr, off, nil
|
||||
return off, nil
|
||||
}
|
||||
`)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(b, "return rr, off, err }\n\n")
|
||||
fmt.Fprintf(b, "return off, nil }\n\n")
|
||||
}
|
||||
// Generate typeToUnpack map
|
||||
fmt.Fprintln(b, "var typeToUnpack = map[uint16]func(RR_Header, []byte, int) (RR, int, error){")
|
||||
for _, name := range namedTypes {
|
||||
if name == "RFC3597" {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(b, "Type%s: unpack%s,\n", name, name)
|
||||
}
|
||||
fmt.Fprintln(b, "}\n")
|
||||
|
||||
// gofmt
|
||||
res, err := format.Source(b.Bytes())
|
||||
|
|
439
msg_helpers.go
439
msg_helpers.go
|
@ -6,7 +6,8 @@ import (
|
|||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"strconv"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// helper functions called from the generated zmsg.go
|
||||
|
@ -25,12 +26,13 @@ func unpackDataA(msg []byte, off int) (net.IP, int, error) {
|
|||
}
|
||||
|
||||
func packDataA(a net.IP, msg []byte, off int) (int, error) {
|
||||
// It must be a slice of 4, even if it is 16, we encode only the first 4
|
||||
if off+net.IPv4len > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing a"}
|
||||
}
|
||||
switch len(a) {
|
||||
case net.IPv4len, net.IPv6len:
|
||||
// It must be a slice of 4, even if it is 16, we encode only the first 4
|
||||
if off+net.IPv4len > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing a"}
|
||||
}
|
||||
|
||||
copy(msg[off:], a.To4())
|
||||
off += net.IPv4len
|
||||
case 0:
|
||||
|
@ -51,12 +53,12 @@ func unpackDataAAAA(msg []byte, off int) (net.IP, int, error) {
|
|||
}
|
||||
|
||||
func packDataAAAA(aaaa net.IP, msg []byte, off int) (int, error) {
|
||||
if off+net.IPv6len > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing aaaa"}
|
||||
}
|
||||
|
||||
switch len(aaaa) {
|
||||
case net.IPv6len:
|
||||
if off+net.IPv6len > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing aaaa"}
|
||||
}
|
||||
|
||||
copy(msg[off:], aaaa)
|
||||
off += net.IPv6len
|
||||
case 0:
|
||||
|
@ -99,14 +101,14 @@ func unpackHeader(msg []byte, off int) (rr RR_Header, off1 int, truncmsg []byte,
|
|||
return hdr, off, msg, err
|
||||
}
|
||||
|
||||
// pack packs an RR header, returning the offset to the end of the header.
|
||||
// packHeader packs an RR header, returning the offset to the end of the header.
|
||||
// See PackDomainName for documentation about the compression.
|
||||
func (hdr RR_Header) pack(msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) {
|
||||
func (hdr RR_Header) packHeader(msg []byte, off int, compression compressionMap, compress bool) (int, error) {
|
||||
if off == len(msg) {
|
||||
return off, nil
|
||||
}
|
||||
|
||||
off, err = PackDomainName(hdr.Name, msg, off, compression, compress)
|
||||
off, err := packDomainName(hdr.Name, msg, off, compression, compress)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
|
@ -122,7 +124,7 @@ func (hdr RR_Header) pack(msg []byte, off int, compression map[string]int, compr
|
|||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
off, err = packUint16(hdr.Rdlength, msg, off)
|
||||
off, err = packUint16(0, msg, off) // The RDLENGTH field will be set later in packRR.
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
|
@ -141,20 +143,24 @@ func truncateMsgFromRdlength(msg []byte, off int, rdlength uint16) (truncmsg []b
|
|||
return msg[:lenrd], nil
|
||||
}
|
||||
|
||||
var base32HexNoPadEncoding = base32.HexEncoding.WithPadding(base32.NoPadding)
|
||||
|
||||
func fromBase32(s []byte) (buf []byte, err error) {
|
||||
for i, b := range s {
|
||||
if b >= 'a' && b <= 'z' {
|
||||
s[i] = b - 32
|
||||
}
|
||||
}
|
||||
buflen := base32.HexEncoding.DecodedLen(len(s))
|
||||
buflen := base32HexNoPadEncoding.DecodedLen(len(s))
|
||||
buf = make([]byte, buflen)
|
||||
n, err := base32.HexEncoding.Decode(buf, s)
|
||||
n, err := base32HexNoPadEncoding.Decode(buf, s)
|
||||
buf = buf[:n]
|
||||
return
|
||||
}
|
||||
|
||||
func toBase32(b []byte) string { return base32.HexEncoding.EncodeToString(b) }
|
||||
func toBase32(b []byte) string {
|
||||
return base32HexNoPadEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
func fromBase64(s []byte) (buf []byte, err error) {
|
||||
buflen := base64.StdEncoding.DecodedLen(len(s))
|
||||
|
@ -173,14 +179,14 @@ func unpackUint8(msg []byte, off int) (i uint8, off1 int, err error) {
|
|||
if off+1 > len(msg) {
|
||||
return 0, len(msg), &Error{err: "overflow unpacking uint8"}
|
||||
}
|
||||
return uint8(msg[off]), off + 1, nil
|
||||
return msg[off], off + 1, nil
|
||||
}
|
||||
|
||||
func packUint8(i uint8, msg []byte, off int) (off1 int, err error) {
|
||||
if off+1 > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing uint8"}
|
||||
}
|
||||
msg[off] = byte(i)
|
||||
msg[off] = i
|
||||
return off + 1, nil
|
||||
}
|
||||
|
||||
|
@ -219,8 +225,8 @@ func unpackUint48(msg []byte, off int) (i uint64, off1 int, err error) {
|
|||
return 0, len(msg), &Error{err: "overflow unpacking uint64 as uint48"}
|
||||
}
|
||||
// Used in TSIG where the last 48 bits are occupied, so for now, assume a uint48 (6 bytes)
|
||||
i = (uint64(uint64(msg[off])<<40 | uint64(msg[off+1])<<32 | uint64(msg[off+2])<<24 | uint64(msg[off+3])<<16 |
|
||||
uint64(msg[off+4])<<8 | uint64(msg[off+5])))
|
||||
i = uint64(msg[off])<<40 | uint64(msg[off+1])<<32 | uint64(msg[off+2])<<24 | uint64(msg[off+3])<<16 |
|
||||
uint64(msg[off+4])<<8 | uint64(msg[off+5])
|
||||
off += 6
|
||||
return i, off, nil
|
||||
}
|
||||
|
@ -260,32 +266,36 @@ func unpackString(msg []byte, off int) (string, int, error) {
|
|||
return "", off, &Error{err: "overflow unpacking txt"}
|
||||
}
|
||||
l := int(msg[off])
|
||||
if off+l+1 > len(msg) {
|
||||
off++
|
||||
if off+l > len(msg) {
|
||||
return "", off, &Error{err: "overflow unpacking txt"}
|
||||
}
|
||||
s := make([]byte, 0, l)
|
||||
for _, b := range msg[off+1 : off+1+l] {
|
||||
switch b {
|
||||
case '"', '\\':
|
||||
s = append(s, '\\', b)
|
||||
default:
|
||||
if b < 32 || b > 127 { // unprintable
|
||||
var buf [3]byte
|
||||
bufs := strconv.AppendInt(buf[:0], int64(b), 10)
|
||||
s = append(s, '\\')
|
||||
for i := 0; i < 3-len(bufs); i++ {
|
||||
s = append(s, '0')
|
||||
}
|
||||
for _, r := range bufs {
|
||||
s = append(s, r)
|
||||
}
|
||||
} else {
|
||||
s = append(s, b)
|
||||
var s strings.Builder
|
||||
consumed := 0
|
||||
for i, b := range msg[off : off+l] {
|
||||
switch {
|
||||
case b == '"' || b == '\\':
|
||||
if consumed == 0 {
|
||||
s.Grow(l * 2)
|
||||
}
|
||||
s.Write(msg[off+consumed : off+i])
|
||||
s.WriteByte('\\')
|
||||
s.WriteByte(b)
|
||||
consumed = i + 1
|
||||
case b < ' ' || b > '~': // unprintable
|
||||
if consumed == 0 {
|
||||
s.Grow(l * 2)
|
||||
}
|
||||
s.Write(msg[off+consumed : off+i])
|
||||
s.WriteString(escapeByte(b))
|
||||
consumed = i + 1
|
||||
}
|
||||
}
|
||||
off += 1 + l
|
||||
return string(s), off, nil
|
||||
if consumed == 0 { // no escaping needed
|
||||
return string(msg[off : off+l]), off + l, nil
|
||||
}
|
||||
s.Write(msg[off+consumed : off+l])
|
||||
return s.String(), off + l, nil
|
||||
}
|
||||
|
||||
func packString(s string, msg []byte, off int) (int, error) {
|
||||
|
@ -359,7 +369,7 @@ func packStringHex(s string, msg []byte, off int) (int, error) {
|
|||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
if off+(len(h)) > len(msg) {
|
||||
if off+len(h) > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing hex"}
|
||||
}
|
||||
copy(msg[off:off+len(h)], h)
|
||||
|
@ -367,6 +377,22 @@ func packStringHex(s string, msg []byte, off int) (int, error) {
|
|||
return off, nil
|
||||
}
|
||||
|
||||
func unpackStringAny(msg []byte, off, end int) (string, int, error) {
|
||||
if end > len(msg) {
|
||||
return "", len(msg), &Error{err: "overflow unpacking anything"}
|
||||
}
|
||||
return string(msg[off:end]), end, nil
|
||||
}
|
||||
|
||||
func packStringAny(s string, msg []byte, off int) (int, error) {
|
||||
if off+len(s) > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing anything"}
|
||||
}
|
||||
copy(msg[off:off+len(s)], s)
|
||||
off += len(s)
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func unpackStringTxt(msg []byte, off int) ([]string, int, error) {
|
||||
txt, off, err := unpackTxt(msg, off)
|
||||
if err != nil {
|
||||
|
@ -387,7 +413,7 @@ func packStringTxt(s []string, msg []byte, off int) (int, error) {
|
|||
func unpackDataOpt(msg []byte, off int) ([]EDNS0, int, error) {
|
||||
var edns []EDNS0
|
||||
Option:
|
||||
code := uint16(0)
|
||||
var code uint16
|
||||
if off+4 > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking opt"}
|
||||
}
|
||||
|
@ -398,79 +424,12 @@ Option:
|
|||
if off+int(optlen) > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking opt"}
|
||||
}
|
||||
switch code {
|
||||
case EDNS0NSID:
|
||||
e := new(EDNS0_NSID)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0SUBNET:
|
||||
e := new(EDNS0_SUBNET)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0COOKIE:
|
||||
e := new(EDNS0_COOKIE)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0UL:
|
||||
e := new(EDNS0_UL)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0LLQ:
|
||||
e := new(EDNS0_LLQ)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0DAU:
|
||||
e := new(EDNS0_DAU)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0DHU:
|
||||
e := new(EDNS0_DHU)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0N3U:
|
||||
e := new(EDNS0_N3U)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0PADDING:
|
||||
e := new(EDNS0_PADDING)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
default:
|
||||
e := new(EDNS0_LOCAL)
|
||||
e.Code = code
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
e := makeDataOpt(code)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
|
||||
if off < len(msg) {
|
||||
goto Option
|
||||
|
@ -482,16 +441,14 @@ Option:
|
|||
func packDataOpt(options []EDNS0, msg []byte, off int) (int, error) {
|
||||
for _, el := range options {
|
||||
b, err := el.pack()
|
||||
if err != nil || off+3 > len(msg) {
|
||||
if err != nil || off+4 > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing opt"}
|
||||
}
|
||||
binary.BigEndian.PutUint16(msg[off:], el.Option()) // Option code
|
||||
binary.BigEndian.PutUint16(msg[off+2:], uint16(len(b))) // Length
|
||||
off += 4
|
||||
if off+len(b) > len(msg) {
|
||||
copy(msg[off:], b)
|
||||
off = len(msg)
|
||||
continue
|
||||
return len(msg), &Error{err: "overflow packing opt"}
|
||||
}
|
||||
// Actual data
|
||||
copy(msg[off:off+len(b)], b)
|
||||
|
@ -519,7 +476,7 @@ func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
|
|||
length, window, lastwindow := 0, 0, -1
|
||||
for off < len(msg) {
|
||||
if off+2 > len(msg) {
|
||||
return nsec, len(msg), &Error{err: "overflow unpacking nsecx"}
|
||||
return nsec, len(msg), &Error{err: "overflow unpacking NSEC(3)"}
|
||||
}
|
||||
window = int(msg[off])
|
||||
length = int(msg[off+1])
|
||||
|
@ -527,22 +484,21 @@ func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
|
|||
if window <= lastwindow {
|
||||
// RFC 4034: Blocks are present in the NSEC RR RDATA in
|
||||
// increasing numerical order.
|
||||
return nsec, len(msg), &Error{err: "out of order NSEC block"}
|
||||
return nsec, len(msg), &Error{err: "out of order NSEC(3) block in type bitmap"}
|
||||
}
|
||||
if length == 0 {
|
||||
// RFC 4034: Blocks with no types present MUST NOT be included.
|
||||
return nsec, len(msg), &Error{err: "empty NSEC block"}
|
||||
return nsec, len(msg), &Error{err: "empty NSEC(3) block in type bitmap"}
|
||||
}
|
||||
if length > 32 {
|
||||
return nsec, len(msg), &Error{err: "NSEC block too long"}
|
||||
return nsec, len(msg), &Error{err: "NSEC(3) block too long in type bitmap"}
|
||||
}
|
||||
if off+length > len(msg) {
|
||||
return nsec, len(msg), &Error{err: "overflowing NSEC block"}
|
||||
return nsec, len(msg), &Error{err: "overflowing NSEC(3) block in type bitmap"}
|
||||
}
|
||||
|
||||
// Walk the bytes in the window and extract the type bits
|
||||
for j := 0; j < length; j++ {
|
||||
b := msg[off+j]
|
||||
for j, b := range msg[off : off+length] {
|
||||
// Check the bits one by one, and set the type
|
||||
if b&0x80 == 0x80 {
|
||||
nsec = append(nsec, uint16(window*256+j*8+0))
|
||||
|
@ -575,13 +531,45 @@ func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
|
|||
return nsec, off, nil
|
||||
}
|
||||
|
||||
// typeBitMapLen is a helper function which computes the "maximum" length of
|
||||
// a the NSEC Type BitMap field.
|
||||
func typeBitMapLen(bitmap []uint16) int {
|
||||
var l int
|
||||
var lastwindow, lastlength uint16
|
||||
for _, t := range bitmap {
|
||||
window := t / 256
|
||||
length := (t-window*256)/8 + 1
|
||||
if window > lastwindow && lastlength != 0 { // New window, jump to the new offset
|
||||
l += int(lastlength) + 2
|
||||
lastlength = 0
|
||||
}
|
||||
if window < lastwindow || length < lastlength {
|
||||
// packDataNsec would return Error{err: "nsec bits out of order"} here, but
|
||||
// when computing the length, we want do be liberal.
|
||||
continue
|
||||
}
|
||||
lastwindow, lastlength = window, length
|
||||
}
|
||||
l += int(lastlength) + 2
|
||||
return l
|
||||
}
|
||||
|
||||
func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) {
|
||||
if len(bitmap) == 0 {
|
||||
return off, nil
|
||||
}
|
||||
if off > len(msg) {
|
||||
return off, &Error{err: "overflow packing nsec"}
|
||||
}
|
||||
toZero := msg[off:]
|
||||
if maxLen := typeBitMapLen(bitmap); maxLen < len(toZero) {
|
||||
toZero = toZero[:maxLen]
|
||||
}
|
||||
for i := range toZero {
|
||||
toZero[i] = 0
|
||||
}
|
||||
var lastwindow, lastlength uint16
|
||||
for j := 0; j < len(bitmap); j++ {
|
||||
t := bitmap[j]
|
||||
for _, t := range bitmap {
|
||||
window := t / 256
|
||||
length := (t-window*256)/8 + 1
|
||||
if window > lastwindow && lastlength != 0 { // New window, jump to the new offset
|
||||
|
@ -599,13 +587,72 @@ func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) {
|
|||
// Setting the octets length
|
||||
msg[off+1] = byte(length)
|
||||
// Setting the bit value for the type in the right octet
|
||||
msg[off+1+int(length)] |= byte(1 << (7 - (t % 8)))
|
||||
msg[off+1+int(length)] |= byte(1 << (7 - t%8))
|
||||
lastwindow, lastlength = window, length
|
||||
}
|
||||
off += int(lastlength) + 2
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func unpackDataSVCB(msg []byte, off int) ([]SVCBKeyValue, int, error) {
|
||||
var xs []SVCBKeyValue
|
||||
var code uint16
|
||||
var length uint16
|
||||
var err error
|
||||
for off < len(msg) {
|
||||
code, off, err = unpackUint16(msg, off)
|
||||
if err != nil {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking SVCB"}
|
||||
}
|
||||
length, off, err = unpackUint16(msg, off)
|
||||
if err != nil || off+int(length) > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking SVCB"}
|
||||
}
|
||||
e := makeSVCBKeyValue(SVCBKey(code))
|
||||
if e == nil {
|
||||
return nil, len(msg), &Error{err: "bad SVCB key"}
|
||||
}
|
||||
if err := e.unpack(msg[off : off+int(length)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
if len(xs) > 0 && e.Key() <= xs[len(xs)-1].Key() {
|
||||
return nil, len(msg), &Error{err: "SVCB keys not in strictly increasing order"}
|
||||
}
|
||||
xs = append(xs, e)
|
||||
off += int(length)
|
||||
}
|
||||
return xs, off, nil
|
||||
}
|
||||
|
||||
func packDataSVCB(pairs []SVCBKeyValue, msg []byte, off int) (int, error) {
|
||||
pairs = append([]SVCBKeyValue(nil), pairs...)
|
||||
sort.Slice(pairs, func(i, j int) bool {
|
||||
return pairs[i].Key() < pairs[j].Key()
|
||||
})
|
||||
prev := svcb_RESERVED
|
||||
for _, el := range pairs {
|
||||
if el.Key() == prev {
|
||||
return len(msg), &Error{err: "repeated SVCB keys are not allowed"}
|
||||
}
|
||||
prev = el.Key()
|
||||
packed, err := el.pack()
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
off, err = packUint16(uint16(el.Key()), msg, off)
|
||||
if err != nil {
|
||||
return len(msg), &Error{err: "overflow packing SVCB"}
|
||||
}
|
||||
off, err = packUint16(uint16(len(packed)), msg, off)
|
||||
if err != nil || off+len(packed) > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing SVCB"}
|
||||
}
|
||||
copy(msg[off:off+len(packed)], packed)
|
||||
off += len(packed)
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func unpackDataDomainNames(msg []byte, off, end int) ([]string, int, error) {
|
||||
var (
|
||||
servers []string
|
||||
|
@ -625,13 +672,141 @@ func unpackDataDomainNames(msg []byte, off, end int) ([]string, int, error) {
|
|||
return servers, off, nil
|
||||
}
|
||||
|
||||
func packDataDomainNames(names []string, msg []byte, off int, compression map[string]int, compress bool) (int, error) {
|
||||
func packDataDomainNames(names []string, msg []byte, off int, compression compressionMap, compress bool) (int, error) {
|
||||
var err error
|
||||
for j := 0; j < len(names); j++ {
|
||||
off, err = PackDomainName(names[j], msg, off, compression, false && compress)
|
||||
for _, name := range names {
|
||||
off, err = packDomainName(name, msg, off, compression, compress)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func packDataApl(data []APLPrefix, msg []byte, off int) (int, error) {
|
||||
var err error
|
||||
for i := range data {
|
||||
off, err = packDataAplPrefix(&data[i], msg, off)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func packDataAplPrefix(p *APLPrefix, msg []byte, off int) (int, error) {
|
||||
if len(p.Network.IP) != len(p.Network.Mask) {
|
||||
return len(msg), &Error{err: "address and mask lengths don't match"}
|
||||
}
|
||||
|
||||
var err error
|
||||
prefix, _ := p.Network.Mask.Size()
|
||||
addr := p.Network.IP.Mask(p.Network.Mask)[:(prefix+7)/8]
|
||||
|
||||
switch len(p.Network.IP) {
|
||||
case net.IPv4len:
|
||||
off, err = packUint16(1, msg, off)
|
||||
case net.IPv6len:
|
||||
off, err = packUint16(2, msg, off)
|
||||
default:
|
||||
err = &Error{err: "unrecognized address family"}
|
||||
}
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
|
||||
off, err = packUint8(uint8(prefix), msg, off)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
|
||||
var n uint8
|
||||
if p.Negation {
|
||||
n = 0x80
|
||||
}
|
||||
|
||||
// trim trailing zero bytes as specified in RFC3123 Sections 4.1 and 4.2.
|
||||
i := len(addr) - 1
|
||||
for ; i >= 0 && addr[i] == 0; i-- {
|
||||
}
|
||||
addr = addr[:i+1]
|
||||
|
||||
adflen := uint8(len(addr)) & 0x7f
|
||||
off, err = packUint8(n|adflen, msg, off)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
|
||||
if off+len(addr) > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing APL prefix"}
|
||||
}
|
||||
off += copy(msg[off:], addr)
|
||||
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func unpackDataApl(msg []byte, off int) ([]APLPrefix, int, error) {
|
||||
var result []APLPrefix
|
||||
for off < len(msg) {
|
||||
prefix, end, err := unpackDataAplPrefix(msg, off)
|
||||
if err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
off = end
|
||||
result = append(result, prefix)
|
||||
}
|
||||
return result, off, nil
|
||||
}
|
||||
|
||||
func unpackDataAplPrefix(msg []byte, off int) (APLPrefix, int, error) {
|
||||
family, off, err := unpackUint16(msg, off)
|
||||
if err != nil {
|
||||
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"}
|
||||
}
|
||||
prefix, off, err := unpackUint8(msg, off)
|
||||
if err != nil {
|
||||
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"}
|
||||
}
|
||||
nlen, off, err := unpackUint8(msg, off)
|
||||
if err != nil {
|
||||
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"}
|
||||
}
|
||||
|
||||
var ip []byte
|
||||
switch family {
|
||||
case 1:
|
||||
ip = make([]byte, net.IPv4len)
|
||||
case 2:
|
||||
ip = make([]byte, net.IPv6len)
|
||||
default:
|
||||
return APLPrefix{}, len(msg), &Error{err: "unrecognized APL address family"}
|
||||
}
|
||||
if int(prefix) > 8*len(ip) {
|
||||
return APLPrefix{}, len(msg), &Error{err: "APL prefix too long"}
|
||||
}
|
||||
afdlen := int(nlen & 0x7f)
|
||||
if afdlen > len(ip) {
|
||||
return APLPrefix{}, len(msg), &Error{err: "APL length too long"}
|
||||
}
|
||||
if off+afdlen > len(msg) {
|
||||
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL address"}
|
||||
}
|
||||
|
||||
// Address MUST NOT contain trailing zero bytes per RFC3123 Sections 4.1 and 4.2.
|
||||
off += copy(ip, msg[off:off+afdlen])
|
||||
if afdlen > 0 {
|
||||
last := ip[afdlen-1]
|
||||
if last == 0 {
|
||||
return APLPrefix{}, len(msg), &Error{err: "extra APL address bits"}
|
||||
}
|
||||
}
|
||||
ipnet := net.IPNet{
|
||||
IP: ip,
|
||||
Mask: net.CIDRMask(int(prefix), 8*len(ip)),
|
||||
}
|
||||
|
||||
return APLPrefix{
|
||||
Negation: (nlen & 0x80) != 0,
|
||||
Network: ipnet,
|
||||
}, off, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,593 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestPacketDataNsec tests generated using fuzz.go and with a message pack
|
||||
// containing the following bytes: 0000\x00\x00000000\x00\x002000000\x0060000\x00\x130000000000000000000"
|
||||
// That bytes sequence created the overflow error and further permutations of that sequence were able to trigger
|
||||
// the other code paths.
|
||||
func TestPackDataNsec(t *testing.T) {
|
||||
type args struct {
|
||||
bitmap []uint16
|
||||
msg []byte
|
||||
off int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantOff int
|
||||
wantBytes []byte
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "overflow",
|
||||
args: args{
|
||||
bitmap: []uint16{
|
||||
8962, 8963, 8970, 8971, 8978, 8979,
|
||||
8986, 8987, 8994, 8995, 9002, 9003,
|
||||
9010, 9011, 9018, 9019, 9026, 9027,
|
||||
9034, 9035, 9042, 9043, 9050, 9051,
|
||||
9058, 9059, 9066,
|
||||
},
|
||||
msg: []byte{
|
||||
48, 48, 48, 48, 0, 0, 0,
|
||||
1, 0, 0, 0, 0, 0, 0, 50,
|
||||
48, 48, 48, 48, 48, 48,
|
||||
0, 54, 48, 48, 48, 48,
|
||||
0, 19, 48, 48,
|
||||
},
|
||||
off: 48,
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "dns: overflow packing nsec",
|
||||
wantOff: 48,
|
||||
},
|
||||
{
|
||||
name: "disordered nsec bits",
|
||||
args: args{
|
||||
bitmap: []uint16{
|
||||
8962,
|
||||
1,
|
||||
},
|
||||
msg: []byte{
|
||||
48, 48, 48, 48, 0, 0, 0, 1, 0, 0, 0, 0,
|
||||
0, 0, 50, 48, 48, 48, 48, 48, 48, 0, 54, 48,
|
||||
48, 48, 48, 0, 19, 48, 48, 48, 48, 48, 48, 0,
|
||||
0, 0, 1, 0, 0, 0, 0, 0, 0, 50, 48, 48,
|
||||
48, 48, 48, 48, 0, 54, 48, 48, 48, 48, 0, 19,
|
||||
48, 48, 48, 48, 48, 48, 0, 0, 0, 1, 0, 0,
|
||||
0, 0, 0, 0, 50, 48, 48, 48, 48, 48, 48, 0,
|
||||
54, 48, 48, 48, 48, 0, 19, 48, 48, 48, 48, 48,
|
||||
48, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 50,
|
||||
48, 48, 48, 48, 48, 48, 0, 54, 48, 48, 48, 48,
|
||||
0, 19, 48, 48, 48, 48, 48, 48, 0, 0, 0, 1,
|
||||
0, 0, 0, 0, 0, 0, 50, 48, 48, 48, 48, 48,
|
||||
48, 0, 54, 48, 48, 48, 48, 0, 19, 48, 48,
|
||||
},
|
||||
off: 0,
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "dns: nsec bits out of order",
|
||||
wantOff: 155,
|
||||
},
|
||||
{
|
||||
name: "simple message with only one window",
|
||||
args: args{
|
||||
bitmap: []uint16{
|
||||
1,
|
||||
},
|
||||
msg: []byte{
|
||||
48, 48, 48, 48, 0, 0,
|
||||
0, 1, 0, 0, 0, 0,
|
||||
0, 0, 50, 48, 48, 48,
|
||||
48, 48, 48, 0, 54, 48,
|
||||
48, 48, 48, 0, 19, 48, 48,
|
||||
},
|
||||
off: 0,
|
||||
},
|
||||
wantErr: false,
|
||||
wantOff: 3,
|
||||
wantBytes: []byte{0, 1, 64},
|
||||
},
|
||||
{
|
||||
name: "multiple types",
|
||||
args: args{
|
||||
bitmap: []uint16{
|
||||
TypeNS, TypeSOA, TypeRRSIG, TypeDNSKEY, TypeNSEC3PARAM,
|
||||
},
|
||||
msg: []byte{
|
||||
48, 48, 48, 48, 0, 0,
|
||||
0, 1, 0, 0, 0, 0,
|
||||
0, 0, 50, 48, 48, 48,
|
||||
48, 48, 48, 0, 54, 48,
|
||||
48, 48, 48, 0, 19, 48, 48,
|
||||
},
|
||||
off: 0,
|
||||
},
|
||||
wantErr: false,
|
||||
wantOff: 9,
|
||||
wantBytes: []byte{0, 7, 34, 0, 0, 0, 0, 2, 144},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotOff, err := packDataNsec(tt.args.bitmap, tt.args.msg, tt.args.off)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("packDataNsec() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if err != nil && tt.wantErrMsg != err.Error() {
|
||||
t.Errorf("packDataNsec() error msg = %v, wantErrMsg %v", err.Error(), tt.wantErrMsg)
|
||||
return
|
||||
}
|
||||
if gotOff != tt.wantOff {
|
||||
t.Errorf("packDataNsec() = %v, want off %v", gotOff, tt.wantOff)
|
||||
}
|
||||
if err == nil && tt.args.off < len(tt.args.msg) && gotOff < len(tt.args.msg) {
|
||||
if want, got := tt.wantBytes, tt.args.msg[tt.args.off:gotOff]; !bytes.Equal(got, want) {
|
||||
t.Errorf("packDataNsec() = %v, want bytes %v", got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackDataNsecDirtyBuffer(t *testing.T) {
|
||||
zeroBuf := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
dirtyBuf := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
off1, _ := packDataNsec([]uint16{TypeNS, TypeSOA, TypeRRSIG}, zeroBuf, 0)
|
||||
off2, _ := packDataNsec([]uint16{TypeNS, TypeSOA, TypeRRSIG}, dirtyBuf, 0)
|
||||
if off1 != off2 {
|
||||
t.Errorf("off1 %v != off2 %v", off1, off2)
|
||||
}
|
||||
if !bytes.Equal(zeroBuf[:off1], dirtyBuf[:off2]) {
|
||||
t.Errorf("dirty buffer differs from zero buffer: %v, %v", zeroBuf[:off1], dirtyBuf[:off2])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPackDataNsec(b *testing.B) {
|
||||
benches := []struct {
|
||||
name string
|
||||
types []uint16
|
||||
}{
|
||||
{"empty", nil},
|
||||
{"typical", []uint16{TypeNS, TypeSOA, TypeRRSIG, TypeDNSKEY, TypeNSEC3PARAM}},
|
||||
{"multiple_windows", []uint16{1, 300, 350, 10000, 20000}},
|
||||
}
|
||||
for _, bb := range benches {
|
||||
b.Run(bb.name, func(b *testing.B) {
|
||||
buf := make([]byte, 100)
|
||||
for n := 0; n < b.N; n++ {
|
||||
packDataNsec(bb.types, buf, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestUnpackString(t *testing.T) {
|
||||
msg := []byte("\x00abcdef\x0f\\\"ghi\x04mmm\x7f")
|
||||
msg[0] = byte(len(msg) - 1)
|
||||
|
||||
got, _, err := unpackString(msg, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if want := `abcdef\015\\\"ghi\004mmm\127`; want != got {
|
||||
t.Errorf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnpackString(b *testing.B) {
|
||||
b.Run("Escaped", func(b *testing.B) {
|
||||
msg := []byte("\x00abcdef\x0f\\\"ghi\x04mmm")
|
||||
msg[0] = byte(len(msg) - 1)
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
got, _, err := unpackString(msg, 0)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if want := `abcdef\015\\\"ghi\004mmm`; want != got {
|
||||
b.Errorf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.Run("Unescaped", func(b *testing.B) {
|
||||
msg := []byte("\x00large.example.com")
|
||||
msg[0] = byte(len(msg) - 1)
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
got, _, err := unpackString(msg, 0)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if want := "large.example.com"; want != got {
|
||||
b.Errorf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackDataAplPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
negation bool
|
||||
ip net.IP
|
||||
mask net.IPMask
|
||||
expect []byte
|
||||
}{
|
||||
{
|
||||
"1:192.0.2.0/24",
|
||||
false,
|
||||
net.ParseIP("192.0.2.0").To4(),
|
||||
net.CIDRMask(24, 32),
|
||||
[]byte{0x00, 0x01, 0x18, 0x03, 192, 0, 2},
|
||||
},
|
||||
{
|
||||
"2:2001:db8:cafe::0/48",
|
||||
false,
|
||||
net.ParseIP("2001:db8:cafe::"),
|
||||
net.CIDRMask(48, 128),
|
||||
[]byte{0x00, 0x02, 0x30, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe},
|
||||
},
|
||||
{
|
||||
"with trailing zero bytes 2:2001:db8:cafe::0/64",
|
||||
false,
|
||||
net.ParseIP("2001:db8:cafe::"),
|
||||
net.CIDRMask(64, 128),
|
||||
[]byte{0x00, 0x02, 0x40, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe},
|
||||
},
|
||||
{
|
||||
"no non-zero bytes 2::/16",
|
||||
false,
|
||||
net.ParseIP("::"),
|
||||
net.CIDRMask(16, 128),
|
||||
[]byte{0x00, 0x02, 0x10, 0x00},
|
||||
},
|
||||
{
|
||||
"!2:2001:db8::/32",
|
||||
true,
|
||||
net.ParseIP("2001:db8::"),
|
||||
net.CIDRMask(32, 128),
|
||||
[]byte{0x00, 0x02, 0x20, 0x84, 0x20, 0x01, 0x0d, 0xb8},
|
||||
},
|
||||
{
|
||||
"normalize 1:198.51.103.255/22",
|
||||
false,
|
||||
net.ParseIP("198.51.103.255").To4(),
|
||||
net.CIDRMask(22, 32),
|
||||
[]byte{0x00, 0x01, 0x16, 0x03, 198, 51, 100}, // 1:198.51.100.0/22
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ap := &APLPrefix{
|
||||
Negation: tt.negation,
|
||||
Network: net.IPNet{IP: tt.ip, Mask: tt.mask},
|
||||
}
|
||||
out := make([]byte, 16)
|
||||
off, err := packDataAplPrefix(ap, out, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %q", err)
|
||||
}
|
||||
if !bytes.Equal(tt.expect, out[:off]) {
|
||||
t.Fatalf("expected output %02x, got %02x", tt.expect, out[:off])
|
||||
}
|
||||
// Make sure the packed bytes would be accepted by its own unpack
|
||||
_, _, err = unpackDataAplPrefix(out, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %q", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackDataAplPrefix_Failures(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ip net.IP
|
||||
mask net.IPMask
|
||||
}{
|
||||
{
|
||||
"family mismatch",
|
||||
net.ParseIP("2001:db8::"),
|
||||
net.CIDRMask(24, 32),
|
||||
},
|
||||
{
|
||||
"unrecognized family",
|
||||
[]byte{0x42},
|
||||
[]byte{0xff},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ap := &APLPrefix{Network: net.IPNet{IP: tt.ip, Mask: tt.mask}}
|
||||
msg := make([]byte, 16)
|
||||
off, err := packDataAplPrefix(ap, msg, 0)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got none")
|
||||
}
|
||||
if off != len(msg) {
|
||||
t.Fatalf("expected %d, got %d", len(msg), off)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackDataAplPrefix_BufferBounds(t *testing.T) {
|
||||
ap := &APLPrefix{
|
||||
Negation: false,
|
||||
Network: net.IPNet{
|
||||
IP: net.ParseIP("2001:db8::"),
|
||||
Mask: net.CIDRMask(32, 128),
|
||||
},
|
||||
}
|
||||
wire := []byte{0x00, 0x02, 0x20, 0x04, 0x20, 0x01, 0x0d, 0xb8}
|
||||
|
||||
t.Run("small", func(t *testing.T) {
|
||||
msg := make([]byte, len(wire))
|
||||
_, err := packDataAplPrefix(ap, msg, 1) // offset
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got none")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("exact fit", func(t *testing.T) {
|
||||
msg := make([]byte, len(wire))
|
||||
off, err := packDataAplPrefix(ap, msg, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %q", err)
|
||||
}
|
||||
if !bytes.Equal(wire, msg[:off]) {
|
||||
t.Fatalf("expected %02x, got %02x", wire, msg[:off])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackDataApl(t *testing.T) {
|
||||
in := []APLPrefix{
|
||||
{
|
||||
Negation: true,
|
||||
Network: net.IPNet{
|
||||
IP: net.ParseIP("198.51.0.0").To4(),
|
||||
Mask: net.CIDRMask(16, 32),
|
||||
},
|
||||
},
|
||||
{
|
||||
Negation: false,
|
||||
Network: net.IPNet{
|
||||
IP: net.ParseIP("2001:db8:beef::"),
|
||||
Mask: net.CIDRMask(48, 128),
|
||||
},
|
||||
},
|
||||
}
|
||||
expect := []byte{
|
||||
// 1:192.51.0.0/16
|
||||
0x00, 0x01, 0x10, 0x82, 0xc6, 0x33,
|
||||
// 2:2001:db8:beef::0/48
|
||||
0x00, 0x02, 0x30, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xbe, 0xef,
|
||||
}
|
||||
|
||||
msg := make([]byte, 32)
|
||||
off, err := packDataApl(in, msg, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %q", err)
|
||||
}
|
||||
if !bytes.Equal(expect, msg[:off]) {
|
||||
t.Fatalf("expected %02x, got %02x", expect, msg[:off])
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnpackDataAplPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
wire []byte
|
||||
negation bool
|
||||
ip net.IP
|
||||
mask net.IPMask
|
||||
}{
|
||||
{
|
||||
"1:192.0.2.0/24",
|
||||
[]byte{0x00, 0x01, 0x18, 0x03, 192, 0, 2},
|
||||
false,
|
||||
net.ParseIP("192.0.2.0").To4(),
|
||||
net.CIDRMask(24, 32),
|
||||
},
|
||||
{
|
||||
"2:2001:db8::/32",
|
||||
[]byte{0x00, 0x02, 0x20, 0x04, 0x20, 0x01, 0x0d, 0xb8},
|
||||
false,
|
||||
net.ParseIP("2001:db8::"),
|
||||
net.CIDRMask(32, 128),
|
||||
},
|
||||
{
|
||||
"!2:2001:db8:8000::/33",
|
||||
[]byte{0x00, 0x02, 0x21, 0x85, 0x20, 0x01, 0x0d, 0xb8, 0x80},
|
||||
true,
|
||||
net.ParseIP("2001:db8:8000::"),
|
||||
net.CIDRMask(33, 128),
|
||||
},
|
||||
{
|
||||
"1:0.0.0.0/0",
|
||||
[]byte{0x00, 0x01, 0x00, 0x00},
|
||||
false,
|
||||
net.ParseIP("0.0.0.0").To4(),
|
||||
net.CIDRMask(0, 32),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, off, err := unpackDataAplPrefix(tt.wire, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %q", err)
|
||||
}
|
||||
if off != len(tt.wire) {
|
||||
t.Fatalf("expected offset %d, got %d", len(tt.wire), off)
|
||||
}
|
||||
if got.Negation != tt.negation {
|
||||
t.Errorf("expected negation %v, got %v", tt.negation, got.Negation)
|
||||
}
|
||||
if !bytes.Equal(got.Network.IP, tt.ip) {
|
||||
t.Errorf("expected IP %02x, got %02x", tt.ip, got.Network.IP)
|
||||
}
|
||||
if !bytes.Equal(got.Network.Mask, tt.mask) {
|
||||
t.Errorf("expected mask %02x, got %02x", tt.mask, got.Network.Mask)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnpackDataAplPrefix_Errors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
wire []byte
|
||||
want string
|
||||
}{
|
||||
{
|
||||
"incomplete header",
|
||||
[]byte{0x00, 0x01, 0x18},
|
||||
"dns: overflow unpacking APL prefix",
|
||||
},
|
||||
{
|
||||
"unrecognized family",
|
||||
[]byte{0x00, 0x03, 0x00, 0x00},
|
||||
"dns: unrecognized APL address family",
|
||||
},
|
||||
{
|
||||
"prefix too large for family IPv4",
|
||||
[]byte{0x00, 0x01, 0x21, 0x04, 192, 0, 2, 0},
|
||||
"dns: APL prefix too long",
|
||||
},
|
||||
{
|
||||
"prefix too large for family IPv6",
|
||||
[]byte{0x00, 0x02, 0x81, 0x85, 0x20, 0x01, 0x0d, 0xb8, 0x80},
|
||||
"dns: APL prefix too long",
|
||||
},
|
||||
{
|
||||
"afdlen too long for address family IPv4",
|
||||
[]byte{0x00, 0x01, 22, 0x05, 192, 0, 2, 0, 0},
|
||||
"dns: APL length too long",
|
||||
},
|
||||
{
|
||||
"overflow unpacking APL address",
|
||||
[]byte{0x00, 0x01, 0x10, 0x02, 192},
|
||||
"dns: overflow unpacking APL address",
|
||||
},
|
||||
{
|
||||
"address included trailing zeros",
|
||||
[]byte{0x00, 0x01, 0x10, 0x04, 192, 0, 2, 0},
|
||||
"dns: extra APL address bits",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, _, err := unpackDataAplPrefix(tt.wire, 0)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got none")
|
||||
}
|
||||
|
||||
if err.Error() != tt.want {
|
||||
t.Errorf("expected %s, got %s", tt.want, err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnpackDataApl(t *testing.T) {
|
||||
wire := []byte{
|
||||
// 2:2001:db8:cafe:4200:0/56
|
||||
0x00, 0x02, 0x38, 0x07, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe, 0x42,
|
||||
// 1:192.0.2.0/24
|
||||
0x00, 0x01, 0x18, 0x03, 192, 0, 2,
|
||||
// !1:192.0.2.128/25
|
||||
0x00, 0x01, 0x19, 0x84, 192, 0, 2, 128,
|
||||
// 1:10.0.0.0/24
|
||||
0x00, 0x01, 0x18, 0x01, 0x0a,
|
||||
// !1:10.0.0.1/32
|
||||
0x00, 0x01, 0x20, 0x84, 0x0a, 0, 0, 1,
|
||||
// !1:0.0.0.0/0
|
||||
0x00, 0x01, 0x00, 0x80,
|
||||
// 2::0/0
|
||||
0x00, 0x02, 0x00, 0x00,
|
||||
}
|
||||
expect := []APLPrefix{
|
||||
{
|
||||
Negation: false,
|
||||
Network: net.IPNet{
|
||||
IP: net.ParseIP("2001:db8:cafe:4200::"),
|
||||
Mask: net.CIDRMask(56, 128),
|
||||
},
|
||||
},
|
||||
{
|
||||
Negation: false,
|
||||
Network: net.IPNet{
|
||||
IP: net.ParseIP("192.0.2.0").To4(),
|
||||
Mask: net.CIDRMask(24, 32),
|
||||
},
|
||||
},
|
||||
{
|
||||
Negation: true,
|
||||
Network: net.IPNet{
|
||||
IP: net.ParseIP("192.0.2.128").To4(),
|
||||
Mask: net.CIDRMask(25, 32),
|
||||
},
|
||||
},
|
||||
{
|
||||
Negation: false,
|
||||
Network: net.IPNet{
|
||||
IP: net.ParseIP("10.0.0.0").To4(),
|
||||
Mask: net.CIDRMask(24, 32),
|
||||
},
|
||||
},
|
||||
{
|
||||
Negation: true,
|
||||
Network: net.IPNet{
|
||||
IP: net.ParseIP("10.0.0.1").To4(),
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
},
|
||||
},
|
||||
{
|
||||
Negation: true,
|
||||
Network: net.IPNet{
|
||||
IP: net.ParseIP("0.0.0.0").To4(),
|
||||
Mask: net.CIDRMask(0, 32),
|
||||
},
|
||||
},
|
||||
{
|
||||
Negation: false,
|
||||
Network: net.IPNet{
|
||||
IP: net.ParseIP("::").To16(),
|
||||
Mask: net.CIDRMask(0, 128),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, off, err := unpackDataApl(wire, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %q", err)
|
||||
}
|
||||
if off != len(wire) {
|
||||
t.Fatalf("expected offset %d, got %d", len(wire), off)
|
||||
}
|
||||
if len(got) != len(expect) {
|
||||
t.Fatalf("expected %d prefixes, got %d", len(expect), len(got))
|
||||
}
|
||||
for i, exp := range expect {
|
||||
if got[i].Negation != exp.Negation {
|
||||
t.Errorf("[%d] expected negation %v, got %v", i, exp.Negation, got[i].Negation)
|
||||
}
|
||||
if !bytes.Equal(got[i].Network.IP, exp.Network.IP) {
|
||||
t.Errorf("[%d] expected IP %02x, got %02x", i, exp.Network.IP, got[i].Network.IP)
|
||||
}
|
||||
if !bytes.Equal(got[i].Network.Mask, exp.Network.Mask) {
|
||||
t.Errorf("[%d] expected mask %02x, got %02x", i, exp.Network.Mask, got[i].Network.Mask)
|
||||
}
|
||||
}
|
||||
}
|
228
msg_test.go
228
msg_test.go
|
@ -8,14 +8,12 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
maxPrintableLabel = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789x"
|
||||
tooLongLabel = maxPrintableLabel + "x"
|
||||
)
|
||||
const maxPrintableLabel = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789x"
|
||||
|
||||
var (
|
||||
longDomain = maxPrintableLabel[:53] + strings.TrimSuffix(
|
||||
strings.Join([]string{".", ".", ".", ".", "."}, maxPrintableLabel[:49]), ".")
|
||||
|
||||
reChar = regexp.MustCompile(`.`)
|
||||
i = -1
|
||||
maxUnprintableLabel = reChar.ReplaceAllStringFunc(maxPrintableLabel, func(ch string) string {
|
||||
|
@ -24,8 +22,105 @@ var (
|
|||
}
|
||||
return fmt.Sprintf("\\%03d", i)
|
||||
})
|
||||
|
||||
// These are the longest possible domain names in presentation format.
|
||||
longestDomain = maxPrintableLabel[:61] + strings.Join([]string{".", ".", ".", "."}, maxPrintableLabel)
|
||||
longestUnprintableDomain = maxUnprintableLabel[:61*4] + strings.Join([]string{".", ".", ".", "."}, maxUnprintableLabel)
|
||||
)
|
||||
|
||||
func TestPackNoSideEffect(t *testing.T) {
|
||||
m := new(Msg)
|
||||
m.SetQuestion(Fqdn("example.com."), TypeNS)
|
||||
|
||||
a := new(Msg)
|
||||
o := &OPT{
|
||||
Hdr: RR_Header{
|
||||
Name: ".",
|
||||
Rrtype: TypeOPT,
|
||||
},
|
||||
}
|
||||
o.SetUDPSize(DefaultMsgSize)
|
||||
|
||||
a.Extra = append(a.Extra, o)
|
||||
a.SetRcode(m, RcodeBadVers)
|
||||
|
||||
a.Pack()
|
||||
if a.Rcode != RcodeBadVers {
|
||||
t.Errorf("after pack: Rcode is expected to be BADVERS")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackExtendedBadCookie(t *testing.T) {
|
||||
m := new(Msg)
|
||||
m.SetQuestion(Fqdn("example.com."), TypeNS)
|
||||
|
||||
a := new(Msg)
|
||||
a.SetReply(m)
|
||||
o := &OPT{
|
||||
Hdr: RR_Header{
|
||||
Name: ".",
|
||||
Rrtype: TypeOPT,
|
||||
},
|
||||
}
|
||||
o.SetUDPSize(DefaultMsgSize)
|
||||
a.Extra = append(a.Extra, o)
|
||||
|
||||
a.SetRcode(m, RcodeBadCookie)
|
||||
|
||||
edns0 := a.IsEdns0()
|
||||
if edns0 == nil {
|
||||
t.Fatal("Expected OPT RR")
|
||||
}
|
||||
// SetExtendedRcode is only called as part of `Pack()`, hence at this stage,
|
||||
// the OPT RR is not set yet.
|
||||
if edns0.ExtendedRcode() == RcodeBadCookie&0xFFFFFFF0 {
|
||||
t.Errorf("ExtendedRcode is expected to not be BADCOOKIE before Pack")
|
||||
}
|
||||
|
||||
a.Pack()
|
||||
|
||||
edns0 = a.IsEdns0()
|
||||
if edns0 == nil {
|
||||
t.Fatal("Expected OPT RR")
|
||||
}
|
||||
|
||||
if edns0.ExtendedRcode() != RcodeBadCookie&0xFFFFFFF0 {
|
||||
t.Errorf("ExtendedRcode is expected to be BADCOOKIE after Pack")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnPackExtendedRcode(t *testing.T) {
|
||||
m := new(Msg)
|
||||
m.SetQuestion(Fqdn("example.com."), TypeNS)
|
||||
|
||||
a := new(Msg)
|
||||
a.SetReply(m)
|
||||
o := &OPT{
|
||||
Hdr: RR_Header{
|
||||
Name: ".",
|
||||
Rrtype: TypeOPT,
|
||||
},
|
||||
}
|
||||
o.SetUDPSize(DefaultMsgSize)
|
||||
a.Extra = append(a.Extra, o)
|
||||
|
||||
a.SetRcode(m, RcodeBadCookie)
|
||||
|
||||
packed, err := a.Pack()
|
||||
if err != nil {
|
||||
t.Fatalf("Could not unpack %v", a)
|
||||
}
|
||||
|
||||
unpacked := new(Msg)
|
||||
if err := unpacked.Unpack(packed); err != nil {
|
||||
t.Fatalf("Failed to unpack message")
|
||||
}
|
||||
|
||||
if unpacked.Rcode != RcodeBadCookie {
|
||||
t.Fatalf("Rcode should be matching RcodeBadCookie (%d), got (%d)", RcodeBadCookie, unpacked.Rcode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnpackDomainName(t *testing.T) {
|
||||
var cases = []struct {
|
||||
label string
|
||||
|
@ -38,19 +133,19 @@ func TestUnpackDomainName(t *testing.T) {
|
|||
".",
|
||||
""},
|
||||
{"long label",
|
||||
string(63) + maxPrintableLabel + "\x00",
|
||||
"?" + maxPrintableLabel + "\x00",
|
||||
maxPrintableLabel + ".",
|
||||
""},
|
||||
{"unprintable label",
|
||||
string(63) + regexp.MustCompile(`\\[0-9]+`).ReplaceAllStringFunc(maxUnprintableLabel,
|
||||
"?" + regexp.MustCompile(`\\[0-9]+`).ReplaceAllStringFunc(maxUnprintableLabel,
|
||||
func(escape string) string {
|
||||
n, _ := strconv.ParseInt(escape[1:], 10, 8)
|
||||
return string(n)
|
||||
return string(rune(n))
|
||||
}) + "\x00",
|
||||
maxUnprintableLabel + ".",
|
||||
""},
|
||||
{"long domain",
|
||||
string(53) + strings.Replace(longDomain, ".", string(49), -1) + "\x00",
|
||||
"5" + strings.Replace(longDomain, ".", "1", -1) + "\x00",
|
||||
longDomain + ".",
|
||||
""},
|
||||
{"compression pointer",
|
||||
|
@ -58,10 +153,9 @@ func TestUnpackDomainName(t *testing.T) {
|
|||
"\x03foo" + "\x05\x03com\x00" + "\x07example" + "\xC0\x05",
|
||||
"foo.\\003com\\000.example.com.",
|
||||
""},
|
||||
|
||||
{"too long domain",
|
||||
string(54) + "x" + strings.Replace(longDomain, ".", string(49), -1) + "\x00",
|
||||
"x" + longDomain + ".",
|
||||
"6" + "x" + strings.Replace(longDomain, ".", "1", -1) + "\x00",
|
||||
"",
|
||||
ErrLongDomain.Error()},
|
||||
{"too long by pointer",
|
||||
// a matryoshka doll name to get over 255 octets after expansion via internal pointers
|
||||
|
@ -103,10 +197,12 @@ func TestUnpackDomainName(t *testing.T) {
|
|||
""},
|
||||
{"truncated name", "\x07example\x03", "", "dns: buffer size too small"},
|
||||
{"non-absolute name", "\x07example\x03com", "", "dns: buffer size too small"},
|
||||
{"compression pointer cycle",
|
||||
{"compression pointer cycle (too many)", "\xC0\x00", "", "dns: too many compression pointers"},
|
||||
{"compression pointer cycle (too long)",
|
||||
"\x03foo" + "\x03bar" + "\x07example" + "\xC0\x04",
|
||||
"",
|
||||
"dns: too many compression pointers"},
|
||||
ErrLongDomain.Error()},
|
||||
{"forward compression pointer", "\x02\xC0\xFF\xC0\x01", "", ErrBuf.Error()},
|
||||
{"reserved compression pointer 0b10", "\x07example\x80", "", "dns: bad rdata"},
|
||||
{"reserved compression pointer 0b01", "\x07example\x40", "", "dns: bad rdata"},
|
||||
}
|
||||
|
@ -122,3 +218,109 @@ func TestUnpackDomainName(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackDomainNameCompressionMap(t *testing.T) {
|
||||
expected := map[string]struct{}{
|
||||
`www\.this.is.\131an.example.org.`: {},
|
||||
`is.\131an.example.org.`: {},
|
||||
`\131an.example.org.`: {},
|
||||
`example.org.`: {},
|
||||
`org.`: {},
|
||||
}
|
||||
|
||||
msg := make([]byte, 256)
|
||||
for _, compress := range []bool{true, false} {
|
||||
compression := make(map[string]int)
|
||||
|
||||
_, err := PackDomainName(`www\.this.is.\131an.example.org.`, msg, 0, compression, compress)
|
||||
if err != nil {
|
||||
t.Fatalf("PackDomainName failed: %v", err)
|
||||
}
|
||||
|
||||
if !compressionMapsEqual(expected, compression) {
|
||||
t.Errorf("expected compression maps to be equal\n%s", compressionMapsDifference(expected, compression))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackDomainNameNSECTypeBitmap(t *testing.T) {
|
||||
ownername := "some-very-long-ownername.com."
|
||||
msg := &Msg{
|
||||
Compress: true,
|
||||
Answer: []RR{
|
||||
&NS{
|
||||
Hdr: RR_Header{
|
||||
Name: ownername,
|
||||
Rrtype: TypeNS,
|
||||
Class: ClassINET,
|
||||
},
|
||||
Ns: "ns1.server.com.",
|
||||
},
|
||||
&NSEC{
|
||||
Hdr: RR_Header{
|
||||
Name: ownername,
|
||||
Rrtype: TypeNSEC,
|
||||
Class: ClassINET,
|
||||
},
|
||||
NextDomain: "a.com.",
|
||||
TypeBitMap: []uint16{TypeNS, TypeNSEC},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Pack msg and then unpack into msg2
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Fatalf("msg.Pack failed: %v", err)
|
||||
}
|
||||
|
||||
var msg2 Msg
|
||||
if err := msg2.Unpack(buf); err != nil {
|
||||
t.Fatalf("msg2.Unpack failed: %v", err)
|
||||
}
|
||||
|
||||
if !IsDuplicate(msg.Answer[1], msg2.Answer[1]) {
|
||||
t.Error("message differs after packing and unpacking")
|
||||
|
||||
// Print NSEC RR for both cases
|
||||
t.Logf("expected: %v", msg.Answer[1])
|
||||
t.Logf("got: %v", msg2.Answer[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackUnpackManyCompressionPointers(t *testing.T) {
|
||||
m := new(Msg)
|
||||
m.Compress = true
|
||||
m.SetQuestion("example.org.", TypeNS)
|
||||
|
||||
for domain := "a."; len(domain) < maxDomainNameWireOctets; domain += "a." {
|
||||
m.Answer = append(m.Answer, &NS{Hdr: RR_Header{Name: domain, Rrtype: TypeNS, Class: ClassINET}, Ns: "example.org."})
|
||||
|
||||
b, err := m.Pack()
|
||||
if err != nil {
|
||||
t.Fatalf("Pack failed for %q and %d records with: %v", domain, len(m.Answer), err)
|
||||
}
|
||||
|
||||
var m2 Msg
|
||||
if err := m2.Unpack(b); err != nil {
|
||||
t.Fatalf("Unpack failed for %q and %d records with: %v", domain, len(m.Answer), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLenDynamicA(t *testing.T) {
|
||||
for _, rr := range []RR{
|
||||
testRR("example.org. A"),
|
||||
testRR("example.org. AAAA"),
|
||||
testRR("example.org. L32"),
|
||||
} {
|
||||
msg := make([]byte, Len(rr))
|
||||
off, err := PackRR(rr, msg, 0, nil, false)
|
||||
if err != nil {
|
||||
t.Fatalf("PackRR failed for %T: %v", rr, err)
|
||||
}
|
||||
if off != len(msg) {
|
||||
t.Errorf("Len(rr) wrong for %T: Len(rr) = %d, PackRR(rr) = %d", rr, len(msg), off)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
package dns
|
||||
|
||||
// Truncate ensures the reply message will fit into the requested buffer
|
||||
// size by removing records that exceed the requested size.
|
||||
//
|
||||
// It will first check if the reply fits without compression and then with
|
||||
// compression. If it won't fit with compression, Truncate then walks the
|
||||
// record adding as many records as possible without exceeding the
|
||||
// requested buffer size.
|
||||
//
|
||||
// If the message fits within the requested size without compression,
|
||||
// Truncate will set the message's Compress attribute to false. It is
|
||||
// the caller's responsibility to set it back to true if they wish to
|
||||
// compress the payload regardless of size.
|
||||
//
|
||||
// The TC bit will be set if any records were excluded from the message.
|
||||
// If the TC bit is already set on the message it will be retained.
|
||||
// TC indicates that the client should retry over TCP.
|
||||
//
|
||||
// According to RFC 2181, the TC bit should only be set if not all of the
|
||||
// "required" RRs can be included in the response. Unfortunately, we have
|
||||
// no way of knowing which RRs are required so we set the TC bit if any RR
|
||||
// had to be omitted from the response.
|
||||
//
|
||||
// The appropriate buffer size can be retrieved from the requests OPT
|
||||
// record, if present, and is transport specific otherwise. dns.MinMsgSize
|
||||
// should be used for UDP requests without an OPT record, and
|
||||
// dns.MaxMsgSize for TCP requests without an OPT record.
|
||||
func (dns *Msg) Truncate(size int) {
|
||||
if dns.IsTsig() != nil {
|
||||
// To simplify this implementation, we don't perform
|
||||
// truncation on responses with a TSIG record.
|
||||
return
|
||||
}
|
||||
|
||||
// RFC 6891 mandates that the payload size in an OPT record
|
||||
// less than 512 (MinMsgSize) bytes must be treated as equal to 512 bytes.
|
||||
//
|
||||
// For ease of use, we impose that restriction here.
|
||||
if size < MinMsgSize {
|
||||
size = MinMsgSize
|
||||
}
|
||||
|
||||
l := msgLenWithCompressionMap(dns, nil) // uncompressed length
|
||||
if l <= size {
|
||||
// Don't waste effort compressing this message.
|
||||
dns.Compress = false
|
||||
return
|
||||
}
|
||||
|
||||
dns.Compress = true
|
||||
|
||||
edns0 := dns.popEdns0()
|
||||
if edns0 != nil {
|
||||
// Account for the OPT record that gets added at the end,
|
||||
// by subtracting that length from our budget.
|
||||
//
|
||||
// The EDNS(0) OPT record must have the root domain and
|
||||
// it's length is thus unaffected by compression.
|
||||
size -= Len(edns0)
|
||||
}
|
||||
|
||||
compression := make(map[string]struct{})
|
||||
|
||||
l = headerSize
|
||||
for _, r := range dns.Question {
|
||||
l += r.len(l, compression)
|
||||
}
|
||||
|
||||
var numAnswer int
|
||||
if l < size {
|
||||
l, numAnswer = truncateLoop(dns.Answer, size, l, compression)
|
||||
}
|
||||
|
||||
var numNS int
|
||||
if l < size {
|
||||
l, numNS = truncateLoop(dns.Ns, size, l, compression)
|
||||
}
|
||||
|
||||
var numExtra int
|
||||
if l < size {
|
||||
_, numExtra = truncateLoop(dns.Extra, size, l, compression)
|
||||
}
|
||||
|
||||
// See the function documentation for when we set this.
|
||||
dns.Truncated = dns.Truncated || len(dns.Answer) > numAnswer ||
|
||||
len(dns.Ns) > numNS || len(dns.Extra) > numExtra
|
||||
|
||||
dns.Answer = dns.Answer[:numAnswer]
|
||||
dns.Ns = dns.Ns[:numNS]
|
||||
dns.Extra = dns.Extra[:numExtra]
|
||||
|
||||
if edns0 != nil {
|
||||
// Add the OPT record back onto the additional section.
|
||||
dns.Extra = append(dns.Extra, edns0)
|
||||
}
|
||||
}
|
||||
|
||||
func truncateLoop(rrs []RR, size, l int, compression map[string]struct{}) (int, int) {
|
||||
for i, r := range rrs {
|
||||
if r == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
l += r.len(l, compression)
|
||||
if l > size {
|
||||
// Return size, rather than l prior to this record,
|
||||
// to prevent any further records being added.
|
||||
return size, i
|
||||
}
|
||||
if l == size {
|
||||
return l, i + 1
|
||||
}
|
||||
}
|
||||
|
||||
return l, len(rrs)
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestTruncateAnswer(t *testing.T) {
|
||||
m := new(Msg)
|
||||
m.SetQuestion("large.example.com.", TypeSRV)
|
||||
|
||||
reply := new(Msg)
|
||||
reply.SetReply(m)
|
||||
for i := 1; i < 200; i++ {
|
||||
reply.Answer = append(reply.Answer, testRR(
|
||||
fmt.Sprintf("large.example.com. 10 IN SRV 0 0 80 10-0-0-%d.default.pod.k8s.example.com.", i)))
|
||||
}
|
||||
|
||||
reply.Truncate(MinMsgSize)
|
||||
if want, got := MinMsgSize, reply.Len(); want < got {
|
||||
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
|
||||
}
|
||||
if !reply.Truncated {
|
||||
t.Errorf("truncated bit should be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestTruncateExtra(t *testing.T) {
|
||||
m := new(Msg)
|
||||
m.SetQuestion("large.example.com.", TypeSRV)
|
||||
|
||||
reply := new(Msg)
|
||||
reply.SetReply(m)
|
||||
for i := 1; i < 200; i++ {
|
||||
reply.Extra = append(reply.Extra, testRR(
|
||||
fmt.Sprintf("large.example.com. 10 IN SRV 0 0 80 10-0-0-%d.default.pod.k8s.example.com.", i)))
|
||||
}
|
||||
|
||||
reply.Truncate(MinMsgSize)
|
||||
if want, got := MinMsgSize, reply.Len(); want < got {
|
||||
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
|
||||
}
|
||||
if !reply.Truncated {
|
||||
t.Errorf("truncated bit should be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestTruncateExtraEdns0(t *testing.T) {
|
||||
const size = 4096
|
||||
|
||||
m := new(Msg)
|
||||
m.SetQuestion("large.example.com.", TypeSRV)
|
||||
m.SetEdns0(size, true)
|
||||
|
||||
reply := new(Msg)
|
||||
reply.SetReply(m)
|
||||
for i := 1; i < 200; i++ {
|
||||
reply.Extra = append(reply.Extra, testRR(
|
||||
fmt.Sprintf("large.example.com. 10 IN SRV 0 0 80 10-0-0-%d.default.pod.k8s.example.com.", i)))
|
||||
}
|
||||
reply.SetEdns0(size, true)
|
||||
|
||||
reply.Truncate(size)
|
||||
if want, got := size, reply.Len(); want < got {
|
||||
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
|
||||
}
|
||||
if !reply.Truncated {
|
||||
t.Errorf("truncated bit should be set")
|
||||
}
|
||||
opt := reply.Extra[len(reply.Extra)-1]
|
||||
if opt.Header().Rrtype != TypeOPT {
|
||||
t.Errorf("expected last RR to be OPT")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestTruncateExtraRegression(t *testing.T) {
|
||||
const size = 2048
|
||||
|
||||
m := new(Msg)
|
||||
m.SetQuestion("large.example.com.", TypeSRV)
|
||||
m.SetEdns0(size, true)
|
||||
|
||||
reply := new(Msg)
|
||||
reply.SetReply(m)
|
||||
for i := 1; i < 33; i++ {
|
||||
reply.Answer = append(reply.Answer, testRR(
|
||||
fmt.Sprintf("large.example.com. 10 IN SRV 0 0 80 10-0-0-%d.default.pod.k8s.example.com.", i)))
|
||||
}
|
||||
for i := 1; i < 33; i++ {
|
||||
reply.Extra = append(reply.Extra, testRR(
|
||||
fmt.Sprintf("10-0-0-%d.default.pod.k8s.example.com. 10 IN A 10.0.0.%d", i, i)))
|
||||
}
|
||||
reply.SetEdns0(size, true)
|
||||
|
||||
reply.Truncate(size)
|
||||
if want, got := size, reply.Len(); want < got {
|
||||
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
|
||||
}
|
||||
if !reply.Truncated {
|
||||
t.Errorf("truncated bit should be set")
|
||||
}
|
||||
opt := reply.Extra[len(reply.Extra)-1]
|
||||
if opt.Header().Rrtype != TypeOPT {
|
||||
t.Errorf("expected last RR to be OPT")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncation(t *testing.T) {
|
||||
reply := new(Msg)
|
||||
|
||||
for i := 0; i < 61; i++ {
|
||||
reply.Answer = append(reply.Answer, testRR(fmt.Sprintf("http.service.tcp.srv.k8s.example.org. 5 IN SRV 0 0 80 10-144-230-%d.default.pod.k8s.example.org.", i)))
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
reply.Extra = append(reply.Extra, testRR(fmt.Sprintf("ip-10-10-52-5%d.subdomain.example.org. 5 IN A 10.10.52.5%d", i, i)))
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
reply.Ns = append(reply.Ns, testRR(fmt.Sprintf("srv.subdomain.example.org. 5 IN NS ip-10-10-33-6%d.subdomain.example.org.", i)))
|
||||
}
|
||||
|
||||
for bufsize := 1024; bufsize <= 4096; bufsize += 12 {
|
||||
m := new(Msg)
|
||||
m.SetQuestion("http.service.tcp.srv.k8s.example.org.", TypeSRV)
|
||||
m.SetEdns0(uint16(bufsize), true)
|
||||
|
||||
copy := reply.Copy()
|
||||
copy.SetReply(m)
|
||||
|
||||
copy.Truncate(bufsize)
|
||||
if want, got := bufsize, copy.Len(); want < got {
|
||||
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestTruncateAnswerExact(t *testing.T) {
|
||||
const size = 867 // Bit fiddly, but this hits the rl == size break clause in Truncate, 52 RRs should remain.
|
||||
|
||||
m := new(Msg)
|
||||
m.SetQuestion("large.example.com.", TypeSRV)
|
||||
m.SetEdns0(size, false)
|
||||
|
||||
reply := new(Msg)
|
||||
reply.SetReply(m)
|
||||
for i := 1; i < 200; i++ {
|
||||
reply.Answer = append(reply.Answer, testRR(fmt.Sprintf("large.example.com. 10 IN A 127.0.0.%d", i)))
|
||||
}
|
||||
|
||||
reply.Truncate(size)
|
||||
if want, got := size, reply.Len(); want < got {
|
||||
t.Errorf("message length should be below %d bytes, got %d bytes", want, got)
|
||||
}
|
||||
if expected := 52; len(reply.Answer) != expected {
|
||||
t.Errorf("wrong number of answers; expected %d, got %d", expected, len(reply.Answer))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMsgTruncate(b *testing.B) {
|
||||
const size = 2048
|
||||
|
||||
m := new(Msg)
|
||||
m.SetQuestion("example.com.", TypeA)
|
||||
m.SetEdns0(size, true)
|
||||
|
||||
reply := new(Msg)
|
||||
reply.SetReply(m)
|
||||
for i := 1; i < 33; i++ {
|
||||
reply.Answer = append(reply.Answer, testRR(
|
||||
fmt.Sprintf("large.example.com. 10 IN SRV 0 0 80 10-0-0-%d.default.pod.k8s.example.com.", i)))
|
||||
}
|
||||
for i := 1; i < 33; i++ {
|
||||
reply.Extra = append(reply.Extra, testRR(
|
||||
fmt.Sprintf("10-0-0-%d.default.pod.k8s.example.com. 10 IN A 10.0.0.%d", i, i)))
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
copy := reply.Copy()
|
||||
b.StartTimer()
|
||||
|
||||
copy.Truncate(size)
|
||||
}
|
||||
}
|
49
nsecx.go
49
nsecx.go
|
@ -2,53 +2,48 @@ package dns
|
|||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"hash"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type saltWireFmt struct {
|
||||
Salt string `dns:"size-hex"`
|
||||
}
|
||||
|
||||
// HashName hashes a string (label) according to RFC 5155. It returns the hashed string in uppercase.
|
||||
func HashName(label string, ha uint8, iter uint16, salt string) string {
|
||||
saltwire := new(saltWireFmt)
|
||||
saltwire.Salt = salt
|
||||
wire := make([]byte, DefaultMsgSize)
|
||||
n, err := packSaltWire(saltwire, wire)
|
||||
if ha != SHA1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
wireSalt := make([]byte, hex.DecodedLen(len(salt)))
|
||||
n, err := packStringHex(salt, wireSalt, 0)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
wire = wire[:n]
|
||||
wireSalt = wireSalt[:n]
|
||||
|
||||
name := make([]byte, 255)
|
||||
off, err := PackDomainName(strings.ToLower(label), name, 0, nil, false)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
name = name[:off]
|
||||
var s hash.Hash
|
||||
switch ha {
|
||||
case SHA1:
|
||||
s = sha1.New()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
s := sha1.New()
|
||||
// k = 0
|
||||
s.Write(name)
|
||||
s.Write(wire)
|
||||
s.Write(wireSalt)
|
||||
nsec3 := s.Sum(nil)
|
||||
|
||||
// k > 0
|
||||
for k := uint16(0); k < iter; k++ {
|
||||
s.Reset()
|
||||
s.Write(nsec3)
|
||||
s.Write(wire)
|
||||
s.Write(wireSalt)
|
||||
nsec3 = s.Sum(nsec3[:0])
|
||||
}
|
||||
|
||||
return toBase32(nsec3)
|
||||
}
|
||||
|
||||
// Cover returns true if a name is covered by the NSEC3 record
|
||||
// Cover returns true if a name is covered by the NSEC3 record.
|
||||
func (rr *NSEC3) Cover(name string) bool {
|
||||
nameHash := HashName(name, rr.Hash, rr.Iterations, rr.Salt)
|
||||
owner := strings.ToUpper(rr.Hdr.Name)
|
||||
|
@ -63,8 +58,10 @@ func (rr *NSEC3) Cover(name string) bool {
|
|||
}
|
||||
|
||||
nextHash := rr.NextDomain
|
||||
if ownerHash == nextHash { // empty interval
|
||||
return false
|
||||
|
||||
// if empty interval found, try cover wildcard hashes so nameHash shouldn't match with ownerHash
|
||||
if ownerHash == nextHash && nameHash != ownerHash { // empty interval
|
||||
return true
|
||||
}
|
||||
if ownerHash > nextHash { // end of zone
|
||||
if nameHash > ownerHash { // covered since there is nothing after ownerHash
|
||||
|
@ -96,11 +93,3 @@ func (rr *NSEC3) Match(name string) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func packSaltWire(sw *saltWireFmt, msg []byte) (int, error) {
|
||||
off, err := packStringHex(sw.Salt, msg, 0)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package dns
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPackNsec3(t *testing.T) {
|
||||
nsec3 := HashName("dnsex.nl.", SHA1, 0, "DEAD")
|
||||
|
@ -112,6 +115,18 @@ func TestNsec3(t *testing.T) {
|
|||
name: "asd.com.",
|
||||
covers: false,
|
||||
},
|
||||
{ // empty interval wildcard
|
||||
rr: &NSEC3{
|
||||
Hdr: RR_Header{Name: "2n1tb3vairuobl6rkdvii42n9tfmialp.com."},
|
||||
Hash: 1,
|
||||
Flags: 1,
|
||||
Iterations: 5,
|
||||
Salt: "F10E9F7EA83FC8F3",
|
||||
NextDomain: "2N1TB3VAIRUOBL6RKDVII42N9TFMIALP",
|
||||
},
|
||||
name: "*.asd.com.",
|
||||
covers: true,
|
||||
},
|
||||
{ // name hash is before owner hash, not covered
|
||||
rr: &NSEC3{
|
||||
Hdr: RR_Header{Name: "3V62ULR0NRE83V0RJA2VJGTLIF9V6RAB.com."},
|
||||
|
@ -131,3 +146,25 @@ func TestNsec3(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNsec3EmptySalt(t *testing.T) {
|
||||
rr, _ := NewRR("CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 86400 IN NSEC3 1 1 0 - CK0Q1GIN43N1ARRC9OSM6QPQR81H5M9A NS SOA RRSIG DNSKEY NSEC3PARAM")
|
||||
|
||||
if !rr.(*NSEC3).Match("com.") {
|
||||
t.Fatalf("expected record to match com. label")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHashName(b *testing.B) {
|
||||
for _, iter := range []uint16{
|
||||
150, 2500, 5000, 10000, ^uint16(0),
|
||||
} {
|
||||
b.Run(strconv.Itoa(int(iter)), func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
if HashName("some.example.org.", SHA1, iter, "deadbeef") == "" {
|
||||
b.Fatalf("HashName failed")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
673
parse_test.go
673
parse_test.go
|
@ -245,8 +245,7 @@ func testTXTRRQuick(t *testing.T) {
|
|||
rrbytes := make([]byte, 0, len(owner)+2+2+4+2+len(rdata))
|
||||
rrbytes = append(rrbytes, owner...)
|
||||
rrbytes = append(rrbytes, typeAndClass...)
|
||||
rrbytes = append(rrbytes, byte(len(rdata)>>8))
|
||||
rrbytes = append(rrbytes, byte(len(rdata)))
|
||||
rrbytes = append(rrbytes, byte(len(rdata)>>8), byte(len(rdata)))
|
||||
rrbytes = append(rrbytes, rdata...)
|
||||
rr, _, err := UnpackRR(rrbytes, 0)
|
||||
if err != nil {
|
||||
|
@ -267,7 +266,7 @@ func testTXTRRQuick(t *testing.T) {
|
|||
return false
|
||||
}
|
||||
if len(rdata) == 0 {
|
||||
// string'ing won't produce any data to parse
|
||||
// stringifying won't produce any data to parse
|
||||
return true
|
||||
}
|
||||
rrString := rr.String()
|
||||
|
@ -341,7 +340,7 @@ func TestParseDirectiveMisc(t *testing.T) {
|
|||
|
||||
func TestNSEC(t *testing.T) {
|
||||
nsectests := map[string]string{
|
||||
"nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F": "nl.\t3600\tIN\tNSEC3PARAM\t1 0 5 30923C44C6CBBB8F",
|
||||
"nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F": "nl.\t3600\tIN\tNSEC3PARAM\t1 0 5 30923C44C6CBBB8F",
|
||||
"p2209hipbpnm681knjnu0m1febshlv4e.nl. IN NSEC3 1 1 5 30923C44C6CBBB8F P90DG1KE8QEAN0B01613LHQDG0SOJ0TA NS SOA TXT RRSIG DNSKEY NSEC3PARAM": "p2209hipbpnm681knjnu0m1febshlv4e.nl.\t3600\tIN\tNSEC3\t1 1 5 30923C44C6CBBB8F P90DG1KE8QEAN0B01613LHQDG0SOJ0TA NS SOA TXT RRSIG DNSKEY NSEC3PARAM",
|
||||
"localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC",
|
||||
"localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC TYPE65534": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC TYPE65534",
|
||||
|
@ -358,12 +357,29 @@ func TestNSEC(t *testing.T) {
|
|||
t.Errorf("`%s' should be equal to\n`%s', but is `%s'", i, o, rr.String())
|
||||
}
|
||||
}
|
||||
rr, err := NewRR("nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F")
|
||||
if err != nil {
|
||||
t.Fatal("failed to parse RR: ", err)
|
||||
}
|
||||
if nsec3param, ok := rr.(*NSEC3PARAM); ok {
|
||||
if nsec3param.SaltLength != 8 {
|
||||
t.Fatalf("nsec3param saltlen %d != 8", nsec3param.SaltLength)
|
||||
}
|
||||
} else {
|
||||
t.Fatal("not nsec3 param: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLOC(t *testing.T) {
|
||||
lt := map[string]string{
|
||||
"SW1A2AA.find.me.uk. LOC 51 30 12.748 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 30 12.748 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m",
|
||||
"SW1A2AA.find.me.uk. LOC 51 0 0.0 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 00 0.000 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m",
|
||||
"SW1A2AA.find.me.uk. LOC 51 0 0.0 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 00 0.000 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m",
|
||||
"SW1A2AA.find.me.uk. LOC 51 30 12.748 N 00 07 39.611 W 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 30 12.748 N 00 07 39.611 W 0m 1m 10000m 10m",
|
||||
// Exercise boundary cases
|
||||
"SW1A2AA.find.me.uk. LOC 90 0 0.0 N 180 0 0.0 W 42849672.95 90000000.00m 90000000.00m 90000000.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t90 00 0.000 N 180 00 0.000 W 42849672.95m 90000000m 90000000m 90000000m",
|
||||
"SW1A2AA.find.me.uk. LOC 89 59 59.999 N 179 59 59.999 W -100000 90000000.00m 90000000.00m 90000000m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t89 59 59.999 N 179 59 59.999 W -100000m 90000000m 90000000m 90000000m",
|
||||
// use float64 to have enough precision.
|
||||
"example.com. LOC 42 21 43.952 N 71 5 6.344 W -24m 1m 200m 10m": "example.com.\t3600\tIN\tLOC\t42 21 43.952 N 71 05 6.344 W -24m 1m 200m 10m",
|
||||
}
|
||||
for i, o := range lt {
|
||||
rr, err := NewRR(i)
|
||||
|
@ -375,6 +391,90 @@ func TestParseLOC(t *testing.T) {
|
|||
t.Errorf("`%s' should be equal to\n`%s', but is `%s'", i, o, rr.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid cases (out of range values)
|
||||
lt = map[string]string{ // Pair of (invalid) RDATA and the bad field name
|
||||
// One of the subfields is out of range.
|
||||
"91 0 0.0 N 00 07 39.611 W 0m": "Latitude",
|
||||
"89 60 0.0 N 00 07 39.611 W 0m": "Latitude",
|
||||
"89 00 60.0 N 00 07 39.611 W 0m": "Latitude",
|
||||
"1 00 -1 N 00 07 39.611 W 0m": "Latitude",
|
||||
"0 0 0.0 N 181 00 0.0 W 0m": "Longitude",
|
||||
"0 0 0.0 N 179 60 0.0 W 0m": "Longitude",
|
||||
"0 0 0.0 N 179 00 60.0 W 0m": "Longitude",
|
||||
"0 0 0.0 N 1 00 -1 W 0m": "Longitude",
|
||||
|
||||
// Each subfield is valid, but resulting latitude would be out of range.
|
||||
"90 01 00.0 N 00 07 39.611 W 0m": "Latitude",
|
||||
"0 0 0.0 N 180 01 0.0 W 0m": "Longitude",
|
||||
}
|
||||
for rdata, field := range lt {
|
||||
_, err := NewRR(fmt.Sprintf("example.com. LOC %s", rdata))
|
||||
if err == nil || !strings.Contains(err.Error(), field) {
|
||||
t.Errorf("expected error to contain %q, but got %v", field, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this tests a subroutine for the LOC RR parser. It's complicated enough to test separately.
|
||||
func TestStringToCm(t *testing.T) {
|
||||
tests := []struct {
|
||||
// Test description: the input token and the expected return values from stringToCm.
|
||||
token string
|
||||
e uint8
|
||||
m uint8
|
||||
ok bool
|
||||
}{
|
||||
{"100", 4, 1, true},
|
||||
{"0100", 4, 1, true}, // leading 0 (allowed)
|
||||
{"100.99", 4, 1, true},
|
||||
{"90000000", 9, 9, true},
|
||||
{"90000000.00", 9, 9, true},
|
||||
{"0", 0, 0, true},
|
||||
{"0.00", 0, 0, true},
|
||||
{"0.01", 0, 1, true},
|
||||
{".01", 0, 1, true}, // empty 'meter' part (allowed)
|
||||
{"0.1", 1, 1, true},
|
||||
|
||||
// out of range (too large)
|
||||
{"90000001", 0, 0, false},
|
||||
{"90000000.01", 0, 0, false},
|
||||
|
||||
// more than 2 digits in 'cmeter' part
|
||||
{"0.000", 0, 0, false},
|
||||
{"0.001", 0, 0, false},
|
||||
{"0.999", 0, 0, false},
|
||||
// with plus or minus sign (disallowed)
|
||||
{"-100", 0, 0, false},
|
||||
{"+100", 0, 0, false},
|
||||
{"0.-10", 0, 0, false},
|
||||
{"0.+10", 0, 0, false},
|
||||
{"0a.00", 0, 0, false}, // invalid string for 'meter' part
|
||||
{".1x", 0, 0, false}, // invalid string for 'cmeter' part
|
||||
{".", 0, 0, false}, // empty 'cmeter' part (disallowed)
|
||||
{"1.", 0, 0, false}, // ditto
|
||||
{"m", 0, 0, false}, // only the "m" suffix
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.token, func(t *testing.T) {
|
||||
// In all cases the expected result is the same with or without the 'm' suffix.
|
||||
// So we test both cases using the same test code.
|
||||
for _, sfx := range []string{"", "m"} {
|
||||
token := tc.token + sfx
|
||||
e, m, ok := stringToCm(token)
|
||||
if ok != tc.ok {
|
||||
t.Fatal("unexpected validation result")
|
||||
}
|
||||
if m != tc.m {
|
||||
t.Fatalf("Expected %d, got %d", tc.m, m)
|
||||
}
|
||||
if e != tc.e {
|
||||
t.Fatalf("Expected %d, got %d", tc.e, e)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDS(t *testing.T) {
|
||||
|
@ -398,16 +498,16 @@ func TestQuotes(t *testing.T) {
|
|||
`t.example.com. IN TXT "a bc"`: "t.example.com.\t3600\tIN\tTXT\t\"a bc\"",
|
||||
`t.example.com. IN TXT "a
|
||||
bc"`: "t.example.com.\t3600\tIN\tTXT\t\"a\\010 bc\"",
|
||||
`t.example.com. IN TXT ""`: "t.example.com.\t3600\tIN\tTXT\t\"\"",
|
||||
`t.example.com. IN TXT "a"`: "t.example.com.\t3600\tIN\tTXT\t\"a\"",
|
||||
`t.example.com. IN TXT "aa"`: "t.example.com.\t3600\tIN\tTXT\t\"aa\"",
|
||||
`t.example.com. IN TXT "aaa" ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
|
||||
`t.example.com. IN TXT "abc" "DEF"`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"",
|
||||
`t.example.com. IN TXT "abc" ( "DEF" )`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"",
|
||||
`t.example.com. IN TXT aaa ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
|
||||
`t.example.com. IN TXT aaa aaa;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"",
|
||||
`t.example.com. IN TXT aaa aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"",
|
||||
`t.example.com. IN TXT aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
|
||||
`t.example.com. IN TXT ""`: "t.example.com.\t3600\tIN\tTXT\t\"\"",
|
||||
`t.example.com. IN TXT "a"`: "t.example.com.\t3600\tIN\tTXT\t\"a\"",
|
||||
`t.example.com. IN TXT "aa"`: "t.example.com.\t3600\tIN\tTXT\t\"aa\"",
|
||||
`t.example.com. IN TXT "aaa" ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
|
||||
`t.example.com. IN TXT "abc" "DEF"`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"",
|
||||
`t.example.com. IN TXT "abc" ( "DEF" )`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"",
|
||||
`t.example.com. IN TXT aaa ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
|
||||
`t.example.com. IN TXT aaa aaa;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"",
|
||||
`t.example.com. IN TXT aaa aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"",
|
||||
`t.example.com. IN TXT aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
|
||||
"cid.urn.arpa. NAPTR 100 50 \"s\" \"z3950+I2L+I2C\" \"\" _z3950._tcp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"z3950+I2L+I2C\" \"\" _z3950._tcp.gatech.edu.",
|
||||
"cid.urn.arpa. NAPTR 100 50 \"s\" \"rcds+I2C\" \"\" _rcds._udp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"rcds+I2C\" \"\" _rcds._udp.gatech.edu.",
|
||||
"cid.urn.arpa. NAPTR 100 50 \"s\" \"http+I2L+I2C+I2R\" \"\" _http._tcp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"http+I2L+I2C+I2R\" \"\" _http._tcp.gatech.edu.",
|
||||
|
@ -432,7 +532,8 @@ 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 {
|
||||
rr, err := NewRR(i)
|
||||
|
@ -533,21 +634,25 @@ $TTL 314
|
|||
example.com. DNAME 10 ; TTL=314 after second $TTL
|
||||
`
|
||||
reCaseFromComment := regexp.MustCompile(`TTL=(\d+)\s+(.*)`)
|
||||
records := ParseZone(strings.NewReader(zone), "", "")
|
||||
z := NewZoneParser(strings.NewReader(zone), "", "")
|
||||
var i int
|
||||
for record := range records {
|
||||
|
||||
for rr, ok := z.Next(); ok; rr, ok = z.Next() {
|
||||
i++
|
||||
if record.Error != nil {
|
||||
t.Error(record.Error)
|
||||
expected := reCaseFromComment.FindStringSubmatch(z.Comment())
|
||||
if len(expected) != 3 {
|
||||
t.Errorf("regexp didn't match for record %d", i)
|
||||
continue
|
||||
}
|
||||
expected := reCaseFromComment.FindStringSubmatch(record.Comment)
|
||||
expectedTTL, _ := strconv.ParseUint(expected[1], 10, 32)
|
||||
ttl := record.RR.Header().Ttl
|
||||
ttl := rr.Header().Ttl
|
||||
if ttl != uint32(expectedTTL) {
|
||||
t.Errorf("%s: expected TTL %d, got %d", expected[2], expectedTTL, ttl)
|
||||
}
|
||||
}
|
||||
if err := z.Err(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if i != 10 {
|
||||
t.Errorf("expected %d records, got %d", 5, i)
|
||||
}
|
||||
|
@ -586,16 +691,12 @@ func TestRelativeNameErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, errorCase := range badZones {
|
||||
entries := ParseZone(strings.NewReader(errorCase.zoneContents), "", "")
|
||||
for entry := range entries {
|
||||
if entry.Error == nil {
|
||||
t.Errorf("%s: expected error, got nil", errorCase.label)
|
||||
continue
|
||||
}
|
||||
err := entry.Error.err
|
||||
if err != errorCase.expectedErr {
|
||||
t.Errorf("%s: expected error `%s`, got `%s`", errorCase.label, errorCase.expectedErr, err)
|
||||
}
|
||||
z := NewZoneParser(strings.NewReader(errorCase.zoneContents), "", "")
|
||||
z.Next()
|
||||
if err := z.Err(); err == nil {
|
||||
t.Errorf("%s: expected error, got nil", errorCase.label)
|
||||
} else if !strings.Contains(err.Error(), errorCase.expectedErr) {
|
||||
t.Errorf("%s: expected error `%s`, got `%s`", errorCase.label, errorCase.expectedErr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -702,9 +803,13 @@ func TestRfc1982(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
for range ParseZone(strings.NewReader(""), "", "") {
|
||||
z := NewZoneParser(strings.NewReader(""), "", "")
|
||||
for _, ok := z.Next(); ok; _, ok = z.Next() {
|
||||
t.Errorf("should be empty")
|
||||
}
|
||||
if err := z.Err(); err != nil {
|
||||
t.Error("got an error when it shouldn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLowercaseTokens(t *testing.T) {
|
||||
|
@ -846,20 +951,25 @@ func TestPX(t *testing.T) {
|
|||
|
||||
func TestComment(t *testing.T) {
|
||||
// Comments we must see
|
||||
comments := map[string]bool{"; this is comment 1": true,
|
||||
"; this is comment 4": true, "; this is comment 6": true,
|
||||
"; this is comment 7": true, "; this is comment 8": true}
|
||||
comments := map[string]bool{
|
||||
"; this is comment 1": true,
|
||||
"; this is comment 2": true,
|
||||
"; this is comment 4": true,
|
||||
"; this is comment 6": true,
|
||||
"; this is comment 7": true,
|
||||
"; this is comment 8": true,
|
||||
}
|
||||
zone := `
|
||||
foo. IN A 10.0.0.1 ; this is comment 1
|
||||
foo. IN A (
|
||||
10.0.0.2 ; this is comment2
|
||||
10.0.0.2 ; this is comment 2
|
||||
)
|
||||
; this is comment3
|
||||
; this is comment 3
|
||||
foo. IN A 10.0.0.3
|
||||
foo. IN A ( 10.0.0.4 ); this is comment 4
|
||||
|
||||
foo. IN A 10.0.0.5
|
||||
; this is comment5
|
||||
; this is comment 5
|
||||
|
||||
foo. IN A 10.0.0.6
|
||||
|
||||
|
@ -867,15 +977,117 @@ foo. IN DNSKEY 256 3 5 AwEAAb+8l ; this is comment 6
|
|||
foo. IN NSEC miek.nl. TXT RRSIG NSEC; this is comment 7
|
||||
foo. IN TXT "THIS IS TEXT MAN"; this is comment 8
|
||||
`
|
||||
for x := range ParseZone(strings.NewReader(zone), ".", "") {
|
||||
if x.Error == nil {
|
||||
if x.Comment != "" {
|
||||
if _, ok := comments[x.Comment]; !ok {
|
||||
t.Errorf("wrong comment %s", x.Comment)
|
||||
}
|
||||
z := NewZoneParser(strings.NewReader(zone), ".", "")
|
||||
for _, ok := z.Next(); ok; _, ok = z.Next() {
|
||||
if z.Comment() != "" {
|
||||
if _, okC := comments[z.Comment()]; !okC {
|
||||
t.Errorf("wrong comment %q", z.Comment())
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := z.Err(); err != nil {
|
||||
t.Error("got an error when it shouldn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestZoneParserComments(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
zone string
|
||||
comments []string
|
||||
}{
|
||||
{
|
||||
`name. IN SOA a6.nstld.com. hostmaster.nic.name. (
|
||||
203362132 ; serial
|
||||
5m ; refresh (5 minutes)
|
||||
5m ; retry (5 minutes)
|
||||
2w ; expire (2 weeks)
|
||||
300 ; minimum (5 minutes)
|
||||
) ; y
|
||||
. 3600000 IN NS ONE.MY-ROOTS.NET. ; x`,
|
||||
[]string{"; serial ; refresh (5 minutes) ; retry (5 minutes) ; expire (2 weeks) ; minimum (5 minutes) ; y", "; x"},
|
||||
},
|
||||
{
|
||||
`name. IN SOA a6.nstld.com. hostmaster.nic.name. (
|
||||
203362132 ; serial
|
||||
5m ; refresh (5 minutes)
|
||||
5m ; retry (5 minutes)
|
||||
2w ; expire (2 weeks)
|
||||
300 ; minimum (5 minutes)
|
||||
) ; y
|
||||
. 3600000 IN NS ONE.MY-ROOTS.NET.`,
|
||||
[]string{"; serial ; refresh (5 minutes) ; retry (5 minutes) ; expire (2 weeks) ; minimum (5 minutes) ; y", ""},
|
||||
},
|
||||
{
|
||||
`name. IN SOA a6.nstld.com. hostmaster.nic.name. (
|
||||
203362132 ; serial
|
||||
5m ; refresh (5 minutes)
|
||||
5m ; retry (5 minutes)
|
||||
2w ; expire (2 weeks)
|
||||
300 ; minimum (5 minutes)
|
||||
)
|
||||
. 3600000 IN NS ONE.MY-ROOTS.NET.`,
|
||||
[]string{"; serial ; refresh (5 minutes) ; retry (5 minutes) ; expire (2 weeks) ; minimum (5 minutes)", ""},
|
||||
},
|
||||
{
|
||||
`name. IN SOA a6.nstld.com. hostmaster.nic.name. (
|
||||
203362132 ; serial
|
||||
5m ; refresh (5 minutes)
|
||||
5m ; retry (5 minutes)
|
||||
2w ; expire (2 weeks)
|
||||
300 ; minimum (5 minutes)
|
||||
)
|
||||
. 3600000 IN NS ONE.MY-ROOTS.NET. ; x`,
|
||||
[]string{"; serial ; refresh (5 minutes) ; retry (5 minutes) ; expire (2 weeks) ; minimum (5 minutes)", "; x"},
|
||||
},
|
||||
{
|
||||
`name. IN SOA a6.nstld.com. hostmaster.nic.name. (
|
||||
203362132 ; serial
|
||||
5m ; refresh (5 minutes)
|
||||
5m ; retry (5 minutes)
|
||||
2w ; expire (2 weeks)
|
||||
300 ; minimum (5 minutes)
|
||||
)`,
|
||||
[]string{"; serial ; refresh (5 minutes) ; retry (5 minutes) ; expire (2 weeks) ; minimum (5 minutes)"},
|
||||
},
|
||||
{
|
||||
`. 3600000 IN NS ONE.MY-ROOTS.NET. ; x`,
|
||||
[]string{"; x"},
|
||||
},
|
||||
{
|
||||
`. 3600000 IN NS ONE.MY-ROOTS.NET.`,
|
||||
[]string{""},
|
||||
},
|
||||
{
|
||||
`. 3600000 IN NS ONE.MY-ROOTS.NET. ;;x`,
|
||||
[]string{";;x"},
|
||||
},
|
||||
} {
|
||||
r := strings.NewReader(test.zone)
|
||||
|
||||
var j int
|
||||
z := NewZoneParser(r, "", "")
|
||||
for rr, ok := z.Next(); ok; rr, ok = z.Next() {
|
||||
if j >= len(test.comments) {
|
||||
t.Fatalf("too many records for zone %d at %d record, expected %d", i, j+1, len(test.comments))
|
||||
}
|
||||
|
||||
if z.Comment() != test.comments[j] {
|
||||
t.Errorf("invalid comment for record %d:%d %v", i, j, rr)
|
||||
t.Logf("expected %q", test.comments[j])
|
||||
t.Logf("got %q", z.Comment())
|
||||
}
|
||||
|
||||
j++
|
||||
}
|
||||
|
||||
if err := z.Err(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if j != len(test.comments) {
|
||||
t.Errorf("too few records for zone %d, got %d, expected %d", i, j, len(test.comments))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEUIxx(t *testing.T) {
|
||||
|
@ -927,8 +1139,8 @@ func TestTXT(t *testing.T) {
|
|||
if rr.String() != `_raop._tcp.local. 60 IN TXT "single value"` {
|
||||
t.Error("bad representation of TXT record:", rr.String())
|
||||
}
|
||||
if rr.len() != 28+1+12 {
|
||||
t.Error("bad size of serialized record:", rr.len())
|
||||
if Len(rr) != 28+1+12 {
|
||||
t.Error("bad size of serialized record:", Len(rr))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -947,8 +1159,8 @@ func TestTXT(t *testing.T) {
|
|||
if rr.String() != `_raop._tcp.local. 60 IN TXT "a=1" "b=2" "c=3" "d=4"` {
|
||||
t.Error("bad representation of TXT multi value record:", rr.String())
|
||||
}
|
||||
if rr.len() != 28+1+3+1+3+1+3+1+3 {
|
||||
t.Error("bad size of serialized multi value record:", rr.len())
|
||||
if Len(rr) != 28+1+3+1+3+1+3+1+3 {
|
||||
t.Error("bad size of serialized multi value record:", Len(rr))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -967,8 +1179,8 @@ func TestTXT(t *testing.T) {
|
|||
if rr.String() != `_raop._tcp.local. 60 IN TXT ""` {
|
||||
t.Error("bad representation of empty-string TXT record:", rr.String())
|
||||
}
|
||||
if rr.len() != 28+1 {
|
||||
t.Error("bad size of serialized record:", rr.len())
|
||||
if Len(rr) != 28+1 {
|
||||
t.Error("bad size of serialized record:", Len(rr))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -997,8 +1209,8 @@ func TestTypeXXXX(t *testing.T) {
|
|||
t.Errorf("this should not work, for TYPE655341")
|
||||
}
|
||||
_, err = NewRR("example.com IN TYPE1 \\# 4 0a000001")
|
||||
if err == nil {
|
||||
t.Errorf("this should not work")
|
||||
if err != nil {
|
||||
t.Errorf("failed to parse TYPE1 RR: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1101,9 +1313,8 @@ func TestNewPrivateKey(t *testing.T) {
|
|||
algorithms := []algorithm{
|
||||
{ECDSAP256SHA256, 256},
|
||||
{ECDSAP384SHA384, 384},
|
||||
{RSASHA1, 1024},
|
||||
{RSASHA256, 2048},
|
||||
{DSA, 1024},
|
||||
{RSASHA1, 512},
|
||||
{RSASHA256, 512},
|
||||
{ED25519, 256},
|
||||
}
|
||||
|
||||
|
@ -1304,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
|
||||
|
@ -1327,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\"",
|
||||
}
|
||||
|
@ -1423,6 +1634,91 @@ func TestParseCSYNC(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestParseSVCB(t *testing.T) {
|
||||
svcbs := map[string]string{
|
||||
`example.com. 3600 IN SVCB 0 cloudflare.com.`: `example.com. 3600 IN SVCB 0 cloudflare.com.`,
|
||||
`example.com. 3600 IN SVCB 65000 cloudflare.com. alpn=h2 ipv4hint=3.4.3.2`: `example.com. 3600 IN SVCB 65000 cloudflare.com. alpn="h2" ipv4hint="3.4.3.2"`,
|
||||
`example.com. 3600 IN SVCB 65000 cloudflare.com. key65000=4\ 3 key65001="\" " key65002 key65003= key65004="" key65005== key65006==\"\" key65007=\254 key65008=\032`: `example.com. 3600 IN SVCB 65000 cloudflare.com. key65000="4\ 3" key65001="\"\ " key65002="" key65003="" key65004="" key65005="=" key65006="=\"\"" key65007="\254" key65008="\ "`,
|
||||
// Explained in svcb.go "In AliasMode, records SHOULD NOT include any SvcParams,"
|
||||
`example.com. 3600 IN SVCB 0 no-default-alpn`: `example.com. 3600 IN SVCB 0 no-default-alpn.`,
|
||||
// From the specification
|
||||
`example.com. HTTPS 0 foo.example.com.`: `example.com. 3600 IN HTTPS 0 foo.example.com.`,
|
||||
`example.com. SVCB 1 .`: `example.com. 3600 IN SVCB 1 .`,
|
||||
`example.com. SVCB 16 foo.example.com. port=53`: `example.com. 3600 IN SVCB 16 foo.example.com. port="53"`,
|
||||
`example.com. SVCB 1 foo.example.com. key667=hello`: `example.com. 3600 IN SVCB 1 foo.example.com. key667="hello"`,
|
||||
`example.com. SVCB 1 foo.example.com. key667="hello\210qoo"`: `example.com. 3600 IN SVCB 1 foo.example.com. key667="hello\210qoo"`,
|
||||
`example.com. SVCB 1 foo.example.com. ipv6hint="2001:db8::1,2001:db8::53:1"`: `example.com. 3600 IN SVCB 1 foo.example.com. ipv6hint="2001:db8::1,2001:db8::53:1"`,
|
||||
`example.com. SVCB 1 example.com. ipv6hint="2001:db8::198.51.100.100"`: `example.com. 3600 IN SVCB 1 example.com. ipv6hint="2001:db8::c633:6464"`,
|
||||
`example.com. SVCB 16 foo.example.org. alpn=h2,h3-19 mandatory=ipv4hint,alpn ipv4hint=192.0.2.1`: `example.com. 3600 IN SVCB 16 foo.example.org. alpn="h2,h3-19" mandatory="ipv4hint,alpn" ipv4hint="192.0.2.1"`,
|
||||
`example.com. SVCB 16 foo.example.org. alpn="f\\\\oo\\,bar,h2"`: `example.com. 3600 IN SVCB 16 foo.example.org. alpn="f\\\092oo\\\044bar,h2"`,
|
||||
`example.com. SVCB 16 foo.example.org. alpn=f\\\092oo\092,bar,h2`: `example.com. 3600 IN SVCB 16 foo.example.org. alpn="f\\\092oo\\\044bar,h2"`,
|
||||
// From draft-ietf-add-ddr-06
|
||||
`_dns.example.net. SVCB 1 example.net. alpn=h2 dohpath=/dns-query{?dns}`: `_dns.example.net. 3600 IN SVCB 1 example.net. alpn="h2" dohpath="/dns-query{?dns}"`,
|
||||
`_dns.example.net. SVCB 1 example.net. alpn=h2 dohpath=/dns\045query{\?dns}`: `_dns.example.net. 3600 IN SVCB 1 example.net. alpn="h2" dohpath="/dns-query{?dns}"`,
|
||||
}
|
||||
for s, o := range svcbs {
|
||||
rr, err := NewRR(s)
|
||||
if err != nil {
|
||||
t.Error("failed to parse RR: ", err)
|
||||
continue
|
||||
}
|
||||
if rr.String() != o {
|
||||
t.Errorf("`%s' should be equal to\n`%s', but is `%s'", s, o, rr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBadSVCB(t *testing.T) {
|
||||
header := `example.com. 3600 IN HTTPS `
|
||||
evils := []string{
|
||||
`65536 . no-default-alpn`, // bad priority
|
||||
`1 ..`, // bad domain
|
||||
`1 . no-default-alpn=1`, // value illegal
|
||||
`1 . key`, // invalid key
|
||||
`1 . key=`, // invalid key
|
||||
`1 . =`, // invalid key
|
||||
`1 . ==`, // invalid key
|
||||
`1 . =a`, // invalid key
|
||||
`1 . ""`, // invalid key
|
||||
`1 . ""=`, // invalid key
|
||||
`1 . "a"`, // invalid key
|
||||
`1 . "a"=`, // invalid key
|
||||
`1 . key1=`, // we know that key
|
||||
`1 . key65535`, // key reserved
|
||||
`1 . key065534`, // key can't be padded
|
||||
`1 . key65534="f`, // unterminated value
|
||||
`1 . key65534="`, // unterminated value
|
||||
`1 . key65534=\2`, // invalid numeric escape
|
||||
`1 . key65534=\24`, // invalid numeric escape
|
||||
`1 . key65534=\256`, // invalid numeric escape
|
||||
`1 . key65534=\`, // invalid numeric escape
|
||||
`1 . key65534=""alpn`, // zQuote ending needs whitespace
|
||||
`1 . key65534="a"alpn`, // zQuote ending needs whitespace
|
||||
`1 . ipv6hint=1.1.1.1`, // not ipv6
|
||||
`1 . ipv6hint=1:1:1:1`, // not ipv6
|
||||
`1 . ipv6hint=a`, // not ipv6
|
||||
`1 . ipv6hint=`, // empty ipv6
|
||||
`1 . ipv4hint=1.1.1.1.1`, // not ipv4
|
||||
`1 . ipv4hint=::fc`, // not ipv4
|
||||
`1 . ipv4hint=..11`, // not ipv4
|
||||
`1 . ipv4hint=a`, // not ipv4
|
||||
`1 . ipv4hint=`, // empty ipv4
|
||||
`1 . port=`, // empty port
|
||||
`1 . echconfig=YUd`, // bad base64
|
||||
`1 . alpn=h\`, // unterminated escape
|
||||
`1 . alpn=h2\\.h3`, // comma-separated list with bad character
|
||||
`1 . alpn=h2,,h3`, // empty protocol identifier
|
||||
`1 . alpn=h3,`, // final protocol identifier empty
|
||||
}
|
||||
for _, o := range evils {
|
||||
_, err := NewRR(header + o)
|
||||
if err == nil {
|
||||
t.Error("failed to reject invalid RR: ", header+o)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBadNAPTR(t *testing.T) {
|
||||
// Should look like: mplus.ims.vodafone.com. 3600 IN NAPTR 10 100 "S" "SIP+D2U" "" _sip._udp.mplus.ims.vodafone.com.
|
||||
naptr := `mplus.ims.vodafone.com. 3600 IN NAPTR 10 100 S SIP+D2U _sip._udp.mplus.ims.vodafone.com.`
|
||||
|
@ -1462,3 +1758,260 @@ func TestBad(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNULLRecord(t *testing.T) {
|
||||
// packet captured from iodine
|
||||
packet := `8116840000010001000000000569627a6c700474657374046d69656b026e6c00000a0001c00c000a0001000000000005497f000001`
|
||||
data, _ := hex.DecodeString(packet)
|
||||
msg := new(Msg)
|
||||
err := msg.Unpack(data)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unpack NULL record")
|
||||
}
|
||||
if _, ok := msg.Answer[0].(*NULL); !ok {
|
||||
t.Fatalf("Expected packet to contain NULL record")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAPL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
"v4",
|
||||
". APL 1:192.0.2.0/24",
|
||||
".\t3600\tIN\tAPL\t1:192.0.2.0/24",
|
||||
},
|
||||
{
|
||||
"v6",
|
||||
". APL 2:2001:db8::/32",
|
||||
".\t3600\tIN\tAPL\t2:2001:db8::/32",
|
||||
},
|
||||
{
|
||||
"null v6",
|
||||
". APL 2:::/0",
|
||||
".\t3600\tIN\tAPL\t2:::/0",
|
||||
},
|
||||
{
|
||||
"null v4",
|
||||
". APL 1:0.0.0.0/0",
|
||||
".\t3600\tIN\tAPL\t1:0.0.0.0/0",
|
||||
},
|
||||
{
|
||||
"full v6",
|
||||
". APL 2:::/0",
|
||||
".\t3600\tIN\tAPL\t2:::/0",
|
||||
},
|
||||
{
|
||||
"full v4",
|
||||
". APL 1:192.0.2.1/32",
|
||||
".\t3600\tIN\tAPL\t1:192.0.2.1/32",
|
||||
},
|
||||
{
|
||||
"full v6",
|
||||
". APL 2:2001:0db8:d2b4:b6ba:50db:49cc:a8d1:5bb1/128",
|
||||
".\t3600\tIN\tAPL\t2:2001:db8:d2b4:b6ba:50db:49cc:a8d1:5bb1/128",
|
||||
},
|
||||
{
|
||||
"v4in6",
|
||||
". APL 2:::ffff:192.0.2.0/120",
|
||||
".\t3600\tIN\tAPL\t2:::ffff:192.0.2.0/120",
|
||||
},
|
||||
{
|
||||
"v4in6 v6 syntax",
|
||||
". APL 2:::ffff:c000:0200/120",
|
||||
".\t3600\tIN\tAPL\t2:::ffff:192.0.2.0/120",
|
||||
},
|
||||
{
|
||||
"negate",
|
||||
". APL !1:192.0.2.0/24",
|
||||
".\t3600\tIN\tAPL\t!1:192.0.2.0/24",
|
||||
},
|
||||
{
|
||||
"multiple",
|
||||
". APL 1:192.0.2.0/24 !1:192.0.2.1/32 2:2001:db8::/32 !2:2001:db8:1::0/48",
|
||||
".\t3600\tIN\tAPL\t1:192.0.2.0/24 !1:192.0.2.1/32 2:2001:db8::/32 !2:2001:db8:1::/48",
|
||||
},
|
||||
{
|
||||
"no address",
|
||||
". APL",
|
||||
".\t3600\tIN\tAPL\t",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rr, err := NewRR(tc.in)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse RR: %s", err)
|
||||
}
|
||||
|
||||
got := rr.String()
|
||||
if got != tc.expect {
|
||||
t.Errorf("expected %q, got %q", tc.expect, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAPLErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
}{
|
||||
{
|
||||
"unexpected",
|
||||
`. APL ""`,
|
||||
},
|
||||
{
|
||||
"unrecognized family",
|
||||
". APL 3:0.0.0.0/0",
|
||||
},
|
||||
{
|
||||
"malformed family",
|
||||
". APL foo:0.0.0.0/0",
|
||||
},
|
||||
{
|
||||
"malformed address",
|
||||
". APL 1:192.0.2/16",
|
||||
},
|
||||
{
|
||||
"extra bits",
|
||||
". APL 2:2001:db8::/0",
|
||||
},
|
||||
{
|
||||
"address mismatch v2",
|
||||
". APL 1:2001:db8::/64",
|
||||
},
|
||||
{
|
||||
"address mismatch v6",
|
||||
". APL 2:192.0.2.1/32",
|
||||
},
|
||||
{
|
||||
"no prefix",
|
||||
". APL 1:192.0.2.1",
|
||||
},
|
||||
{
|
||||
"no family",
|
||||
". APL 0.0.0.0/0",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := NewRR(tc.in)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnpackRRWithHeaderInvalidLengths(t *testing.T) {
|
||||
rr, err := NewRR("test.example.org. 300 IN SSHFP 1 2 BC6533CDC95A79078A39A56EA7635984ED655318ADA9B6159E30723665DA95BB")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse SSHFP record: %v", err)
|
||||
}
|
||||
|
||||
buf := make([]byte, Len(rr))
|
||||
headerEnd, end, err := packRR(rr, buf, 0, compressionMap{}, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to pack A record: %v", err)
|
||||
}
|
||||
|
||||
rr.Header().Rdlength = uint16(end - headerEnd)
|
||||
for _, off := range []int{
|
||||
-1,
|
||||
end + 1,
|
||||
1<<16 - 1,
|
||||
} {
|
||||
_, _, err := UnpackRRWithHeader(*rr.Header(), buf, off)
|
||||
if de, ok := err.(*Error); !ok || de.err != "bad off" {
|
||||
t.Errorf("UnpackRRWithHeader with bad offset (%d) returned wrong or no error: %v", off, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, rdlength := range []uint16{
|
||||
uint16(end - headerEnd + 1),
|
||||
uint16(end),
|
||||
1<<16 - 1,
|
||||
} {
|
||||
rr.Header().Rdlength = rdlength
|
||||
|
||||
_, _, err := UnpackRRWithHeader(*rr.Header(), buf, headerEnd)
|
||||
if de, ok := err.(*Error); !ok || de.err != "bad rdlength" {
|
||||
t.Errorf("UnpackRRWithHeader with bad rdlength (%d) returned wrong or no error: %v", rdlength, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseZONEMD(t *testing.T) {
|
||||
// Uses examples from https://tools.ietf.org/html/rfc8976
|
||||
dt := map[string]string{
|
||||
// Simple Zone
|
||||
`example. 86400 IN ZONEMD 2018031900 1 1 (
|
||||
c68090d90a7aed71
|
||||
6bc459f9340e3d7c
|
||||
1370d4d24b7e2fc3
|
||||
a1ddc0b9a87153b9
|
||||
a9713b3c9ae5cc27
|
||||
777f98b8e730044c )
|
||||
`: "example.\t86400\tIN\tZONEMD\t2018031900 1 1 c68090d90a7aed716bc459f9340e3d7c1370d4d24b7e2fc3a1ddc0b9a87153b9a9713b3c9ae5cc27777f98b8e730044c",
|
||||
// Complex Zone
|
||||
`example. 86400 IN ZONEMD 2018031900 1 1 (
|
||||
a3b69bad980a3504
|
||||
e1cffcb0fd6397f9
|
||||
3848071c93151f55
|
||||
2ae2f6b1711d4bd2
|
||||
d8b39808226d7b9d
|
||||
b71e34b72077f8fe )
|
||||
`: "example.\t86400\tIN\tZONEMD\t2018031900 1 1 a3b69bad980a3504e1cffcb0fd6397f93848071c93151f552ae2f6b1711d4bd2d8b39808226d7b9db71e34b72077f8fe",
|
||||
// Multiple Digests Zone
|
||||
`example. 86400 IN ZONEMD 2018031900 1 1 (
|
||||
62e6cf51b02e54b9
|
||||
b5f967d547ce4313
|
||||
6792901f9f88e637
|
||||
493daaf401c92c27
|
||||
9dd10f0edb1c56f8
|
||||
080211f8480ee306 )
|
||||
`: "example.\t86400\tIN\tZONEMD\t2018031900 1 1 62e6cf51b02e54b9b5f967d547ce43136792901f9f88e637493daaf401c92c279dd10f0edb1c56f8080211f8480ee306",
|
||||
`example. 86400 IN ZONEMD 2018031900 1 2 (
|
||||
08cfa1115c7b948c
|
||||
4163a901270395ea
|
||||
226a930cd2cbcf2f
|
||||
a9a5e6eb85f37c8a
|
||||
4e114d884e66f176
|
||||
eab121cb02db7d65
|
||||
2e0cc4827e7a3204
|
||||
f166b47e5613fd27 )
|
||||
`: "example.\t86400\tIN\tZONEMD\t2018031900 1 2 08cfa1115c7b948c4163a901270395ea226a930cd2cbcf2fa9a5e6eb85f37c8a4e114d884e66f176eab121cb02db7d652e0cc4827e7a3204f166b47e5613fd27",
|
||||
`example. 86400 IN ZONEMD 2018031900 1 240 (
|
||||
e2d523f654b9422a
|
||||
96c5a8f44607bbee )
|
||||
`: "example. 86400 IN ZONEMD 2018031900 1 240 e2d523f654b9422a96c5a8f44607bbee",
|
||||
`example. 86400 IN ZONEMD 2018031900 241 1 (
|
||||
e1846540e33a9e41
|
||||
89792d18d5d131f6
|
||||
05fc283e )
|
||||
`: "example. 86400 IN ZONEMD 2018031900 241 1 e1846540e33a9e4189792d18d5d131f605fc283e",
|
||||
// URI.ARPA zone
|
||||
`uri.arpa. 3600 IN ZONEMD 2018100702 1 1 (
|
||||
0dbc3c4dbfd75777c12ca19c337854b1577799901307c482e9d91d5d15
|
||||
cd934d16319d98e30c4201cf25a1d5a0254960 )`: "uri.arpa.\t3600\tIN\tZONEMD\t2018100702 1 1 0dbc3c4dbfd75777c12ca19c337854b1577799901307c482e9d91d5d15cd934d16319d98e30c4201cf25a1d5a0254960",
|
||||
// ROOT-SERVERS.NET Zone
|
||||
`root-servers.net. 3600000 IN ZONEMD 2018091100 1 1 (
|
||||
f1ca0ccd91bd5573d9f431c00ee0101b2545c97602be0a97
|
||||
8a3b11dbfc1c776d5b3e86ae3d973d6b5349ba7f04340f79 )
|
||||
`: "root-servers.net.\t3600000\tIN\tZONEMD\t2018091100 1 1 f1ca0ccd91bd5573d9f431c00ee0101b2545c97602be0a978a3b11dbfc1c776d5b3e86ae3d973d6b5349ba7f04340f79",
|
||||
}
|
||||
for i, o := range dt {
|
||||
rr, err := NewRR(i)
|
||||
if err != nil {
|
||||
t.Error("failed to parse RR: ", err)
|
||||
continue
|
||||
}
|
||||
if rr.String() != o {
|
||||
t.Errorf("`%s' should be equal to\n`%s', but is `%s'", i, o, rr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
134
privaterr.go
134
privaterr.go
|
@ -1,24 +1,20 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
import "strings"
|
||||
|
||||
// PrivateRdata is an interface used for implementing "Private Use" RR types, see
|
||||
// RFC 6895. This allows one to experiment with new RR types, without requesting an
|
||||
// official type code. Also see dns.PrivateHandle and dns.PrivateHandleRemove.
|
||||
type PrivateRdata interface {
|
||||
// String returns the text presentaton of the Rdata of the Private RR.
|
||||
// String returns the text presentation of the Rdata of the Private RR.
|
||||
String() string
|
||||
// Parse parses the Rdata of the private RR.
|
||||
Parse([]string) error
|
||||
// Pack is used when packing a private RR into a buffer.
|
||||
Pack([]byte) (int, error)
|
||||
// Unpack is used when unpacking a private RR from a buffer.
|
||||
// TODO(miek): diff. signature than Pack, see edns0.go for instance.
|
||||
Unpack([]byte) (int, error)
|
||||
// Copy copies the Rdata.
|
||||
// Copy copies the Rdata into the PrivateRdata argument.
|
||||
Copy(PrivateRdata) error
|
||||
// Len returns the length in octets of the Rdata.
|
||||
Len() int
|
||||
|
@ -29,21 +25,8 @@ type PrivateRdata interface {
|
|||
type PrivateRR struct {
|
||||
Hdr RR_Header
|
||||
Data PrivateRdata
|
||||
}
|
||||
|
||||
func mkPrivateRR(rrtype uint16) *PrivateRR {
|
||||
// Panics if RR is not an instance of PrivateRR.
|
||||
rrfunc, ok := TypeToRR[rrtype]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("dns: invalid operation with Private RR type %d", rrtype))
|
||||
}
|
||||
|
||||
anyrr := rrfunc()
|
||||
switch rr := anyrr.(type) {
|
||||
case *PrivateRR:
|
||||
return rr
|
||||
}
|
||||
panic(fmt.Sprintf("dns: RR is not a PrivateRR, TypeToRR[%d] generator returned %T", rrtype, anyrr))
|
||||
generator func() PrivateRdata // for copy
|
||||
}
|
||||
|
||||
// Header return the RR header of r.
|
||||
|
@ -52,98 +35,79 @@ func (r *PrivateRR) Header() *RR_Header { return &r.Hdr }
|
|||
func (r *PrivateRR) String() string { return r.Hdr.String() + r.Data.String() }
|
||||
|
||||
// Private len and copy parts to satisfy RR interface.
|
||||
func (r *PrivateRR) len() int { return r.Hdr.len() + r.Data.Len() }
|
||||
func (r *PrivateRR) len(off int, compression map[string]struct{}) int {
|
||||
l := r.Hdr.len(off, compression)
|
||||
l += r.Data.Len()
|
||||
return l
|
||||
}
|
||||
|
||||
func (r *PrivateRR) copy() RR {
|
||||
// make new RR like this:
|
||||
rr := mkPrivateRR(r.Hdr.Rrtype)
|
||||
newh := r.Hdr.copyHeader()
|
||||
rr.Hdr = *newh
|
||||
rr := &PrivateRR{r.Hdr, r.generator(), r.generator}
|
||||
|
||||
err := r.Data.Copy(rr.Data)
|
||||
if err != nil {
|
||||
panic("dns: got value that could not be used to copy Private rdata")
|
||||
if err := r.Data.Copy(rr.Data); err != nil {
|
||||
panic("dns: got value that could not be used to copy Private rdata: " + err.Error())
|
||||
}
|
||||
|
||||
return rr
|
||||
}
|
||||
func (r *PrivateRR) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) {
|
||||
off, err := r.Hdr.pack(msg, off, compression, compress)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
headerEnd := off
|
||||
|
||||
func (r *PrivateRR) pack(msg []byte, off int, compression compressionMap, compress bool) (int, error) {
|
||||
n, err := r.Data.Pack(msg[off:])
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
off += n
|
||||
r.Header().Rdlength = uint16(off - headerEnd)
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func (r *PrivateRR) unpack(msg []byte, off int) (int, error) {
|
||||
off1, err := r.Data.Unpack(msg[off:])
|
||||
off += off1
|
||||
return off, err
|
||||
}
|
||||
|
||||
func (r *PrivateRR) parse(c *zlexer, origin string) *ParseError {
|
||||
var l lex
|
||||
text := make([]string, 0, 2) // could be 0..N elements, median is probably 1
|
||||
Fetch:
|
||||
for {
|
||||
// TODO(miek): we could also be returning _QUOTE, this might or might not
|
||||
// be an issue (basically parsing TXT becomes hard)
|
||||
switch l, _ = c.Next(); l.value {
|
||||
case zNewline, zEOF:
|
||||
break Fetch
|
||||
case zString:
|
||||
text = append(text, l.token)
|
||||
}
|
||||
}
|
||||
|
||||
err := r.Data.Parse(text)
|
||||
if err != nil {
|
||||
return &ParseError{"", err.Error(), l}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PrivateRR) isDuplicate(r2 RR) bool { return false }
|
||||
|
||||
// PrivateHandle registers a private resource record type. It requires
|
||||
// string and numeric representation of private RR type and generator function as argument.
|
||||
func PrivateHandle(rtypestr string, rtype uint16, generator func() PrivateRdata) {
|
||||
rtypestr = strings.ToUpper(rtypestr)
|
||||
|
||||
TypeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator()} }
|
||||
TypeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator(), generator} }
|
||||
TypeToString[rtype] = rtypestr
|
||||
StringToType[rtypestr] = rtype
|
||||
|
||||
typeToUnpack[rtype] = func(h RR_Header, msg []byte, off int) (RR, int, error) {
|
||||
if noRdata(h) {
|
||||
return &h, off, nil
|
||||
}
|
||||
var err error
|
||||
|
||||
rr := mkPrivateRR(h.Rrtype)
|
||||
rr.Hdr = h
|
||||
|
||||
off1, err := rr.Data.Unpack(msg[off:])
|
||||
off += off1
|
||||
if err != nil {
|
||||
return rr, off, err
|
||||
}
|
||||
return rr, off, err
|
||||
}
|
||||
|
||||
setPrivateRR := func(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) {
|
||||
rr := mkPrivateRR(h.Rrtype)
|
||||
rr.Hdr = h
|
||||
|
||||
var l lex
|
||||
text := make([]string, 0, 2) // could be 0..N elements, median is probably 1
|
||||
Fetch:
|
||||
for {
|
||||
// TODO(miek): we could also be returning _QUOTE, this might or might not
|
||||
// be an issue (basically parsing TXT becomes hard)
|
||||
switch l = <-c; l.value {
|
||||
case zNewline, zEOF:
|
||||
break Fetch
|
||||
case zString:
|
||||
text = append(text, l.token)
|
||||
}
|
||||
}
|
||||
|
||||
err := rr.Data.Parse(text)
|
||||
if err != nil {
|
||||
return nil, &ParseError{f, err.Error(), l}, ""
|
||||
}
|
||||
|
||||
return rr, nil, ""
|
||||
}
|
||||
|
||||
typeToparserFunc[rtype] = parserFunc{setPrivateRR, true}
|
||||
}
|
||||
|
||||
// PrivateHandleRemove removes defenitions required to support private RR type.
|
||||
// PrivateHandleRemove removes definitions required to support private RR type.
|
||||
func PrivateHandleRemove(rtype uint16) {
|
||||
rtypestr, ok := TypeToString[rtype]
|
||||
if ok {
|
||||
delete(TypeToRR, rtype)
|
||||
delete(TypeToString, rtype)
|
||||
delete(typeToparserFunc, rtype)
|
||||
delete(StringToType, rtypestr)
|
||||
delete(typeToUnpack, rtype)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -158,9 +158,11 @@ func TestPrivateZoneParser(t *testing.T) {
|
|||
defer dns.PrivateHandleRemove(TypeVERSION)
|
||||
|
||||
r := strings.NewReader(smallzone)
|
||||
for x := range dns.ParseZone(r, ".", "") {
|
||||
if err := x.Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
z := dns.NewZoneParser(r, ".", "")
|
||||
|
||||
for _, ok := z.Next(); ok; _, ok = z.Next() {
|
||||
}
|
||||
if err := z.Err(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
49
rawmsg.go
49
rawmsg.go
|
@ -1,49 +0,0 @@
|
|||
package dns
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// rawSetRdlength sets the rdlength in the header of
|
||||
// the RR. The offset 'off' must be positioned at the
|
||||
// start of the header of the RR, 'end' must be the
|
||||
// end of the RR.
|
||||
func rawSetRdlength(msg []byte, off, end int) bool {
|
||||
l := len(msg)
|
||||
Loop:
|
||||
for {
|
||||
if off+1 > l {
|
||||
return false
|
||||
}
|
||||
c := int(msg[off])
|
||||
off++
|
||||
switch c & 0xC0 {
|
||||
case 0x00:
|
||||
if c == 0x00 {
|
||||
// End of the domainname
|
||||
break Loop
|
||||
}
|
||||
if off+c > l {
|
||||
return false
|
||||
}
|
||||
off += c
|
||||
|
||||
case 0xC0:
|
||||
// pointer, next byte included, ends domainname
|
||||
off++
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
// The domainname has been seen, we at the start of the fixed part in the header.
|
||||
// Type is 2 bytes, class is 2 bytes, ttl 4 and then 2 bytes for the length.
|
||||
off += 2 + 2 + 4
|
||||
if off+2 > l {
|
||||
return false
|
||||
}
|
||||
//off+1 is the end of the header, 'end' is the end of the rr
|
||||
//so 'end' - 'off+2' is the length of the rdata
|
||||
rdatalen := end - (off + 2)
|
||||
if rdatalen > 0xFFFF {
|
||||
return false
|
||||
}
|
||||
binary.BigEndian.PutUint16(msg[off:], uint16(rdatalen))
|
||||
return true
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package dns
|
||||
|
||||
import "testing"
|
||||
|
||||
const LinodeAddr = "176.58.119.54:53"
|
||||
|
||||
func TestClientRemote(t *testing.T) {
|
||||
m := new(Msg)
|
||||
m.SetQuestion("go.dns.miek.nl.", TypeTXT)
|
||||
|
||||
c := new(Client)
|
||||
r, _, err := c.Exchange(m, LinodeAddr)
|
||||
if err != nil {
|
||||
t.Errorf("failed to exchange: %v", err)
|
||||
}
|
||||
if r != nil && r.Rcode != RcodeSuccess {
|
||||
t.Errorf("failed to get an valid answer\n%v", r)
|
||||
}
|
||||
}
|
14
reverse.go
14
reverse.go
|
@ -12,6 +12,20 @@ var StringToOpcode = reverseInt(OpcodeToString)
|
|||
// StringToRcode is a map of rcodes to strings.
|
||||
var StringToRcode = reverseInt(RcodeToString)
|
||||
|
||||
func init() {
|
||||
// Preserve previous NOTIMP typo, see github.com/miekg/dns/issues/733.
|
||||
StringToRcode["NOTIMPL"] = RcodeNotImplemented
|
||||
}
|
||||
|
||||
// StringToAlgorithm is the reverse of AlgorithmToString.
|
||||
var StringToAlgorithm = reverseInt8(AlgorithmToString)
|
||||
|
||||
// StringToHash is a map of names to hash IDs.
|
||||
var StringToHash = reverseInt8(HashToString)
|
||||
|
||||
// StringToCertType is the reverseof CertTypeToString.
|
||||
var StringToCertType = reverseInt16(CertTypeToString)
|
||||
|
||||
// Reverse a map
|
||||
func reverseInt8(m map[uint8]string) map[string]uint8 {
|
||||
n := make(map[string]uint8, len(m))
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package dns
|
||||
|
||||
// testRR returns the RR from string s. The error is thrown away.
|
||||
// testRR is a helper that wraps a call to NewRR and panics if the error is non-nil.
|
||||
func testRR(s string) RR {
|
||||
r, _ := NewRR(s)
|
||||
r, err := NewRR(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ package dns
|
|||
// rrs.
|
||||
// m is used to store the RRs temporary. If it is nil a new map will be allocated.
|
||||
func Dedup(rrs []RR, m map[string]RR) []RR {
|
||||
|
||||
if m == nil {
|
||||
m = make(map[string]RR)
|
||||
}
|
||||
|
@ -14,10 +15,11 @@ func Dedup(rrs []RR, m map[string]RR) []RR {
|
|||
for _, r := range rrs {
|
||||
key := normalizedString(r)
|
||||
keys = append(keys, &key)
|
||||
if _, ok := m[key]; ok {
|
||||
if mr, ok := m[key]; ok {
|
||||
// Shortest TTL wins.
|
||||
if m[key].Header().Ttl > r.Header().Ttl {
|
||||
m[key].Header().Ttl = r.Header().Ttl
|
||||
rh, mrh := r.Header(), mr.Header()
|
||||
if mrh.Ttl > rh.Ttl {
|
||||
mrh.Ttl = rh.Ttl
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
|
2041
scan_rr.go
2041
scan_rr.go
File diff suppressed because it is too large
Load Diff
331
scan_test.go
331
scan_test.go
|
@ -1,18 +1,60 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseZoneInclude(t *testing.T) {
|
||||
func TestZoneParserGenerate(t *testing.T) {
|
||||
zone := "$ORIGIN example.org.\n$GENERATE 10-12 foo${2,3,d} IN A 127.0.0.$"
|
||||
|
||||
wantRRs := []RR{
|
||||
&A{Hdr: RR_Header{Name: "foo012.example.org."}, A: net.ParseIP("127.0.0.10")},
|
||||
&A{Hdr: RR_Header{Name: "foo013.example.org."}, A: net.ParseIP("127.0.0.11")},
|
||||
&A{Hdr: RR_Header{Name: "foo014.example.org."}, A: net.ParseIP("127.0.0.12")},
|
||||
}
|
||||
|
||||
wantIdx := 0
|
||||
|
||||
z := NewZoneParser(strings.NewReader(zone), "", "")
|
||||
|
||||
for rr, ok := z.Next(); ok; rr, ok = z.Next() {
|
||||
if wantIdx >= len(wantRRs) {
|
||||
t.Fatalf("expected %d RRs, but got more", len(wantRRs))
|
||||
}
|
||||
if got, want := rr.Header().Name, wantRRs[wantIdx].Header().Name; got != want {
|
||||
t.Fatalf("expected name %s, but got %s", want, got)
|
||||
}
|
||||
a, okA := rr.(*A)
|
||||
if !okA {
|
||||
t.Fatalf("expected *A RR, but got %T", rr)
|
||||
}
|
||||
if got, want := a.A, wantRRs[wantIdx].(*A).A; !got.Equal(want) {
|
||||
t.Fatalf("expected A with IP %v, but got %v", got, want)
|
||||
}
|
||||
wantIdx++
|
||||
}
|
||||
|
||||
if err := z.Err(); err != nil {
|
||||
t.Fatalf("expected no error, but got %s", err)
|
||||
}
|
||||
|
||||
if wantIdx != len(wantRRs) {
|
||||
t.Errorf("too few records, expected %d, got %d", len(wantRRs), wantIdx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZoneParserInclude(t *testing.T) {
|
||||
|
||||
tmpfile, err := ioutil.TempFile("", "dns")
|
||||
if err != nil {
|
||||
t.Fatalf("could not create tmpfile for test: %s", err)
|
||||
}
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
if _, err := tmpfile.WriteString("foo\tIN\tA\t127.0.0.1"); err != nil {
|
||||
t.Fatalf("unable to write content to tmpfile %q: %s", tmpfile.Name(), err)
|
||||
|
@ -21,28 +63,285 @@ func TestParseZoneInclude(t *testing.T) {
|
|||
t.Fatalf("could not close tmpfile %q: %s", tmpfile.Name(), err)
|
||||
}
|
||||
|
||||
zone := "$ORIGIN example.org.\n$INCLUDE " + tmpfile.Name()
|
||||
zone := "$ORIGIN example.org.\n$INCLUDE " + tmpfile.Name() + "\nbar\tIN\tA\t127.0.0.2"
|
||||
|
||||
tok := ParseZone(strings.NewReader(zone), "", "")
|
||||
for x := range tok {
|
||||
if x.Error != nil {
|
||||
t.Fatalf("expected no error, but got %s", x.Error)
|
||||
}
|
||||
if x.RR.Header().Name != "foo.example.org." {
|
||||
t.Fatalf("expected %s, but got %s", "foo.example.org.", x.RR.Header().Name)
|
||||
var got int
|
||||
z := NewZoneParser(strings.NewReader(zone), "", "")
|
||||
z.SetIncludeAllowed(true)
|
||||
for rr, ok := z.Next(); ok; _, ok = z.Next() {
|
||||
switch rr.Header().Name {
|
||||
case "foo.example.org.", "bar.example.org.":
|
||||
default:
|
||||
t.Fatalf("expected foo.example.org. or bar.example.org., but got %s", rr.Header().Name)
|
||||
}
|
||||
got++
|
||||
}
|
||||
if err := z.Err(); err != nil {
|
||||
t.Fatalf("expected no error, but got %s", err)
|
||||
}
|
||||
|
||||
if expected := 2; got != expected {
|
||||
t.Errorf("failed to parse zone after include, expected %d records, got %d", expected, got)
|
||||
}
|
||||
|
||||
os.Remove(tmpfile.Name())
|
||||
|
||||
tok = ParseZone(strings.NewReader(zone), "", "")
|
||||
for x := range tok {
|
||||
if x.Error == nil {
|
||||
t.Fatalf("expected first token to contain an error but it didn't")
|
||||
z = NewZoneParser(strings.NewReader(zone), "", "")
|
||||
z.SetIncludeAllowed(true)
|
||||
z.Next()
|
||||
if err := z.Err(); err == nil ||
|
||||
!strings.Contains(err.Error(), "failed to open") ||
|
||||
!strings.Contains(err.Error(), tmpfile.Name()) ||
|
||||
!strings.Contains(err.Error(), "no such file or directory") {
|
||||
t.Fatalf(`expected error to contain: "failed to open", %q and "no such file or directory" but got: %s`,
|
||||
tmpfile.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZoneParserIncludeDisallowed(t *testing.T) {
|
||||
tmpfile, err := ioutil.TempFile("", "dns")
|
||||
if err != nil {
|
||||
t.Fatalf("could not create tmpfile for test: %s", err)
|
||||
}
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
if _, err := tmpfile.WriteString("foo\tIN\tA\t127.0.0.1"); err != nil {
|
||||
t.Fatalf("unable to write content to tmpfile %q: %s", tmpfile.Name(), err)
|
||||
}
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
t.Fatalf("could not close tmpfile %q: %s", tmpfile.Name(), err)
|
||||
}
|
||||
|
||||
zp := NewZoneParser(strings.NewReader("$INCLUDE "+tmpfile.Name()), "example.org.", "")
|
||||
|
||||
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
|
||||
}
|
||||
|
||||
const expect = "$INCLUDE directive not allowed"
|
||||
if err := zp.Err(); err == nil || !strings.Contains(err.Error(), expect) {
|
||||
t.Errorf("expected error to contain %q, got %v", expect, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZoneParserAddressAAAA(t *testing.T) {
|
||||
tests := []struct {
|
||||
record string
|
||||
want *AAAA
|
||||
}{
|
||||
{
|
||||
record: "1.example.org. 600 IN AAAA ::1",
|
||||
want: &AAAA{Hdr: RR_Header{Name: "1.example.org."}, AAAA: net.IPv6loopback},
|
||||
},
|
||||
{
|
||||
record: "2.example.org. 600 IN AAAA ::FFFF:127.0.0.1",
|
||||
want: &AAAA{Hdr: RR_Header{Name: "2.example.org."}, AAAA: net.ParseIP("::FFFF:127.0.0.1")},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
got, err := NewRR(tc.record)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, but got %s", err)
|
||||
}
|
||||
if !strings.Contains(x.Error.Error(), "failed to open") ||
|
||||
!strings.Contains(x.Error.Error(), tmpfile.Name()) {
|
||||
t.Fatalf(`expected error to contain: "failed to open" and %q but got: %s`, tmpfile.Name(), x.Error)
|
||||
aaaa, ok := got.(*AAAA)
|
||||
if !ok {
|
||||
t.Fatalf("expected *AAAA RR, but got %T", got)
|
||||
}
|
||||
if !aaaa.AAAA.Equal(tc.want.AAAA) {
|
||||
t.Fatalf("expected AAAA with IP %v, but got %v", tc.want.AAAA, aaaa.AAAA)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestZoneParserAddressBad(t *testing.T) {
|
||||
records := []string{
|
||||
"1.bad.example.org. 600 IN A ::1",
|
||||
"2.bad.example.org. 600 IN A ::FFFF:127.0.0.1",
|
||||
"3.bad.example.org. 600 IN AAAA 127.0.0.1",
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
const expect = "bad A"
|
||||
if got, err := NewRR(record); err == nil || !strings.Contains(err.Error(), expect) {
|
||||
t.Errorf("NewRR(%v) = %v, want err to contain %q", record, got, expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTA(t *testing.T) {
|
||||
rr, err := NewRR(` Ta 0 0 0`)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, but got %s", err)
|
||||
}
|
||||
if rr == nil {
|
||||
t.Fatal(`expected a normal RR, but got nil`)
|
||||
}
|
||||
}
|
||||
|
||||
var errTestReadError = &Error{"test error"}
|
||||
|
||||
type errReader struct{}
|
||||
|
||||
func (errReader) Read(p []byte) (int, error) { return 0, errTestReadError }
|
||||
|
||||
func TestParseZoneReadError(t *testing.T) {
|
||||
rr, err := ReadRR(errReader{}, "")
|
||||
if err == nil || !strings.Contains(err.Error(), errTestReadError.Error()) {
|
||||
t.Errorf("expected error to contain %q, but got %v", errTestReadError, err)
|
||||
}
|
||||
if rr != nil {
|
||||
t.Errorf("expected a nil RR, but got %v", rr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnexpectedNewline(t *testing.T) {
|
||||
zone := `
|
||||
example.com. 60 PX
|
||||
1000 TXT 1K
|
||||
`
|
||||
zp := NewZoneParser(strings.NewReader(zone), "example.com.", "")
|
||||
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
|
||||
}
|
||||
|
||||
const expect = `dns: unexpected newline: "\n" at line: 2:18`
|
||||
if err := zp.Err(); err == nil || err.Error() != expect {
|
||||
t.Errorf("expected error to contain %q, got %v", expect, err)
|
||||
}
|
||||
|
||||
// Test that newlines inside braces still work.
|
||||
zone = `
|
||||
example.com. 60 PX (
|
||||
1000 TXT 1K )
|
||||
`
|
||||
zp = NewZoneParser(strings.NewReader(zone), "example.com.", "")
|
||||
|
||||
var count int
|
||||
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
|
||||
count++
|
||||
}
|
||||
|
||||
if count != 1 {
|
||||
t.Errorf("expected 1 record, got %d", count)
|
||||
}
|
||||
|
||||
if err := zp.Err(); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRFC3597InvalidLength(t *testing.T) {
|
||||
// We need to space separate the 00s otherwise it will exceed the maximum token size
|
||||
// of the zone lexer.
|
||||
_, err := NewRR("example. 3600 CLASS1 TYPE1 \\# 65536 " + strings.Repeat("00 ", 65536))
|
||||
if err == nil {
|
||||
t.Error("should not have parsed excessively long RFC3579 record")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseKnownRRAsRFC3597(t *testing.T) {
|
||||
t.Run("with RDATA", func(t *testing.T) {
|
||||
// This was found by oss-fuzz.
|
||||
_, err := NewRR("example. 3600 tYpe44 \\# 03 75 0100")
|
||||
if err != nil {
|
||||
t.Errorf("failed to parse RFC3579 format: %v", err)
|
||||
}
|
||||
|
||||
rr, err := NewRR("example. 3600 CLASS1 TYPE1 \\# 4 7f000001")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse RFC3579 format: %v", err)
|
||||
}
|
||||
|
||||
if rr.Header().Rrtype != TypeA {
|
||||
t.Errorf("expected TypeA (1) Rrtype, but got %v", rr.Header().Rrtype)
|
||||
}
|
||||
|
||||
a, ok := rr.(*A)
|
||||
if !ok {
|
||||
t.Fatalf("expected *A RR, but got %T", rr)
|
||||
}
|
||||
|
||||
localhost := net.IPv4(127, 0, 0, 1)
|
||||
if !a.A.Equal(localhost) {
|
||||
t.Errorf("expected A with IP %v, but got %v", localhost, a.A)
|
||||
}
|
||||
})
|
||||
t.Run("without RDATA", func(t *testing.T) {
|
||||
rr, err := NewRR("example. 3600 CLASS1 TYPE1 \\# 0")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse RFC3579 format: %v", err)
|
||||
}
|
||||
|
||||
if rr.Header().Rrtype != TypeA {
|
||||
t.Errorf("expected TypeA (1) Rrtype, but got %v", rr.Header().Rrtype)
|
||||
}
|
||||
|
||||
a, ok := rr.(*A)
|
||||
if !ok {
|
||||
t.Fatalf("expected *A RR, but got %T", rr)
|
||||
}
|
||||
|
||||
if len(a.A) != 0 {
|
||||
t.Errorf("expected A with empty IP, but got %v", a.A)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkNewRR(b *testing.B) {
|
||||
const name1 = "12345678901234567890123456789012345.12345678.123."
|
||||
const s = name1 + " 3600 IN MX 10 " + name1
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, err := NewRR(s)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReadRR(b *testing.B) {
|
||||
const name1 = "12345678901234567890123456789012345.12345678.123."
|
||||
const s = name1 + " 3600 IN MX 10 " + name1 + "\n"
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
r := struct{ io.Reader }{strings.NewReader(s)}
|
||||
// r is now only an io.Reader and won't benefit from the
|
||||
// io.ByteReader special-case in zlexer.Next.
|
||||
|
||||
_, err := ReadRR(r, "")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const benchZone = `
|
||||
foo. IN A 10.0.0.1 ; this is comment 1
|
||||
foo. IN A (
|
||||
10.0.0.2 ; this is comment 2
|
||||
)
|
||||
; this is comment 3
|
||||
foo. IN A 10.0.0.3
|
||||
foo. IN A ( 10.0.0.4 ); this is comment 4
|
||||
|
||||
foo. IN A 10.0.0.5
|
||||
; this is comment 5
|
||||
|
||||
foo. IN A 10.0.0.6
|
||||
|
||||
foo. IN DNSKEY 256 3 5 AwEAAb+8l ; this is comment 6
|
||||
foo. IN NSEC miek.nl. TXT RRSIG NSEC; this is comment 7
|
||||
foo. IN TXT "THIS IS TEXT MAN"; this is comment 8
|
||||
`
|
||||
|
||||
func BenchmarkZoneParser(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
zp := NewZoneParser(strings.NewReader(benchZone), "example.org.", "")
|
||||
|
||||
for _, ok := zp.Next(); ok; _, ok = zp.Next() {
|
||||
}
|
||||
|
||||
if err := zp.Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
56
scanner.go
56
scanner.go
|
@ -1,56 +0,0 @@
|
|||
package dns
|
||||
|
||||
// Implement a simple scanner, return a byte stream from an io reader.
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"io"
|
||||
"text/scanner"
|
||||
)
|
||||
|
||||
type scan struct {
|
||||
src *bufio.Reader
|
||||
position scanner.Position
|
||||
eof bool // Have we just seen a eof
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func scanInit(r io.Reader) (*scan, context.CancelFunc) {
|
||||
s := new(scan)
|
||||
s.src = bufio.NewReader(r)
|
||||
s.position.Line = 1
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
s.ctx = ctx
|
||||
|
||||
return s, cancel
|
||||
}
|
||||
|
||||
// tokenText returns the next byte from the input
|
||||
func (s *scan) tokenText() (byte, error) {
|
||||
c, err := s.src.ReadByte()
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return c, context.Canceled
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
// delay the newline handling until the next token is delivered,
|
||||
// fixes off-by-one errors when reporting a parse error.
|
||||
if s.eof == true {
|
||||
s.position.Line++
|
||||
s.position.Column = 0
|
||||
s.eof = false
|
||||
}
|
||||
if c == '\n' {
|
||||
s.eof = true
|
||||
return c, nil
|
||||
}
|
||||
s.position.Column++
|
||||
return c, nil
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ServeMux is an DNS request multiplexer. It matches the zone name of
|
||||
// each incoming request against a list of registered patterns add calls
|
||||
// the handler for the pattern that most closely matches the zone name.
|
||||
//
|
||||
// ServeMux is DNSSEC aware, meaning that queries for the DS record are
|
||||
// redirected to the parent zone (if that is also registered), otherwise
|
||||
// the child gets the query.
|
||||
//
|
||||
// ServeMux is also safe for concurrent access from multiple goroutines.
|
||||
//
|
||||
// The zero ServeMux is empty and ready for use.
|
||||
type ServeMux struct {
|
||||
z map[string]Handler
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
// NewServeMux allocates and returns a new ServeMux.
|
||||
func NewServeMux() *ServeMux {
|
||||
return new(ServeMux)
|
||||
}
|
||||
|
||||
// DefaultServeMux is the default ServeMux used by Serve.
|
||||
var DefaultServeMux = NewServeMux()
|
||||
|
||||
func (mux *ServeMux) match(q string, t uint16) Handler {
|
||||
mux.m.RLock()
|
||||
defer mux.m.RUnlock()
|
||||
if mux.z == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
q = CanonicalName(q)
|
||||
|
||||
var handler Handler
|
||||
for off, end := 0, false; !end; off, end = NextLabel(q, off) {
|
||||
if h, ok := mux.z[q[off:]]; ok {
|
||||
if t != TypeDS {
|
||||
return h
|
||||
}
|
||||
// Continue for DS to see if we have a parent too, if so delegate to the parent
|
||||
handler = h
|
||||
}
|
||||
}
|
||||
|
||||
// Wildcard match, if we have found nothing try the root zone as a last resort.
|
||||
if h, ok := mux.z["."]; ok {
|
||||
return h
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
// Handle adds a handler to the ServeMux for pattern.
|
||||
func (mux *ServeMux) Handle(pattern string, handler Handler) {
|
||||
if pattern == "" {
|
||||
panic("dns: invalid pattern " + pattern)
|
||||
}
|
||||
mux.m.Lock()
|
||||
if mux.z == nil {
|
||||
mux.z = make(map[string]Handler)
|
||||
}
|
||||
mux.z[CanonicalName(pattern)] = handler
|
||||
mux.m.Unlock()
|
||||
}
|
||||
|
||||
// HandleFunc adds a handler function to the ServeMux for pattern.
|
||||
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) {
|
||||
mux.Handle(pattern, HandlerFunc(handler))
|
||||
}
|
||||
|
||||
// HandleRemove deregisters the handler specific for pattern from the ServeMux.
|
||||
func (mux *ServeMux) HandleRemove(pattern string) {
|
||||
if pattern == "" {
|
||||
panic("dns: invalid pattern " + pattern)
|
||||
}
|
||||
mux.m.Lock()
|
||||
delete(mux.z, CanonicalName(pattern))
|
||||
mux.m.Unlock()
|
||||
}
|
||||
|
||||
// ServeDNS dispatches the request to the handler whose pattern most
|
||||
// closely matches the request message.
|
||||
//
|
||||
// ServeDNS is DNSSEC aware, meaning that queries for the DS record
|
||||
// are redirected to the parent zone (if that is also registered),
|
||||
// otherwise the child gets the query.
|
||||
//
|
||||
// If no handler is found, or there is no question, a standard REFUSED
|
||||
// message is returned
|
||||
func (mux *ServeMux) ServeDNS(w ResponseWriter, req *Msg) {
|
||||
var h Handler
|
||||
if len(req.Question) >= 1 { // allow more than one question
|
||||
h = mux.match(req.Question[0].Name, req.Question[0].Qtype)
|
||||
}
|
||||
|
||||
if h != nil {
|
||||
h.ServeDNS(w, req)
|
||||
} else {
|
||||
handleRefused(w, req)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle registers the handler with the given pattern
|
||||
// in the DefaultServeMux. The documentation for
|
||||
// ServeMux explains how patterns are matched.
|
||||
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
|
||||
|
||||
// HandleRemove deregisters the handle with the given pattern
|
||||
// in the DefaultServeMux.
|
||||
func HandleRemove(pattern string) { DefaultServeMux.HandleRemove(pattern) }
|
||||
|
||||
// HandleFunc registers the handler function with the given pattern
|
||||
// in the DefaultServeMux.
|
||||
func HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) {
|
||||
DefaultServeMux.HandleFunc(pattern, handler)
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package dns
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDotAsCatchAllWildcard(t *testing.T) {
|
||||
mux := NewServeMux()
|
||||
mux.Handle(".", HandlerFunc(HelloServer))
|
||||
mux.Handle("example.com.", HandlerFunc(AnotherHelloServer))
|
||||
|
||||
handler := mux.match("www.miek.nl.", TypeTXT)
|
||||
if handler == nil {
|
||||
t.Error("wildcard match failed")
|
||||
}
|
||||
|
||||
handler = mux.match("www.example.com.", TypeTXT)
|
||||
if handler == nil {
|
||||
t.Error("example.com match failed")
|
||||
}
|
||||
|
||||
handler = mux.match("a.www.example.com.", TypeTXT)
|
||||
if handler == nil {
|
||||
t.Error("a.www.example.com match failed")
|
||||
}
|
||||
|
||||
handler = mux.match("boe.", TypeTXT)
|
||||
if handler == nil {
|
||||
t.Error("boe. match failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaseFolding(t *testing.T) {
|
||||
mux := NewServeMux()
|
||||
mux.Handle("_udp.example.com.", HandlerFunc(HelloServer))
|
||||
|
||||
handler := mux.match("_dns._udp.example.com.", TypeSRV)
|
||||
if handler == nil {
|
||||
t.Error("case sensitive characters folded")
|
||||
}
|
||||
|
||||
handler = mux.match("_DNS._UDP.EXAMPLE.COM.", TypeSRV)
|
||||
if handler == nil {
|
||||
t.Error("case insensitive characters not folded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRootServer(t *testing.T) {
|
||||
mux := NewServeMux()
|
||||
mux.Handle(".", HandlerFunc(HelloServer))
|
||||
|
||||
handler := mux.match(".", TypeNS)
|
||||
if handler == nil {
|
||||
t.Error("root match failed")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMuxMatch(b *testing.B) {
|
||||
mux := NewServeMux()
|
||||
mux.Handle("_udp.example.com.", HandlerFunc(HelloServer))
|
||||
|
||||
bench := func(q string) func(*testing.B) {
|
||||
return func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
handler := mux.match(q, TypeSRV)
|
||||
if handler == nil {
|
||||
b.Fatal("couldn't find match")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
b.Run("lowercase", bench("_dns._udp.example.com."))
|
||||
b.Run("uppercase", bench("_DNS._UDP.EXAMPLE.COM."))
|
||||
}
|
898
server_test.go
898
server_test.go
File diff suppressed because it is too large
Load Diff
98
sig0.go
98
sig0.go
|
@ -2,8 +2,8 @@ package dns
|
|||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
|
@ -18,18 +18,14 @@ func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) {
|
|||
if k == nil {
|
||||
return nil, ErrPrivKey
|
||||
}
|
||||
if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 {
|
||||
if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 {
|
||||
return nil, ErrKey
|
||||
}
|
||||
rr.Header().Rrtype = TypeSIG
|
||||
rr.Header().Class = ClassANY
|
||||
rr.Header().Ttl = 0
|
||||
rr.Header().Name = "."
|
||||
rr.OrigTtl = 0
|
||||
rr.TypeCovered = 0
|
||||
rr.Labels = 0
|
||||
|
||||
buf := make([]byte, m.Len()+rr.len())
|
||||
rr.Hdr = RR_Header{Name: ".", Rrtype: TypeSIG, Class: ClassANY, Ttl: 0}
|
||||
rr.OrigTtl, rr.TypeCovered, rr.Labels = 0, 0, 0
|
||||
|
||||
buf := make([]byte, m.Len()+Len(rr))
|
||||
mbuf, err := m.PackBuffer(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -43,18 +39,17 @@ func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) {
|
|||
}
|
||||
buf = buf[:off:cap(buf)]
|
||||
|
||||
hash, ok := AlgorithmToHash[rr.Algorithm]
|
||||
if !ok {
|
||||
return nil, ErrAlg
|
||||
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hasher := hash.New()
|
||||
// Write SIG rdata
|
||||
hasher.Write(buf[len(mbuf)+1+2+2+4+2:])
|
||||
h.Write(buf[len(mbuf)+1+2+2+4+2:])
|
||||
// Write message
|
||||
hasher.Write(buf[:len(mbuf)])
|
||||
h.Write(buf[:len(mbuf)])
|
||||
|
||||
signature, err := sign(k, hasher.Sum(nil), hash, rr.Algorithm)
|
||||
signature, err := sign(k, h.Sum(nil), cryptohash, rr.Algorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -83,32 +78,21 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
|
|||
if k == nil {
|
||||
return ErrKey
|
||||
}
|
||||
if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 {
|
||||
if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 {
|
||||
return ErrKey
|
||||
}
|
||||
|
||||
var hash crypto.Hash
|
||||
switch rr.Algorithm {
|
||||
case DSA, RSASHA1:
|
||||
hash = crypto.SHA1
|
||||
case RSASHA256, ECDSAP256SHA256:
|
||||
hash = crypto.SHA256
|
||||
case ECDSAP384SHA384:
|
||||
hash = crypto.SHA384
|
||||
case RSASHA512:
|
||||
hash = crypto.SHA512
|
||||
default:
|
||||
return ErrAlg
|
||||
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hasher := hash.New()
|
||||
|
||||
buflen := len(buf)
|
||||
qdc := binary.BigEndian.Uint16(buf[4:])
|
||||
anc := binary.BigEndian.Uint16(buf[6:])
|
||||
auc := binary.BigEndian.Uint16(buf[8:])
|
||||
adc := binary.BigEndian.Uint16(buf[10:])
|
||||
offset := 12
|
||||
var err error
|
||||
offset := headerSize
|
||||
for i := uint16(0); i < qdc && offset < buflen; i++ {
|
||||
_, offset, err = UnpackDomainName(buf, offset)
|
||||
if err != nil {
|
||||
|
@ -127,8 +111,7 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
|
|||
if offset+1 >= buflen {
|
||||
continue
|
||||
}
|
||||
var rdlen uint16
|
||||
rdlen = binary.BigEndian.Uint16(buf[offset:])
|
||||
rdlen := binary.BigEndian.Uint16(buf[offset:])
|
||||
offset += 2
|
||||
offset += int(rdlen)
|
||||
}
|
||||
|
@ -168,47 +151,40 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
|
|||
}
|
||||
// If key has come from the DNS name compression might
|
||||
// have mangled the case of the name
|
||||
if strings.ToLower(signername) != strings.ToLower(k.Header().Name) {
|
||||
if !strings.EqualFold(signername, k.Header().Name) {
|
||||
return &Error{err: "signer name doesn't match key name"}
|
||||
}
|
||||
sigend := offset
|
||||
hasher.Write(buf[sigstart:sigend])
|
||||
hasher.Write(buf[:10])
|
||||
hasher.Write([]byte{
|
||||
h.Write(buf[sigstart:sigend])
|
||||
h.Write(buf[:10])
|
||||
h.Write([]byte{
|
||||
byte((adc - 1) << 8),
|
||||
byte(adc - 1),
|
||||
})
|
||||
hasher.Write(buf[12:bodyend])
|
||||
h.Write(buf[12:bodyend])
|
||||
|
||||
hashed := hasher.Sum(nil)
|
||||
hashed := h.Sum(nil)
|
||||
sig := buf[sigend:]
|
||||
switch k.Algorithm {
|
||||
case DSA:
|
||||
pk := k.publicKeyDSA()
|
||||
sig = sig[1:]
|
||||
r := big.NewInt(0)
|
||||
r.SetBytes(sig[:len(sig)/2])
|
||||
s := big.NewInt(0)
|
||||
s.SetBytes(sig[len(sig)/2:])
|
||||
case RSASHA1, RSASHA256, RSASHA512:
|
||||
pk := k.publicKeyRSA()
|
||||
if pk != nil {
|
||||
if dsa.Verify(pk, hashed, r, s) {
|
||||
return rsa.VerifyPKCS1v15(pk, cryptohash, hashed, sig)
|
||||
}
|
||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||
pk := k.publicKeyECDSA()
|
||||
r := new(big.Int).SetBytes(sig[:len(sig)/2])
|
||||
s := new(big.Int).SetBytes(sig[len(sig)/2:])
|
||||
if pk != nil {
|
||||
if ecdsa.Verify(pk, hashed, r, s) {
|
||||
return nil
|
||||
}
|
||||
return ErrSig
|
||||
}
|
||||
case RSASHA1, RSASHA256, RSASHA512:
|
||||
pk := k.publicKeyRSA()
|
||||
case ED25519:
|
||||
pk := k.publicKeyED25519()
|
||||
if pk != nil {
|
||||
return rsa.VerifyPKCS1v15(pk, hash, hashed, sig)
|
||||
}
|
||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||
pk := k.publicKeyECDSA()
|
||||
r := big.NewInt(0)
|
||||
r.SetBytes(sig[:len(sig)/2])
|
||||
s := big.NewInt(0)
|
||||
s.SetBytes(sig[len(sig)/2:])
|
||||
if pk != nil {
|
||||
if ecdsa.Verify(pk, hashed, r, s) {
|
||||
if ed25519.Verify(pk, hashed, sig) {
|
||||
return nil
|
||||
}
|
||||
return ErrSig
|
||||
|
|
22
sig0_test.go
22
sig0_test.go
|
@ -12,23 +12,25 @@ func TestSIG0(t *testing.T) {
|
|||
}
|
||||
m := new(Msg)
|
||||
m.SetQuestion("example.org.", TypeSOA)
|
||||
for _, alg := range []uint8{ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256, RSASHA512} {
|
||||
for _, alg := range []uint8{ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256, RSASHA512, ED25519} {
|
||||
algstr := AlgorithmToString[alg]
|
||||
keyrr := new(KEY)
|
||||
keyrr.Hdr.Name = algstr + "."
|
||||
keyrr.Hdr.Rrtype = TypeKEY
|
||||
keyrr.Hdr.Class = ClassINET
|
||||
keyrr.Algorithm = alg
|
||||
keysize := 1024
|
||||
keysize := 512
|
||||
switch alg {
|
||||
case ECDSAP256SHA256:
|
||||
case ECDSAP256SHA256, ED25519:
|
||||
keysize = 256
|
||||
case ECDSAP384SHA384:
|
||||
keysize = 384
|
||||
case RSASHA512:
|
||||
keysize = 1024
|
||||
}
|
||||
pk, err := keyrr.Generate(keysize)
|
||||
if err != nil {
|
||||
t.Errorf("failed to generate key for “%s”: %v", algstr, err)
|
||||
t.Errorf("failed to generate key for %q: %v", algstr, err)
|
||||
continue
|
||||
}
|
||||
now := uint32(time.Now().Unix())
|
||||
|
@ -43,16 +45,16 @@ func TestSIG0(t *testing.T) {
|
|||
sigrr.SignerName = keyrr.Hdr.Name
|
||||
mb, err := sigrr.Sign(pk.(crypto.Signer), m)
|
||||
if err != nil {
|
||||
t.Errorf("failed to sign message using “%s”: %v", algstr, err)
|
||||
t.Errorf("failed to sign message using %q: %v", algstr, err)
|
||||
continue
|
||||
}
|
||||
m := new(Msg)
|
||||
if err := m.Unpack(mb); err != nil {
|
||||
t.Errorf("failed to unpack message signed using “%s”: %v", algstr, err)
|
||||
t.Errorf("failed to unpack message signed using %q: %v", algstr, err)
|
||||
continue
|
||||
}
|
||||
if len(m.Extra) != 1 {
|
||||
t.Errorf("missing SIG for message signed using “%s”", algstr)
|
||||
t.Errorf("missing SIG for message signed using %q", algstr)
|
||||
continue
|
||||
}
|
||||
var sigrrwire *SIG
|
||||
|
@ -69,20 +71,20 @@ func TestSIG0(t *testing.T) {
|
|||
id = "sigrrwire"
|
||||
}
|
||||
if err := rr.Verify(keyrr, mb); err != nil {
|
||||
t.Errorf("failed to verify “%s” signed SIG(%s): %v", algstr, id, err)
|
||||
t.Errorf("failed to verify %q signed SIG(%s): %v", algstr, id, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
mb[13]++
|
||||
if err := sigrr.Verify(keyrr, mb); err == nil {
|
||||
t.Errorf("verify succeeded on an altered message using “%s”", algstr)
|
||||
t.Errorf("verify succeeded on an altered message using %q", algstr)
|
||||
continue
|
||||
}
|
||||
sigrr.Expiration = 2
|
||||
sigrr.Inception = 1
|
||||
mb, _ = sigrr.Sign(pk.(crypto.Signer), m)
|
||||
if err := sigrr.Verify(keyrr, mb); err == nil {
|
||||
t.Errorf("verify succeeded on an expired message using “%s”", algstr)
|
||||
t.Errorf("verify succeeded on an expired message using %q", algstr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ type call struct {
|
|||
type singleflight struct {
|
||||
sync.Mutex // protects m
|
||||
m map[string]*call // lazily initialized
|
||||
|
||||
dontDeleteForTesting bool // this is only to be used by TestConcurrentExchanges
|
||||
}
|
||||
|
||||
// Do executes and returns the results of the given function, making
|
||||
|
@ -49,9 +51,11 @@ func (g *singleflight) Do(key string, fn func() (*Msg, time.Duration, error)) (v
|
|||
c.val, c.rtt, c.err = fn()
|
||||
c.wg.Done()
|
||||
|
||||
g.Lock()
|
||||
delete(g.m, key)
|
||||
g.Unlock()
|
||||
if !g.dontDeleteForTesting {
|
||||
g.Lock()
|
||||
delete(g.m, key)
|
||||
g.Unlock()
|
||||
}
|
||||
|
||||
return c.val, c.rtt, c.err, c.dups > 0
|
||||
}
|
||||
|
|
|
@ -14,10 +14,7 @@ func (r *SMIMEA) Sign(usage, selector, matchingType int, cert *x509.Certificate)
|
|||
r.MatchingType = uint8(matchingType)
|
||||
|
||||
r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify verifies a SMIMEA record against an SSL certificate. If it is OK
|
||||
|
|
|
@ -0,0 +1,935 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SVCBKey is the type of the keys used in the SVCB RR.
|
||||
type SVCBKey uint16
|
||||
|
||||
// Keys defined in draft-ietf-dnsop-svcb-https-08 Section 14.3.2.
|
||||
const (
|
||||
SVCB_MANDATORY SVCBKey = iota
|
||||
SVCB_ALPN
|
||||
SVCB_NO_DEFAULT_ALPN
|
||||
SVCB_PORT
|
||||
SVCB_IPV4HINT
|
||||
SVCB_ECHCONFIG
|
||||
SVCB_IPV6HINT
|
||||
SVCB_DOHPATH // draft-ietf-add-svcb-dns-02 Section 9
|
||||
|
||||
svcb_RESERVED SVCBKey = 65535
|
||||
)
|
||||
|
||||
var svcbKeyToStringMap = map[SVCBKey]string{
|
||||
SVCB_MANDATORY: "mandatory",
|
||||
SVCB_ALPN: "alpn",
|
||||
SVCB_NO_DEFAULT_ALPN: "no-default-alpn",
|
||||
SVCB_PORT: "port",
|
||||
SVCB_IPV4HINT: "ipv4hint",
|
||||
SVCB_ECHCONFIG: "ech",
|
||||
SVCB_IPV6HINT: "ipv6hint",
|
||||
SVCB_DOHPATH: "dohpath",
|
||||
}
|
||||
|
||||
var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap)
|
||||
|
||||
func reverseSVCBKeyMap(m map[SVCBKey]string) map[string]SVCBKey {
|
||||
n := make(map[string]SVCBKey, len(m))
|
||||
for u, s := range m {
|
||||
n[s] = u
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// String takes the numerical code of an SVCB key and returns its name.
|
||||
// Returns an empty string for reserved keys.
|
||||
// Accepts unassigned keys as well as experimental/private keys.
|
||||
func (key SVCBKey) String() string {
|
||||
if x := svcbKeyToStringMap[key]; x != "" {
|
||||
return x
|
||||
}
|
||||
if key == svcb_RESERVED {
|
||||
return ""
|
||||
}
|
||||
return "key" + strconv.FormatUint(uint64(key), 10)
|
||||
}
|
||||
|
||||
// svcbStringToKey returns the numerical code of an SVCB key.
|
||||
// Returns svcb_RESERVED for reserved/invalid keys.
|
||||
// Accepts unassigned keys as well as experimental/private keys.
|
||||
func svcbStringToKey(s string) SVCBKey {
|
||||
if strings.HasPrefix(s, "key") {
|
||||
a, err := strconv.ParseUint(s[3:], 10, 16)
|
||||
// no leading zeros
|
||||
// key shouldn't be registered
|
||||
if err != nil || a == 65535 || s[3] == '0' || svcbKeyToStringMap[SVCBKey(a)] != "" {
|
||||
return svcb_RESERVED
|
||||
}
|
||||
return SVCBKey(a)
|
||||
}
|
||||
if key, ok := svcbStringToKeyMap[s]; ok {
|
||||
return key
|
||||
}
|
||||
return svcb_RESERVED
|
||||
}
|
||||
|
||||
func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
|
||||
l, _ := c.Next()
|
||||
i, e := strconv.ParseUint(l.token, 10, 16)
|
||||
if e != nil || l.err {
|
||||
return &ParseError{l.token, "bad SVCB priority", l}
|
||||
}
|
||||
rr.Priority = uint16(i)
|
||||
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next() // zString
|
||||
rr.Target = l.token
|
||||
|
||||
name, nameOk := toAbsoluteName(l.token, o)
|
||||
if l.err || !nameOk {
|
||||
return &ParseError{l.token, "bad SVCB Target", l}
|
||||
}
|
||||
rr.Target = name
|
||||
|
||||
// Values (if any)
|
||||
l, _ = c.Next()
|
||||
var xs []SVCBKeyValue
|
||||
// Helps require whitespace between pairs.
|
||||
// Prevents key1000="a"key1001=...
|
||||
canHaveNextKey := true
|
||||
for l.value != zNewline && l.value != zEOF {
|
||||
switch l.value {
|
||||
case zString:
|
||||
if !canHaveNextKey {
|
||||
// The key we can now read was probably meant to be
|
||||
// a part of the last value.
|
||||
return &ParseError{l.token, "bad SVCB value quotation", l}
|
||||
}
|
||||
|
||||
// In key=value pairs, value does not have to be quoted unless value
|
||||
// contains whitespace. And keys don't need to have values.
|
||||
// Similarly, keys with an equality signs after them don't need values.
|
||||
// l.token includes at least up to the first equality sign.
|
||||
idx := strings.IndexByte(l.token, '=')
|
||||
var key, value string
|
||||
if idx < 0 {
|
||||
// Key with no value and no equality sign
|
||||
key = l.token
|
||||
} else if idx == 0 {
|
||||
return &ParseError{l.token, "bad SVCB key", l}
|
||||
} else {
|
||||
key, value = l.token[:idx], l.token[idx+1:]
|
||||
|
||||
if value == "" {
|
||||
// We have a key and an equality sign. Maybe we have nothing
|
||||
// after "=" or we have a double quote.
|
||||
l, _ = c.Next()
|
||||
if l.value == zQuote {
|
||||
// Only needed when value ends with double quotes.
|
||||
// Any value starting with zQuote ends with it.
|
||||
canHaveNextKey = false
|
||||
|
||||
l, _ = c.Next()
|
||||
switch l.value {
|
||||
case zString:
|
||||
// We have a value in double quotes.
|
||||
value = l.token
|
||||
l, _ = c.Next()
|
||||
if l.value != zQuote {
|
||||
return &ParseError{l.token, "SVCB unterminated value", l}
|
||||
}
|
||||
case zQuote:
|
||||
// There's nothing in double quotes.
|
||||
default:
|
||||
return &ParseError{l.token, "bad SVCB value", l}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
kv := makeSVCBKeyValue(svcbStringToKey(key))
|
||||
if kv == nil {
|
||||
return &ParseError{l.token, "bad SVCB key", l}
|
||||
}
|
||||
if err := kv.parse(value); err != nil {
|
||||
return &ParseError{l.token, err.Error(), l}
|
||||
}
|
||||
xs = append(xs, kv)
|
||||
case zQuote:
|
||||
return &ParseError{l.token, "SVCB key can't contain double quotes", l}
|
||||
case zBlank:
|
||||
canHaveNextKey = true
|
||||
default:
|
||||
return &ParseError{l.token, "bad SVCB values", l}
|
||||
}
|
||||
l, _ = c.Next()
|
||||
}
|
||||
|
||||
// "In AliasMode, records SHOULD NOT include any SvcParams, and recipients MUST
|
||||
// ignore any SvcParams that are present."
|
||||
// However, we don't check rr.Priority == 0 && len(xs) > 0 here
|
||||
// It is the responsibility of the user of the library to check this.
|
||||
// This is to encourage the fixing of the source of this error.
|
||||
|
||||
rr.Value = xs
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeSVCBKeyValue returns an SVCBKeyValue struct with the key or nil for reserved keys.
|
||||
func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
|
||||
switch key {
|
||||
case SVCB_MANDATORY:
|
||||
return new(SVCBMandatory)
|
||||
case SVCB_ALPN:
|
||||
return new(SVCBAlpn)
|
||||
case SVCB_NO_DEFAULT_ALPN:
|
||||
return new(SVCBNoDefaultAlpn)
|
||||
case SVCB_PORT:
|
||||
return new(SVCBPort)
|
||||
case SVCB_IPV4HINT:
|
||||
return new(SVCBIPv4Hint)
|
||||
case SVCB_ECHCONFIG:
|
||||
return new(SVCBECHConfig)
|
||||
case SVCB_IPV6HINT:
|
||||
return new(SVCBIPv6Hint)
|
||||
case SVCB_DOHPATH:
|
||||
return new(SVCBDoHPath)
|
||||
case svcb_RESERVED:
|
||||
return nil
|
||||
default:
|
||||
e := new(SVCBLocal)
|
||||
e.KeyCode = key
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
// SVCB RR. See RFC xxxx (https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-08).
|
||||
//
|
||||
// NOTE: The HTTPS/SVCB RFCs are in the draft stage.
|
||||
// The API, including constants and types related to SVCBKeyValues, may
|
||||
// change in future versions in accordance with the latest drafts.
|
||||
type SVCB struct {
|
||||
Hdr RR_Header
|
||||
Priority uint16 // If zero, Value must be empty or discarded by the user of this library
|
||||
Target string `dns:"domain-name"`
|
||||
Value []SVCBKeyValue `dns:"pairs"`
|
||||
}
|
||||
|
||||
// HTTPS RR. Everything valid for SVCB applies to HTTPS as well.
|
||||
// Except that the HTTPS record is intended for use with the HTTP and HTTPS protocols.
|
||||
//
|
||||
// NOTE: The HTTPS/SVCB RFCs are in the draft stage.
|
||||
// The API, including constants and types related to SVCBKeyValues, may
|
||||
// change in future versions in accordance with the latest drafts.
|
||||
type HTTPS struct {
|
||||
SVCB
|
||||
}
|
||||
|
||||
func (rr *HTTPS) String() string {
|
||||
return rr.SVCB.String()
|
||||
}
|
||||
|
||||
func (rr *HTTPS) parse(c *zlexer, o string) *ParseError {
|
||||
return rr.SVCB.parse(c, o)
|
||||
}
|
||||
|
||||
// SVCBKeyValue defines a key=value pair for the SVCB RR type.
|
||||
// An SVCB RR can have multiple SVCBKeyValues appended to it.
|
||||
type SVCBKeyValue interface {
|
||||
Key() SVCBKey // Key returns the numerical key code.
|
||||
pack() ([]byte, error) // pack returns the encoded value.
|
||||
unpack([]byte) error // unpack sets the value.
|
||||
String() string // String returns the string representation of the value.
|
||||
parse(string) error // parse sets the value to the given string representation of the value.
|
||||
copy() SVCBKeyValue // copy returns a deep-copy of the pair.
|
||||
len() int // len returns the length of value in the wire format.
|
||||
}
|
||||
|
||||
// SVCBMandatory pair adds to required keys that must be interpreted for the RR
|
||||
// to be functional. If ignored, the whole RRSet must be ignored.
|
||||
// "port" and "no-default-alpn" are mandatory by default if present,
|
||||
// so they shouldn't be included here.
|
||||
//
|
||||
// It is incumbent upon the user of this library to reject the RRSet if
|
||||
// or avoid constructing such an RRSet that:
|
||||
// - "mandatory" is included as one of the keys of mandatory
|
||||
// - no key is listed multiple times in mandatory
|
||||
// - all keys listed in mandatory are present
|
||||
// - escape sequences are not used in mandatory
|
||||
// - mandatory, when present, lists at least one key
|
||||
//
|
||||
// Basic use pattern for creating a mandatory option:
|
||||
//
|
||||
// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
|
||||
// e := new(dns.SVCBMandatory)
|
||||
// e.Code = []uint16{dns.SVCB_ALPN}
|
||||
// s.Value = append(s.Value, e)
|
||||
// t := new(dns.SVCBAlpn)
|
||||
// t.Alpn = []string{"xmpp-client"}
|
||||
// s.Value = append(s.Value, t)
|
||||
type SVCBMandatory struct {
|
||||
Code []SVCBKey
|
||||
}
|
||||
|
||||
func (*SVCBMandatory) Key() SVCBKey { return SVCB_MANDATORY }
|
||||
|
||||
func (s *SVCBMandatory) String() string {
|
||||
str := make([]string, len(s.Code))
|
||||
for i, e := range s.Code {
|
||||
str[i] = e.String()
|
||||
}
|
||||
return strings.Join(str, ",")
|
||||
}
|
||||
|
||||
func (s *SVCBMandatory) pack() ([]byte, error) {
|
||||
codes := append([]SVCBKey(nil), s.Code...)
|
||||
sort.Slice(codes, func(i, j int) bool {
|
||||
return codes[i] < codes[j]
|
||||
})
|
||||
b := make([]byte, 2*len(codes))
|
||||
for i, e := range codes {
|
||||
binary.BigEndian.PutUint16(b[2*i:], uint16(e))
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (s *SVCBMandatory) unpack(b []byte) error {
|
||||
if len(b)%2 != 0 {
|
||||
return errors.New("dns: svcbmandatory: value length is not a multiple of 2")
|
||||
}
|
||||
codes := make([]SVCBKey, 0, len(b)/2)
|
||||
for i := 0; i < len(b); i += 2 {
|
||||
// We assume strictly increasing order.
|
||||
codes = append(codes, SVCBKey(binary.BigEndian.Uint16(b[i:])))
|
||||
}
|
||||
s.Code = codes
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBMandatory) parse(b string) error {
|
||||
str := strings.Split(b, ",")
|
||||
codes := make([]SVCBKey, 0, len(str))
|
||||
for _, e := range str {
|
||||
codes = append(codes, svcbStringToKey(e))
|
||||
}
|
||||
s.Code = codes
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBMandatory) len() int {
|
||||
return 2 * len(s.Code)
|
||||
}
|
||||
|
||||
func (s *SVCBMandatory) copy() SVCBKeyValue {
|
||||
return &SVCBMandatory{
|
||||
append([]SVCBKey(nil), s.Code...),
|
||||
}
|
||||
}
|
||||
|
||||
// SVCBAlpn pair is used to list supported connection protocols.
|
||||
// The user of this library must ensure that at least one protocol is listed when alpn is present.
|
||||
// Protocol IDs can be found at:
|
||||
// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
|
||||
// Basic use pattern for creating an alpn option:
|
||||
//
|
||||
// h := new(dns.HTTPS)
|
||||
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||
// e := new(dns.SVCBAlpn)
|
||||
// e.Alpn = []string{"h2", "http/1.1"}
|
||||
// h.Value = append(h.Value, e)
|
||||
type SVCBAlpn struct {
|
||||
Alpn []string
|
||||
}
|
||||
|
||||
func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN }
|
||||
|
||||
func (s *SVCBAlpn) String() string {
|
||||
// An ALPN value is a comma-separated list of values, each of which can be
|
||||
// an arbitrary binary value. In order to allow parsing, the comma and
|
||||
// backslash characters are themselves escaped.
|
||||
//
|
||||
// However, this escaping is done in addition to the normal escaping which
|
||||
// happens in zone files, meaning that these values must be
|
||||
// double-escaped. This looks terrible, so if you see a never-ending
|
||||
// sequence of backslash in a zone file this may be why.
|
||||
//
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-08#appendix-A.1
|
||||
var str strings.Builder
|
||||
for i, alpn := range s.Alpn {
|
||||
// 4*len(alpn) is the worst case where we escape every character in the alpn as \123, plus 1 byte for the ',' separating the alpn from others
|
||||
str.Grow(4*len(alpn) + 1)
|
||||
if i > 0 {
|
||||
str.WriteByte(',')
|
||||
}
|
||||
for j := 0; j < len(alpn); j++ {
|
||||
e := alpn[j]
|
||||
if ' ' > e || e > '~' {
|
||||
str.WriteString(escapeByte(e))
|
||||
continue
|
||||
}
|
||||
switch e {
|
||||
// We escape a few characters which may confuse humans or parsers.
|
||||
case '"', ';', ' ':
|
||||
str.WriteByte('\\')
|
||||
str.WriteByte(e)
|
||||
// The comma and backslash characters themselves must be
|
||||
// doubly-escaped. We use `\\` for the first backslash and
|
||||
// the escaped numeric value for the other value. We especially
|
||||
// don't want a comma in the output.
|
||||
case ',':
|
||||
str.WriteString(`\\\044`)
|
||||
case '\\':
|
||||
str.WriteString(`\\\092`)
|
||||
default:
|
||||
str.WriteByte(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
|
||||
func (s *SVCBAlpn) pack() ([]byte, error) {
|
||||
// Liberally estimate the size of an alpn as 10 octets
|
||||
b := make([]byte, 0, 10*len(s.Alpn))
|
||||
for _, e := range s.Alpn {
|
||||
if e == "" {
|
||||
return nil, errors.New("dns: svcbalpn: empty alpn-id")
|
||||
}
|
||||
if len(e) > 255 {
|
||||
return nil, errors.New("dns: svcbalpn: alpn-id too long")
|
||||
}
|
||||
b = append(b, byte(len(e)))
|
||||
b = append(b, e...)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (s *SVCBAlpn) unpack(b []byte) error {
|
||||
// Estimate the size of the smallest alpn as 4 bytes
|
||||
alpn := make([]string, 0, len(b)/4)
|
||||
for i := 0; i < len(b); {
|
||||
length := int(b[i])
|
||||
i++
|
||||
if i+length > len(b) {
|
||||
return errors.New("dns: svcbalpn: alpn array overflowing")
|
||||
}
|
||||
alpn = append(alpn, string(b[i:i+length]))
|
||||
i += length
|
||||
}
|
||||
s.Alpn = alpn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBAlpn) parse(b string) error {
|
||||
if len(b) == 0 {
|
||||
s.Alpn = []string{}
|
||||
return nil
|
||||
}
|
||||
|
||||
alpn := []string{}
|
||||
a := []byte{}
|
||||
for p := 0; p < len(b); {
|
||||
c, q := nextByte(b, p)
|
||||
if q == 0 {
|
||||
return errors.New("dns: svcbalpn: unterminated escape")
|
||||
}
|
||||
p += q
|
||||
// If we find a comma, we have finished reading an alpn.
|
||||
if c == ',' {
|
||||
if len(a) == 0 {
|
||||
return errors.New("dns: svcbalpn: empty protocol identifier")
|
||||
}
|
||||
alpn = append(alpn, string(a))
|
||||
a = []byte{}
|
||||
continue
|
||||
}
|
||||
// If it's a backslash, we need to handle a comma-separated list.
|
||||
if c == '\\' {
|
||||
dc, dq := nextByte(b, p)
|
||||
if dq == 0 {
|
||||
return errors.New("dns: svcbalpn: unterminated escape decoding comma-separated list")
|
||||
}
|
||||
if dc != '\\' && dc != ',' {
|
||||
return errors.New("dns: svcbalpn: bad escaped character decoding comma-separated list")
|
||||
}
|
||||
p += dq
|
||||
c = dc
|
||||
}
|
||||
a = append(a, c)
|
||||
}
|
||||
// Add the final alpn.
|
||||
if len(a) == 0 {
|
||||
return errors.New("dns: svcbalpn: last protocol identifier empty")
|
||||
}
|
||||
s.Alpn = append(alpn, string(a))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBAlpn) len() int {
|
||||
var l int
|
||||
for _, e := range s.Alpn {
|
||||
l += 1 + len(e)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (s *SVCBAlpn) copy() SVCBKeyValue {
|
||||
return &SVCBAlpn{
|
||||
append([]string(nil), s.Alpn...),
|
||||
}
|
||||
}
|
||||
|
||||
// SVCBNoDefaultAlpn pair signifies no support for default connection protocols.
|
||||
// Should be used in conjunction with alpn.
|
||||
// Basic use pattern for creating a no-default-alpn option:
|
||||
//
|
||||
// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
|
||||
// t := new(dns.SVCBAlpn)
|
||||
// t.Alpn = []string{"xmpp-client"}
|
||||
// s.Value = append(s.Value, t)
|
||||
// e := new(dns.SVCBNoDefaultAlpn)
|
||||
// s.Value = append(s.Value, e)
|
||||
type SVCBNoDefaultAlpn struct{}
|
||||
|
||||
func (*SVCBNoDefaultAlpn) Key() SVCBKey { return SVCB_NO_DEFAULT_ALPN }
|
||||
func (*SVCBNoDefaultAlpn) copy() SVCBKeyValue { return &SVCBNoDefaultAlpn{} }
|
||||
func (*SVCBNoDefaultAlpn) pack() ([]byte, error) { return []byte{}, nil }
|
||||
func (*SVCBNoDefaultAlpn) String() string { return "" }
|
||||
func (*SVCBNoDefaultAlpn) len() int { return 0 }
|
||||
|
||||
func (*SVCBNoDefaultAlpn) unpack(b []byte) error {
|
||||
if len(b) != 0 {
|
||||
return errors.New("dns: svcbnodefaultalpn: no-default-alpn must have no value")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*SVCBNoDefaultAlpn) parse(b string) error {
|
||||
if b != "" {
|
||||
return errors.New("dns: svcbnodefaultalpn: no-default-alpn must have no value")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SVCBPort pair defines the port for connection.
|
||||
// Basic use pattern for creating a port option:
|
||||
//
|
||||
// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
|
||||
// e := new(dns.SVCBPort)
|
||||
// e.Port = 80
|
||||
// s.Value = append(s.Value, e)
|
||||
type SVCBPort struct {
|
||||
Port uint16
|
||||
}
|
||||
|
||||
func (*SVCBPort) Key() SVCBKey { return SVCB_PORT }
|
||||
func (*SVCBPort) len() int { return 2 }
|
||||
func (s *SVCBPort) String() string { return strconv.FormatUint(uint64(s.Port), 10) }
|
||||
func (s *SVCBPort) copy() SVCBKeyValue { return &SVCBPort{s.Port} }
|
||||
|
||||
func (s *SVCBPort) unpack(b []byte) error {
|
||||
if len(b) != 2 {
|
||||
return errors.New("dns: svcbport: port length is not exactly 2 octets")
|
||||
}
|
||||
s.Port = binary.BigEndian.Uint16(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBPort) pack() ([]byte, error) {
|
||||
b := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(b, s.Port)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (s *SVCBPort) parse(b string) error {
|
||||
port, err := strconv.ParseUint(b, 10, 16)
|
||||
if err != nil {
|
||||
return errors.New("dns: svcbport: port out of range")
|
||||
}
|
||||
s.Port = uint16(port)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SVCBIPv4Hint pair suggests an IPv4 address which may be used to open connections
|
||||
// if A and AAAA record responses for SVCB's Target domain haven't been received.
|
||||
// In that case, optionally, A and AAAA requests can be made, after which the connection
|
||||
// to the hinted IP address may be terminated and a new connection may be opened.
|
||||
// Basic use pattern for creating an ipv4hint option:
|
||||
//
|
||||
// h := new(dns.HTTPS)
|
||||
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||
// e := new(dns.SVCBIPv4Hint)
|
||||
// e.Hint = []net.IP{net.IPv4(1,1,1,1).To4()}
|
||||
//
|
||||
// Or
|
||||
//
|
||||
// e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()}
|
||||
// h.Value = append(h.Value, e)
|
||||
type SVCBIPv4Hint struct {
|
||||
Hint []net.IP
|
||||
}
|
||||
|
||||
func (*SVCBIPv4Hint) Key() SVCBKey { return SVCB_IPV4HINT }
|
||||
func (s *SVCBIPv4Hint) len() int { return 4 * len(s.Hint) }
|
||||
|
||||
func (s *SVCBIPv4Hint) pack() ([]byte, error) {
|
||||
b := make([]byte, 0, 4*len(s.Hint))
|
||||
for _, e := range s.Hint {
|
||||
x := e.To4()
|
||||
if x == nil {
|
||||
return nil, errors.New("dns: svcbipv4hint: expected ipv4, hint is ipv6")
|
||||
}
|
||||
b = append(b, x...)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (s *SVCBIPv4Hint) unpack(b []byte) error {
|
||||
if len(b) == 0 || len(b)%4 != 0 {
|
||||
return errors.New("dns: svcbipv4hint: ipv4 address byte array length is not a multiple of 4")
|
||||
}
|
||||
x := make([]net.IP, 0, len(b)/4)
|
||||
for i := 0; i < len(b); i += 4 {
|
||||
x = append(x, net.IP(b[i:i+4]))
|
||||
}
|
||||
s.Hint = x
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBIPv4Hint) String() string {
|
||||
str := make([]string, len(s.Hint))
|
||||
for i, e := range s.Hint {
|
||||
x := e.To4()
|
||||
if x == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
str[i] = x.String()
|
||||
}
|
||||
return strings.Join(str, ",")
|
||||
}
|
||||
|
||||
func (s *SVCBIPv4Hint) parse(b string) error {
|
||||
if strings.Contains(b, ":") {
|
||||
return errors.New("dns: svcbipv4hint: expected ipv4, got ipv6")
|
||||
}
|
||||
str := strings.Split(b, ",")
|
||||
dst := make([]net.IP, len(str))
|
||||
for i, e := range str {
|
||||
ip := net.ParseIP(e).To4()
|
||||
if ip == nil {
|
||||
return errors.New("dns: svcbipv4hint: bad ip")
|
||||
}
|
||||
dst[i] = ip
|
||||
}
|
||||
s.Hint = dst
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
|
||||
hint := make([]net.IP, len(s.Hint))
|
||||
for i, ip := range s.Hint {
|
||||
hint[i] = copyIP(ip)
|
||||
}
|
||||
|
||||
return &SVCBIPv4Hint{
|
||||
Hint: hint,
|
||||
}
|
||||
}
|
||||
|
||||
// SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx].
|
||||
// Basic use pattern for creating an ech option:
|
||||
//
|
||||
// h := new(dns.HTTPS)
|
||||
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||
// e := new(dns.SVCBECHConfig)
|
||||
// e.ECH = []byte{0xfe, 0x08, ...}
|
||||
// h.Value = append(h.Value, e)
|
||||
type SVCBECHConfig struct {
|
||||
ECH []byte // Specifically ECHConfigList including the redundant length prefix
|
||||
}
|
||||
|
||||
func (*SVCBECHConfig) Key() SVCBKey { return SVCB_ECHCONFIG }
|
||||
func (s *SVCBECHConfig) String() string { return toBase64(s.ECH) }
|
||||
func (s *SVCBECHConfig) len() int { return len(s.ECH) }
|
||||
|
||||
func (s *SVCBECHConfig) pack() ([]byte, error) {
|
||||
return append([]byte(nil), s.ECH...), nil
|
||||
}
|
||||
|
||||
func (s *SVCBECHConfig) copy() SVCBKeyValue {
|
||||
return &SVCBECHConfig{
|
||||
append([]byte(nil), s.ECH...),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SVCBECHConfig) unpack(b []byte) error {
|
||||
s.ECH = append([]byte(nil), b...)
|
||||
return nil
|
||||
}
|
||||
func (s *SVCBECHConfig) parse(b string) error {
|
||||
x, err := fromBase64([]byte(b))
|
||||
if err != nil {
|
||||
return errors.New("dns: svcbech: bad base64 ech")
|
||||
}
|
||||
s.ECH = x
|
||||
return nil
|
||||
}
|
||||
|
||||
// SVCBIPv6Hint pair suggests an IPv6 address which may be used to open connections
|
||||
// if A and AAAA record responses for SVCB's Target domain haven't been received.
|
||||
// In that case, optionally, A and AAAA requests can be made, after which the
|
||||
// connection to the hinted IP address may be terminated and a new connection may be opened.
|
||||
// Basic use pattern for creating an ipv6hint option:
|
||||
//
|
||||
// h := new(dns.HTTPS)
|
||||
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||
// e := new(dns.SVCBIPv6Hint)
|
||||
// e.Hint = []net.IP{net.ParseIP("2001:db8::1")}
|
||||
// h.Value = append(h.Value, e)
|
||||
type SVCBIPv6Hint struct {
|
||||
Hint []net.IP
|
||||
}
|
||||
|
||||
func (*SVCBIPv6Hint) Key() SVCBKey { return SVCB_IPV6HINT }
|
||||
func (s *SVCBIPv6Hint) len() int { return 16 * len(s.Hint) }
|
||||
|
||||
func (s *SVCBIPv6Hint) pack() ([]byte, error) {
|
||||
b := make([]byte, 0, 16*len(s.Hint))
|
||||
for _, e := range s.Hint {
|
||||
if len(e) != net.IPv6len || e.To4() != nil {
|
||||
return nil, errors.New("dns: svcbipv6hint: expected ipv6, hint is ipv4")
|
||||
}
|
||||
b = append(b, e...)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (s *SVCBIPv6Hint) unpack(b []byte) error {
|
||||
if len(b) == 0 || len(b)%16 != 0 {
|
||||
return errors.New("dns: svcbipv6hint: ipv6 address byte array length not a multiple of 16")
|
||||
}
|
||||
x := make([]net.IP, 0, len(b)/16)
|
||||
for i := 0; i < len(b); i += 16 {
|
||||
ip := net.IP(b[i : i+16])
|
||||
if ip.To4() != nil {
|
||||
return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4")
|
||||
}
|
||||
x = append(x, ip)
|
||||
}
|
||||
s.Hint = x
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBIPv6Hint) String() string {
|
||||
str := make([]string, len(s.Hint))
|
||||
for i, e := range s.Hint {
|
||||
if x := e.To4(); x != nil {
|
||||
return "<nil>"
|
||||
}
|
||||
str[i] = e.String()
|
||||
}
|
||||
return strings.Join(str, ",")
|
||||
}
|
||||
|
||||
func (s *SVCBIPv6Hint) parse(b string) error {
|
||||
str := strings.Split(b, ",")
|
||||
dst := make([]net.IP, len(str))
|
||||
for i, e := range str {
|
||||
ip := net.ParseIP(e)
|
||||
if ip == nil {
|
||||
return errors.New("dns: svcbipv6hint: bad ip")
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4-mapped-ipv6")
|
||||
}
|
||||
dst[i] = ip
|
||||
}
|
||||
s.Hint = dst
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
|
||||
hint := make([]net.IP, len(s.Hint))
|
||||
for i, ip := range s.Hint {
|
||||
hint[i] = copyIP(ip)
|
||||
}
|
||||
|
||||
return &SVCBIPv6Hint{
|
||||
Hint: hint,
|
||||
}
|
||||
}
|
||||
|
||||
// SVCBDoHPath pair is used to indicate the URI template that the
|
||||
// clients may use to construct a DNS over HTTPS URI.
|
||||
//
|
||||
// See RFC xxxx (https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02)
|
||||
// and RFC yyyy (https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-06).
|
||||
//
|
||||
// A basic example of using the dohpath option together with the alpn
|
||||
// option to indicate support for DNS over HTTPS on a certain path:
|
||||
//
|
||||
// s := new(dns.SVCB)
|
||||
// s.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}
|
||||
// e := new(dns.SVCBAlpn)
|
||||
// e.Alpn = []string{"h2", "h3"}
|
||||
// p := new(dns.SVCBDoHPath)
|
||||
// p.Template = "/dns-query{?dns}"
|
||||
// s.Value = append(s.Value, e, p)
|
||||
//
|
||||
// The parsing currently doesn't validate that Template is a valid
|
||||
// RFC 6570 URI template.
|
||||
type SVCBDoHPath struct {
|
||||
Template string
|
||||
}
|
||||
|
||||
func (*SVCBDoHPath) Key() SVCBKey { return SVCB_DOHPATH }
|
||||
func (s *SVCBDoHPath) String() string { return svcbParamToStr([]byte(s.Template)) }
|
||||
func (s *SVCBDoHPath) len() int { return len(s.Template) }
|
||||
func (s *SVCBDoHPath) pack() ([]byte, error) { return []byte(s.Template), nil }
|
||||
|
||||
func (s *SVCBDoHPath) unpack(b []byte) error {
|
||||
s.Template = string(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBDoHPath) parse(b string) error {
|
||||
template, err := svcbParseParam(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dns: svcbdohpath: %w", err)
|
||||
}
|
||||
s.Template = string(template)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBDoHPath) copy() SVCBKeyValue {
|
||||
return &SVCBDoHPath{
|
||||
Template: s.Template,
|
||||
}
|
||||
}
|
||||
|
||||
// SVCBLocal pair is intended for experimental/private use. The key is recommended
|
||||
// to be in the range [SVCB_PRIVATE_LOWER, SVCB_PRIVATE_UPPER].
|
||||
// Basic use pattern for creating a keyNNNNN option:
|
||||
//
|
||||
// h := new(dns.HTTPS)
|
||||
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
|
||||
// e := new(dns.SVCBLocal)
|
||||
// e.KeyCode = 65400
|
||||
// e.Data = []byte("abc")
|
||||
// h.Value = append(h.Value, e)
|
||||
type SVCBLocal struct {
|
||||
KeyCode SVCBKey // Never 65535 or any assigned keys.
|
||||
Data []byte // All byte sequences are allowed.
|
||||
}
|
||||
|
||||
func (s *SVCBLocal) Key() SVCBKey { return s.KeyCode }
|
||||
func (s *SVCBLocal) String() string { return svcbParamToStr(s.Data) }
|
||||
func (s *SVCBLocal) pack() ([]byte, error) { return append([]byte(nil), s.Data...), nil }
|
||||
func (s *SVCBLocal) len() int { return len(s.Data) }
|
||||
|
||||
func (s *SVCBLocal) unpack(b []byte) error {
|
||||
s.Data = append([]byte(nil), b...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBLocal) parse(b string) error {
|
||||
data, err := svcbParseParam(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dns: svcblocal: svcb private/experimental key %w", err)
|
||||
}
|
||||
s.Data = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SVCBLocal) copy() SVCBKeyValue {
|
||||
return &SVCBLocal{s.KeyCode,
|
||||
append([]byte(nil), s.Data...),
|
||||
}
|
||||
}
|
||||
|
||||
func (rr *SVCB) String() string {
|
||||
s := rr.Hdr.String() +
|
||||
strconv.Itoa(int(rr.Priority)) + " " +
|
||||
sprintName(rr.Target)
|
||||
for _, e := range rr.Value {
|
||||
s += " " + e.Key().String() + "=\"" + e.String() + "\""
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// areSVCBPairArraysEqual checks if SVCBKeyValue arrays are equal after sorting their
|
||||
// copies. arrA and arrB have equal lengths, otherwise zduplicate.go wouldn't call this function.
|
||||
func areSVCBPairArraysEqual(a []SVCBKeyValue, b []SVCBKeyValue) bool {
|
||||
a = append([]SVCBKeyValue(nil), a...)
|
||||
b = append([]SVCBKeyValue(nil), b...)
|
||||
sort.Slice(a, func(i, j int) bool { return a[i].Key() < a[j].Key() })
|
||||
sort.Slice(b, func(i, j int) bool { return b[i].Key() < b[j].Key() })
|
||||
for i, e := range a {
|
||||
if e.Key() != b[i].Key() {
|
||||
return false
|
||||
}
|
||||
b1, err1 := e.pack()
|
||||
b2, err2 := b[i].pack()
|
||||
if err1 != nil || err2 != nil || !bytes.Equal(b1, b2) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// svcbParamStr converts the value of an SVCB parameter into a DNS presentation-format string.
|
||||
func svcbParamToStr(s []byte) string {
|
||||
var str strings.Builder
|
||||
str.Grow(4 * len(s))
|
||||
for _, e := range s {
|
||||
if ' ' <= e && e <= '~' {
|
||||
switch e {
|
||||
case '"', ';', ' ', '\\':
|
||||
str.WriteByte('\\')
|
||||
str.WriteByte(e)
|
||||
default:
|
||||
str.WriteByte(e)
|
||||
}
|
||||
} else {
|
||||
str.WriteString(escapeByte(e))
|
||||
}
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
|
||||
// svcbParseParam parses a DNS presentation-format string into an SVCB parameter value.
|
||||
func svcbParseParam(b string) ([]byte, error) {
|
||||
data := make([]byte, 0, len(b))
|
||||
for i := 0; i < len(b); {
|
||||
if b[i] != '\\' {
|
||||
data = append(data, b[i])
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if i+1 == len(b) {
|
||||
return nil, errors.New("escape unterminated")
|
||||
}
|
||||
if isDigit(b[i+1]) {
|
||||
if i+3 < len(b) && isDigit(b[i+2]) && isDigit(b[i+3]) {
|
||||
a, err := strconv.ParseUint(b[i+1:i+4], 10, 8)
|
||||
if err == nil {
|
||||
i += 4
|
||||
data = append(data, byte(a))
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil, errors.New("bad escaped octet")
|
||||
} else {
|
||||
data = append(data, b[i+1])
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This tests everything valid about SVCB but parsing.
|
||||
// Parsing tests belong to parse_test.go.
|
||||
func TestSVCB(t *testing.T) {
|
||||
svcbs := []struct {
|
||||
key string
|
||||
data string
|
||||
}{
|
||||
{`mandatory`, `alpn,key65000`},
|
||||
{`alpn`, `h2,h2c`},
|
||||
{`port`, `499`},
|
||||
{`ipv4hint`, `3.4.3.2,1.1.1.1`},
|
||||
{`no-default-alpn`, ``},
|
||||
{`ipv6hint`, `1::4:4:4:4,1::3:3:3:3`},
|
||||
{`ech`, `YUdWc2JHOD0=`},
|
||||
{`dohpath`, `/dns-query{?dns}`},
|
||||
{`key65000`, `4\ 3`},
|
||||
{`key65001`, `\"\ `},
|
||||
{`key65002`, ``},
|
||||
{`key65003`, `=\"\"`},
|
||||
{`key65004`, `\254\ \ \030\000`},
|
||||
}
|
||||
|
||||
for _, o := range svcbs {
|
||||
keyCode := svcbStringToKey(o.key)
|
||||
kv := makeSVCBKeyValue(keyCode)
|
||||
if kv == nil {
|
||||
t.Error("failed to parse svc key: ", o.key)
|
||||
continue
|
||||
}
|
||||
if kv.Key() != keyCode {
|
||||
t.Error("key constant is not in sync: ", keyCode)
|
||||
continue
|
||||
}
|
||||
err := kv.parse(o.data)
|
||||
if err != nil {
|
||||
t.Error("failed to parse svc pair: ", o.key)
|
||||
continue
|
||||
}
|
||||
b, err := kv.pack()
|
||||
if err != nil {
|
||||
t.Error("failed to pack value of svc pair: ", o.key, err)
|
||||
continue
|
||||
}
|
||||
if len(b) != int(kv.len()) {
|
||||
t.Errorf("expected packed svc value %s to be of length %d but got %d", o.key, int(kv.len()), len(b))
|
||||
}
|
||||
err = kv.unpack(b)
|
||||
if err != nil {
|
||||
t.Error("failed to unpack value of svc pair: ", o.key, err)
|
||||
continue
|
||||
}
|
||||
if str := kv.String(); str != o.data {
|
||||
t.Errorf("`%s' should be equal to\n`%s', but is `%s'", o.key, o.data, str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeBadSVCB(t *testing.T) {
|
||||
svcbs := []struct {
|
||||
key SVCBKey
|
||||
data []byte
|
||||
}{
|
||||
{
|
||||
key: SVCB_ALPN,
|
||||
data: []byte{3, 0, 0}, // There aren't three octets after 3
|
||||
},
|
||||
{
|
||||
key: SVCB_NO_DEFAULT_ALPN,
|
||||
data: []byte{0},
|
||||
},
|
||||
{
|
||||
key: SVCB_PORT,
|
||||
data: []byte{},
|
||||
},
|
||||
{
|
||||
key: SVCB_IPV4HINT,
|
||||
data: []byte{0, 0, 0},
|
||||
},
|
||||
{
|
||||
key: SVCB_IPV6HINT,
|
||||
data: []byte{0, 0, 0},
|
||||
},
|
||||
}
|
||||
for _, o := range svcbs {
|
||||
err := makeSVCBKeyValue(SVCBKey(o.key)).unpack(o.data)
|
||||
if err == nil {
|
||||
t.Error("accepted invalid svc value with key ", SVCBKey(o.key).String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPresentationSVCBAlpn(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
"h2": "h2",
|
||||
"http": "http",
|
||||
"\xfa": `\250`,
|
||||
"some\"other,chars": `some\"other\\\044chars`,
|
||||
}
|
||||
for input, want := range tests {
|
||||
e := new(SVCBAlpn)
|
||||
e.Alpn = []string{input}
|
||||
if e.String() != want {
|
||||
t.Errorf("improper conversion with String(), wanted %v got %v", want, e.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSVCBAlpn(t *testing.T) {
|
||||
tests := map[string][]string{
|
||||
`. 1 IN SVCB 10 one.test. alpn=h2`: {"h2"},
|
||||
`. 2 IN SVCB 20 two.test. alpn=h2,h3-19`: {"h2", "h3-19"},
|
||||
`. 3 IN SVCB 30 three.test. alpn="f\\\\oo\\,bar,h2"`: {`f\oo,bar`, "h2"},
|
||||
`. 4 IN SVCB 40 four.test. alpn="part1,part2,part3\\,part4\\\\"`: {"part1", "part2", `part3,part4\`},
|
||||
`. 5 IN SVCB 50 five.test. alpn=part1\,\p\a\r\t2\044part3\092,part4\092\\`: {"part1", "part2", `part3,part4\`},
|
||||
}
|
||||
for s, v := range tests {
|
||||
rr, err := NewRR(s)
|
||||
if err != nil {
|
||||
t.Error("failed to parse RR: ", err)
|
||||
continue
|
||||
}
|
||||
alpn := rr.(*SVCB).Value[0].(*SVCBAlpn).Alpn
|
||||
if len(v) != len(alpn) {
|
||||
t.Fatalf("parsing alpn failed, wanted %v got %v", v, alpn)
|
||||
}
|
||||
for i := range v {
|
||||
if v[i] != alpn[i] {
|
||||
t.Fatalf("parsing alpn failed, wanted %v got %v", v, alpn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareSVCB(t *testing.T) {
|
||||
val1 := []SVCBKeyValue{
|
||||
&SVCBPort{
|
||||
Port: 117,
|
||||
},
|
||||
&SVCBAlpn{
|
||||
Alpn: []string{"h2", "h3"},
|
||||
},
|
||||
}
|
||||
val2 := []SVCBKeyValue{
|
||||
&SVCBAlpn{
|
||||
Alpn: []string{"h2", "h3"},
|
||||
},
|
||||
&SVCBPort{
|
||||
Port: 117,
|
||||
},
|
||||
}
|
||||
if !areSVCBPairArraysEqual(val1, val2) {
|
||||
t.Error("svcb pairs were compared without sorting")
|
||||
}
|
||||
if val1[0].Key() != SVCB_PORT || val2[0].Key() != SVCB_ALPN {
|
||||
t.Error("original svcb pairs were reordered during comparison")
|
||||
}
|
||||
}
|
5
tlsa.go
5
tlsa.go
|
@ -14,10 +14,7 @@ func (r *TLSA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (
|
|||
r.MatchingType = uint8(matchingType)
|
||||
|
||||
r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify verifies a TLSA record against an SSL certificate. If it is OK
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
// We include our tool dependencies for `go generate` here to ensure they're
|
||||
// properly tracked by the go tool. See the Go Wiki for the rationale behind this:
|
||||
// https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module.
|
||||
|
||||
package dns
|
||||
|
||||
import _ "golang.org/x/tools/go/packages"
|
228
tsig.go
228
tsig.go
|
@ -2,7 +2,6 @@ package dns
|
|||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
|
@ -16,12 +15,83 @@ import (
|
|||
|
||||
// HMAC hashing codes. These are transmitted as domain names.
|
||||
const (
|
||||
HmacMD5 = "hmac-md5.sig-alg.reg.int."
|
||||
HmacSHA1 = "hmac-sha1."
|
||||
HmacSHA224 = "hmac-sha224."
|
||||
HmacSHA256 = "hmac-sha256."
|
||||
HmacSHA384 = "hmac-sha384."
|
||||
HmacSHA512 = "hmac-sha512."
|
||||
|
||||
HmacMD5 = "hmac-md5.sig-alg.reg.int." // Deprecated: HmacMD5 is no longer supported.
|
||||
)
|
||||
|
||||
// TsigProvider provides the API to plug-in a custom TSIG implementation.
|
||||
type TsigProvider interface {
|
||||
// Generate is passed the DNS message to be signed and the partial TSIG RR. It returns the signature and nil, otherwise an error.
|
||||
Generate(msg []byte, t *TSIG) ([]byte, error)
|
||||
// Verify is passed the DNS message to be verified and the TSIG RR. If the signature is valid it will return nil, otherwise an error.
|
||||
Verify(msg []byte, t *TSIG) error
|
||||
}
|
||||
|
||||
type tsigHMACProvider string
|
||||
|
||||
func (key tsigHMACProvider) Generate(msg []byte, t *TSIG) ([]byte, error) {
|
||||
// If we barf here, the caller is to blame
|
||||
rawsecret, err := fromBase64([]byte(key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var h hash.Hash
|
||||
switch CanonicalName(t.Algorithm) {
|
||||
case HmacSHA1:
|
||||
h = hmac.New(sha1.New, rawsecret)
|
||||
case HmacSHA224:
|
||||
h = hmac.New(sha256.New224, rawsecret)
|
||||
case HmacSHA256:
|
||||
h = hmac.New(sha256.New, rawsecret)
|
||||
case HmacSHA384:
|
||||
h = hmac.New(sha512.New384, rawsecret)
|
||||
case HmacSHA512:
|
||||
h = hmac.New(sha512.New, rawsecret)
|
||||
default:
|
||||
return nil, ErrKeyAlg
|
||||
}
|
||||
h.Write(msg)
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
func (key tsigHMACProvider) Verify(msg []byte, t *TSIG) error {
|
||||
b, err := key.Generate(msg, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mac, err := hex.DecodeString(t.MAC)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hmac.Equal(b, mac) {
|
||||
return ErrSig
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type tsigSecretProvider map[string]string
|
||||
|
||||
func (ts tsigSecretProvider) Generate(msg []byte, t *TSIG) ([]byte, error) {
|
||||
key, ok := ts[t.Hdr.Name]
|
||||
if !ok {
|
||||
return nil, ErrSecret
|
||||
}
|
||||
return tsigHMACProvider(key).Generate(msg, t)
|
||||
}
|
||||
|
||||
func (ts tsigSecretProvider) Verify(msg []byte, t *TSIG) error {
|
||||
key, ok := ts[t.Hdr.Name]
|
||||
if !ok {
|
||||
return ErrSecret
|
||||
}
|
||||
return tsigHMACProvider(key).Verify(msg, t)
|
||||
}
|
||||
|
||||
// TSIG is the RR the holds the transaction signature of a message.
|
||||
// See RFC 2845 and RFC 4635.
|
||||
type TSIG struct {
|
||||
|
@ -40,7 +110,7 @@ type TSIG struct {
|
|||
// TSIG has no official presentation format, but this will suffice.
|
||||
|
||||
func (rr *TSIG) String() string {
|
||||
s := "\n;; TSIG PSEUDOSECTION:\n"
|
||||
s := "\n;; TSIG PSEUDOSECTION:\n; " // add another semi-colon to signify TSIG does not have a presentation format
|
||||
s += rr.Hdr.String() +
|
||||
" " + rr.Algorithm +
|
||||
" " + tsigTimeToString(rr.TimeSigned) +
|
||||
|
@ -54,6 +124,10 @@ func (rr *TSIG) String() string {
|
|||
return s
|
||||
}
|
||||
|
||||
func (*TSIG) parse(c *zlexer, origin string) *ParseError {
|
||||
return &ParseError{err: "TSIG records do not have a presentation format"}
|
||||
}
|
||||
|
||||
// The following values must be put in wireformat, so that the MAC can be calculated.
|
||||
// RFC 2845, section 3.4.2. TSIG Variables.
|
||||
type tsigWireFmt struct {
|
||||
|
@ -84,22 +158,20 @@ type timerWireFmt struct {
|
|||
}
|
||||
|
||||
// TsigGenerate fills out the TSIG record attached to the message.
|
||||
// The message should contain
|
||||
// a "stub" TSIG RR with the algorithm, key name (owner name of the RR),
|
||||
// time fudge (defaults to 300 seconds) and the current time
|
||||
// The TSIG MAC is saved in that Tsig RR.
|
||||
// When TsigGenerate is called for the first time requestMAC is set to the empty string and
|
||||
// timersOnly is false.
|
||||
// If something goes wrong an error is returned, otherwise it is nil.
|
||||
// The message should contain a "stub" TSIG RR with the algorithm, key name
|
||||
// (owner name of the RR), time fudge (defaults to 300 seconds) and the current
|
||||
// time The TSIG MAC is saved in that Tsig RR. When TsigGenerate is called for
|
||||
// the first time requestMAC should be set to the empty string and timersOnly to
|
||||
// false.
|
||||
func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) {
|
||||
return TsigGenerateWithProvider(m, tsigHMACProvider(secret), requestMAC, timersOnly)
|
||||
}
|
||||
|
||||
// TsigGenerateWithProvider is similar to TsigGenerate, but allows for a custom TsigProvider.
|
||||
func TsigGenerateWithProvider(m *Msg, provider TsigProvider, requestMAC string, timersOnly bool) ([]byte, string, error) {
|
||||
if m.IsTsig() == nil {
|
||||
panic("dns: TSIG not last RR in additional")
|
||||
}
|
||||
// If we barf here, the caller is to blame
|
||||
rawsecret, err := fromBase64([]byte(secret))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
rr := m.Extra[len(m.Extra)-1].(*TSIG)
|
||||
m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg
|
||||
|
@ -107,69 +179,75 @@ func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, s
|
|||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
buf := tsigBuffer(mbuf, rr, requestMAC, timersOnly)
|
||||
|
||||
t := new(TSIG)
|
||||
var h hash.Hash
|
||||
switch strings.ToLower(rr.Algorithm) {
|
||||
case HmacMD5:
|
||||
h = hmac.New(md5.New, []byte(rawsecret))
|
||||
case HmacSHA1:
|
||||
h = hmac.New(sha1.New, []byte(rawsecret))
|
||||
case HmacSHA256:
|
||||
h = hmac.New(sha256.New, []byte(rawsecret))
|
||||
case HmacSHA512:
|
||||
h = hmac.New(sha512.New, []byte(rawsecret))
|
||||
default:
|
||||
return nil, "", ErrKeyAlg
|
||||
}
|
||||
h.Write(buf)
|
||||
t.MAC = hex.EncodeToString(h.Sum(nil))
|
||||
t.MACSize = uint16(len(t.MAC) / 2) // Size is half!
|
||||
|
||||
t.Hdr = RR_Header{Name: rr.Hdr.Name, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0}
|
||||
t.Fudge = rr.Fudge
|
||||
t.TimeSigned = rr.TimeSigned
|
||||
t.Algorithm = rr.Algorithm
|
||||
t.OrigId = m.Id
|
||||
|
||||
tbuf := make([]byte, t.len())
|
||||
if off, err := PackRR(t, tbuf, 0, nil, false); err == nil {
|
||||
tbuf = tbuf[:off] // reset to actual size used
|
||||
} else {
|
||||
buf, err := tsigBuffer(mbuf, rr, requestMAC, timersOnly)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
mbuf = append(mbuf, tbuf...)
|
||||
|
||||
t := new(TSIG)
|
||||
// Copy all TSIG fields except MAC, its size, and time signed which are filled when signing.
|
||||
*t = *rr
|
||||
t.TimeSigned = 0
|
||||
t.MAC = ""
|
||||
t.MACSize = 0
|
||||
|
||||
// Sign unless there is a key or MAC validation error (RFC 8945 5.3.2)
|
||||
if rr.Error != RcodeBadKey && rr.Error != RcodeBadSig {
|
||||
mac, err := provider.Generate(buf, rr)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
t.TimeSigned = rr.TimeSigned
|
||||
t.MAC = hex.EncodeToString(mac)
|
||||
t.MACSize = uint16(len(t.MAC) / 2) // Size is half!
|
||||
}
|
||||
|
||||
tbuf := make([]byte, Len(t))
|
||||
off, err := PackRR(t, tbuf, 0, nil, false)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
mbuf = append(mbuf, tbuf[:off]...)
|
||||
// Update the ArCount directly in the buffer.
|
||||
binary.BigEndian.PutUint16(mbuf[10:], uint16(len(m.Extra)+1))
|
||||
|
||||
return mbuf, t.MAC, nil
|
||||
}
|
||||
|
||||
// TsigVerify verifies the TSIG on a message.
|
||||
// If the signature does not validate err contains the
|
||||
// error, otherwise it is nil.
|
||||
// TsigVerify verifies the TSIG on a message. If the signature does not
|
||||
// validate the returned error contains the cause. If the signature is OK, the
|
||||
// error is nil.
|
||||
func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
|
||||
rawsecret, err := fromBase64([]byte(secret))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tsigVerify(msg, tsigHMACProvider(secret), requestMAC, timersOnly, uint64(time.Now().Unix()))
|
||||
}
|
||||
|
||||
// TsigVerifyWithProvider is similar to TsigVerify, but allows for a custom TsigProvider.
|
||||
func TsigVerifyWithProvider(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool) error {
|
||||
return tsigVerify(msg, provider, requestMAC, timersOnly, uint64(time.Now().Unix()))
|
||||
}
|
||||
|
||||
// actual implementation of TsigVerify, taking the current time ('now') as a parameter for the convenience of tests.
|
||||
func tsigVerify(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool, now uint64) error {
|
||||
// Strip the TSIG from the incoming msg
|
||||
stripped, tsig, err := stripTsig(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgMAC, err := hex.DecodeString(tsig.MAC)
|
||||
buf, err := tsigBuffer(stripped, tsig, requestMAC, timersOnly)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := tsigBuffer(stripped, tsig, requestMAC, timersOnly)
|
||||
if err := provider.Verify(buf, tsig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fudge factor works both ways. A message can arrive before it was signed because
|
||||
// of clock skew.
|
||||
now := uint64(time.Now().Unix())
|
||||
// We check this after verifying the signature, following draft-ietf-dnsop-rfc2845bis
|
||||
// instead of RFC2845, in order to prevent a security vulnerability as reported in CVE-2017-3142/3143.
|
||||
ti := now - tsig.TimeSigned
|
||||
if now < tsig.TimeSigned {
|
||||
ti = tsig.TimeSigned - now
|
||||
|
@ -178,28 +256,11 @@ func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
|
|||
return ErrTime
|
||||
}
|
||||
|
||||
var h hash.Hash
|
||||
switch strings.ToLower(tsig.Algorithm) {
|
||||
case HmacMD5:
|
||||
h = hmac.New(md5.New, rawsecret)
|
||||
case HmacSHA1:
|
||||
h = hmac.New(sha1.New, rawsecret)
|
||||
case HmacSHA256:
|
||||
h = hmac.New(sha256.New, rawsecret)
|
||||
case HmacSHA512:
|
||||
h = hmac.New(sha512.New, rawsecret)
|
||||
default:
|
||||
return ErrKeyAlg
|
||||
}
|
||||
h.Write(buf)
|
||||
if !hmac.Equal(h.Sum(nil), msgMAC) {
|
||||
return ErrSig
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a wiredata buffer for the MAC calculation.
|
||||
func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []byte {
|
||||
func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) ([]byte, error) {
|
||||
var buf []byte
|
||||
if rr.TimeSigned == 0 {
|
||||
rr.TimeSigned = uint64(time.Now().Unix())
|
||||
|
@ -216,7 +277,10 @@ func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []b
|
|||
m.MACSize = uint16(len(requestMAC) / 2)
|
||||
m.MAC = requestMAC
|
||||
buf = make([]byte, len(requestMAC)) // long enough
|
||||
n, _ := packMacWire(m, buf)
|
||||
n, err := packMacWire(m, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = buf[:n]
|
||||
}
|
||||
|
||||
|
@ -225,20 +289,26 @@ func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []b
|
|||
tsig := new(timerWireFmt)
|
||||
tsig.TimeSigned = rr.TimeSigned
|
||||
tsig.Fudge = rr.Fudge
|
||||
n, _ := packTimerWire(tsig, tsigvar)
|
||||
n, err := packTimerWire(tsig, tsigvar)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tsigvar = tsigvar[:n]
|
||||
} else {
|
||||
tsig := new(tsigWireFmt)
|
||||
tsig.Name = strings.ToLower(rr.Hdr.Name)
|
||||
tsig.Name = CanonicalName(rr.Hdr.Name)
|
||||
tsig.Class = ClassANY
|
||||
tsig.Ttl = rr.Hdr.Ttl
|
||||
tsig.Algorithm = strings.ToLower(rr.Algorithm)
|
||||
tsig.Algorithm = CanonicalName(rr.Algorithm)
|
||||
tsig.TimeSigned = rr.TimeSigned
|
||||
tsig.Fudge = rr.Fudge
|
||||
tsig.Error = rr.Error
|
||||
tsig.OtherLen = rr.OtherLen
|
||||
tsig.OtherData = rr.OtherData
|
||||
n, _ := packTsigWire(tsig, tsigvar)
|
||||
n, err := packTsigWire(tsig, tsigvar)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tsigvar = tsigvar[:n]
|
||||
}
|
||||
|
||||
|
@ -248,7 +318,7 @@ func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []b
|
|||
} else {
|
||||
buf = append(msgbuf, tsigvar...)
|
||||
}
|
||||
return buf
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Strip the TSIG from the raw message.
|
||||
|
|
365
tsig_test.go
365
tsig_test.go
|
@ -2,6 +2,10 @@ package dns
|
|||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
@ -14,7 +18,7 @@ func newTsig(algo string) *Msg {
|
|||
}
|
||||
|
||||
func TestTsig(t *testing.T) {
|
||||
m := newTsig(HmacMD5)
|
||||
m := newTsig(HmacSHA256)
|
||||
buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -26,13 +30,13 @@ func TestTsig(t *testing.T) {
|
|||
|
||||
// TSIG accounts for ID substitution. This means if the message ID is
|
||||
// changed by a forwarder, we should still be able to verify the TSIG.
|
||||
m = newTsig(HmacMD5)
|
||||
m = newTsig(HmacSHA256)
|
||||
buf, _, err = TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint16(buf[0:2], uint16(42))
|
||||
binary.BigEndian.PutUint16(buf[0:2], 42)
|
||||
err = TsigVerify(buf, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -40,7 +44,7 @@ func TestTsig(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTsigCase(t *testing.T) {
|
||||
m := newTsig("HmAc-mD5.sig-ALg.rEg.int.") // HmacMD5
|
||||
m := newTsig(strings.ToUpper(HmacSHA256))
|
||||
buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -50,3 +54,356 @@ func TestTsigCase(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTsigErrorResponse(t *testing.T) {
|
||||
for _, rcode := range []uint16{RcodeBadSig, RcodeBadKey} {
|
||||
m := newTsig(strings.ToUpper(HmacSHA256))
|
||||
m.IsTsig().Error = rcode
|
||||
buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = m.Unpack(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mTsig := m.IsTsig()
|
||||
if mTsig.MAC != "" {
|
||||
t.Error("Expected empty MAC")
|
||||
}
|
||||
if mTsig.MACSize != 0 {
|
||||
t.Error("Expected 0 MACSize")
|
||||
}
|
||||
if mTsig.TimeSigned != 0 {
|
||||
t.Errorf("Expected TimeSigned to be 0, got %v", mTsig.TimeSigned)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTsigBadTimeResponse(t *testing.T) {
|
||||
clientTime := uint64(time.Now().Unix()) - 3600
|
||||
m := newTsig(strings.ToUpper(HmacSHA256))
|
||||
m.IsTsig().Error = RcodeBadTime
|
||||
m.IsTsig().TimeSigned = clientTime
|
||||
|
||||
buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = m.Unpack(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mTsig := m.IsTsig()
|
||||
if mTsig.MAC == "" {
|
||||
t.Error("Expected non-empty MAC")
|
||||
}
|
||||
if int(mTsig.MACSize) != len(mTsig.MAC)/2 {
|
||||
t.Errorf("Expected MACSize %v, got %v", len(mTsig.MAC)/2, mTsig.MACSize)
|
||||
}
|
||||
|
||||
if mTsig.TimeSigned != clientTime {
|
||||
t.Errorf("Expected TimeSigned %v to be retained, got %v", clientTime, mTsig.TimeSigned)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// A template wire-format DNS message (in hex form) containing a TSIG RR.
|
||||
// Its time signed field will be filled by tests.
|
||||
wireMsg = "c60028000001000000010001076578616d706c6503636f6d00000600010161c00c0001000100000e100004c0000201077465" +
|
||||
"73746b65790000fa00ff00000000003d0b686d61632d73686132353600" +
|
||||
"%012x" + // placeholder for the "time signed" field
|
||||
"012c00208cf23e0081d915478a182edcea7ff48ad102948e6c7ef8e887536957d1fa5616c60000000000"
|
||||
// A secret (in base64 format) with which the TSIG in wireMsg will be validated
|
||||
testSecret = "NoTCJU+DMqFWywaPyxSijrDEA/eC3nK0xi3AMEZuPVk="
|
||||
// the 'time signed' field value that would make the TSIG RR valid with testSecret
|
||||
timeSigned uint64 = 1594855491
|
||||
)
|
||||
|
||||
func TestTsigErrors(t *testing.T) {
|
||||
// Helper shortcut to build wire-format test message.
|
||||
// TsigVerify can modify the slice, so we need to create a new one for each test case below.
|
||||
buildMsgData := func(tm uint64) []byte {
|
||||
msgData, err := hex.DecodeString(fmt.Sprintf(wireMsg, tm))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return msgData
|
||||
}
|
||||
|
||||
// the signature is valid but 'time signed' is too far from the "current time".
|
||||
if err := tsigVerify(buildMsgData(timeSigned), tsigHMACProvider(testSecret), "", false, timeSigned+301); err != ErrTime {
|
||||
t.Fatalf("expected an error '%v' but got '%v'", ErrTime, err)
|
||||
}
|
||||
if err := tsigVerify(buildMsgData(timeSigned), tsigHMACProvider(testSecret), "", false, timeSigned-301); err != ErrTime {
|
||||
t.Fatalf("expected an error '%v' but got '%v'", ErrTime, err)
|
||||
}
|
||||
|
||||
// the signature is invalid and 'time signed' is too far.
|
||||
// the signature should be checked first, so we should see ErrSig.
|
||||
if err := tsigVerify(buildMsgData(timeSigned+301), tsigHMACProvider(testSecret), "", false, timeSigned); err != ErrSig {
|
||||
t.Fatalf("expected an error '%v' but got '%v'", ErrSig, err)
|
||||
}
|
||||
|
||||
// tweak the algorithm name in the wire data, resulting in the "unknown algorithm" error.
|
||||
msgData := buildMsgData(timeSigned)
|
||||
copy(msgData[67:], "bogus")
|
||||
if err := tsigVerify(msgData, tsigHMACProvider(testSecret), "", false, timeSigned); err != ErrKeyAlg {
|
||||
t.Fatalf("expected an error '%v' but got '%v'", ErrKeyAlg, err)
|
||||
}
|
||||
|
||||
// call TsigVerify with a message that doesn't contain a TSIG
|
||||
msgData, tsig, err := stripTsig(buildMsgData(timeSigned))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tsigVerify(msgData, tsigHMACProvider(testSecret), "", false, timeSigned); err != ErrNoSig {
|
||||
t.Fatalf("expected an error '%v' but got '%v'", ErrNoSig, err)
|
||||
}
|
||||
|
||||
// replace the test TSIG with a bogus one with large "other data", which would cause overflow in TsigVerify.
|
||||
// The overflow should be caught without disruption.
|
||||
tsig.OtherData = strings.Repeat("00", 4096)
|
||||
tsig.OtherLen = uint16(len(tsig.OtherData) / 2)
|
||||
msg := new(Msg)
|
||||
if err = msg.Unpack(msgData); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
msg.Extra = append(msg.Extra, tsig)
|
||||
if msgData, err = msg.Pack(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = tsigVerify(msgData, tsigHMACProvider(testSecret), "", false, timeSigned)
|
||||
if err == nil || !strings.Contains(err.Error(), "overflow") {
|
||||
t.Errorf("expected error to contain %q, but got %v", "overflow", err)
|
||||
}
|
||||
}
|
||||
|
||||
// This test exercises some more corner cases for TsigGenerate.
|
||||
func TestTsigGenerate(t *testing.T) {
|
||||
// This is a template TSIG to be used for signing.
|
||||
tsig := TSIG{
|
||||
Hdr: RR_Header{Name: "testkey.", Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0},
|
||||
Algorithm: HmacSHA256,
|
||||
TimeSigned: timeSigned,
|
||||
Fudge: 300,
|
||||
OrigId: 42,
|
||||
Error: RcodeBadTime, // use a non-0 value to make sure it's indeed used
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
desc string // test description
|
||||
requestMAC string // request MAC to be passed to TsigGenerate (arbitrary choice)
|
||||
otherData string // other data specified in the TSIG (arbitrary choice)
|
||||
expectedMAC string // pre-computed expected (correct) MAC in hex form
|
||||
}{
|
||||
{"with request MAC", "3684c225", "",
|
||||
"c110e3f62694755c10761dc8717462431ee34340b7c9d1eee09449150757c5b1"},
|
||||
{"no request MAC", "", "",
|
||||
"385449a425c6d52b9bf2c65c0726eefa0ad8084cdaf488f24547e686605b9610"},
|
||||
{"with other data", "3684c225", "666f6f",
|
||||
"15b91571ca80b3b410a77e2b44f8cc4f35ace22b26020138439dd94803e23b5d"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
// Build TSIG for signing from the template
|
||||
testTSIG := tsig
|
||||
testTSIG.OtherLen = uint16(len(tc.otherData) / 2)
|
||||
testTSIG.OtherData = tc.otherData
|
||||
req := &Msg{
|
||||
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
|
||||
Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
|
||||
Extra: []RR{&testTSIG},
|
||||
}
|
||||
|
||||
// Call generate, and check the returned MAC against the expected value
|
||||
msgData, mac, err := TsigGenerate(req, testSecret, tc.requestMAC, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if mac != tc.expectedMAC {
|
||||
t.Fatalf("MAC doesn't match: expected '%s', but got '%s'", tc.expectedMAC, mac)
|
||||
}
|
||||
|
||||
// Retrieve the TSIG to be sent out, confirm the MAC in it
|
||||
_, outTSIG, err := stripTsig(msgData)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if outTSIG.MAC != tc.expectedMAC {
|
||||
t.Fatalf("MAC doesn't match: expected '%s', but got '%s'", tc.expectedMAC, outTSIG.MAC)
|
||||
}
|
||||
// Confirm other fields of MAC.
|
||||
// RDLENGTH should be valid as stripTsig succeeded, so we exclude it from comparison
|
||||
outTSIG.MACSize = 0
|
||||
outTSIG.MAC = ""
|
||||
testTSIG.Hdr.Rdlength = outTSIG.Hdr.Rdlength
|
||||
if *outTSIG != testTSIG {
|
||||
t.Fatalf("TSIG RR doesn't match: expected '%v', but got '%v'", *outTSIG, testTSIG)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTSIGHMAC224And384(t *testing.T) {
|
||||
tests := []struct {
|
||||
algorithm string // TSIG algorithm, also used as test description
|
||||
secret string // (arbitrarily chosen) secret suitable for the algorithm in base64 format
|
||||
expectedMAC string // pre-computed expected (correct) MAC in hex form
|
||||
}{
|
||||
{HmacSHA224, "hVEkQuAqnTmBuRrT9KF1Udr91gOMGWPw9LaTtw==",
|
||||
"d6daf9ea189e48bc38f9aed63d6cc4140cdfa38a7a333ee2eefdbd31",
|
||||
},
|
||||
{HmacSHA384, "Qjer2TL2lAdpq9w6Gjs98/ClCQx/L3vtgVHCmrZ8l/oKEPjqUUMFO18gMCRwd5H4",
|
||||
"89a48936d29187870c325cbdba5ad71609bd038d0459d6010c844d659c570e881d3650e4fe7310be53ebe5178d0d1001",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.algorithm, func(t *testing.T) {
|
||||
// Build a DNS message with TSIG for the test scenario
|
||||
tsig := TSIG{
|
||||
Hdr: RR_Header{Name: "testkey.", Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0},
|
||||
Algorithm: tc.algorithm,
|
||||
TimeSigned: timeSigned,
|
||||
Fudge: 300,
|
||||
OrigId: 42,
|
||||
}
|
||||
req := &Msg{
|
||||
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
|
||||
Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
|
||||
Extra: []RR{&tsig},
|
||||
}
|
||||
|
||||
// Confirm both Generate and Verify recognize the algorithm and handle it correctly
|
||||
msgData, mac, err := TsigGenerate(req, tc.secret, "", false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if mac != tc.expectedMAC {
|
||||
t.Fatalf("MAC doesn't match: expected '%s' but got '%s'", tc.expectedMAC, mac)
|
||||
}
|
||||
if err = tsigVerify(msgData, tsigHMACProvider(tc.secret), "", false, timeSigned); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const testGoodKeyName = "goodkey."
|
||||
|
||||
var (
|
||||
errBadKey = errors.New("this is an intentional error")
|
||||
testGoodMAC = []byte{0, 1, 2, 3}
|
||||
)
|
||||
|
||||
// testProvider always generates the same MAC and only accepts the one signature
|
||||
type testProvider struct {
|
||||
GenerateAllKeys bool
|
||||
}
|
||||
|
||||
func (provider *testProvider) Generate(_ []byte, t *TSIG) ([]byte, error) {
|
||||
if t.Hdr.Name == testGoodKeyName || provider.GenerateAllKeys {
|
||||
return testGoodMAC, nil
|
||||
}
|
||||
return nil, errBadKey
|
||||
}
|
||||
|
||||
func (*testProvider) Verify(_ []byte, t *TSIG) error {
|
||||
if t.Hdr.Name == testGoodKeyName {
|
||||
return nil
|
||||
}
|
||||
return errBadKey
|
||||
}
|
||||
|
||||
func TestTsigGenerateProvider(t *testing.T) {
|
||||
tables := []struct {
|
||||
keyname string
|
||||
mac []byte
|
||||
err error
|
||||
}{
|
||||
{
|
||||
testGoodKeyName,
|
||||
testGoodMAC,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"badkey.",
|
||||
nil,
|
||||
errBadKey,
|
||||
},
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
t.Run(table.keyname, func(t *testing.T) {
|
||||
tsig := TSIG{
|
||||
Hdr: RR_Header{Name: table.keyname, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0},
|
||||
Algorithm: HmacSHA1,
|
||||
TimeSigned: timeSigned,
|
||||
Fudge: 300,
|
||||
OrigId: 42,
|
||||
}
|
||||
req := &Msg{
|
||||
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
|
||||
Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
|
||||
Extra: []RR{&tsig},
|
||||
}
|
||||
|
||||
_, mac, err := TsigGenerateWithProvider(req, new(testProvider), "", false)
|
||||
if err != table.err {
|
||||
t.Fatalf("error doesn't match: expected '%s' but got '%s'", table.err, err)
|
||||
}
|
||||
expectedMAC := hex.EncodeToString(table.mac)
|
||||
if mac != expectedMAC {
|
||||
t.Fatalf("MAC doesn't match: expected '%s' but got '%s'", table.mac, expectedMAC)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTsigVerifyProvider(t *testing.T) {
|
||||
tables := []struct {
|
||||
keyname string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
testGoodKeyName,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"badkey.",
|
||||
errBadKey,
|
||||
},
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
t.Run(table.keyname, func(t *testing.T) {
|
||||
tsig := TSIG{
|
||||
Hdr: RR_Header{Name: table.keyname, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0},
|
||||
Algorithm: HmacSHA1,
|
||||
TimeSigned: timeSigned,
|
||||
Fudge: 300,
|
||||
OrigId: 42,
|
||||
}
|
||||
req := &Msg{
|
||||
MsgHdr: MsgHdr{Opcode: OpcodeUpdate},
|
||||
Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}},
|
||||
Extra: []RR{&tsig},
|
||||
}
|
||||
|
||||
provider := &testProvider{true}
|
||||
msgData, _, err := TsigGenerateWithProvider(req, provider, "", false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err = tsigVerify(msgData, provider, "", false, timeSigned); err != table.err {
|
||||
t.Fatalf("error doesn't match: expected '%s' but got '%s'", table.err, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
474
types.go
474
types.go
|
@ -1,6 +1,7 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
@ -61,6 +62,7 @@ const (
|
|||
TypeCERT uint16 = 37
|
||||
TypeDNAME uint16 = 39
|
||||
TypeOPT uint16 = 41 // EDNS
|
||||
TypeAPL uint16 = 42
|
||||
TypeDS uint16 = 43
|
||||
TypeSSHFP uint16 = 44
|
||||
TypeRRSIG uint16 = 46
|
||||
|
@ -79,6 +81,9 @@ const (
|
|||
TypeCDNSKEY uint16 = 60
|
||||
TypeOPENPGPKEY uint16 = 61
|
||||
TypeCSYNC uint16 = 62
|
||||
TypeZONEMD uint16 = 63
|
||||
TypeSVCB uint16 = 64
|
||||
TypeHTTPS uint16 = 65
|
||||
TypeSPF uint16 = 99
|
||||
TypeUINFO uint16 = 100
|
||||
TypeUID uint16 = 101
|
||||
|
@ -146,6 +151,14 @@ const (
|
|||
OpcodeUpdate = 5
|
||||
)
|
||||
|
||||
// Used in ZONEMD https://tools.ietf.org/html/rfc8976
|
||||
const (
|
||||
ZoneMDSchemeSimple = 1
|
||||
|
||||
ZoneMDHashAlgSHA384 = 1
|
||||
ZoneMDHashAlgSHA512 = 2
|
||||
)
|
||||
|
||||
// Header is the wire format for the DNS packet header.
|
||||
type Header struct {
|
||||
Id uint16
|
||||
|
@ -163,11 +176,11 @@ const (
|
|||
_RD = 1 << 8 // recursion desired
|
||||
_RA = 1 << 7 // recursion available
|
||||
_Z = 1 << 6 // Z
|
||||
_AD = 1 << 5 // authticated data
|
||||
_AD = 1 << 5 // authenticated data
|
||||
_CD = 1 << 4 // checking disabled
|
||||
)
|
||||
|
||||
// Various constants used in the LOC RR, See RFC 1887.
|
||||
// Various constants used in the LOC RR. See RFC 1887.
|
||||
const (
|
||||
LOC_EQUATOR = 1 << 31 // RFC 1876, Section 2.
|
||||
LOC_PRIMEMERIDIAN = 1 << 31 // RFC 1876, Section 2.
|
||||
|
@ -205,21 +218,23 @@ var CertTypeToString = map[uint16]string{
|
|||
CertOID: "OID",
|
||||
}
|
||||
|
||||
// StringToCertType is the reverseof CertTypeToString.
|
||||
var StringToCertType = reverseInt16(CertTypeToString)
|
||||
|
||||
//go:generate go run types_generate.go
|
||||
|
||||
// Question holds a DNS question. There can be multiple questions in the
|
||||
// question section of a message. Usually there is just one.
|
||||
// Question holds a DNS question. Usually there is just one. While the
|
||||
// original DNS RFCs allow multiple questions in the question section of a
|
||||
// message, in practice it never works. Because most DNS servers see multiple
|
||||
// questions as an error, it is recommended to only have one question per
|
||||
// message.
|
||||
type Question struct {
|
||||
Name string `dns:"cdomain-name"` // "cdomain-name" specifies encoding (and may be compressed)
|
||||
Qtype uint16
|
||||
Qclass uint16
|
||||
}
|
||||
|
||||
func (q *Question) len() int {
|
||||
return len(q.Name) + 1 + 2 + 2
|
||||
func (q *Question) len(off int, compression map[string]struct{}) int {
|
||||
l := domainNameLen(q.Name, off, compression, true)
|
||||
l += 2 + 2
|
||||
return l
|
||||
}
|
||||
|
||||
func (q *Question) String() (s string) {
|
||||
|
@ -230,7 +245,7 @@ func (q *Question) String() (s string) {
|
|||
return s
|
||||
}
|
||||
|
||||
// ANY is a wildcard record. See RFC 1035, Section 3.2.3. ANY
|
||||
// ANY is a wild card record. See RFC 1035, Section 3.2.3. ANY
|
||||
// is named "*" there.
|
||||
type ANY struct {
|
||||
Hdr RR_Header
|
||||
|
@ -239,6 +254,25 @@ type ANY struct {
|
|||
|
||||
func (rr *ANY) String() string { return rr.Hdr.String() }
|
||||
|
||||
func (*ANY) parse(c *zlexer, origin string) *ParseError {
|
||||
return &ParseError{err: "ANY records do not have a presentation format"}
|
||||
}
|
||||
|
||||
// NULL RR. See RFC 1035.
|
||||
type NULL struct {
|
||||
Hdr RR_Header
|
||||
Data string `dns:"any"`
|
||||
}
|
||||
|
||||
func (rr *NULL) String() string {
|
||||
// There is no presentation format; prefix string with a comment.
|
||||
return ";" + rr.Hdr.String() + rr.Data
|
||||
}
|
||||
|
||||
func (*NULL) parse(c *zlexer, origin string) *ParseError {
|
||||
return &ParseError{err: "NULL records do not have a presentation format"}
|
||||
}
|
||||
|
||||
// CNAME RR. See RFC 1034.
|
||||
type CNAME struct {
|
||||
Hdr RR_Header
|
||||
|
@ -330,7 +364,7 @@ func (rr *MX) String() string {
|
|||
type AFSDB struct {
|
||||
Hdr RR_Header
|
||||
Subtype uint16
|
||||
Hostname string `dns:"cdomain-name"`
|
||||
Hostname string `dns:"domain-name"`
|
||||
}
|
||||
|
||||
func (rr *AFSDB) String() string {
|
||||
|
@ -351,7 +385,7 @@ func (rr *X25) String() string {
|
|||
type RT struct {
|
||||
Hdr RR_Header
|
||||
Preference uint16
|
||||
Host string `dns:"cdomain-name"`
|
||||
Host string `dns:"domain-name"` // RFC 3597 prohibits compressing records not defined in RFC 1035.
|
||||
}
|
||||
|
||||
func (rr *RT) String() string {
|
||||
|
@ -386,7 +420,7 @@ type RP struct {
|
|||
}
|
||||
|
||||
func (rr *RP) String() string {
|
||||
return rr.Hdr.String() + rr.Mbox + " " + sprintTxt([]string{rr.Txt})
|
||||
return rr.Hdr.String() + sprintName(rr.Mbox) + " " + sprintName(rr.Txt)
|
||||
}
|
||||
|
||||
// SOA RR. See RFC 1035.
|
||||
|
@ -419,128 +453,172 @@ type TXT struct {
|
|||
func (rr *TXT) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) }
|
||||
|
||||
func sprintName(s string) string {
|
||||
src := []byte(s)
|
||||
dst := make([]byte, 0, len(src))
|
||||
for i := 0; i < len(src); {
|
||||
if i+1 < len(src) && src[i] == '\\' && src[i+1] == '.' {
|
||||
dst = append(dst, src[i:i+2]...)
|
||||
i += 2
|
||||
} else {
|
||||
b, n := nextByte(src, i)
|
||||
if n == 0 {
|
||||
i++ // dangling back slash
|
||||
} else if b == '.' {
|
||||
dst = append(dst, b)
|
||||
} else {
|
||||
dst = appendDomainNameByte(dst, b)
|
||||
var dst strings.Builder
|
||||
|
||||
for i := 0; i < len(s); {
|
||||
if s[i] == '.' {
|
||||
if dst.Len() != 0 {
|
||||
dst.WriteByte('.')
|
||||
}
|
||||
i += n
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
b, n := nextByte(s, i)
|
||||
if n == 0 {
|
||||
// Drop "dangling" incomplete escapes.
|
||||
if dst.Len() == 0 {
|
||||
return s[:i]
|
||||
}
|
||||
break
|
||||
}
|
||||
if isDomainNameLabelSpecial(b) {
|
||||
if dst.Len() == 0 {
|
||||
dst.Grow(len(s) * 2)
|
||||
dst.WriteString(s[:i])
|
||||
}
|
||||
dst.WriteByte('\\')
|
||||
dst.WriteByte(b)
|
||||
} else if b < ' ' || b > '~' { // unprintable, use \DDD
|
||||
if dst.Len() == 0 {
|
||||
dst.Grow(len(s) * 2)
|
||||
dst.WriteString(s[:i])
|
||||
}
|
||||
dst.WriteString(escapeByte(b))
|
||||
} else {
|
||||
if dst.Len() != 0 {
|
||||
dst.WriteByte(b)
|
||||
}
|
||||
}
|
||||
i += n
|
||||
}
|
||||
return string(dst)
|
||||
if dst.Len() == 0 {
|
||||
return s
|
||||
}
|
||||
return dst.String()
|
||||
}
|
||||
|
||||
func sprintTxtOctet(s string) string {
|
||||
src := []byte(s)
|
||||
dst := make([]byte, 0, len(src))
|
||||
dst = append(dst, '"')
|
||||
for i := 0; i < len(src); {
|
||||
if i+1 < len(src) && src[i] == '\\' && src[i+1] == '.' {
|
||||
dst = append(dst, src[i:i+2]...)
|
||||
var dst strings.Builder
|
||||
dst.Grow(2 + len(s))
|
||||
dst.WriteByte('"')
|
||||
for i := 0; i < len(s); {
|
||||
if i+1 < len(s) && s[i] == '\\' && s[i+1] == '.' {
|
||||
dst.WriteString(s[i : i+2])
|
||||
i += 2
|
||||
} else {
|
||||
b, n := nextByte(src, i)
|
||||
if n == 0 {
|
||||
i++ // dangling back slash
|
||||
} else if b == '.' {
|
||||
dst = append(dst, b)
|
||||
} else {
|
||||
if b < ' ' || b > '~' {
|
||||
dst = appendByte(dst, b)
|
||||
} else {
|
||||
dst = append(dst, b)
|
||||
}
|
||||
}
|
||||
i += n
|
||||
continue
|
||||
}
|
||||
|
||||
b, n := nextByte(s, i)
|
||||
if n == 0 {
|
||||
i++ // dangling back slash
|
||||
} else {
|
||||
writeTXTStringByte(&dst, b)
|
||||
}
|
||||
i += n
|
||||
}
|
||||
dst = append(dst, '"')
|
||||
return string(dst)
|
||||
dst.WriteByte('"')
|
||||
return dst.String()
|
||||
}
|
||||
|
||||
func sprintTxt(txt []string) string {
|
||||
var out []byte
|
||||
var out strings.Builder
|
||||
for i, s := range txt {
|
||||
out.Grow(3 + len(s))
|
||||
if i > 0 {
|
||||
out = append(out, ` "`...)
|
||||
out.WriteString(` "`)
|
||||
} else {
|
||||
out = append(out, '"')
|
||||
out.WriteByte('"')
|
||||
}
|
||||
bs := []byte(s)
|
||||
for j := 0; j < len(bs); {
|
||||
b, n := nextByte(bs, j)
|
||||
for j := 0; j < len(s); {
|
||||
b, n := nextByte(s, j)
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
out = appendTXTStringByte(out, b)
|
||||
writeTXTStringByte(&out, b)
|
||||
j += n
|
||||
}
|
||||
out = append(out, '"')
|
||||
out.WriteByte('"')
|
||||
}
|
||||
return string(out)
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func appendDomainNameByte(s []byte, b byte) []byte {
|
||||
func writeTXTStringByte(s *strings.Builder, b byte) {
|
||||
switch {
|
||||
case b == '"' || b == '\\':
|
||||
s.WriteByte('\\')
|
||||
s.WriteByte(b)
|
||||
case b < ' ' || b > '~':
|
||||
s.WriteString(escapeByte(b))
|
||||
default:
|
||||
s.WriteByte(b)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
escapedByteSmall = "" +
|
||||
`\000\001\002\003\004\005\006\007\008\009` +
|
||||
`\010\011\012\013\014\015\016\017\018\019` +
|
||||
`\020\021\022\023\024\025\026\027\028\029` +
|
||||
`\030\031`
|
||||
escapedByteLarge = `\127\128\129` +
|
||||
`\130\131\132\133\134\135\136\137\138\139` +
|
||||
`\140\141\142\143\144\145\146\147\148\149` +
|
||||
`\150\151\152\153\154\155\156\157\158\159` +
|
||||
`\160\161\162\163\164\165\166\167\168\169` +
|
||||
`\170\171\172\173\174\175\176\177\178\179` +
|
||||
`\180\181\182\183\184\185\186\187\188\189` +
|
||||
`\190\191\192\193\194\195\196\197\198\199` +
|
||||
`\200\201\202\203\204\205\206\207\208\209` +
|
||||
`\210\211\212\213\214\215\216\217\218\219` +
|
||||
`\220\221\222\223\224\225\226\227\228\229` +
|
||||
`\230\231\232\233\234\235\236\237\238\239` +
|
||||
`\240\241\242\243\244\245\246\247\248\249` +
|
||||
`\250\251\252\253\254\255`
|
||||
)
|
||||
|
||||
// escapeByte returns the \DDD escaping of b which must
|
||||
// satisfy b < ' ' || b > '~'.
|
||||
func escapeByte(b byte) string {
|
||||
if b < ' ' {
|
||||
return escapedByteSmall[b*4 : b*4+4]
|
||||
}
|
||||
|
||||
b -= '~' + 1
|
||||
// The cast here is needed as b*4 may overflow byte.
|
||||
return escapedByteLarge[int(b)*4 : int(b)*4+4]
|
||||
}
|
||||
|
||||
// isDomainNameLabelSpecial returns true if
|
||||
// a domain name label byte should be prefixed
|
||||
// with an escaping backslash.
|
||||
func isDomainNameLabelSpecial(b byte) bool {
|
||||
switch b {
|
||||
case '.', ' ', '\'', '@', ';', '(', ')': // additional chars to escape
|
||||
return append(s, '\\', b)
|
||||
case '.', ' ', '\'', '@', ';', '(', ')', '"', '\\':
|
||||
return true
|
||||
}
|
||||
return appendTXTStringByte(s, b)
|
||||
return false
|
||||
}
|
||||
|
||||
func appendTXTStringByte(s []byte, b byte) []byte {
|
||||
switch b {
|
||||
case '"', '\\':
|
||||
return append(s, '\\', b)
|
||||
}
|
||||
if b < ' ' || b > '~' {
|
||||
return appendByte(s, b)
|
||||
}
|
||||
return append(s, b)
|
||||
}
|
||||
|
||||
func appendByte(s []byte, b byte) []byte {
|
||||
var buf [3]byte
|
||||
bufs := strconv.AppendInt(buf[:0], int64(b), 10)
|
||||
s = append(s, '\\')
|
||||
for i := 0; i < 3-len(bufs); i++ {
|
||||
s = append(s, '0')
|
||||
}
|
||||
for _, r := range bufs {
|
||||
s = append(s, r)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func nextByte(b []byte, offset int) (byte, int) {
|
||||
if offset >= len(b) {
|
||||
func nextByte(s string, offset int) (byte, int) {
|
||||
if offset >= len(s) {
|
||||
return 0, 0
|
||||
}
|
||||
if b[offset] != '\\' {
|
||||
if s[offset] != '\\' {
|
||||
// not an escape sequence
|
||||
return b[offset], 1
|
||||
return s[offset], 1
|
||||
}
|
||||
switch len(b) - offset {
|
||||
switch len(s) - offset {
|
||||
case 1: // dangling escape
|
||||
return 0, 0
|
||||
case 2, 3: // too short to be \ddd
|
||||
default: // maybe \ddd
|
||||
if isDigit(b[offset+1]) && isDigit(b[offset+2]) && isDigit(b[offset+3]) {
|
||||
return dddToByte(b[offset+1:]), 4
|
||||
if isDigit(s[offset+1]) && isDigit(s[offset+2]) && isDigit(s[offset+3]) {
|
||||
return dddStringToByte(s[offset+1:]), 4
|
||||
}
|
||||
}
|
||||
// not \ddd, just an RFC 1035 "quoted" character
|
||||
return b[offset+1], 2
|
||||
return s[offset+1], 2
|
||||
}
|
||||
|
||||
// SPF RR. See RFC 4408, Section 3.1.1.
|
||||
|
@ -694,8 +772,8 @@ type LOC struct {
|
|||
Altitude uint32
|
||||
}
|
||||
|
||||
// cmToM takes a cm value expressed in RFC1876 SIZE mantissa/exponent
|
||||
// format and returns a string in m (two decimals for the cm)
|
||||
// cmToM takes a cm value expressed in RFC 1876 SIZE mantissa/exponent
|
||||
// format and returns a string in m (two decimals for the cm).
|
||||
func cmToM(m, e uint8) string {
|
||||
if e < 2 {
|
||||
if e == 1 {
|
||||
|
@ -728,7 +806,7 @@ func (rr *LOC) String() string {
|
|||
lat = lat % LOC_DEGREES
|
||||
m := lat / LOC_HOURS
|
||||
lat = lat % LOC_HOURS
|
||||
s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, (float64(lat) / 1000), ns)
|
||||
s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, float64(lat)/1000, ns)
|
||||
|
||||
lon := rr.Longitude
|
||||
ew := "E"
|
||||
|
@ -742,7 +820,7 @@ func (rr *LOC) String() string {
|
|||
lon = lon % LOC_DEGREES
|
||||
m = lon / LOC_HOURS
|
||||
lon = lon % LOC_HOURS
|
||||
s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, (float64(lon) / 1000), ew)
|
||||
s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, float64(lon)/1000, ew)
|
||||
|
||||
var alt = float64(rr.Altitude) / 100
|
||||
alt -= LOC_ALTITUDEBASE
|
||||
|
@ -752,9 +830,9 @@ func (rr *LOC) String() string {
|
|||
s += fmt.Sprintf("%.0fm ", alt)
|
||||
}
|
||||
|
||||
s += cmToM((rr.Size&0xf0)>>4, rr.Size&0x0f) + "m "
|
||||
s += cmToM((rr.HorizPre&0xf0)>>4, rr.HorizPre&0x0f) + "m "
|
||||
s += cmToM((rr.VertPre&0xf0)>>4, rr.VertPre&0x0f) + "m"
|
||||
s += cmToM(rr.Size&0xf0>>4, rr.Size&0x0f) + "m "
|
||||
s += cmToM(rr.HorizPre&0xf0>>4, rr.HorizPre&0x0f) + "m "
|
||||
s += cmToM(rr.VertPre&0xf0>>4, rr.VertPre&0x0f) + "m"
|
||||
|
||||
return s
|
||||
}
|
||||
|
@ -801,22 +879,16 @@ type NSEC struct {
|
|||
|
||||
func (rr *NSEC) String() string {
|
||||
s := rr.Hdr.String() + sprintName(rr.NextDomain)
|
||||
for i := 0; i < len(rr.TypeBitMap); i++ {
|
||||
s += " " + Type(rr.TypeBitMap[i]).String()
|
||||
for _, t := range rr.TypeBitMap {
|
||||
s += " " + Type(t).String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (rr *NSEC) len() int {
|
||||
l := rr.Hdr.len() + len(rr.NextDomain) + 1
|
||||
lastwindow := uint32(2 ^ 32 + 1)
|
||||
for _, t := range rr.TypeBitMap {
|
||||
window := t / 256
|
||||
if uint32(window) != lastwindow {
|
||||
l += 1 + 32
|
||||
}
|
||||
lastwindow = uint32(window)
|
||||
}
|
||||
func (rr *NSEC) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.NextDomain, off+l, compression, false)
|
||||
l += typeBitMapLen(rr.TypeBitMap)
|
||||
return l
|
||||
}
|
||||
|
||||
|
@ -966,22 +1038,16 @@ func (rr *NSEC3) String() string {
|
|||
" " + strconv.Itoa(int(rr.Iterations)) +
|
||||
" " + saltToString(rr.Salt) +
|
||||
" " + rr.NextDomain
|
||||
for i := 0; i < len(rr.TypeBitMap); i++ {
|
||||
s += " " + Type(rr.TypeBitMap[i]).String()
|
||||
for _, t := range rr.TypeBitMap {
|
||||
s += " " + Type(t).String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (rr *NSEC3) len() int {
|
||||
l := rr.Hdr.len() + 6 + len(rr.Salt)/2 + 1 + len(rr.NextDomain) + 1
|
||||
lastwindow := uint32(2 ^ 32 + 1)
|
||||
for _, t := range rr.TypeBitMap {
|
||||
window := t / 256
|
||||
if uint32(window) != lastwindow {
|
||||
l += 1 + 32
|
||||
}
|
||||
lastwindow = uint32(window)
|
||||
}
|
||||
func (rr *NSEC3) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 6 + len(rr.Salt)/2 + 1 + len(rr.NextDomain) + 1
|
||||
l += typeBitMapLen(rr.TypeBitMap)
|
||||
return l
|
||||
}
|
||||
|
||||
|
@ -1020,10 +1086,16 @@ type TKEY struct {
|
|||
|
||||
// TKEY has no official presentation format, but this will suffice.
|
||||
func (rr *TKEY) String() string {
|
||||
s := "\n;; TKEY PSEUDOSECTION:\n"
|
||||
s += rr.Hdr.String() + " " + rr.Algorithm + " " +
|
||||
strconv.Itoa(int(rr.KeySize)) + " " + rr.Key + " " +
|
||||
strconv.Itoa(int(rr.OtherLen)) + " " + rr.OtherData
|
||||
s := ";" + rr.Hdr.String() +
|
||||
" " + rr.Algorithm +
|
||||
" " + TimeToString(rr.Inception) +
|
||||
" " + TimeToString(rr.Expiration) +
|
||||
" " + strconv.Itoa(int(rr.Mode)) +
|
||||
" " + strconv.Itoa(int(rr.Error)) +
|
||||
" " + strconv.Itoa(int(rr.KeySize)) +
|
||||
" " + rr.Key +
|
||||
" " + strconv.Itoa(int(rr.OtherLen)) +
|
||||
" " + rr.OtherData
|
||||
return s
|
||||
}
|
||||
|
||||
|
@ -1059,6 +1131,7 @@ type URI struct {
|
|||
Target string `dns:"octet"`
|
||||
}
|
||||
|
||||
// rr.Target to be parsed as a sequence of character encoded octets according to RFC 3986
|
||||
func (rr *URI) String() string {
|
||||
return rr.Hdr.String() + strconv.Itoa(int(rr.Priority)) +
|
||||
" " + strconv.Itoa(int(rr.Weight)) + " " + sprintTxtOctet(rr.Target)
|
||||
|
@ -1220,6 +1293,7 @@ type CAA struct {
|
|||
Value string `dns:"octet"`
|
||||
}
|
||||
|
||||
// rr.Value Is the character-string encoding of the value field as specified in RFC 1035, Section 5.1.
|
||||
func (rr *CAA) String() string {
|
||||
return rr.Hdr.String() + strconv.Itoa(int(rr.Flag)) + " " + rr.Tag + " " + sprintTxtOctet(rr.Value)
|
||||
}
|
||||
|
@ -1283,34 +1357,127 @@ type CSYNC struct {
|
|||
func (rr *CSYNC) String() string {
|
||||
s := rr.Hdr.String() + strconv.FormatInt(int64(rr.Serial), 10) + " " + strconv.Itoa(int(rr.Flags))
|
||||
|
||||
for i := 0; i < len(rr.TypeBitMap); i++ {
|
||||
s += " " + Type(rr.TypeBitMap[i]).String()
|
||||
for _, t := range rr.TypeBitMap {
|
||||
s += " " + Type(t).String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (rr *CSYNC) len() int {
|
||||
l := rr.Hdr.len() + 4 + 2
|
||||
lastwindow := uint32(2 ^ 32 + 1)
|
||||
for _, t := range rr.TypeBitMap {
|
||||
window := t / 256
|
||||
if uint32(window) != lastwindow {
|
||||
l += 1 + 32
|
||||
}
|
||||
lastwindow = uint32(window)
|
||||
}
|
||||
func (rr *CSYNC) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 4 + 2
|
||||
l += typeBitMapLen(rr.TypeBitMap)
|
||||
return l
|
||||
}
|
||||
|
||||
// ZONEMD RR, from draft-ietf-dnsop-dns-zone-digest
|
||||
type ZONEMD struct {
|
||||
Hdr RR_Header
|
||||
Serial uint32
|
||||
Scheme uint8
|
||||
Hash uint8
|
||||
Digest string `dns:"hex"`
|
||||
}
|
||||
|
||||
func (rr *ZONEMD) String() string {
|
||||
return rr.Hdr.String() +
|
||||
strconv.Itoa(int(rr.Serial)) +
|
||||
" " + strconv.Itoa(int(rr.Scheme)) +
|
||||
" " + strconv.Itoa(int(rr.Hash)) +
|
||||
" " + rr.Digest
|
||||
}
|
||||
|
||||
// APL RR. See RFC 3123.
|
||||
type APL struct {
|
||||
Hdr RR_Header
|
||||
Prefixes []APLPrefix `dns:"apl"`
|
||||
}
|
||||
|
||||
// APLPrefix is an address prefix hold by an APL record.
|
||||
type APLPrefix struct {
|
||||
Negation bool
|
||||
Network net.IPNet
|
||||
}
|
||||
|
||||
// String returns presentation form of the APL record.
|
||||
func (rr *APL) String() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(rr.Hdr.String())
|
||||
for i, p := range rr.Prefixes {
|
||||
if i > 0 {
|
||||
sb.WriteByte(' ')
|
||||
}
|
||||
sb.WriteString(p.str())
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// str returns presentation form of the APL prefix.
|
||||
func (a *APLPrefix) str() string {
|
||||
var sb strings.Builder
|
||||
if a.Negation {
|
||||
sb.WriteByte('!')
|
||||
}
|
||||
|
||||
switch len(a.Network.IP) {
|
||||
case net.IPv4len:
|
||||
sb.WriteByte('1')
|
||||
case net.IPv6len:
|
||||
sb.WriteByte('2')
|
||||
}
|
||||
|
||||
sb.WriteByte(':')
|
||||
|
||||
switch len(a.Network.IP) {
|
||||
case net.IPv4len:
|
||||
sb.WriteString(a.Network.IP.String())
|
||||
case net.IPv6len:
|
||||
// add prefix for IPv4-mapped IPv6
|
||||
if v4 := a.Network.IP.To4(); v4 != nil {
|
||||
sb.WriteString("::ffff:")
|
||||
}
|
||||
sb.WriteString(a.Network.IP.String())
|
||||
}
|
||||
|
||||
sb.WriteByte('/')
|
||||
|
||||
prefix, _ := a.Network.Mask.Size()
|
||||
sb.WriteString(strconv.Itoa(prefix))
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// equals reports whether two APL prefixes are identical.
|
||||
func (a *APLPrefix) equals(b *APLPrefix) bool {
|
||||
return a.Negation == b.Negation &&
|
||||
bytes.Equal(a.Network.IP, b.Network.IP) &&
|
||||
bytes.Equal(a.Network.Mask, b.Network.Mask)
|
||||
}
|
||||
|
||||
// copy returns a copy of the APL prefix.
|
||||
func (a *APLPrefix) copy() APLPrefix {
|
||||
return APLPrefix{
|
||||
Negation: a.Negation,
|
||||
Network: copyNet(a.Network),
|
||||
}
|
||||
}
|
||||
|
||||
// len returns size of the prefix in wire format.
|
||||
func (a *APLPrefix) len() int {
|
||||
// 4-byte header and the network address prefix (see Section 4 of RFC 3123)
|
||||
prefix, _ := a.Network.Mask.Size()
|
||||
return 4 + (prefix+7)/8
|
||||
}
|
||||
|
||||
// TimeToString translates the RRSIG's incep. and expir. times to the
|
||||
// string representation used when printing the record.
|
||||
// It takes serial arithmetic (RFC 1982) into account.
|
||||
func TimeToString(t uint32) string {
|
||||
mod := ((int64(t) - time.Now().Unix()) / year68) - 1
|
||||
mod := (int64(t)-time.Now().Unix())/year68 - 1
|
||||
if mod < 0 {
|
||||
mod = 0
|
||||
}
|
||||
ti := time.Unix(int64(t)-(mod*year68), 0).UTC()
|
||||
ti := time.Unix(int64(t)-mod*year68, 0).UTC()
|
||||
return ti.Format("20060102150405")
|
||||
}
|
||||
|
||||
|
@ -1322,16 +1489,16 @@ func StringToTime(s string) (uint32, error) {
|
|||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
mod := (t.Unix() / year68) - 1
|
||||
mod := t.Unix()/year68 - 1
|
||||
if mod < 0 {
|
||||
mod = 0
|
||||
}
|
||||
return uint32(t.Unix() - (mod * year68)), nil
|
||||
return uint32(t.Unix() - mod*year68), nil
|
||||
}
|
||||
|
||||
// saltToString converts a NSECX salt to uppercase and returns "-" when it is empty.
|
||||
func saltToString(s string) string {
|
||||
if len(s) == 0 {
|
||||
if s == "" {
|
||||
return "-"
|
||||
}
|
||||
return strings.ToUpper(s)
|
||||
|
@ -1358,6 +1525,17 @@ func copyIP(ip net.IP) net.IP {
|
|||
return p
|
||||
}
|
||||
|
||||
// copyNet returns a copy of a subnet.
|
||||
func copyNet(n net.IPNet) net.IPNet {
|
||||
m := make(net.IPMask, len(n.Mask))
|
||||
copy(m, n.Mask)
|
||||
|
||||
return net.IPNet{
|
||||
IP: copyIP(n.IP),
|
||||
Mask: m,
|
||||
}
|
||||
}
|
||||
|
||||
// SplitN splits a string into N sized string chunks.
|
||||
// This might become an exported function once.
|
||||
func splitN(s string, n int) []string {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//+build ignore
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
// types_generate.go is meant to run with go generate. It will use
|
||||
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||
|
@ -11,12 +12,13 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"go/importer"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
var skipLen = map[string]struct{}{
|
||||
|
@ -71,6 +73,9 @@ func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
|
|||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if st.NumFields() == 0 {
|
||||
return nil, false
|
||||
}
|
||||
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
||||
return st, false
|
||||
}
|
||||
|
@ -81,9 +86,19 @@ func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
|
|||
return nil, false
|
||||
}
|
||||
|
||||
// loadModule retrieves package description for a given module.
|
||||
func loadModule(name string) (*types.Package, error) {
|
||||
conf := packages.Config{Mode: packages.NeedTypes | packages.NeedTypesInfo}
|
||||
pkgs, err := packages.Load(&conf, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pkgs[0].Types, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Import and type-check the package
|
||||
pkg, err := importer.Default().Import("github.com/miekg/dns")
|
||||
pkg, err := loadModule("github.com/miekg/dns")
|
||||
fatalIfErr(err)
|
||||
scope := pkg.Scope()
|
||||
|
||||
|
@ -153,8 +168,8 @@ func main() {
|
|||
if isEmbedded {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(b, "func (rr *%s) len() int {\n", name)
|
||||
fmt.Fprintf(b, "l := rr.Hdr.len()\n")
|
||||
fmt.Fprintf(b, "func (rr *%s) len(off int, compression map[string]struct{}) int {\n", name)
|
||||
fmt.Fprintf(b, "l := rr.Hdr.len(off, compression)\n")
|
||||
for i := 1; i < st.NumFields(); i++ {
|
||||
o := func(s string) { fmt.Fprintf(b, s, st.Field(i).Name()) }
|
||||
|
||||
|
@ -162,8 +177,16 @@ func main() {
|
|||
switch st.Tag(i) {
|
||||
case `dns:"-"`:
|
||||
// ignored
|
||||
case `dns:"cdomain-name"`, `dns:"domain-name"`, `dns:"txt"`:
|
||||
case `dns:"cdomain-name"`:
|
||||
o("for _, x := range rr.%s { l += domainNameLen(x, off+l, compression, true) }\n")
|
||||
case `dns:"domain-name"`:
|
||||
o("for _, x := range rr.%s { l += domainNameLen(x, off+l, compression, false) }\n")
|
||||
case `dns:"txt"`:
|
||||
o("for _, x := range rr.%s { l += len(x) + 1 }\n")
|
||||
case `dns:"apl"`:
|
||||
o("for _, x := range rr.%s { l += x.len() }\n")
|
||||
case `dns:"pairs"`:
|
||||
o("for _, x := range rr.%s { l += 4 + int(x.len()) }\n")
|
||||
default:
|
||||
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
||||
}
|
||||
|
@ -173,8 +196,10 @@ func main() {
|
|||
switch {
|
||||
case st.Tag(i) == `dns:"-"`:
|
||||
// ignored
|
||||
case st.Tag(i) == `dns:"cdomain-name"`, st.Tag(i) == `dns:"domain-name"`:
|
||||
o("l += len(rr.%s) + 1\n")
|
||||
case st.Tag(i) == `dns:"cdomain-name"`:
|
||||
o("l += domainNameLen(rr.%s, off+l, compression, true)\n")
|
||||
case st.Tag(i) == `dns:"domain-name"`:
|
||||
o("l += domainNameLen(rr.%s, off+l, compression, false)\n")
|
||||
case st.Tag(i) == `dns:"octet"`:
|
||||
o("l += len(rr.%s)\n")
|
||||
case strings.HasPrefix(st.Tag(i), `dns:"size-base64`):
|
||||
|
@ -183,14 +208,14 @@ func main() {
|
|||
o("l += base64.StdEncoding.DecodedLen(len(rr.%s))\n")
|
||||
case strings.HasPrefix(st.Tag(i), `dns:"size-hex:`): // this has an extra field where the length is stored
|
||||
o("l += len(rr.%s)/2\n")
|
||||
case strings.HasPrefix(st.Tag(i), `dns:"size-hex`):
|
||||
fallthrough
|
||||
case st.Tag(i) == `dns:"hex"`:
|
||||
o("l += len(rr.%s)/2 + 1\n")
|
||||
o("l += len(rr.%s)/2\n")
|
||||
case st.Tag(i) == `dns:"any"`:
|
||||
o("l += len(rr.%s)\n")
|
||||
case st.Tag(i) == `dns:"a"`:
|
||||
o("l += net.IPv4len // %s\n")
|
||||
o("if len(rr.%s) != 0 { l += net.IPv4len }\n")
|
||||
case st.Tag(i) == `dns:"aaaa"`:
|
||||
o("l += net.IPv6len // %s\n")
|
||||
o("if len(rr.%s) != 0 { l += net.IPv6len }\n")
|
||||
case st.Tag(i) == `dns:"txt"`:
|
||||
o("for _, t := range rr.%s { l += len(t) + 1 }\n")
|
||||
case st.Tag(i) == `dns:"uint48"`:
|
||||
|
@ -222,11 +247,15 @@ func main() {
|
|||
for _, name := range namedTypes {
|
||||
o := scope.Lookup(name)
|
||||
st, isEmbedded := getTypeStruct(o.Type(), scope)
|
||||
if isEmbedded {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(b, "func (rr *%s) copy() RR {\n", name)
|
||||
fields := []string{"*rr.Hdr.copyHeader()"}
|
||||
fields := make([]string, 0, st.NumFields())
|
||||
if isEmbedded {
|
||||
a, _ := o.Type().Underlying().(*types.Struct)
|
||||
parent := a.Field(0).Name()
|
||||
fields = append(fields, "*rr."+parent+".copy().(*"+parent+")")
|
||||
goto WriteCopy
|
||||
}
|
||||
fields = append(fields, "rr.Hdr")
|
||||
for i := 1; i < st.NumFields(); i++ {
|
||||
f := st.Field(i).Name()
|
||||
if sl, ok := st.Field(i).Type().(*types.Slice); ok {
|
||||
|
@ -236,6 +265,25 @@ func main() {
|
|||
splits := strings.Split(t, ".")
|
||||
t = splits[len(splits)-1]
|
||||
}
|
||||
// For the EDNS0 interface (used in the OPT RR), we need to call the copy method on each element.
|
||||
if t == "EDNS0" {
|
||||
fmt.Fprintf(b, "%s := make([]%s, len(rr.%s));\nfor i,e := range rr.%s {\n %s[i] = e.copy()\n}\n",
|
||||
f, t, f, f, f)
|
||||
fields = append(fields, f)
|
||||
continue
|
||||
}
|
||||
if t == "APLPrefix" {
|
||||
fmt.Fprintf(b, "%s := make([]%s, len(rr.%s));\nfor i,e := range rr.%s {\n %s[i] = e.copy()\n}\n",
|
||||
f, t, f, f, f)
|
||||
fields = append(fields, f)
|
||||
continue
|
||||
}
|
||||
if t == "SVCBKeyValue" {
|
||||
fmt.Fprintf(b, "%s := make([]%s, len(rr.%s));\nfor i,e := range rr.%s {\n %s[i] = e.copy()\n}\n",
|
||||
f, t, f, f, f)
|
||||
fields = append(fields, f)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(b, "%s := make([]%s, len(rr.%s)); copy(%s, rr.%s)\n",
|
||||
f, t, f, f, f)
|
||||
fields = append(fields, f)
|
||||
|
@ -247,6 +295,7 @@ func main() {
|
|||
}
|
||||
fields = append(fields, "rr."+f)
|
||||
}
|
||||
WriteCopy:
|
||||
fmt.Fprintf(b, "return &%s{%s}\n", name, strings.Join(fields, ","))
|
||||
fmt.Fprintf(b, "}\n")
|
||||
}
|
||||
|
|
119
types_test.go
119
types_test.go
|
@ -72,3 +72,122 @@ func TestSplitN(t *testing.T) {
|
|||
t.Errorf("failure to split 510 char long string: %d", len(xs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSprintName(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
// Non-numeric escaping of special printable characters.
|
||||
" '@;()\"\\..example": `\ \'\@\;\(\)\"\..example`,
|
||||
"\\032\\039\\064\\059\\040\\041\\034\\046\\092.example": `\ \'\@\;\(\)\"\.\\.example`,
|
||||
|
||||
// Numeric escaping of nonprintable characters.
|
||||
"\x00\x07\x09\x0a\x1f.\x7f\x80\xad\xef\xff": `\000\007\009\010\031.\127\128\173\239\255`,
|
||||
"\\000\\007\\009\\010\\031.\\127\\128\\173\\239\\255": `\000\007\009\010\031.\127\128\173\239\255`,
|
||||
|
||||
// No escaping of other printable characters, at least after a prior escape.
|
||||
";[a-zA-Z0-9_]+/*.~": `\;[a-zA-Z0-9_]+/*.~`,
|
||||
";\\091\\097\\045\\122\\065\\045\\090\\048\\045\\057\\095\\093\\043\\047\\042.\\126": `\;[a-zA-Z0-9_]+/*.~`,
|
||||
// "\\091\\097\\045\\122\\065\\045\\090\\048\\045\\057\\095\\093\\043\\047\\042.\\126": `[a-zA-Z0-9_]+/*.~`,
|
||||
|
||||
// Incomplete "dangling" escapes are dropped regardless of prior escaping.
|
||||
"a\\": `a`,
|
||||
";\\": `\;`,
|
||||
|
||||
// Escaped dots stay escaped regardless of prior escaping.
|
||||
"a\\.\\046.\\.\\046": `a\.\..\.\.`,
|
||||
"a\\046\\..\\046\\.": `a\.\..\.\.`,
|
||||
}
|
||||
for input, want := range tests {
|
||||
got := sprintName(input)
|
||||
if got != want {
|
||||
t.Errorf("input %q: expected %q, got %q", input, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSprintTxtOctet(t *testing.T) {
|
||||
got := sprintTxtOctet("abc\\.def\007\"\127@\255\x05\xef\\")
|
||||
|
||||
if want := "\"abc\\.def\\007\\\"W@\\173\\005\\239\""; got != want {
|
||||
t.Errorf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSprintTxt(t *testing.T) {
|
||||
got := sprintTxt([]string{
|
||||
"abc\\.def\007\"\127@\255\x05\xef\\",
|
||||
"example.com",
|
||||
})
|
||||
|
||||
if want := "\"abc.def\\007\\\"W@\\173\\005\\239\" \"example.com\""; got != want {
|
||||
t.Errorf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRPStringer(t *testing.T) {
|
||||
rp := &RP{
|
||||
Hdr: RR_Header{
|
||||
Name: "test.example.com.",
|
||||
Rrtype: TypeRP,
|
||||
Class: ClassINET,
|
||||
Ttl: 600,
|
||||
},
|
||||
Mbox: "\x05first.example.com.",
|
||||
Txt: "second.\x07example.com.",
|
||||
}
|
||||
|
||||
const expected = "test.example.com.\t600\tIN\tRP\t\\005first.example.com. second.\\007example.com."
|
||||
if rp.String() != expected {
|
||||
t.Errorf("expected %v, got %v", expected, rp)
|
||||
}
|
||||
|
||||
_, err := NewRR(rp.String())
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing %q: %v", rp, err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSprintName(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
got := sprintName("abc\\.def\007\"\127@\255\x05\xef\\")
|
||||
|
||||
if want := "abc\\.def\\007\\\"W\\@\\173\\005\\239"; got != want {
|
||||
b.Fatalf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSprintName_NoEscape(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
got := sprintName("large.example.com")
|
||||
|
||||
if want := "large.example.com"; got != want {
|
||||
b.Fatalf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSprintTxtOctet(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
got := sprintTxtOctet("abc\\.def\007\"\127@\255\x05\xef\\")
|
||||
|
||||
if want := "\"abc\\.def\\007\\\"W@\\173\\005\\239\""; got != want {
|
||||
b.Fatalf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSprintTxt(b *testing.B) {
|
||||
txt := []string{
|
||||
"abc\\.def\007\"\127@\255\x05\xef\\",
|
||||
"example.com",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
got := sprintTxt(txt)
|
||||
|
||||
if want := "\"abc.def\\007\\\"W@\\173\\005\\239\" \"example.com\""; got != want {
|
||||
b.Fatalf("expected %q, got %q", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
34
udp.go
34
udp.go
|
@ -1,3 +1,4 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package dns
|
||||
|
@ -9,6 +10,22 @@ import (
|
|||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
// This is the required size of the OOB buffer to pass to ReadMsgUDP.
|
||||
var udpOOBSize = func() int {
|
||||
// We can't know whether we'll get an IPv4 control message or an
|
||||
// IPv6 control message ahead of time. To get around this, we size
|
||||
// the buffer equal to the largest of the two.
|
||||
|
||||
oob4 := ipv4.NewControlMessage(ipv4.FlagDst | ipv4.FlagInterface)
|
||||
oob6 := ipv6.NewControlMessage(ipv6.FlagDst | ipv6.FlagInterface)
|
||||
|
||||
if len(oob4) > len(oob6) {
|
||||
return len(oob4)
|
||||
}
|
||||
|
||||
return len(oob6)
|
||||
}()
|
||||
|
||||
// SessionUDP holds the remote address and the associated
|
||||
// out-of-band data.
|
||||
type SessionUDP struct {
|
||||
|
@ -22,7 +39,7 @@ func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr }
|
|||
// ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a
|
||||
// net.UDPAddr.
|
||||
func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) {
|
||||
oob := make([]byte, 40)
|
||||
oob := make([]byte, udpOOBSize)
|
||||
n, oobn, _, raddr, err := conn.ReadMsgUDP(b, oob)
|
||||
if err != nil {
|
||||
return n, nil, err
|
||||
|
@ -53,18 +70,15 @@ func parseDstFromOOB(oob []byte) net.IP {
|
|||
// Start with IPv6 and then fallback to IPv4
|
||||
// TODO(fastest963): Figure out a way to prefer one or the other. Looking at
|
||||
// the lvl of the header for a 0 or 41 isn't cross-platform.
|
||||
var dst net.IP
|
||||
cm6 := new(ipv6.ControlMessage)
|
||||
if cm6.Parse(oob) == nil {
|
||||
dst = cm6.Dst
|
||||
if cm6.Parse(oob) == nil && cm6.Dst != nil {
|
||||
return cm6.Dst
|
||||
}
|
||||
if dst == nil {
|
||||
cm4 := new(ipv4.ControlMessage)
|
||||
if cm4.Parse(oob) == nil {
|
||||
dst = cm4.Dst
|
||||
}
|
||||
cm4 := new(ipv4.ControlMessage)
|
||||
if cm4.Parse(oob) == nil && cm4.Dst != nil {
|
||||
return cm4.Dst
|
||||
}
|
||||
return dst
|
||||
return nil
|
||||
}
|
||||
|
||||
// correctSource takes oob data and returns new oob data with the Src equal to the Dst
|
||||
|
|
22
udp_test.go
22
udp_test.go
|
@ -1,3 +1,4 @@
|
|||
//go:build linux && !appengine
|
||||
// +build linux,!appengine
|
||||
|
||||
package dns
|
||||
|
@ -5,6 +6,7 @@ package dns
|
|||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -34,7 +36,8 @@ func TestSetUDPSocketOptions(t *testing.T) {
|
|||
b := make([]byte, 1)
|
||||
_, sess, err := ReadFromSessionUDP(c, b)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read from conn: %v", err)
|
||||
t.Errorf("failed to read from conn: %v", err)
|
||||
// fallthrough to chan send below
|
||||
}
|
||||
ch <- sess
|
||||
}()
|
||||
|
@ -47,6 +50,10 @@ func TestSetUDPSocketOptions(t *testing.T) {
|
|||
t.Fatalf("failed to write to conn: %v", err)
|
||||
}
|
||||
sess := <-ch
|
||||
if sess == nil {
|
||||
// t.Error was already called in the goroutine above.
|
||||
t.FailNow()
|
||||
}
|
||||
if len(sess.context) == 0 {
|
||||
t.Fatalf("empty session context: %v", sess)
|
||||
}
|
||||
|
@ -74,6 +81,14 @@ func TestSetUDPSocketOptions(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseDstFromOOB(t *testing.T) {
|
||||
if runtime.GOARCH != "amd64" {
|
||||
// The cmsghdr struct differs in the width (32/64-bit) of
|
||||
// lengths and the struct padding between architectures.
|
||||
// The data below was only written with amd64 in mind, and
|
||||
// thus the test must be skipped on other architectures.
|
||||
t.Skip("skipping test on unsupported architecture")
|
||||
}
|
||||
|
||||
// dst is :ffff:100.100.100.100
|
||||
oob := []byte{36, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 100, 100, 100, 100, 2, 0, 0, 0}
|
||||
dst := parseDstFromOOB(oob)
|
||||
|
@ -106,6 +121,11 @@ func TestParseDstFromOOB(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCorrectSource(t *testing.T) {
|
||||
if runtime.GOARCH != "amd64" {
|
||||
// See comment above in TestParseDstFromOOB.
|
||||
t.Skip("skipping test on unsupported architecture")
|
||||
}
|
||||
|
||||
// dst is :ffff:100.100.100.100 which should be counted as IPv4
|
||||
oob := []byte{36, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 100, 100, 100, 100, 2, 0, 0, 0}
|
||||
soob := correctSource(oob)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package dns
|
||||
|
@ -20,15 +21,13 @@ func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) {
|
|||
if err != nil {
|
||||
return n, nil, err
|
||||
}
|
||||
session := &SessionUDP{raddr.(*net.UDPAddr)}
|
||||
return n, session, err
|
||||
return n, &SessionUDP{raddr.(*net.UDPAddr)}, err
|
||||
}
|
||||
|
||||
// WriteToSessionUDP acts just like net.UDPConn.WriteTo(), but uses a *SessionUDP instead of a net.Addr.
|
||||
// TODO(fastest963): Once go1.10 is released, use WriteMsgUDP.
|
||||
func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) {
|
||||
n, err := conn.WriteTo(b, session.raddr)
|
||||
return n, err
|
||||
return conn.WriteTo(b, session.raddr)
|
||||
}
|
||||
|
||||
// TODO(fastest963): Once go1.10 is released and we can use *MsgUDP methods
|
||||
|
|
18
update.go
18
update.go
|
@ -32,7 +32,9 @@ func (u *Msg) Used(rr []RR) {
|
|||
u.Answer = make([]RR, 0, len(rr))
|
||||
}
|
||||
for _, r := range rr {
|
||||
r.Header().Class = u.Question[0].Qclass
|
||||
hdr := r.Header()
|
||||
hdr.Class = u.Question[0].Qclass
|
||||
hdr.Ttl = 0
|
||||
u.Answer = append(u.Answer, r)
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +46,8 @@ func (u *Msg) RRsetUsed(rr []RR) {
|
|||
u.Answer = make([]RR, 0, len(rr))
|
||||
}
|
||||
for _, r := range rr {
|
||||
u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassANY}})
|
||||
h := r.Header()
|
||||
u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: h.Name, Ttl: 0, Rrtype: h.Rrtype, Class: ClassANY}})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,7 +58,8 @@ func (u *Msg) RRsetNotUsed(rr []RR) {
|
|||
u.Answer = make([]RR, 0, len(rr))
|
||||
}
|
||||
for _, r := range rr {
|
||||
u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassNONE}})
|
||||
h := r.Header()
|
||||
u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: h.Name, Ttl: 0, Rrtype: h.Rrtype, Class: ClassNONE}})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +83,8 @@ func (u *Msg) RemoveRRset(rr []RR) {
|
|||
u.Ns = make([]RR, 0, len(rr))
|
||||
}
|
||||
for _, r := range rr {
|
||||
u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassANY}})
|
||||
h := r.Header()
|
||||
u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: h.Name, Ttl: 0, Rrtype: h.Rrtype, Class: ClassANY}})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,8 +104,9 @@ func (u *Msg) Remove(rr []RR) {
|
|||
u.Ns = make([]RR, 0, len(rr))
|
||||
}
|
||||
for _, r := range rr {
|
||||
r.Header().Class = ClassNONE
|
||||
r.Header().Ttl = 0
|
||||
h := r.Header()
|
||||
h.Class = ClassNONE
|
||||
h.Ttl = 0
|
||||
u.Ns = append(u.Ns, r)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,15 +6,28 @@ import (
|
|||
)
|
||||
|
||||
func TestDynamicUpdateParsing(t *testing.T) {
|
||||
prefix := "example.com. IN "
|
||||
for _, typ := range TypeToString {
|
||||
if typ == "OPT" || typ == "AXFR" || typ == "IXFR" || typ == "ANY" || typ == "TKEY" ||
|
||||
typ == "TSIG" || typ == "ISDN" || typ == "UNSPEC" || typ == "NULL" || typ == "ATMA" ||
|
||||
typ == "Reserved" || typ == "None" || typ == "NXT" || typ == "MAILB" || typ == "MAILA" {
|
||||
const prefix = "example.com. IN "
|
||||
|
||||
for typ, name := range TypeToString {
|
||||
switch typ {
|
||||
case TypeNone, TypeReserved:
|
||||
continue
|
||||
case TypeANY:
|
||||
// ANY is ambiguous here and ends up parsed as a CLASS.
|
||||
//
|
||||
// TODO(tmthrgd): Using TYPE255 here doesn't seem to work and also
|
||||
// seems to fail for some other record types. Investigate.
|
||||
continue
|
||||
}
|
||||
if _, err := NewRR(prefix + typ); err != nil {
|
||||
t.Errorf("failure to parse: %s %s: %v", prefix, typ, err)
|
||||
|
||||
s := prefix + name
|
||||
if _, err := NewRR(s); err != nil {
|
||||
t.Errorf("failure to parse: %s: %v", s, err)
|
||||
}
|
||||
|
||||
s += " \\# 0"
|
||||
if _, err := NewRR(s); err != nil {
|
||||
t.Errorf("failure to parse: %s: %v", s, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +92,7 @@ func TestRemoveRRset(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPreReqAndRemovals(t *testing.T) {
|
||||
// Build a list of multiple prereqs and then somes removes followed by an insert.
|
||||
// Build a list of multiple prereqs and then some removes followed by an insert.
|
||||
// We should be able to add multiple prereqs and updates.
|
||||
m := new(Msg)
|
||||
m.SetUpdate("example.org.")
|
||||
|
@ -119,15 +132,15 @@ func TestPreReqAndRemovals(t *testing.T) {
|
|||
;example.org. IN SOA
|
||||
|
||||
;; ANSWER SECTION:
|
||||
name_used. 0 ANY ANY
|
||||
name_used. 0 CLASS255 ANY
|
||||
name_not_used. 0 NONE ANY
|
||||
rrset_used1. 0 ANY A
|
||||
rrset_used2. 3600 IN A 127.0.0.1
|
||||
rrset_used1. 0 CLASS255 A
|
||||
rrset_used2. 0 IN A 127.0.0.1
|
||||
rrset_not_used. 0 NONE A
|
||||
|
||||
;; AUTHORITY SECTION:
|
||||
remove1. 0 ANY ANY
|
||||
remove2. 0 ANY A
|
||||
remove1. 0 CLASS255 ANY
|
||||
remove2. 0 CLASS255 A
|
||||
remove3. 0 NONE A 127.0.0.1
|
||||
insert. 3600 IN A 127.0.0.1
|
||||
`
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
# Treat all files in this repo as binary, with no git magic updating
|
||||
# line endings. Windows users contributing to Go will need to use a
|
||||
# modern version of git and editors capable of LF line endings.
|
||||
#
|
||||
# We'll prevent accidental CRLF line endings from entering the repo
|
||||
# via the git-review gofmt checks.
|
||||
#
|
||||
# See golang.org/issue/9281
|
||||
|
||||
* -text
|
|
@ -1,2 +0,0 @@
|
|||
# Add no patterns to .hgignore except for files generated by the build.
|
||||
last-change
|
|
@ -1,3 +0,0 @@
|
|||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at https://tip.golang.org/AUTHORS.
|
|
@ -1,31 +0,0 @@
|
|||
# Contributing to Go
|
||||
|
||||
Go is an open source project.
|
||||
|
||||
It is the work of hundreds of contributors. We appreciate your help!
|
||||
|
||||
|
||||
## Filing issues
|
||||
|
||||
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
|
||||
|
||||
1. What version of Go are you using (`go version`)?
|
||||
2. What operating system and processor architecture are you using?
|
||||
3. What did you do?
|
||||
4. What did you expect to see?
|
||||
5. What did you see instead?
|
||||
|
||||
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
|
||||
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
|
||||
|
||||
## Contributing code
|
||||
|
||||
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
|
||||
before sending patches.
|
||||
|
||||
**We do not accept GitHub pull requests**
|
||||
(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review).
|
||||
|
||||
Unless otherwise noted, the Go source files are distributed under
|
||||
the BSD-style license found in the LICENSE file.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at https://tip.golang.org/CONTRIBUTORS.
|
|
@ -1,27 +0,0 @@
|
|||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,22 +0,0 @@
|
|||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
|
@ -1,21 +0,0 @@
|
|||
# Go Cryptography
|
||||
|
||||
This repository holds supplementary Go cryptography libraries.
|
||||
|
||||
## Download/Install
|
||||
|
||||
The easiest way to install is to run `go get -u golang.org/x/crypto/...`. You
|
||||
can also manually git clone the repository to `$GOPATH/src/golang.org/x/crypto`.
|
||||
|
||||
## Report Issues / Send Patches
|
||||
|
||||
This repository uses Gerrit for code changes. To learn how to submit changes to
|
||||
this repository, see https://golang.org/doc/contribute.html.
|
||||
|
||||
The main issue tracker for the crypto repository is located at
|
||||
https://github.com/golang/go/issues. Prefix your issue with "x/crypto:" in the
|
||||
subject line, so it is easy to find.
|
||||
|
||||
Note that contributions to the cryptography package receive additional scrutiny
|
||||
due to their sensitive nature. Patches may take longer than normal to receive
|
||||
feedback.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue