[chore] Validate/set account domain (#619)
* add miekg/dns dependency * set/validate accountDomain
This commit is contained in:
parent
dfdc473cef
commit
cf5c6d724d
|
@ -224,6 +224,7 @@ The following libraries and frameworks are used by GoToSocial, with gratitude
|
|||
- [jackc/pgx](https://github.com/jackc/pgx); Postgres driver. [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [mcuadros/go-syslog](https://github.com/mcuadros/go-syslog); Syslog server library. [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday); HTML user-input sanitization. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).
|
||||
- [miekg/dns](https://github.com/miekg/dns); DNS utilities. [Go License](https://go.dev/LICENSE).
|
||||
- [mitchellh/mapstructure](https://github.com/mitchellh/mapstructure); Go interface => struct parsing. [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [modernc.org/sqlite](https://gitlab.com/cznic/sqlite); cgo-free port of SQLite. [Other License](https://gitlab.com/cznic/sqlite/-/blob/master/LICENSE).
|
||||
- [modernc.org/ccgo](https://gitlab.com/cznic/ccgo); c99 AST -> Go translater. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).
|
||||
|
|
1
go.mod
1
go.mod
|
@ -94,6 +94,7 @@ require (
|
|||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/miekg/dns v1.1.49 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
|
|
10
go.sum
10
go.sum
|
@ -391,6 +391,8 @@ github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4
|
|||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo=
|
||||
github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
||||
github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
|
||||
github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@ -551,6 +553,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.1/go.mod h1:TSQ0KjMH+pht+bRyvVooJ1rBpvvngSGaPISafq9MxJk=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
|
@ -622,6 +625,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -660,7 +664,9 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220524220425-1d687d428aca h1:xTaFYiPROfpPhqrfTIDXj0ri1SpfueYT951s4bAuDO8=
|
||||
|
@ -687,6 +693,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -732,8 +739,10 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/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-20210423185535-09eb48e85fd7/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -812,6 +821,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
|
|||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -31,10 +32,23 @@ func Validate() error {
|
|||
errs := []error{}
|
||||
|
||||
// host
|
||||
if GetHost() == "" {
|
||||
host := GetHost()
|
||||
if host == "" {
|
||||
errs = append(errs, fmt.Errorf("%s must be set", HostFlag()))
|
||||
}
|
||||
|
||||
// accountDomain; only check if host was set, otherwise there's no point
|
||||
if host != "" {
|
||||
switch ad := GetAccountDomain(); ad {
|
||||
case "":
|
||||
SetAccountDomain(GetHost())
|
||||
default:
|
||||
if !dns.IsSubDomain(ad, host) {
|
||||
errs = append(errs, fmt.Errorf("%s was %s and %s was %s, but %s is not a valid subdomain of %s", HostFlag(), host, AccountDomainFlag(), ad, host, ad))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// protocol
|
||||
switch proto := GetProtocol(); proto {
|
||||
case "https":
|
||||
|
|
|
@ -46,6 +46,54 @@ func (suite *ConfigValidateTestSuite) TestValidateConfigNoHost() {
|
|||
suite.EqualError(err, "host must be set")
|
||||
}
|
||||
|
||||
func (suite *ConfigValidateTestSuite) TestValidateAccountDomainOK1() {
|
||||
testrig.InitTestConfig()
|
||||
|
||||
err := config.Validate()
|
||||
suite.NoError(err)
|
||||
|
||||
suite.Equal(config.GetHost(), config.GetAccountDomain())
|
||||
}
|
||||
|
||||
func (suite *ConfigValidateTestSuite) TestValidateAccountDomainOK2() {
|
||||
testrig.InitTestConfig()
|
||||
|
||||
config.SetAccountDomain("localhost:8080")
|
||||
|
||||
err := config.Validate()
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ConfigValidateTestSuite) TestValidateAccountDomainOK3() {
|
||||
testrig.InitTestConfig()
|
||||
|
||||
config.SetHost("gts.example.org")
|
||||
config.SetAccountDomain("example.org")
|
||||
|
||||
err := config.Validate()
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ConfigValidateTestSuite) TestValidateAccountDomainNotSubdomain1() {
|
||||
testrig.InitTestConfig()
|
||||
|
||||
config.SetHost("gts.example.org")
|
||||
config.SetAccountDomain("example.com")
|
||||
|
||||
err := config.Validate()
|
||||
suite.EqualError(err, "host was gts.example.org and account-domain was example.com, but gts.example.org is not a valid subdomain of example.com")
|
||||
}
|
||||
|
||||
func (suite *ConfigValidateTestSuite) TestValidateAccountDomainNotSubdomain2() {
|
||||
testrig.InitTestConfig()
|
||||
|
||||
config.SetHost("example.org")
|
||||
config.SetAccountDomain("gts.example.org")
|
||||
|
||||
err := config.Validate()
|
||||
suite.EqualError(err, "host was example.org and account-domain was gts.example.org, but example.org is not a valid subdomain of gts.example.org")
|
||||
}
|
||||
|
||||
func (suite *ConfigValidateTestSuite) TestValidateConfigNoProtocol() {
|
||||
testrig.InitTestConfig()
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 40%
|
||||
threshold: null
|
||||
patch: false
|
||||
changes: false
|
|
@ -0,0 +1,4 @@
|
|||
*.6
|
||||
tags
|
||||
test.out
|
||||
a.out
|
|
@ -0,0 +1 @@
|
|||
Miek Gieben <miek@miek.nl>
|
|
@ -0,0 +1 @@
|
|||
* @miekg @tmthrgd
|
|
@ -0,0 +1,10 @@
|
|||
Alex A. Skinner
|
||||
Andrew Tunnell-Jones
|
||||
Ask Bjørn Hansen
|
||||
Dave Cheney
|
||||
Dusty Wilson
|
||||
Marek Majkowski
|
||||
Peter van Dijk
|
||||
Omri Bahumi
|
||||
Alex Sergeyev
|
||||
James Hartig
|
|
@ -0,0 +1,9 @@
|
|||
Copyright 2009 The Go Authors. All rights reserved. Use of this source code
|
||||
is governed by a BSD-style license that can be found in the LICENSE file.
|
||||
Extensions of the original work are copyright (c) 2011 Miek Gieben
|
||||
|
||||
Copyright 2011 Miek Gieben. All rights reserved. Use of this source code is
|
||||
governed by a BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
Copyright 2014 CloudFlare. All rights reserved. Use of this source code is
|
||||
governed by a BSD-style license that can be found in the LICENSE file.
|
|
@ -0,0 +1,30 @@
|
|||
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.
|
||||
|
||||
As this is fork of the official Go code the same license applies.
|
||||
Extensions of the original work are copyright (c) 2011 Miek Gieben
|
|
@ -0,0 +1,33 @@
|
|||
# Makefile for fuzzing
|
||||
#
|
||||
# Use go-fuzz and needs the tools installed.
|
||||
# See https://blog.cloudflare.com/dns-parser-meet-go-fuzzer/
|
||||
#
|
||||
# Installing go-fuzz:
|
||||
# $ make -f Makefile.fuzz get
|
||||
# Installs:
|
||||
# * github.com/dvyukov/go-fuzz/go-fuzz
|
||||
# * get github.com/dvyukov/go-fuzz/go-fuzz-build
|
||||
|
||||
all: build
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go-fuzz-build -tags fuzz github.com/miekg/dns
|
||||
|
||||
.PHONY: build-newrr
|
||||
build-newrr:
|
||||
go-fuzz-build -func FuzzNewRR -tags fuzz github.com/miekg/dns
|
||||
|
||||
.PHONY: fuzz
|
||||
fuzz:
|
||||
go-fuzz -bin=dns-fuzz.zip -workdir=fuzz
|
||||
|
||||
.PHONY: get
|
||||
get:
|
||||
go get github.com/dvyukov/go-fuzz/go-fuzz
|
||||
go get github.com/dvyukov/go-fuzz/go-fuzz-build
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm *-fuzz.zip
|
|
@ -0,0 +1,52 @@
|
|||
# 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 artifacts so there is nothing
|
||||
# to upload to github.
|
||||
#
|
||||
# * Up the version in version.go
|
||||
# * Run: make -f Makefile.release release
|
||||
# * will *commit* your change with 'Release $VERSION'
|
||||
# * push to github
|
||||
#
|
||||
|
||||
define GO
|
||||
//+build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(dns.Version.String())
|
||||
}
|
||||
endef
|
||||
|
||||
$(file > version_release.go,$(GO))
|
||||
VERSION:=$(shell go run version_release.go)
|
||||
TAG="v$(VERSION)"
|
||||
|
||||
all:
|
||||
@echo Use the \'release\' target to start a release $(VERSION)
|
||||
rm -f version_release.go
|
||||
|
||||
.PHONY: release
|
||||
release: commit push
|
||||
@echo Released $(VERSION)
|
||||
rm -f version_release.go
|
||||
|
||||
.PHONY: commit
|
||||
commit:
|
||||
@echo Committing release $(VERSION)
|
||||
git commit -am"Release $(VERSION)"
|
||||
git tag $(TAG)
|
||||
|
||||
.PHONY: push
|
||||
push:
|
||||
@echo Pushing release $(VERSION) to master
|
||||
git push --tags
|
||||
git push
|
|
@ -0,0 +1,186 @@
|
|||
[![Build Status](https://travis-ci.org/miekg/dns.svg?branch=master)](https://travis-ci.org/miekg/dns)
|
||||
[![Code Coverage](https://img.shields.io/codecov/c/github/miekg/dns/master.svg)](https://codecov.io/github/miekg/dns?branch=master)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/miekg/dns)](https://goreportcard.com/report/miekg/dns)
|
||||
[![](https://godoc.org/github.com/miekg/dns?status.svg)](https://godoc.org/github.com/miekg/dns)
|
||||
|
||||
# Alternative (more granular) approach to a DNS library
|
||||
|
||||
> Less is more.
|
||||
|
||||
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.
|
||||
|
||||
# Goals
|
||||
|
||||
* KISS;
|
||||
* Fast;
|
||||
* Small API. If it's easy to code in Go, don't make a function for it.
|
||||
|
||||
# Users
|
||||
|
||||
A not-so-up-to-date-list-that-may-be-actually-current:
|
||||
|
||||
* https://github.com/coredns/coredns
|
||||
* 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
|
||||
* http://www.dns-lg.com/
|
||||
* https://github.com/fcambus/rrda
|
||||
* https://github.com/kenshinx/godns
|
||||
* https://github.com/skynetservices/skydns
|
||||
* https://github.com/hashicorp/consul
|
||||
* https://github.com/DevelopersPL/godnsagent
|
||||
* https://github.com/duedil-ltd/discodns
|
||||
* https://github.com/StalkR/dns-reverse-proxy
|
||||
* https://github.com/tianon/rawdns
|
||||
* https://mesosphere.github.io/mesos-dns/
|
||||
* https://github.com/fcambus/statzone
|
||||
* https://github.com/benschw/dns-clb-go
|
||||
* https://github.com/corny/dnscheck for <http://public-dns.info/>
|
||||
* https://github.com/miekg/unbound
|
||||
* https://github.com/miekg/exdns
|
||||
* https://dnslookup.org
|
||||
* https://github.com/looterz/grimd
|
||||
* https://github.com/phamhongviet/serf-dns
|
||||
* https://github.com/mehrdadrad/mylg
|
||||
* https://github.com/bamarni/dockness
|
||||
* https://github.com/fffaraz/microdns
|
||||
* https://github.com/ipdcode/hades <https://jd.com>
|
||||
* https://github.com/StackExchange/dnscontrol/
|
||||
* https://www.dnsperf.com/
|
||||
* https://dnssectest.net/
|
||||
* 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
|
||||
|
||||
|
||||
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
|
||||
* 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
|
||||
|
||||
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`).
|
||||
|
||||
Example programs can be found in the `github.com/miekg/exdns` repository.
|
||||
|
||||
## Supported RFCs
|
||||
|
||||
*all of them*
|
||||
|
||||
* 103{4,5} - DNS standard
|
||||
* 1348 - NSAP record (removed the record)
|
||||
* 1982 - Serial Arithmetic
|
||||
* 1876 - LOC record
|
||||
* 1995 - IXFR
|
||||
* 1996 - DNS notify
|
||||
* 2136 - DNS Update (dynamic updates)
|
||||
* 2181 - RRset definition - there is no RRset type though, just []RR
|
||||
* 2537 - RSAMD5 DNS keys
|
||||
* 2065 - DNSSEC (updated in later RFCs)
|
||||
* 2671 - EDNS record
|
||||
* 2782 - SRV record
|
||||
* 2845 - TSIG record
|
||||
* 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
|
||||
* 3597 - Unknown RRs
|
||||
* 403{3,4,5} - DNSSEC + validation functions
|
||||
* 4255 - SSHFP record
|
||||
* 4343 - Case insensitivity
|
||||
* 4408 - SPF record
|
||||
* 4509 - SHA256 Hash in DS
|
||||
* 4592 - Wildcards in the DNS
|
||||
* 4635 - HMAC SHA TSIG
|
||||
* 4701 - DHCID
|
||||
* 4892 - id.server
|
||||
* 5001 - NSID
|
||||
* 5155 - NSEC3 record
|
||||
* 5205 - HIP record
|
||||
* 5702 - SHA2 in the DNS
|
||||
* 5936 - AXFR
|
||||
* 5966 - TCP implementation recommendations
|
||||
* 6605 - ECDSA
|
||||
* 6725 - IANA Registry Update
|
||||
* 6742 - ILNP DNS
|
||||
* 6840 - Clarifications and Implementation Notes for DNS Security
|
||||
* 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
|
||||
* 7477 - CSYNC RR
|
||||
* 7828 - edns-tcp-keepalive EDNS0 Option
|
||||
* 7553 - URI record
|
||||
* 7858 - DNS over TLS: Initiation and Performance Considerations
|
||||
* 7871 - EDNS0 Client Subnet
|
||||
* 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
|
||||
|
||||
* 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,62 @@
|
|||
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,484 @@
|
|||
package dns
|
||||
|
||||
// A client implementation.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// A Conn represents a connection to a DNS server.
|
||||
type Conn struct {
|
||||
net.Conn // a net.Conn holding the connection
|
||||
UDPSize uint16 // minimum receive buffer for UDP messages
|
||||
TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
|
||||
TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
|
||||
tsigRequestMAC string
|
||||
}
|
||||
|
||||
func (co *Conn) tsigProvider() TsigProvider {
|
||||
if co.TsigProvider != nil {
|
||||
return co.TsigProvider
|
||||
}
|
||||
// tsigSecretProvider will return ErrSecret if co.TsigSecret is nil.
|
||||
return tsigSecretProvider(co.TsigSecret)
|
||||
}
|
||||
|
||||
// A Client defines parameters for a DNS client.
|
||||
type Client struct {
|
||||
Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP)
|
||||
UDPSize uint16 // minimum receive buffer for UDP messages
|
||||
TLSConfig *tls.Config // TLS connection configuration
|
||||
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 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
|
||||
}
|
||||
|
||||
// Exchange performs a synchronous UDP query. It sends the message m to the address
|
||||
// contained in a and waits for a reply. Exchange does not retry a failed query, nor
|
||||
// will it fall back to TCP in case of truncation.
|
||||
// See client.Exchange for more information on setting larger buffer sizes.
|
||||
func Exchange(m *Msg, a string) (r *Msg, err error) {
|
||||
client := Client{Net: "udp"}
|
||||
r, _, err = client.Exchange(m, a)
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (c *Client) dialTimeout() time.Duration {
|
||||
if c.Timeout != 0 {
|
||||
return c.Timeout
|
||||
}
|
||||
if c.DialTimeout != 0 {
|
||||
return c.DialTimeout
|
||||
}
|
||||
return dnsTimeout
|
||||
}
|
||||
|
||||
func (c *Client) readTimeout() time.Duration {
|
||||
if c.ReadTimeout != 0 {
|
||||
return c.ReadTimeout
|
||||
}
|
||||
return dnsTimeout
|
||||
}
|
||||
|
||||
func (c *Client) writeTimeout() time.Duration {
|
||||
if c.WriteTimeout != 0 {
|
||||
return c.WriteTimeout
|
||||
}
|
||||
return dnsTimeout
|
||||
}
|
||||
|
||||
// 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{Timeout: c.getTimeoutForRequest(c.dialTimeout())}
|
||||
} else {
|
||||
d = *c.Dialer
|
||||
}
|
||||
|
||||
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.DialContext(ctx, network, address)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.UDPSize = c.UDPSize
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Exchange performs a synchronous query. It sends the message m to the address
|
||||
// contained in a and waits for a reply. Basic use pattern with a *dns.Client:
|
||||
//
|
||||
// c := new(dns.Client)
|
||||
// in, rtt, err := c.Exchange(message, "127.0.0.1:53")
|
||||
//
|
||||
// Exchange does not retry a failed query, nor will it fall back to TCP in
|
||||
// case of truncation.
|
||||
// It is up to the caller to create a message that allows for larger responses to be
|
||||
// returned. Specifically this means adding an EDNS0 OPT RR that will advertise a larger
|
||||
// buffer, see SetEdns0. Messages without an OPT RR will fallback to the historic limit
|
||||
// of 512 bytes
|
||||
// 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) {
|
||||
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 cancelation 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 cancelation, 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 {
|
||||
co.UDPSize = opt.UDPSize()
|
||||
}
|
||||
// Otherwise use the client's configured UDP size.
|
||||
if opt == nil && c.UDPSize >= MinMsgSize {
|
||||
co.UDPSize = c.UDPSize
|
||||
}
|
||||
|
||||
// write with the appropriate write timeout
|
||||
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
|
||||
}
|
||||
|
||||
if isPacketConn(co.Conn) {
|
||||
for {
|
||||
r, err = co.ReadMsg()
|
||||
// Ignore replies with mismatched IDs because they might be
|
||||
// responses to earlier queries that timed out.
|
||||
if err != nil || r.Id == m.Id {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r, err = co.ReadMsg()
|
||||
if err == nil && r.Id != m.Id {
|
||||
err = ErrId
|
||||
}
|
||||
}
|
||||
rtt = time.Since(t)
|
||||
return r, rtt, err
|
||||
}
|
||||
|
||||
// ReadMsg reads a message from the connection co.
|
||||
// If the received message contains a TSIG record the transaction signature
|
||||
// is verified. This method always tries to return the message, however if an
|
||||
// error is returned there are no guarantees that the returned message is a
|
||||
// valid representation of the packet read.
|
||||
func (co *Conn) ReadMsg() (*Msg, error) {
|
||||
p, err := co.ReadMsgHeader(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := new(Msg)
|
||||
if err := m.Unpack(p); err != nil {
|
||||
// If an error was returned, we still want to allow the user to use
|
||||
// the message, but naively they can just check err if they don't want
|
||||
// to use an erroneous message
|
||||
return m, err
|
||||
}
|
||||
if t := m.IsTsig(); t != nil {
|
||||
// Need to work on the original message p, as that was used to calculate the tsig.
|
||||
err = tsigVerifyProvider(p, co.tsigProvider(), co.tsigRequestMAC, false)
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
// ReadMsgHeader reads a DNS message, parses and populates hdr (when hdr is not nil).
|
||||
// Returns message as a byte slice to be parsed with Msg.Unpack later on.
|
||||
// Note that error handling on the message body is not possible as only the header is parsed.
|
||||
func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
|
||||
var (
|
||||
p []byte
|
||||
n int
|
||||
err error
|
||||
)
|
||||
|
||||
if isPacketConn(co.Conn) {
|
||||
if co.UDPSize > MinMsgSize {
|
||||
p = make([]byte, co.UDPSize)
|
||||
} else {
|
||||
p = make([]byte, MinMsgSize)
|
||||
}
|
||||
n, err = co.Read(p)
|
||||
} else {
|
||||
var length uint16
|
||||
if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p = make([]byte, length)
|
||||
n, err = io.ReadFull(co.Conn, p)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if n < headerSize {
|
||||
return nil, ErrShortRead
|
||||
}
|
||||
|
||||
p = p[:n]
|
||||
if hdr != nil {
|
||||
dh, _, err := unpackMsgHdr(p, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*hdr = dh
|
||||
}
|
||||
return p, 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 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
|
||||
}
|
||||
|
||||
return io.ReadFull(co.Conn, p[:length])
|
||||
}
|
||||
|
||||
// WriteMsg sends a message through the connection co.
|
||||
// If the message m contains a TSIG record the transaction
|
||||
// signature is calculated.
|
||||
func (co *Conn) WriteMsg(m *Msg) (err error) {
|
||||
var out []byte
|
||||
if t := m.IsTsig(); t != nil {
|
||||
// Set tsigRequestMAC for the next read, although only used in zone transfers.
|
||||
out, co.tsigRequestMAC, err = tsigGenerateProvider(m, co.tsigProvider(), co.tsigRequestMAC, false)
|
||||
} else {
|
||||
out, err = m.Pack()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = co.Write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
// Write implements the net.Conn Write method.
|
||||
func (co *Conn) Write(p []byte) (int, error) {
|
||||
if len(p) > MaxMsgSize {
|
||||
return 0, &Error{err: "message too large"}
|
||||
}
|
||||
|
||||
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
|
||||
func (c *Client) getTimeoutForRequest(timeout time.Duration) time.Duration {
|
||||
var requestTimeout time.Duration
|
||||
if c.Timeout != 0 {
|
||||
requestTimeout = c.Timeout
|
||||
} else {
|
||||
requestTimeout = timeout
|
||||
}
|
||||
// net.Dialer.Timeout has priority if smaller than the timeouts computed so
|
||||
// far
|
||||
if c.Dialer != nil && c.Dialer.Timeout != 0 {
|
||||
if c.Dialer.Timeout < requestTimeout {
|
||||
requestTimeout = c.Dialer.Timeout
|
||||
}
|
||||
}
|
||||
return requestTimeout
|
||||
}
|
||||
|
||||
// Dial connects to the address on the named network.
|
||||
func Dial(network, address string) (conn *Conn, err error) {
|
||||
conn = new(Conn)
|
||||
conn.Conn, err = net.Dial(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// ExchangeContext performs a synchronous UDP query, like Exchange. It
|
||||
// additionally obeys deadlines from the passed Context.
|
||||
func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) {
|
||||
client := Client{Net: "udp"}
|
||||
r, _, err = client.ExchangeContext(ctx, m, a)
|
||||
// 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.
|
||||
// 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)
|
||||
co.Conn = c
|
||||
if err = co.WriteMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err = co.ReadMsg()
|
||||
if err == nil && r.Id != m.Id {
|
||||
err = ErrId
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
// 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}}
|
||||
return client.Dial(address)
|
||||
}
|
||||
|
||||
// DialWithTLS connects to the address on the named network with TLS.
|
||||
func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, err error) {
|
||||
if !strings.HasSuffix(network, "-tls") {
|
||||
network += "-tls"
|
||||
}
|
||||
client := Client{Net: network, TLSConfig: tlsConfig}
|
||||
return client.Dial(address)
|
||||
}
|
||||
|
||||
// DialTimeoutWithTLS acts like DialWithTLS but takes a timeout.
|
||||
func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout time.Duration) (conn *Conn, err error) {
|
||||
if !strings.HasSuffix(network, "-tls") {
|
||||
network += "-tls"
|
||||
}
|
||||
client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}, TLSConfig: tlsConfig}
|
||||
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) {
|
||||
conn, err := c.DialContext(ctx, a)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
return c.exchangeWithConnContext(ctx, m, conn)
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ClientConfig wraps the contents of the /etc/resolv.conf file.
|
||||
type ClientConfig struct {
|
||||
Servers []string // servers to use
|
||||
Search []string // suffixes to append to local name
|
||||
Port string // what port to use
|
||||
Ndots int // number of dots in name to trigger absolute lookup
|
||||
Timeout int // seconds before giving up on packet
|
||||
Attempts int // lost packets before giving up on server, not used in the package dns
|
||||
}
|
||||
|
||||
// ClientConfigFromFile parses a resolv.conf(5) like file and returns
|
||||
// a *ClientConfig.
|
||||
func ClientConfigFromFile(resolvconf string) (*ClientConfig, error) {
|
||||
file, err := os.Open(resolvconf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
return ClientConfigFromReader(file)
|
||||
}
|
||||
|
||||
// ClientConfigFromReader works like ClientConfigFromFile but takes an io.Reader as argument
|
||||
func ClientConfigFromReader(resolvconf io.Reader) (*ClientConfig, error) {
|
||||
c := new(ClientConfig)
|
||||
scanner := bufio.NewScanner(resolvconf)
|
||||
c.Servers = make([]string, 0)
|
||||
c.Search = make([]string, 0)
|
||||
c.Port = "53"
|
||||
c.Ndots = 1
|
||||
c.Timeout = 5
|
||||
c.Attempts = 2
|
||||
|
||||
for scanner.Scan() {
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
line := scanner.Text()
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 1 {
|
||||
continue
|
||||
}
|
||||
switch f[0] {
|
||||
case "nameserver": // add one name server
|
||||
if len(f) > 1 {
|
||||
// One more check: make sure server name is
|
||||
// just an IP address. Otherwise we need DNS
|
||||
// to look it up.
|
||||
name := f[1]
|
||||
c.Servers = append(c.Servers, name)
|
||||
}
|
||||
|
||||
case "domain": // set search path to just this domain
|
||||
if len(f) > 1 {
|
||||
c.Search = make([]string, 1)
|
||||
c.Search[0] = f[1]
|
||||
} else {
|
||||
c.Search = make([]string, 0)
|
||||
}
|
||||
|
||||
case "search": // set search path to given servers
|
||||
c.Search = append([]string(nil), f[1:]...)
|
||||
|
||||
case "options": // magic options
|
||||
for _, s := range f[1:] {
|
||||
switch {
|
||||
case len(s) >= 6 && s[:6] == "ndots:":
|
||||
n, _ := strconv.Atoi(s[6:])
|
||||
if n < 0 {
|
||||
n = 0
|
||||
} else if n > 15 {
|
||||
n = 15
|
||||
}
|
||||
c.Ndots = n
|
||||
case len(s) >= 8 && s[:8] == "timeout:":
|
||||
n, _ := strconv.Atoi(s[8:])
|
||||
if n < 1 {
|
||||
n = 1
|
||||
}
|
||||
c.Timeout = n
|
||||
case len(s) >= 9 && s[:9] == "attempts:":
|
||||
n, _ := strconv.Atoi(s[9:])
|
||||
if n < 1 {
|
||||
n = 1
|
||||
}
|
||||
c.Attempts = n
|
||||
case s == "rotate":
|
||||
/* not imp */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NameList returns all of the names that should be queried based on the
|
||||
// config. It is based off of go's net/dns name building, but it does not
|
||||
// check the length of the resulting names.
|
||||
func (c *ClientConfig) NameList(name string) []string {
|
||||
// if this domain is already fully qualified, no append needed.
|
||||
if IsFqdn(name) {
|
||||
return []string{name}
|
||||
}
|
||||
|
||||
// Check to see if the name has more labels than Ndots. Do this before making
|
||||
// the domain fully qualified.
|
||||
hasNdots := CountLabel(name) > c.Ndots
|
||||
// Make the domain fully qualified.
|
||||
name = Fqdn(name)
|
||||
|
||||
// Make a list of names based off search.
|
||||
names := []string{}
|
||||
|
||||
// If name has enough dots, try that first.
|
||||
if hasNdots {
|
||||
names = append(names, name)
|
||||
}
|
||||
for _, s := range c.Search {
|
||||
names = append(names, Fqdn(name+s))
|
||||
}
|
||||
// If we didn't have enough dots, try after suffixes.
|
||||
if !hasNdots {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// CertificateToDANE converts a certificate to a hex string as used in the TLSA or SMIMEA records.
|
||||
func CertificateToDANE(selector, matchingType uint8, cert *x509.Certificate) (string, error) {
|
||||
switch matchingType {
|
||||
case 0:
|
||||
switch selector {
|
||||
case 0:
|
||||
return hex.EncodeToString(cert.Raw), nil
|
||||
case 1:
|
||||
return hex.EncodeToString(cert.RawSubjectPublicKeyInfo), nil
|
||||
}
|
||||
case 1:
|
||||
h := sha256.New()
|
||||
switch selector {
|
||||
case 0:
|
||||
h.Write(cert.Raw)
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
case 1:
|
||||
h.Write(cert.RawSubjectPublicKeyInfo)
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
case 2:
|
||||
h := sha512.New()
|
||||
switch selector {
|
||||
case 0:
|
||||
h.Write(cert.Raw)
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
case 1:
|
||||
h.Write(cert.RawSubjectPublicKeyInfo)
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("dns: bad MatchingType or Selector")
|
||||
}
|
|
@ -0,0 +1,381 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const hexDigit = "0123456789abcdef"
|
||||
|
||||
// Everything is assumed in ClassINET.
|
||||
|
||||
// SetReply creates a reply message from a request message.
|
||||
func (dns *Msg) SetReply(request *Msg) *Msg {
|
||||
dns.Id = request.Id
|
||||
dns.Response = true
|
||||
dns.Opcode = request.Opcode
|
||||
if dns.Opcode == OpcodeQuery {
|
||||
dns.RecursionDesired = request.RecursionDesired // Copy rd bit
|
||||
dns.CheckingDisabled = request.CheckingDisabled // Copy cd bit
|
||||
}
|
||||
dns.Rcode = RcodeSuccess
|
||||
if len(request.Question) > 0 {
|
||||
dns.Question = make([]Question, 1)
|
||||
dns.Question[0] = request.Question[0]
|
||||
}
|
||||
return dns
|
||||
}
|
||||
|
||||
// SetQuestion creates a question message, it sets the Question
|
||||
// section, generates an Id and sets the RecursionDesired (RD)
|
||||
// bit to true.
|
||||
func (dns *Msg) SetQuestion(z string, t uint16) *Msg {
|
||||
dns.Id = Id()
|
||||
dns.RecursionDesired = true
|
||||
dns.Question = make([]Question, 1)
|
||||
dns.Question[0] = Question{z, t, ClassINET}
|
||||
return dns
|
||||
}
|
||||
|
||||
// SetNotify creates a notify message, it sets the Question
|
||||
// section, generates an Id and sets the Authoritative (AA)
|
||||
// bit to true.
|
||||
func (dns *Msg) SetNotify(z string) *Msg {
|
||||
dns.Opcode = OpcodeNotify
|
||||
dns.Authoritative = true
|
||||
dns.Id = Id()
|
||||
dns.Question = make([]Question, 1)
|
||||
dns.Question[0] = Question{z, TypeSOA, ClassINET}
|
||||
return dns
|
||||
}
|
||||
|
||||
// SetRcode creates an error message suitable for the request.
|
||||
func (dns *Msg) SetRcode(request *Msg, rcode int) *Msg {
|
||||
dns.SetReply(request)
|
||||
dns.Rcode = rcode
|
||||
return dns
|
||||
}
|
||||
|
||||
// SetRcodeFormatError creates a message with FormError set.
|
||||
func (dns *Msg) SetRcodeFormatError(request *Msg) *Msg {
|
||||
dns.Rcode = RcodeFormatError
|
||||
dns.Opcode = OpcodeQuery
|
||||
dns.Response = true
|
||||
dns.Authoritative = false
|
||||
dns.Id = request.Id
|
||||
return dns
|
||||
}
|
||||
|
||||
// SetUpdate makes the message a dynamic update message. It
|
||||
// sets the ZONE section to: z, TypeSOA, ClassINET.
|
||||
func (dns *Msg) SetUpdate(z string) *Msg {
|
||||
dns.Id = Id()
|
||||
dns.Response = false
|
||||
dns.Opcode = OpcodeUpdate
|
||||
dns.Compress = false // BIND9 cannot handle compression
|
||||
dns.Question = make([]Question, 1)
|
||||
dns.Question[0] = Question{z, TypeSOA, ClassINET}
|
||||
return dns
|
||||
}
|
||||
|
||||
// SetIxfr creates message for requesting an IXFR.
|
||||
func (dns *Msg) SetIxfr(z string, serial uint32, ns, mbox string) *Msg {
|
||||
dns.Id = Id()
|
||||
dns.Question = make([]Question, 1)
|
||||
dns.Ns = make([]RR, 1)
|
||||
s := new(SOA)
|
||||
s.Hdr = RR_Header{z, TypeSOA, ClassINET, defaultTtl, 0}
|
||||
s.Serial = serial
|
||||
s.Ns = ns
|
||||
s.Mbox = mbox
|
||||
dns.Question[0] = Question{z, TypeIXFR, ClassINET}
|
||||
dns.Ns[0] = s
|
||||
return dns
|
||||
}
|
||||
|
||||
// SetAxfr creates message for requesting an AXFR.
|
||||
func (dns *Msg) SetAxfr(z string) *Msg {
|
||||
dns.Id = Id()
|
||||
dns.Question = make([]Question, 1)
|
||||
dns.Question[0] = Question{z, TypeAXFR, ClassINET}
|
||||
return dns
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg {
|
||||
t := new(TSIG)
|
||||
t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0}
|
||||
t.Algorithm = algo
|
||||
t.Fudge = fudge
|
||||
t.TimeSigned = uint64(timesigned)
|
||||
t.OrigId = dns.Id
|
||||
dns.Extra = append(dns.Extra, t)
|
||||
return dns
|
||||
}
|
||||
|
||||
// SetEdns0 appends a EDNS0 OPT RR to the message.
|
||||
// TSIG should always the last RR in a message.
|
||||
func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg {
|
||||
e := new(OPT)
|
||||
e.Hdr.Name = "."
|
||||
e.Hdr.Rrtype = TypeOPT
|
||||
e.SetUDPSize(udpsize)
|
||||
if do {
|
||||
e.SetDo()
|
||||
}
|
||||
dns.Extra = append(dns.Extra, e)
|
||||
return dns
|
||||
}
|
||||
|
||||
// IsTsig checks if the message has a TSIG record as the last record
|
||||
// in the additional section. It returns the TSIG record found or nil.
|
||||
func (dns *Msg) IsTsig() *TSIG {
|
||||
if len(dns.Extra) > 0 {
|
||||
if dns.Extra[len(dns.Extra)-1].Header().Rrtype == TypeTSIG {
|
||||
return dns.Extra[len(dns.Extra)-1].(*TSIG)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsEdns0 checks if the message has a EDNS0 (OPT) record, any EDNS0
|
||||
// record in the additional section will do. It returns the OPT record
|
||||
// found or nil.
|
||||
func (dns *Msg) IsEdns0() *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 {
|
||||
return dns.Extra[i].(*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 and that the entire name will fit into the 255
|
||||
// octet wire format limit.
|
||||
func IsDomainName(s string) (labels int, ok bool) {
|
||||
// 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 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
|
||||
// are the same domain true is returned as well.
|
||||
func IsSubDomain(parent, child string) bool {
|
||||
// Entire child is contained in parent
|
||||
return CompareDomainName(parent, child) == CountLabel(parent)
|
||||
}
|
||||
|
||||
// IsMsg sanity checks buf and returns an error if it isn't a valid DNS packet.
|
||||
// The checking is performed on the binary payload.
|
||||
func IsMsg(buf []byte) error {
|
||||
// Header
|
||||
if len(buf) < headerSize {
|
||||
return errors.New("dns: bad message header")
|
||||
}
|
||||
// Header: Opcode
|
||||
// TODO(miek): more checks here, e.g. check all header bits.
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsFqdn checks if a domain name is fully qualified.
|
||||
func IsFqdn(s string) bool {
|
||||
s2 := strings.TrimSuffix(s, ".")
|
||||
if s == s2 {
|
||||
return false
|
||||
}
|
||||
|
||||
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.
|
||||
// This means the RRs need to have the same type, name, and class. Returns true
|
||||
// if the RR set is valid, otherwise false.
|
||||
func IsRRset(rrset []RR) bool {
|
||||
if len(rrset) == 0 {
|
||||
return false
|
||||
}
|
||||
if len(rrset) == 1 {
|
||||
return true
|
||||
}
|
||||
rrHeader := rrset[0].Header()
|
||||
rrType := rrHeader.Rrtype
|
||||
rrClass := rrHeader.Class
|
||||
rrName := rrHeader.Name
|
||||
|
||||
for _, rr := range rrset[1:] {
|
||||
curRRHeader := rr.Header()
|
||||
if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName {
|
||||
// Mismatch between the records, so this is not a valid rrset for
|
||||
//signing/verifying
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Fqdn return the fully qualified domain name from s.
|
||||
// If s is already fully qualified, it behaves as the identity function.
|
||||
func Fqdn(s string) string {
|
||||
if IsFqdn(s) {
|
||||
return s
|
||||
}
|
||||
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
|
||||
// address suitable for reverse DNS (PTR) record lookups or an error if it fails
|
||||
// to parse the IP address.
|
||||
func ReverseAddr(addr string) (arpa string, err error) {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
return "", &Error{err: "unrecognized address: " + addr}
|
||||
}
|
||||
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, 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], '.', hexDigit[v>>4], '.')
|
||||
}
|
||||
// Append "ip6.arpa." and return (buf already has the final .)
|
||||
buf = append(buf, "ip6.arpa."...)
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// String returns the string representation for the type t.
|
||||
func (t Type) String() string {
|
||||
if t1, ok := TypeToString[uint16(t)]; ok {
|
||||
return t1
|
||||
}
|
||||
return "TYPE" + strconv.Itoa(int(t))
|
||||
}
|
||||
|
||||
// String returns the string representation for the class c.
|
||||
func (c Class) String() string {
|
||||
if s, ok := ClassToString[uint16(c)]; ok {
|
||||
// Only emit mnemonics when they are unambiguous, specially ANY is in both.
|
||||
if _, ok := StringToType[s]; !ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return "CLASS" + strconv.Itoa(int(c))
|
||||
}
|
||||
|
||||
// String returns the string representation for the name n.
|
||||
func (n Name) String() string {
|
||||
return sprintName(string(n))
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits.
|
||||
defaultTtl = 3600 // Default internal TTL.
|
||||
|
||||
// DefaultMsgSize is the standard default for messages larger than 512 bytes.
|
||||
DefaultMsgSize = 4096
|
||||
// MinMsgSize is the minimal size of a DNS packet.
|
||||
MinMsgSize = 512
|
||||
// MaxMsgSize is the largest possible DNS packet.
|
||||
MaxMsgSize = 65535
|
||||
)
|
||||
|
||||
// Error represents a DNS error.
|
||||
type Error struct{ err string }
|
||||
|
||||
func (e *Error) Error() string {
|
||||
if e == nil {
|
||||
return "dns: <nil>"
|
||||
}
|
||||
return "dns: " + e.err
|
||||
}
|
||||
|
||||
// An RR represents a resource record.
|
||||
type RR interface {
|
||||
// Header returns the header of an resource record. The header contains
|
||||
// everything up to the rdata.
|
||||
Header() *RR_Header
|
||||
// String returns the text representation of the resource record.
|
||||
String() string
|
||||
|
||||
// copy returns a copy of the RR
|
||||
copy() RR
|
||||
|
||||
// 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.
|
||||
type RR_Header struct {
|
||||
Name string `dns:"cdomain-name"`
|
||||
Rrtype uint16
|
||||
Class uint16
|
||||
Ttl uint32
|
||||
Rdlength uint16 // Length of data after header.
|
||||
}
|
||||
|
||||
// Header returns itself. This is here to make RR_Header implements the RR interface.
|
||||
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) String() string {
|
||||
var s string
|
||||
|
||||
if h.Rrtype == TypeOPT {
|
||||
s = ";"
|
||||
// and maybe other things
|
||||
}
|
||||
|
||||
s += sprintName(h.Name) + "\t"
|
||||
s += strconv.FormatInt(int64(h.Ttl), 10) + "\t"
|
||||
s += Class(h.Class).String() + "\t"
|
||||
s += Type(h.Rrtype).String() + "\t"
|
||||
return s
|
||||
}
|
||||
|
||||
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, Len(r))
|
||||
headerEnd, off, err := packRR(r, buf, 0, compressionMap{}, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf = buf[:off]
|
||||
|
||||
*rr = RFC3597{Hdr: *r.Header()}
|
||||
rr.Hdr.Rdlength = uint16(off - headerEnd)
|
||||
|
||||
if noRdata(rr.Hdr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, 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
|
||||
}
|
||||
|
||||
_, err = r.unpack(msg, 0)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,749 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
_ "crypto/sha1" // need its init function
|
||||
_ "crypto/sha256" // need its init function
|
||||
_ "crypto/sha512" // need its init function
|
||||
"encoding/asn1"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DNSSEC encryption algorithm codes.
|
||||
const (
|
||||
_ uint8 = iota
|
||||
RSAMD5
|
||||
DH
|
||||
DSA
|
||||
_ // Skip 4, RFC 6725, section 2.1
|
||||
RSASHA1
|
||||
DSANSEC3SHA1
|
||||
RSASHA1NSEC3SHA1
|
||||
RSASHA256
|
||||
_ // Skip 9, RFC 6725, section 2.1
|
||||
RSASHA512
|
||||
_ // Skip 11, RFC 6725, section 2.1
|
||||
ECCGOST
|
||||
ECDSAP256SHA256
|
||||
ECDSAP384SHA384
|
||||
ED25519
|
||||
ED448
|
||||
INDIRECT uint8 = 252
|
||||
PRIVATEDNS uint8 = 253 // Private (experimental keys)
|
||||
PRIVATEOID uint8 = 254
|
||||
)
|
||||
|
||||
// AlgorithmToString is a map of algorithm IDs to algorithm names.
|
||||
var AlgorithmToString = map[uint8]string{
|
||||
RSAMD5: "RSAMD5",
|
||||
DH: "DH",
|
||||
DSA: "DSA",
|
||||
RSASHA1: "RSASHA1",
|
||||
DSANSEC3SHA1: "DSA-NSEC3-SHA1",
|
||||
RSASHA1NSEC3SHA1: "RSASHA1-NSEC3-SHA1",
|
||||
RSASHA256: "RSASHA256",
|
||||
RSASHA512: "RSASHA512",
|
||||
ECCGOST: "ECC-GOST",
|
||||
ECDSAP256SHA256: "ECDSAP256SHA256",
|
||||
ECDSAP384SHA384: "ECDSAP384SHA384",
|
||||
ED25519: "ED25519",
|
||||
ED448: "ED448",
|
||||
INDIRECT: "INDIRECT",
|
||||
PRIVATEDNS: "PRIVATEDNS",
|
||||
PRIVATEOID: "PRIVATEOID",
|
||||
}
|
||||
|
||||
// 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: 0,
|
||||
}
|
||||
|
||||
// DNSSEC hashing algorithm codes.
|
||||
const (
|
||||
_ uint8 = iota
|
||||
SHA1 // RFC 4034
|
||||
SHA256 // RFC 4509
|
||||
GOST94 // RFC 5933
|
||||
SHA384 // Experimental
|
||||
SHA512 // Experimental
|
||||
)
|
||||
|
||||
// HashToString is a map of hash IDs to names.
|
||||
var HashToString = map[uint8]string{
|
||||
SHA1: "SHA1",
|
||||
SHA256: "SHA256",
|
||||
GOST94: "GOST94",
|
||||
SHA384: "SHA384",
|
||||
SHA512: "SHA512",
|
||||
}
|
||||
|
||||
// DNSKEY flag values.
|
||||
const (
|
||||
SEP = 1
|
||||
REVOKE = 1 << 7
|
||||
ZONE = 1 << 8
|
||||
)
|
||||
|
||||
// The RRSIG needs to be converted to wireformat with some of the rdata (the signature) missing.
|
||||
type rrsigWireFmt struct {
|
||||
TypeCovered uint16
|
||||
Algorithm uint8
|
||||
Labels uint8
|
||||
OrigTtl uint32
|
||||
Expiration uint32
|
||||
Inception uint32
|
||||
KeyTag uint16
|
||||
SignerName string `dns:"domain-name"`
|
||||
/* No Signature */
|
||||
}
|
||||
|
||||
// Used for converting DNSKEY's rdata to wirefmt.
|
||||
type dnskeyWireFmt struct {
|
||||
Flags uint16
|
||||
Protocol uint8
|
||||
Algorithm uint8
|
||||
PublicKey string `dns:"base64"`
|
||||
/* Nothing is left out */
|
||||
}
|
||||
|
||||
func divRoundUp(a, b int) int {
|
||||
return (a + b - 1) / b
|
||||
}
|
||||
|
||||
// KeyTag calculates the keytag (or key-id) of the DNSKEY.
|
||||
func (k *DNSKEY) KeyTag() uint16 {
|
||||
if k == nil {
|
||||
return 0
|
||||
}
|
||||
var keytag int
|
||||
switch k.Algorithm {
|
||||
case RSAMD5:
|
||||
// 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)-3:])
|
||||
keytag = int(x)
|
||||
}
|
||||
default:
|
||||
keywire := new(dnskeyWireFmt)
|
||||
keywire.Flags = k.Flags
|
||||
keywire.Protocol = k.Protocol
|
||||
keywire.Algorithm = k.Algorithm
|
||||
keywire.PublicKey = k.PublicKey
|
||||
wire := make([]byte, DefaultMsgSize)
|
||||
n, err := packKeyWire(keywire, wire)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
wire = wire[:n]
|
||||
for i, v := range wire {
|
||||
if i&1 != 0 {
|
||||
keytag += int(v) // must be larger than uint32
|
||||
} else {
|
||||
keytag += int(v) << 8
|
||||
}
|
||||
}
|
||||
keytag += keytag >> 16 & 0xFFFF
|
||||
keytag &= 0xFFFF
|
||||
}
|
||||
return uint16(keytag)
|
||||
}
|
||||
|
||||
// ToDS converts a DNSKEY record to a DS record.
|
||||
func (k *DNSKEY) ToDS(h uint8) *DS {
|
||||
if k == nil {
|
||||
return nil
|
||||
}
|
||||
ds := new(DS)
|
||||
ds.Hdr.Name = k.Hdr.Name
|
||||
ds.Hdr.Class = k.Hdr.Class
|
||||
ds.Hdr.Rrtype = TypeDS
|
||||
ds.Hdr.Ttl = k.Hdr.Ttl
|
||||
ds.Algorithm = k.Algorithm
|
||||
ds.DigestType = h
|
||||
ds.KeyTag = k.KeyTag()
|
||||
|
||||
keywire := new(dnskeyWireFmt)
|
||||
keywire.Flags = k.Flags
|
||||
keywire.Protocol = k.Protocol
|
||||
keywire.Algorithm = k.Algorithm
|
||||
keywire.PublicKey = k.PublicKey
|
||||
wire := make([]byte, DefaultMsgSize)
|
||||
n, err := packKeyWire(keywire, wire)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
wire = wire[:n]
|
||||
|
||||
owner := make([]byte, 255)
|
||||
off, err1 := PackDomainName(CanonicalName(k.Hdr.Name), owner, 0, nil, false)
|
||||
if err1 != nil {
|
||||
return nil
|
||||
}
|
||||
owner = owner[:off]
|
||||
// RFC4034:
|
||||
// digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA);
|
||||
// "|" denotes concatenation
|
||||
// DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key.
|
||||
|
||||
var hash crypto.Hash
|
||||
switch h {
|
||||
case SHA1:
|
||||
hash = crypto.SHA1
|
||||
case SHA256:
|
||||
hash = crypto.SHA256
|
||||
case SHA384:
|
||||
hash = crypto.SHA384
|
||||
case SHA512:
|
||||
hash = crypto.SHA512
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
s := hash.New()
|
||||
s.Write(owner)
|
||||
s.Write(wire)
|
||||
ds.Digest = hex.EncodeToString(s.Sum(nil))
|
||||
return ds
|
||||
}
|
||||
|
||||
// ToCDNSKEY converts a DNSKEY record to a CDNSKEY record.
|
||||
func (k *DNSKEY) ToCDNSKEY() *CDNSKEY {
|
||||
c := &CDNSKEY{DNSKEY: *k}
|
||||
c.Hdr = k.Hdr
|
||||
c.Hdr.Rrtype = TypeCDNSKEY
|
||||
return c
|
||||
}
|
||||
|
||||
// ToCDS converts a DS record to a CDS record.
|
||||
func (d *DS) ToCDS() *CDS {
|
||||
c := &CDS{DS: *d}
|
||||
c.Hdr = d.Hdr
|
||||
c.Hdr.Rrtype = TypeCDS
|
||||
return c
|
||||
}
|
||||
|
||||
// Sign signs an RRSet. The signature needs to be filled in with the values:
|
||||
// Inception, Expiration, KeyTag, SignerName and Algorithm. The rest is copied
|
||||
// from the RRset. Sign returns a non-nill error when the signing went OK.
|
||||
// There is no check if RRSet is a proper (RFC 2181) RRSet. If OrigTTL is non
|
||||
// zero, it is used as-is, otherwise the TTL of the RRset is used as the
|
||||
// OrigTTL.
|
||||
func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
|
||||
if k == nil {
|
||||
return ErrPrivKey
|
||||
}
|
||||
// s.Inception and s.Expiration may be 0 (rollover etc.), the rest must be set
|
||||
if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 {
|
||||
return ErrKey
|
||||
}
|
||||
|
||||
h0 := rrset[0].Header()
|
||||
rr.Hdr.Rrtype = TypeRRSIG
|
||||
rr.Hdr.Name = h0.Name
|
||||
rr.Hdr.Class = h0.Class
|
||||
if rr.OrigTtl == 0 { // If set don't override
|
||||
rr.OrigTtl = h0.Ttl
|
||||
}
|
||||
rr.TypeCovered = h0.Rrtype
|
||||
rr.Labels = uint8(CountLabel(h0.Name))
|
||||
|
||||
if strings.HasPrefix(h0.Name, "*") {
|
||||
rr.Labels-- // wildcard, remove from label count
|
||||
}
|
||||
|
||||
sigwire := new(rrsigWireFmt)
|
||||
sigwire.TypeCovered = rr.TypeCovered
|
||||
sigwire.Algorithm = rr.Algorithm
|
||||
sigwire.Labels = rr.Labels
|
||||
sigwire.OrigTtl = rr.OrigTtl
|
||||
sigwire.Expiration = rr.Expiration
|
||||
sigwire.Inception = rr.Inception
|
||||
sigwire.KeyTag = rr.KeyTag
|
||||
// For signing, lowercase this name
|
||||
sigwire.SignerName = CanonicalName(rr.SignerName)
|
||||
|
||||
// Create the desired binary blob
|
||||
signdata := make([]byte, DefaultMsgSize)
|
||||
n, err := packSigWire(sigwire, signdata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signdata = signdata[:n]
|
||||
wire, err := rawSignatureData(rrset, rr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch rr.Algorithm {
|
||||
case RSAMD5, DSA, DSANSEC3SHA1:
|
||||
// See RFC 6944.
|
||||
return ErrAlg
|
||||
default:
|
||||
h.Write(signdata)
|
||||
h.Write(wire)
|
||||
|
||||
signature, err := sign(k, h.Sum(nil), cryptohash, rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rr.Signature = toBase64(signature)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte, error) {
|
||||
signature, err := k.Sign(rand.Reader, hashed, hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch alg {
|
||||
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, ED25519:
|
||||
return signature, nil
|
||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||
ecdsaSignature := &struct {
|
||||
R, S *big.Int
|
||||
}{}
|
||||
if _, err := asn1.Unmarshal(signature, ecdsaSignature); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var intlen int
|
||||
switch alg {
|
||||
case ECDSAP256SHA256:
|
||||
intlen = 32
|
||||
case ECDSAP384SHA384:
|
||||
intlen = 48
|
||||
}
|
||||
|
||||
signature := intToBytes(ecdsaSignature.R, intlen)
|
||||
signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...)
|
||||
return signature, nil
|
||||
default:
|
||||
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) {
|
||||
return ErrRRset
|
||||
}
|
||||
if rr.KeyTag != k.KeyTag() {
|
||||
return ErrKey
|
||||
}
|
||||
if rr.Hdr.Class != k.Hdr.Class {
|
||||
return ErrKey
|
||||
}
|
||||
if rr.Algorithm != k.Algorithm {
|
||||
return ErrKey
|
||||
}
|
||||
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 h0 := rrset[0].Header(); h0.Class != rr.Hdr.Class || h0.Rrtype != rr.TypeCovered {
|
||||
return ErrRRset
|
||||
}
|
||||
|
||||
// RFC 4035 5.3.2. Reconstructing the Signed Data
|
||||
// Copy the sig, except the rrsig data
|
||||
sigwire := new(rrsigWireFmt)
|
||||
sigwire.TypeCovered = rr.TypeCovered
|
||||
sigwire.Algorithm = rr.Algorithm
|
||||
sigwire.Labels = rr.Labels
|
||||
sigwire.OrigTtl = rr.OrigTtl
|
||||
sigwire.Expiration = rr.Expiration
|
||||
sigwire.Inception = rr.Inception
|
||||
sigwire.KeyTag = rr.KeyTag
|
||||
sigwire.SignerName = CanonicalName(rr.SignerName)
|
||||
// Create the desired binary blob
|
||||
signeddata := make([]byte, DefaultMsgSize)
|
||||
n, err := packSigWire(sigwire, signeddata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signeddata = signeddata[:n]
|
||||
wire, err := rawSignatureData(rrset, rr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sigbuf := rr.sigBuf() // Get the binary signature data
|
||||
if rr.Algorithm == PRIVATEDNS { // PRIVATEOID
|
||||
// TODO(miek)
|
||||
// remove the domain name and assume its ours?
|
||||
}
|
||||
|
||||
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch rr.Algorithm {
|
||||
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.Write(signeddata)
|
||||
h.Write(wire)
|
||||
return rsa.VerifyPKCS1v15(pubkey, cryptohash, h.Sum(nil), sigbuf)
|
||||
|
||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||
pubkey := k.publicKeyECDSA()
|
||||
if pubkey == nil {
|
||||
return ErrKey
|
||||
}
|
||||
|
||||
// Split sigbuf into the r and s coordinates
|
||||
r := new(big.Int).SetBytes(sigbuf[:len(sigbuf)/2])
|
||||
s := new(big.Int).SetBytes(sigbuf[len(sigbuf)/2:])
|
||||
|
||||
h.Write(signeddata)
|
||||
h.Write(wire)
|
||||
if ecdsa.Verify(pubkey, h.Sum(nil), r, s) {
|
||||
return nil
|
||||
}
|
||||
return ErrSig
|
||||
|
||||
case ED25519:
|
||||
pubkey := k.publicKeyED25519()
|
||||
if pubkey == nil {
|
||||
return ErrKey
|
||||
}
|
||||
|
||||
if ed25519.Verify(pubkey, append(signeddata, wire...), sigbuf) {
|
||||
return nil
|
||||
}
|
||||
return ErrSig
|
||||
|
||||
default:
|
||||
return ErrAlg
|
||||
}
|
||||
}
|
||||
|
||||
// ValidityPeriod uses RFC1982 serial arithmetic to calculate
|
||||
// if a signature period is valid. If t is the zero time, the
|
||||
// current time is taken other t is. Returns true if the signature
|
||||
// is valid at the given time, otherwise returns false.
|
||||
func (rr *RRSIG) ValidityPeriod(t time.Time) bool {
|
||||
var utc int64
|
||||
if t.IsZero() {
|
||||
utc = time.Now().UTC().Unix()
|
||||
} else {
|
||||
utc = t.UTC().Unix()
|
||||
}
|
||||
modi := (int64(rr.Inception) - utc) / year68
|
||||
mode := (int64(rr.Expiration) - utc) / year68
|
||||
ti := int64(rr.Inception) + modi*year68
|
||||
te := int64(rr.Expiration) + mode*year68
|
||||
return ti <= utc && utc <= te
|
||||
}
|
||||
|
||||
// Return the signatures base64 encoding sigdata as a byte slice.
|
||||
func (rr *RRSIG) sigBuf() []byte {
|
||||
sigbuf, err := fromBase64([]byte(rr.Signature))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return sigbuf
|
||||
}
|
||||
|
||||
// publicKeyRSA returns the RSA public key from a DNSKEY record.
|
||||
func (k *DNSKEY) publicKeyRSA() *rsa.PublicKey {
|
||||
keybuf, err := fromBase64([]byte(k.PublicKey))
|
||||
if err != nil {
|
||||
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
|
||||
explen := uint16(keybuf[0])
|
||||
keyoff := 1
|
||||
if explen == 0 {
|
||||
explen = uint16(keybuf[1])<<8 | uint16(keybuf[2])
|
||||
keyoff = 3
|
||||
}
|
||||
|
||||
if explen > 4 || explen == 0 || keybuf[keyoff] == 0 {
|
||||
// Exponent larger than supported by the crypto package,
|
||||
// empty, or contains prohibited leading zero.
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// publicKeyECDSA returns the Curve public key from the DNSKEY record.
|
||||
func (k *DNSKEY) publicKeyECDSA() *ecdsa.PublicKey {
|
||||
keybuf, err := fromBase64([]byte(k.PublicKey))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
pubkey := new(ecdsa.PublicKey)
|
||||
switch k.Algorithm {
|
||||
case ECDSAP256SHA256:
|
||||
pubkey.Curve = elliptic.P256()
|
||||
if len(keybuf) != 64 {
|
||||
// wrongly encoded key
|
||||
return nil
|
||||
}
|
||||
case ECDSAP384SHA384:
|
||||
pubkey.Curve = elliptic.P384()
|
||||
if len(keybuf) != 96 {
|
||||
// Wrongly encoded key
|
||||
return nil
|
||||
}
|
||||
}
|
||||
pubkey.X = new(big.Int).SetBytes(keybuf[:len(keybuf)/2])
|
||||
pubkey.Y = new(big.Int).SetBytes(keybuf[len(keybuf)/2:])
|
||||
return pubkey
|
||||
}
|
||||
|
||||
func (k *DNSKEY) publicKeyED25519() ed25519.PublicKey {
|
||||
keybuf, err := fromBase64([]byte(k.PublicKey))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if len(keybuf) != ed25519.PublicKeySize {
|
||||
return nil
|
||||
}
|
||||
return keybuf
|
||||
}
|
||||
|
||||
type wireSlice [][]byte
|
||||
|
||||
func (p wireSlice) Len() int { return len(p) }
|
||||
func (p wireSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p wireSlice) Less(i, j int) bool {
|
||||
_, ioff, _ := UnpackDomainName(p[i], 0)
|
||||
_, joff, _ := UnpackDomainName(p[j], 0)
|
||||
return bytes.Compare(p[i][ioff+10:], p[j][joff+10:]) < 0
|
||||
}
|
||||
|
||||
// Return the raw signature data.
|
||||
func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) {
|
||||
wires := make(wireSlice, len(rrset))
|
||||
for i, r := range rrset {
|
||||
r1 := r.copy()
|
||||
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
|
||||
h.Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "."
|
||||
}
|
||||
// RFC 4034: 6.2. Canonical RR Form. (2) - domain name to lowercase
|
||||
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,
|
||||
// SRV, DNAME, A6
|
||||
//
|
||||
// RFC 6840 - Clarifications and Implementation Notes for DNS Security (DNSSEC):
|
||||
// Section 6.2 of [RFC4034] also erroneously lists HINFO as a record
|
||||
// that needs conversion to lowercase, and twice at that. Since HINFO
|
||||
// records contain no domain names, they are not subject to case
|
||||
// conversion.
|
||||
switch x := r1.(type) {
|
||||
case *NS:
|
||||
x.Ns = CanonicalName(x.Ns)
|
||||
case *MD:
|
||||
x.Md = CanonicalName(x.Md)
|
||||
case *MF:
|
||||
x.Mf = CanonicalName(x.Mf)
|
||||
case *CNAME:
|
||||
x.Target = CanonicalName(x.Target)
|
||||
case *SOA:
|
||||
x.Ns = CanonicalName(x.Ns)
|
||||
x.Mbox = CanonicalName(x.Mbox)
|
||||
case *MB:
|
||||
x.Mb = CanonicalName(x.Mb)
|
||||
case *MG:
|
||||
x.Mg = CanonicalName(x.Mg)
|
||||
case *MR:
|
||||
x.Mr = CanonicalName(x.Mr)
|
||||
case *PTR:
|
||||
x.Ptr = CanonicalName(x.Ptr)
|
||||
case *MINFO:
|
||||
x.Rmail = CanonicalName(x.Rmail)
|
||||
x.Email = CanonicalName(x.Email)
|
||||
case *MX:
|
||||
x.Mx = CanonicalName(x.Mx)
|
||||
case *RP:
|
||||
x.Mbox = CanonicalName(x.Mbox)
|
||||
x.Txt = CanonicalName(x.Txt)
|
||||
case *AFSDB:
|
||||
x.Hostname = CanonicalName(x.Hostname)
|
||||
case *RT:
|
||||
x.Host = CanonicalName(x.Host)
|
||||
case *SIG:
|
||||
x.SignerName = CanonicalName(x.SignerName)
|
||||
case *PX:
|
||||
x.Map822 = CanonicalName(x.Map822)
|
||||
x.Mapx400 = CanonicalName(x.Mapx400)
|
||||
case *NAPTR:
|
||||
x.Replacement = CanonicalName(x.Replacement)
|
||||
case *KX:
|
||||
x.Exchanger = CanonicalName(x.Exchanger)
|
||||
case *SRV:
|
||||
x.Target = CanonicalName(x.Target)
|
||||
case *DNAME:
|
||||
x.Target = CanonicalName(x.Target)
|
||||
}
|
||||
// 6.2. Canonical RR Form. (5) - origTTL
|
||||
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
|
||||
}
|
||||
wire = wire[:off]
|
||||
wires[i] = wire
|
||||
}
|
||||
sort.Sort(wires)
|
||||
for i, wire := range wires {
|
||||
if i > 0 && bytes.Equal(wire, wires[i-1]) {
|
||||
continue
|
||||
}
|
||||
buf = append(buf, wire...)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func packSigWire(sw *rrsigWireFmt, msg []byte) (int, error) {
|
||||
// copied from zmsg.go RRSIG packing
|
||||
off, err := packUint16(sw.TypeCovered, msg, 0)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint8(sw.Algorithm, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint8(sw.Labels, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint32(sw.OrigTtl, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint32(sw.Expiration, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint32(sw.Inception, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint16(sw.KeyTag, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = PackDomainName(sw.SignerName, msg, off, nil, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func packKeyWire(dw *dnskeyWireFmt, msg []byte) (int, error) {
|
||||
// copied from zmsg.go DNSKEY packing
|
||||
off, err := packUint16(dw.Flags, msg, 0)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint8(dw.Protocol, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint8(dw.Algorithm, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packStringBase64(dw.PublicKey, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
return off, nil
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Generate generates a DNSKEY of the given bit size.
|
||||
// The public part is put inside the DNSKEY record.
|
||||
// The Algorithm in the key must be set as this will define
|
||||
// what kind of DNSKEY will be generated.
|
||||
// The ECDSA algorithms imply a fixed keysize, in that case
|
||||
// bits should be set to the size of the algorithm.
|
||||
func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) {
|
||||
switch k.Algorithm {
|
||||
case RSASHA1, RSASHA256, RSASHA1NSEC3SHA1:
|
||||
if bits < 512 || bits > 4096 {
|
||||
return nil, ErrKeySize
|
||||
}
|
||||
case RSASHA512:
|
||||
if bits < 1024 || bits > 4096 {
|
||||
return nil, ErrKeySize
|
||||
}
|
||||
case ECDSAP256SHA256:
|
||||
if bits != 256 {
|
||||
return nil, ErrKeySize
|
||||
}
|
||||
case ECDSAP384SHA384:
|
||||
if bits != 384 {
|
||||
return nil, ErrKeySize
|
||||
}
|
||||
case ED25519:
|
||||
if bits != 256 {
|
||||
return nil, ErrKeySize
|
||||
}
|
||||
default:
|
||||
return nil, ErrAlg
|
||||
}
|
||||
|
||||
switch k.Algorithm {
|
||||
case RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1:
|
||||
priv, err := rsa.GenerateKey(rand.Reader, bits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k.setPublicKeyRSA(priv.PublicKey.E, priv.PublicKey.N)
|
||||
return priv, nil
|
||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||
var c elliptic.Curve
|
||||
switch k.Algorithm {
|
||||
case ECDSAP256SHA256:
|
||||
c = elliptic.P256()
|
||||
case ECDSAP384SHA384:
|
||||
c = elliptic.P384()
|
||||
}
|
||||
priv, err := ecdsa.GenerateKey(c, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k.setPublicKeyECDSA(priv.PublicKey.X, priv.PublicKey.Y)
|
||||
return priv, nil
|
||||
case ED25519:
|
||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k.setPublicKeyED25519(pub)
|
||||
return priv, nil
|
||||
default:
|
||||
return nil, ErrAlg
|
||||
}
|
||||
}
|
||||
|
||||
// Set the public key (the value E and N)
|
||||
func (k *DNSKEY) setPublicKeyRSA(_E int, _N *big.Int) bool {
|
||||
if _E == 0 || _N == nil {
|
||||
return false
|
||||
}
|
||||
buf := exponentToBuf(_E)
|
||||
buf = append(buf, _N.Bytes()...)
|
||||
k.PublicKey = toBase64(buf)
|
||||
return true
|
||||
}
|
||||
|
||||
// Set the public key for Elliptic Curves
|
||||
func (k *DNSKEY) setPublicKeyECDSA(_X, _Y *big.Int) bool {
|
||||
if _X == nil || _Y == nil {
|
||||
return false
|
||||
}
|
||||
var intlen int
|
||||
switch k.Algorithm {
|
||||
case ECDSAP256SHA256:
|
||||
intlen = 32
|
||||
case ECDSAP384SHA384:
|
||||
intlen = 48
|
||||
}
|
||||
k.PublicKey = toBase64(curveToBuf(_X, _Y, intlen))
|
||||
return true
|
||||
}
|
||||
|
||||
// Set the public key for Ed25519
|
||||
func (k *DNSKEY) setPublicKeyED25519(_K ed25519.PublicKey) bool {
|
||||
if _K == nil {
|
||||
return false
|
||||
}
|
||||
k.PublicKey = toBase64(_K)
|
||||
return true
|
||||
}
|
||||
|
||||
// Set the public key (the values E and N) for RSA
|
||||
// RFC 3110: Section 2. RSA Public KEY Resource Records
|
||||
func exponentToBuf(_E int) []byte {
|
||||
var buf []byte
|
||||
i := big.NewInt(int64(_E)).Bytes()
|
||||
if len(i) < 256 {
|
||||
buf = make([]byte, 1, 1+len(i))
|
||||
buf[0] = uint8(len(i))
|
||||
} else {
|
||||
buf = make([]byte, 3, 3+len(i))
|
||||
buf[0] = 0
|
||||
buf[1] = uint8(len(i) >> 8)
|
||||
buf[2] = uint8(len(i))
|
||||
}
|
||||
buf = append(buf, i...)
|
||||
return buf
|
||||
}
|
||||
|
||||
// Set the public key for X and Y for Curve. The two
|
||||
// values are just concatenated.
|
||||
func curveToBuf(_X, _Y *big.Int, intlen int) []byte {
|
||||
buf := intToBytes(_X, intlen)
|
||||
buf = append(buf, intToBytes(_Y, intlen)...)
|
||||
return buf
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"io"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewPrivateKey returns a PrivateKey by parsing the string s.
|
||||
// s should be in the same form of the BIND private key files.
|
||||
func (k *DNSKEY) NewPrivateKey(s string) (crypto.PrivateKey, error) {
|
||||
if s == "" || s[len(s)-1] != '\n' { // We need a closing newline
|
||||
return k.ReadPrivateKey(strings.NewReader(s+"\n"), "")
|
||||
}
|
||||
return k.ReadPrivateKey(strings.NewReader(s), "")
|
||||
}
|
||||
|
||||
// ReadPrivateKey reads a private key from the io.Reader q. The string file is
|
||||
// only used in error reporting.
|
||||
// The public key must be known, because some cryptographic algorithms embed
|
||||
// the public inside the privatekey.
|
||||
func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, error) {
|
||||
m, err := parseKey(q, file)
|
||||
if m == nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := m["private-key-format"]; !ok {
|
||||
return nil, ErrPrivKey
|
||||
}
|
||||
if m["private-key-format"] != "v1.2" && m["private-key-format"] != "v1.3" {
|
||||
return nil, ErrPrivKey
|
||||
}
|
||||
// TODO(mg): check if the pubkey matches the private key
|
||||
algo, err := strconv.ParseUint(strings.SplitN(m["algorithm"], " ", 2)[0], 10, 8)
|
||||
if err != nil {
|
||||
return nil, ErrPrivKey
|
||||
}
|
||||
switch uint8(algo) {
|
||||
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
|
||||
priv, err := readPrivateKeyRSA(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pub := k.publicKeyRSA()
|
||||
if pub == nil {
|
||||
return nil, ErrKey
|
||||
}
|
||||
priv.PublicKey = *pub
|
||||
return priv, nil
|
||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||
priv, err := readPrivateKeyECDSA(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pub := k.publicKeyECDSA()
|
||||
if pub == nil {
|
||||
return nil, ErrKey
|
||||
}
|
||||
priv.PublicKey = *pub
|
||||
return priv, nil
|
||||
case ED25519:
|
||||
return readPrivateKeyED25519(m)
|
||||
default:
|
||||
return nil, ErrAlg
|
||||
}
|
||||
}
|
||||
|
||||
// Read a private key (file) string and create a public key. Return the private key.
|
||||
func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) {
|
||||
p := new(rsa.PrivateKey)
|
||||
p.Primes = []*big.Int{nil, nil}
|
||||
for k, v := range m {
|
||||
switch k {
|
||||
case "modulus", "publicexponent", "privateexponent", "prime1", "prime2":
|
||||
v1, err := fromBase64([]byte(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch k {
|
||||
case "modulus":
|
||||
p.PublicKey.N = new(big.Int).SetBytes(v1)
|
||||
case "publicexponent":
|
||||
i := new(big.Int).SetBytes(v1)
|
||||
p.PublicKey.E = int(i.Int64()) // int64 should be large enough
|
||||
case "privateexponent":
|
||||
p.D = new(big.Int).SetBytes(v1)
|
||||
case "prime1":
|
||||
p.Primes[0] = new(big.Int).SetBytes(v1)
|
||||
case "prime2":
|
||||
p.Primes[1] = new(big.Int).SetBytes(v1)
|
||||
}
|
||||
case "exponent1", "exponent2", "coefficient":
|
||||
// not used in Go (yet)
|
||||
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 = new(big.Int)
|
||||
// TODO: validate that the required flags are present
|
||||
for k, v := range m {
|
||||
switch k {
|
||||
case "privatekey":
|
||||
v1, err := fromBase64([]byte(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.D.SetBytes(v1)
|
||||
case "created", "publish", "activate":
|
||||
/* not used in Go (yet) */
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func readPrivateKeyED25519(m map[string]string) (ed25519.PrivateKey, error) {
|
||||
var p ed25519.PrivateKey
|
||||
// TODO: validate that the required flags are present
|
||||
for k, v := range m {
|
||||
switch k {
|
||||
case "privatekey":
|
||||
p1, err := fromBase64([]byte(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(p1) != ed25519.SeedSize {
|
||||
return nil, ErrPrivKey
|
||||
}
|
||||
p = ed25519.NewKeyFromSeed(p1)
|
||||
case "created", "publish", "activate":
|
||||
/* not used in Go (yet) */
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
m := make(map[string]string)
|
||||
var k string
|
||||
|
||||
c := newKLexer(r)
|
||||
|
||||
for l, ok := c.Next(); ok; l, ok = c.Next() {
|
||||
// It should alternate
|
||||
switch l.value {
|
||||
case zKey:
|
||||
k = l.token
|
||||
case zValue:
|
||||
if k == "" {
|
||||
return nil, &ParseError{file, "no private key seen", l}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 || !kl.key {
|
||||
break
|
||||
}
|
||||
|
||||
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':
|
||||
if commt {
|
||||
// Reset a comment
|
||||
commt = false
|
||||
}
|
||||
|
||||
if kl.key && str.Len() == 0 {
|
||||
// ignore empty lines
|
||||
break
|
||||
}
|
||||
|
||||
kl.key = true
|
||||
|
||||
l.value = zValue
|
||||
l.token = str.String()
|
||||
return l, true
|
||||
default:
|
||||
if commt {
|
||||
break
|
||||
}
|
||||
|
||||
str.WriteByte(x)
|
||||
}
|
||||
}
|
||||
|
||||
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.value = zValue
|
||||
l.token = str.String()
|
||||
return l, true
|
||||
}
|
||||
|
||||
return lex{value: zEOF}, false
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"math/big"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
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 ed25519.PrivateKey.
|
||||
func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string {
|
||||
algorithm := strconv.Itoa(int(r.Algorithm))
|
||||
algorithm += " (" + AlgorithmToString[r.Algorithm] + ")"
|
||||
|
||||
switch p := p.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
modulus := toBase64(p.PublicKey.N.Bytes())
|
||||
e := big.NewInt(int64(p.PublicKey.E))
|
||||
publicExponent := toBase64(e.Bytes())
|
||||
privateExponent := toBase64(p.D.Bytes())
|
||||
prime1 := toBase64(p.Primes[0].Bytes())
|
||||
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
|
||||
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())
|
||||
coefficient := toBase64(coeff.Bytes())
|
||||
|
||||
return format +
|
||||
"Algorithm: " + algorithm + "\n" +
|
||||
"Modulus: " + modulus + "\n" +
|
||||
"PublicExponent: " + publicExponent + "\n" +
|
||||
"PrivateExponent: " + privateExponent + "\n" +
|
||||
"Prime1: " + prime1 + "\n" +
|
||||
"Prime2: " + prime2 + "\n" +
|
||||
"Exponent1: " + exponent1 + "\n" +
|
||||
"Exponent2: " + exponent2 + "\n" +
|
||||
"Coefficient: " + coefficient + "\n"
|
||||
|
||||
case *ecdsa.PrivateKey:
|
||||
var intlen int
|
||||
switch r.Algorithm {
|
||||
case ECDSAP256SHA256:
|
||||
intlen = 32
|
||||
case ECDSAP384SHA384:
|
||||
intlen = 48
|
||||
}
|
||||
private := toBase64(intToBytes(p.D, intlen))
|
||||
return format +
|
||||
"Algorithm: " + algorithm + "\n" +
|
||||
"PrivateKey: " + private + "\n"
|
||||
|
||||
case ed25519.PrivateKey:
|
||||
private := toBase64(p.Seed())
|
||||
return format +
|
||||
"Algorithm: " + algorithm + "\n" +
|
||||
"PrivateKey: " + private + "\n"
|
||||
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
Package dns implements a full featured interface to the Domain Name System.
|
||||
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.
|
||||
|
||||
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
|
||||
names in a message will result in a packing failure.
|
||||
|
||||
Resource records are native types. They are not stored in wire format. Basic
|
||||
usage pattern for creating a new resource record:
|
||||
|
||||
r := new(dns.MX)
|
||||
r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 3600}
|
||||
r.Preference = 10
|
||||
r.Mx = "mx.miek.nl."
|
||||
|
||||
Or directly from a string:
|
||||
|
||||
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")
|
||||
|
||||
Or even:
|
||||
|
||||
mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek")
|
||||
|
||||
In the DNS messages are exchanged, these messages contain resource records
|
||||
(sets). Use pattern for creating a message:
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||
|
||||
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 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}
|
||||
|
||||
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")
|
||||
|
||||
Suppressing multiple outstanding queries (with the same question, type and
|
||||
class) is as easy as setting:
|
||||
|
||||
c.SingleInflight = true
|
||||
|
||||
More advanced options are available using a net.Dialer and the corresponding API.
|
||||
For example it is possible to set a timeout, or to specify a source IP address
|
||||
and port to use for the connection:
|
||||
|
||||
c := new(dns.Client)
|
||||
laddr := net.UDPAddr{
|
||||
IP: net.ParseIP("[::1]"),
|
||||
Port: 12345,
|
||||
Zone: "",
|
||||
}
|
||||
c.Dialer := &net.Dialer{
|
||||
Timeout: 200 * time.Millisecond,
|
||||
LocalAddr: &laddr,
|
||||
}
|
||||
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
|
||||
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.
|
||||
|
||||
Each of these sections (except the Question section) contain a []RR. Basic
|
||||
use pattern for accessing the rdata of a TXT RR as the first RR in
|
||||
the Answer section:
|
||||
|
||||
if t, ok := in.Answer[0].(*dns.TXT); ok {
|
||||
// do something with t.Txt
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
For domain names, in addition to the above rules brackets, periods, spaces,
|
||||
semicolons and the at symbol are escaped.
|
||||
|
||||
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.
|
||||
|
||||
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)
|
||||
|
||||
Signature generation, signature verification and key generation are all supported.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
CLASS TYPE RDATA Meaning Function
|
||||
--------------------------------------------------------------
|
||||
ANY ANY empty Name is in use dns.NameUsed
|
||||
ANY rrset empty RRset exists (value indep) dns.RRsetUsed
|
||||
NONE ANY empty Name is not in use dns.NameNotUsed
|
||||
NONE rrset empty RRset does not exist dns.RRsetNotUsed
|
||||
zone rrset rr RRset exists (value dep) dns.Used
|
||||
|
||||
The prerequisite section can also be left empty. If you have decided on the
|
||||
prerequisites you can tell what RRs should be added or deleted. The next table
|
||||
shows the options you have and what functions to call.
|
||||
|
||||
3.4.2.6 - Table Of Metavalues Used In Update Section
|
||||
|
||||
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
|
||||
|
||||
An TSIG or transaction signature adds a HMAC TSIG record to each message sent.
|
||||
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
|
||||
"so6ZGir4GPAqINNh9U5c3A==":
|
||||
|
||||
If an incoming message contains a TSIG record it MUST be the last record in
|
||||
the additional section (RFC2845 3.2). This means that you should make the
|
||||
call to SetTsig last, right before executing the query. If you make any
|
||||
changes to the RRset after calling SetTsig() the signature will be incorrect.
|
||||
|
||||
c := new(dns.Client)
|
||||
c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||
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:
|
||||
|
||||
t := new(dns.Transfer)
|
||||
m := new(dns.Msg)
|
||||
t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
|
||||
m.SetAxfr("miek.nl.")
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
server := &dns.Server{Addr: ":53", Net: "udp"}
|
||||
server.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
|
||||
go server.ListenAndServe()
|
||||
dns.HandleFunc(".", handleRequest)
|
||||
|
||||
func handleRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
if r.IsTsig() != nil {
|
||||
if w.TsigStatus() == nil {
|
||||
// *Msg r has an TSIG record and it was validated
|
||||
m.SetTsig("axfr.", dns.HmacSHA256, 300, time.Now().Unix())
|
||||
} else {
|
||||
// *Msg r has an TSIG records and it was not validated
|
||||
}
|
||||
}
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
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
|
||||
can be used, before requesting an official type code from IANA.
|
||||
|
||||
See https://miek.nl/2014/september/21/idn-and-private-rr-in-go-dns/ for more
|
||||
information.
|
||||
|
||||
EDNS0
|
||||
|
||||
EDNS0 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 (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
|
||||
for _, s := range o.Option {
|
||||
switch e := s.(type) {
|
||||
case *dns.EDNS0_NSID:
|
||||
// do stuff with e.Nsid
|
||||
case *dns.EDNS0_SUBNET:
|
||||
// access e.Family, e.Address, etc.
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
*/
|
||||
package dns
|
|
@ -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,851 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// EDNS0 Option codes.
|
||||
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
|
||||
EDNS0SUBNET = 0x8 // client-subnet (See RFC 7871)
|
||||
EDNS0EXPIRE = 0x9 // EDNS0 expire
|
||||
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 {
|
||||
Hdr RR_Header
|
||||
Option []EDNS0 `dns:"opt"`
|
||||
}
|
||||
|
||||
func (rr *OPT) String() string {
|
||||
s := "\n;; OPT PSEUDOSECTION:\n; EDNS: version " + strconv.Itoa(int(rr.Version())) + "; "
|
||||
if rr.Do() {
|
||||
s += "flags: do; "
|
||||
} else {
|
||||
s += "flags: ; "
|
||||
}
|
||||
s += "udp: " + strconv.Itoa(int(rr.UDPSize()))
|
||||
|
||||
for _, o := range rr.Option {
|
||||
switch o.(type) {
|
||||
case *EDNS0_NSID:
|
||||
s += "\n; NSID: " + o.String()
|
||||
h, e := o.pack()
|
||||
var r string
|
||||
if e == nil {
|
||||
for _, c := range h {
|
||||
r += "(" + string(c) + ")"
|
||||
}
|
||||
s += " " + r
|
||||
}
|
||||
case *EDNS0_SUBNET:
|
||||
s += "\n; SUBNET: " + o.String()
|
||||
case *EDNS0_COOKIE:
|
||||
s += "\n; COOKIE: " + o.String()
|
||||
case *EDNS0_TCP_KEEPALIVE:
|
||||
s += "\n; KEEPALIVE: " + o.String()
|
||||
case *EDNS0_UL:
|
||||
s += "\n; UPDATE LEASE: " + o.String()
|
||||
case *EDNS0_LLQ:
|
||||
s += "\n; LONG LIVED QUERIES: " + o.String()
|
||||
case *EDNS0_DAU:
|
||||
s += "\n; DNSSEC ALGORITHM UNDERSTOOD: " + o.String()
|
||||
case *EDNS0_DHU:
|
||||
s += "\n; DS HASH UNDERSTOOD: " + o.String()
|
||||
case *EDNS0_N3U:
|
||||
s += "\n; NSEC3 HASH UNDERSTOOD: " + o.String()
|
||||
case *EDNS0_LOCAL:
|
||||
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(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, _ := 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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) << 4
|
||||
}
|
||||
|
||||
// SetExtendedRcode sets the EDNS extended RCODE field.
|
||||
//
|
||||
// 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.
|
||||
func (rr *OPT) UDPSize() uint16 {
|
||||
return rr.Hdr.Class
|
||||
}
|
||||
|
||||
// SetUDPSize sets the UDP buffer size.
|
||||
func (rr *OPT) SetUDPSize(size uint16) {
|
||||
rr.Hdr.Class = size
|
||||
}
|
||||
|
||||
// Do returns the value of the DO (DNSSEC OK) bit.
|
||||
func (rr *OPT) Do() bool {
|
||||
return rr.Hdr.Ttl&_DO == _DO
|
||||
}
|
||||
|
||||
// SetDo sets the DO (DNSSEC OK) bit.
|
||||
// If we pass an argument, set the DO bit to that value.
|
||||
// It is possible to pass 2 or more arguments. Any arguments after the 1st is silently ignored.
|
||||
func (rr *OPT) SetDo(do ...bool) {
|
||||
if len(do) == 1 {
|
||||
if do[0] {
|
||||
rr.Hdr.Ttl |= _DO
|
||||
} else {
|
||||
rr.Hdr.Ttl &^= _DO
|
||||
}
|
||||
} else {
|
||||
rr.Hdr.Ttl |= _DO
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
Option() uint16
|
||||
// pack returns the bytes of the option data.
|
||||
pack() ([]byte, error)
|
||||
// unpack sets the data as found in the buffer. Is also sets
|
||||
// the length of the slice as the length of the option data.
|
||||
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
|
||||
// identifier. When sending a request Nsid must be set to the empty string
|
||||
// The identifier is an opaque string encoded as hex.
|
||||
// Basic use pattern for creating an nsid option:
|
||||
//
|
||||
// o := new(dns.OPT)
|
||||
// o.Hdr.Name = "."
|
||||
// o.Hdr.Rrtype = dns.TypeOPT
|
||||
// e := new(dns.EDNS0_NSID)
|
||||
// e.Code = dns.EDNS0NSID
|
||||
// e.Nsid = "AA"
|
||||
// o.Option = append(o.Option, e)
|
||||
type EDNS0_NSID struct {
|
||||
Code uint16 // Always EDNS0NSID
|
||||
Nsid string // This string needs to be hex encoded
|
||||
}
|
||||
|
||||
func (e *EDNS0_NSID) pack() ([]byte, error) {
|
||||
h, err := hex.DecodeString(e.Nsid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// 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 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
|
||||
// answer depending on the location or network topology.
|
||||
// Basic use pattern for creating an subnet option:
|
||||
//
|
||||
// o := new(dns.OPT)
|
||||
// o.Hdr.Name = "."
|
||||
// o.Hdr.Rrtype = dns.TypeOPT
|
||||
// e := new(dns.EDNS0_SUBNET)
|
||||
// e.Code = dns.EDNS0SUBNET
|
||||
// e.Family = 1 // 1 for IPv4 source address, 2 for IPv6
|
||||
// e.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6
|
||||
// e.SourceScope = 0
|
||||
// e.Address = net.ParseIP("127.0.0.1").To4() // for IPv4
|
||||
// // e.Address = net.ParseIP("2001:7b8:32a::2") // for IPV6
|
||||
// o.Option = append(o.Option, e)
|
||||
//
|
||||
// This code will parse all the available bits when unpacking (up to optlen).
|
||||
// When packing it will apply SourceNetmask. If you need more advanced logic,
|
||||
// patches welcome and good luck.
|
||||
type EDNS0_SUBNET struct {
|
||||
Code uint16 // Always EDNS0SUBNET
|
||||
Family uint16 // 1 for IP, 2 for IP6
|
||||
SourceNetmask uint8
|
||||
SourceScope uint8
|
||||
Address net.IP
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
func (e *EDNS0_SUBNET) Option() uint16 { return EDNS0SUBNET }
|
||||
|
||||
func (e *EDNS0_SUBNET) pack() ([]byte, error) {
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(b[0:], e.Family)
|
||||
b[2] = e.SourceNetmask
|
||||
b[3] = e.SourceScope
|
||||
switch e.Family {
|
||||
case 0:
|
||||
// "dig" sets AddressFamily to 0 if SourceNetmask is also 0
|
||||
// We might don't need to complain either
|
||||
if e.SourceNetmask != 0 {
|
||||
return nil, errors.New("dns: bad address family")
|
||||
}
|
||||
case 1:
|
||||
if e.SourceNetmask > net.IPv4len*8 {
|
||||
return nil, errors.New("dns: bad netmask")
|
||||
}
|
||||
if len(e.Address.To4()) != net.IPv4len {
|
||||
return nil, errors.New("dns: bad address")
|
||||
}
|
||||
ip := e.Address.To4().Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv4len*8))
|
||||
needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up
|
||||
b = append(b, ip[:needLength]...)
|
||||
case 2:
|
||||
if e.SourceNetmask > net.IPv6len*8 {
|
||||
return nil, errors.New("dns: bad netmask")
|
||||
}
|
||||
if len(e.Address) != net.IPv6len {
|
||||
return nil, errors.New("dns: bad address")
|
||||
}
|
||||
ip := e.Address.Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv6len*8))
|
||||
needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up
|
||||
b = append(b, ip[:needLength]...)
|
||||
default:
|
||||
return nil, errors.New("dns: bad address family")
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_SUBNET) unpack(b []byte) error {
|
||||
if len(b) < 4 {
|
||||
return ErrBuf
|
||||
}
|
||||
e.Family = binary.BigEndian.Uint16(b)
|
||||
e.SourceNetmask = b[2]
|
||||
e.SourceScope = b[3]
|
||||
switch e.Family {
|
||||
case 0:
|
||||
// "dig" sets AddressFamily to 0 if SourceNetmask is also 0
|
||||
// It's okay to accept such a packet
|
||||
if e.SourceNetmask != 0 {
|
||||
return errors.New("dns: bad address family")
|
||||
}
|
||||
e.Address = net.IPv4(0, 0, 0, 0)
|
||||
case 1:
|
||||
if e.SourceNetmask > net.IPv4len*8 || e.SourceScope > net.IPv4len*8 {
|
||||
return errors.New("dns: bad netmask")
|
||||
}
|
||||
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(net.IP, net.IPv6len)
|
||||
copy(addr, b[4:])
|
||||
e.Address = addr
|
||||
default:
|
||||
return errors.New("dns: bad address family")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_SUBNET) String() (s string) {
|
||||
if e.Address == nil {
|
||||
s = "<nil>"
|
||||
} else if e.Address.To4() != nil {
|
||||
s = e.Address.String()
|
||||
} else {
|
||||
s = "[" + e.Address.String() + "]"
|
||||
}
|
||||
s += "/" + strconv.Itoa(int(e.SourceNetmask)) + "/" + strconv.Itoa(int(e.SourceScope))
|
||||
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)
|
||||
// o.Hdr.Name = "."
|
||||
// o.Hdr.Rrtype = dns.TypeOPT
|
||||
// e := new(dns.EDNS0_COOKIE)
|
||||
// e.Code = dns.EDNS0COOKIE
|
||||
// e.Cookie = "24a5ac.."
|
||||
// o.Option = append(o.Option, e)
|
||||
//
|
||||
// The Cookie field consists out of a client cookie (RFC 7873 Section 4), that is
|
||||
// always 8 bytes. It may then optionally be followed by the server cookie. The server
|
||||
// cookie is of variable length, 8 to a maximum of 32 bytes. In other words:
|
||||
//
|
||||
// cCookie := o.Cookie[:16]
|
||||
// sCookie := o.Cookie[16:]
|
||||
//
|
||||
// There is no guarantee that the Cookie string has a specific length.
|
||||
type EDNS0_COOKIE struct {
|
||||
Code uint16 // Always EDNS0COOKIE
|
||||
Cookie string // Hex-encoded cookie data
|
||||
}
|
||||
|
||||
func (e *EDNS0_COOKIE) pack() ([]byte, error) {
|
||||
h, err := hex.DecodeString(e.Cookie)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
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
|
||||
// https://tools.ietf.org/html/draft-sekar-dns-ul-02
|
||||
//
|
||||
// o := new(dns.OPT)
|
||||
// o.Hdr.Name = "."
|
||||
// o.Hdr.Rrtype = dns.TypeOPT
|
||||
// e := new(dns.EDNS0_UL)
|
||||
// e.Code = dns.EDNS0UL
|
||||
// e.Lease = 120 // in seconds
|
||||
// o.Option = append(o.Option, e)
|
||||
type EDNS0_UL struct {
|
||||
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 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) {
|
||||
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 {
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EDNS0_LLQ stands for Long Lived Queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01
|
||||
// Implemented for completeness, as the EDNS0 type code is assigned.
|
||||
type EDNS0_LLQ struct {
|
||||
Code uint16 // Always EDNS0LLQ
|
||||
Version uint16
|
||||
Opcode uint16
|
||||
Error uint16
|
||||
Id uint64
|
||||
LeaseLife uint32
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
func (e *EDNS0_LLQ) Option() uint16 { return EDNS0LLQ }
|
||||
|
||||
func (e *EDNS0_LLQ) pack() ([]byte, error) {
|
||||
b := make([]byte, 18)
|
||||
binary.BigEndian.PutUint16(b[0:], e.Version)
|
||||
binary.BigEndian.PutUint16(b[2:], e.Opcode)
|
||||
binary.BigEndian.PutUint16(b[4:], e.Error)
|
||||
binary.BigEndian.PutUint64(b[6:], e.Id)
|
||||
binary.BigEndian.PutUint32(b[14:], e.LeaseLife)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_LLQ) unpack(b []byte) error {
|
||||
if len(b) < 18 {
|
||||
return ErrBuf
|
||||
}
|
||||
e.Version = binary.BigEndian.Uint16(b[0:])
|
||||
e.Opcode = binary.BigEndian.Uint16(b[2:])
|
||||
e.Error = binary.BigEndian.Uint16(b[4:])
|
||||
e.Id = binary.BigEndian.Uint64(b[6:])
|
||||
e.LeaseLife = binary.BigEndian.Uint32(b[14:])
|
||||
return nil
|
||||
}
|
||||
|
||||
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(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_DAU implements the EDNS0 "DNSSEC Algorithm Understood" option. See RFC 6975.
|
||||
type EDNS0_DAU struct {
|
||||
Code uint16 // Always EDNS0DAU
|
||||
AlgCode []uint8
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
func (e *EDNS0_DAU) Option() uint16 { return EDNS0DAU }
|
||||
func (e *EDNS0_DAU) pack() ([]byte, error) { return e.AlgCode, nil }
|
||||
func (e *EDNS0_DAU) unpack(b []byte) error { e.AlgCode = b; return nil }
|
||||
|
||||
func (e *EDNS0_DAU) String() string {
|
||||
s := ""
|
||||
for _, alg := range e.AlgCode {
|
||||
if a, ok := AlgorithmToString[alg]; ok {
|
||||
s += " " + a
|
||||
} else {
|
||||
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 {
|
||||
Code uint16 // Always EDNS0DHU
|
||||
AlgCode []uint8
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
func (e *EDNS0_DHU) Option() uint16 { return EDNS0DHU }
|
||||
func (e *EDNS0_DHU) pack() ([]byte, error) { return e.AlgCode, nil }
|
||||
func (e *EDNS0_DHU) unpack(b []byte) error { e.AlgCode = b; return nil }
|
||||
|
||||
func (e *EDNS0_DHU) String() string {
|
||||
s := ""
|
||||
for _, alg := range e.AlgCode {
|
||||
if a, ok := HashToString[alg]; ok {
|
||||
s += " " + a
|
||||
} else {
|
||||
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 {
|
||||
Code uint16 // Always EDNS0N3U
|
||||
AlgCode []uint8
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
func (e *EDNS0_N3U) Option() uint16 { return EDNS0N3U }
|
||||
func (e *EDNS0_N3U) pack() ([]byte, error) { return e.AlgCode, nil }
|
||||
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 _, alg := range e.AlgCode {
|
||||
if a, ok := HashToString[alg]; ok {
|
||||
s += " " + a
|
||||
} else {
|
||||
s += " " + strconv.Itoa(int(alg))
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} }
|
||||
|
||||
// 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) copy() EDNS0 { return &EDNS0_EXPIRE{e.Code, e.Expire, e.Empty} }
|
||||
|
||||
func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
|
||||
if e.Empty {
|
||||
return []byte{}, nil
|
||||
}
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, e.Expire)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
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
|
||||
// the option is made available in Data, unaltered.
|
||||
// Basic use pattern for creating a local option:
|
||||
//
|
||||
// o := new(dns.OPT)
|
||||
// o.Hdr.Name = "."
|
||||
// o.Hdr.Rrtype = dns.TypeOPT
|
||||
// e := new(dns.EDNS0_LOCAL)
|
||||
// e.Code = dns.EDNS0LOCALSTART
|
||||
// e.Data = []byte{72, 82, 74}
|
||||
// o.Option = append(o.Option, e)
|
||||
type EDNS0_LOCAL struct {
|
||||
Code uint16
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
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))
|
||||
copied := copy(b, e.Data)
|
||||
if copied != len(e.Data) {
|
||||
return nil, ErrBuf
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_LOCAL) unpack(b []byte) error {
|
||||
e.Data = make([]byte, len(b))
|
||||
copied := copy(e.Data, b)
|
||||
if copied != len(b) {
|
||||
return ErrBuf
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// 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 {
|
||||
b := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(b, e.Timeout)
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_TCP_KEEPALIVE) unpack(b []byte) error {
|
||||
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() string {
|
||||
s := "use tcp keep-alive"
|
||||
if e.Timeout == 0 {
|
||||
s += ", timeout omitted"
|
||||
} else {
|
||||
s += fmt.Sprintf(", timeout %dms", e.Timeout*100)
|
||||
}
|
||||
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.
|
||||
type EDNS0_PADDING struct {
|
||||
Padding []byte
|
||||
}
|
||||
|
||||
// Option implements the EDNS0 interface.
|
||||
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
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// NumField returns the number of rdata fields r has.
|
||||
func NumField(r RR) int {
|
||||
return reflect.ValueOf(r).Elem().NumField() - 1 // Remove RR_Header
|
||||
}
|
||||
|
||||
// Field returns the rdata field i as a string. Fields are indexed starting from 1.
|
||||
// RR types that holds slice data, for instance the NSEC type bitmap will return a single
|
||||
// string where the types are concatenated using a space.
|
||||
// Accessing non existing fields will cause a panic.
|
||||
func Field(r RR, i int) string {
|
||||
if i == 0 {
|
||||
return ""
|
||||
}
|
||||
d := reflect.ValueOf(r).Elem().Field(i)
|
||||
switch d.Kind() {
|
||||
case reflect.String:
|
||||
return d.String()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return strconv.FormatInt(d.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return strconv.FormatUint(d.Uint(), 10)
|
||||
case reflect.Slice:
|
||||
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()),
|
||||
byte(d.Index(2).Uint()),
|
||||
byte(d.Index(3).Uint())).String()
|
||||
}
|
||||
return net.IPv4(byte(d.Index(12).Uint()),
|
||||
byte(d.Index(13).Uint()),
|
||||
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()),
|
||||
byte(d.Index(2).Uint()),
|
||||
byte(d.Index(3).Uint()),
|
||||
byte(d.Index(4).Uint()),
|
||||
byte(d.Index(5).Uint()),
|
||||
byte(d.Index(6).Uint()),
|
||||
byte(d.Index(7).Uint()),
|
||||
byte(d.Index(8).Uint()),
|
||||
byte(d.Index(9).Uint()),
|
||||
byte(d.Index(10).Uint()),
|
||||
byte(d.Index(11).Uint()),
|
||||
byte(d.Index(12).Uint()),
|
||||
byte(d.Index(13).Uint()),
|
||||
byte(d.Index(14).Uint()),
|
||||
byte(d.Index(15).Uint()),
|
||||
}.String()
|
||||
case `dns:"nsec"`:
|
||||
if d.Len() == 0 {
|
||||
return ""
|
||||
}
|
||||
s := Type(d.Index(0).Uint()).String()
|
||||
for i := 1; i < d.Len(); i++ {
|
||||
s += " " + Type(d.Index(i).Uint()).String()
|
||||
}
|
||||
return s
|
||||
default:
|
||||
// if it does not have a tag its a string slice
|
||||
fallthrough
|
||||
case `dns:"txt"`:
|
||||
if d.Len() == 0 {
|
||||
return ""
|
||||
}
|
||||
s := d.Index(0).String()
|
||||
for i := 1; i < d.Len(); i++ {
|
||||
s += " " + d.Index(i).String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// +build fuzz
|
||||
|
||||
package dns
|
||||
|
||||
import "strings"
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
msg := new(Msg)
|
||||
|
||||
if err := msg.Unpack(data); err != nil {
|
||||
return 0
|
||||
}
|
||||
if _, err := msg.Pack(); err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
func FuzzNewRR(data []byte) int {
|
||||
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,247 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Parse the $GENERATE statement as used in BIND9 zones.
|
||||
// See http://www.zytrax.com/books/dns/ch8/generate.html for instance.
|
||||
// We are called after '$GENERATE '. After which we expect:
|
||||
// * the range (12-24/2)
|
||||
// * lhs (ownername)
|
||||
// * [[ttl][class]]
|
||||
// * type
|
||||
// * rhs (rdata)
|
||||
// But we are lazy here, only the range is parsed *all* occurrences
|
||||
// of $ after that are interpreted.
|
||||
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)
|
||||
}
|
||||
|
||||
s, err := strconv.ParseInt(token[i+1:], 10, 64)
|
||||
if err != nil || s <= 0 {
|
||||
return zp.setParseError("bad step in $GENERATE range", l)
|
||||
}
|
||||
|
||||
step = s
|
||||
token = token[:i]
|
||||
}
|
||||
|
||||
sx := strings.SplitN(token, "-", 2)
|
||||
if len(sx) != 2 {
|
||||
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)
|
||||
}
|
||||
|
||||
// Create a complete new string, which we then parse again.
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
|
||||
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"
|
||||
}
|
||||
|
||||
switch base {
|
||||
case "o", "d", "x", "X":
|
||||
default:
|
||||
return "", 0, "bad base in $GENERATE"
|
||||
}
|
||||
|
||||
offset, err := strconv.ParseInt(offStr, 10, 64)
|
||||
if err != nil {
|
||||
return "", 0, "bad offset in $GENERATE"
|
||||
}
|
||||
|
||||
width, err := strconv.ParseInt(widthStr, 10, 64)
|
||||
if err != nil || width < 0 || width > 255 {
|
||||
return "", 0, "bad width in $GENERATE"
|
||||
}
|
||||
|
||||
if width == 0 {
|
||||
return "%" + base, offset, ""
|
||||
}
|
||||
|
||||
return "%0" + widthStr + base, offset, ""
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
package dns
|
||||
|
||||
// Holds a bunch of helper functions for dealing with labels.
|
||||
|
||||
// SplitDomainName splits a name string into it's labels.
|
||||
// www.miek.nl. returns []string{"www", "miek", "nl"}
|
||||
// .www.miek.nl. returns []string{"", "www", "miek", "nl"},
|
||||
// The root label (.) returns nil. Note that using
|
||||
// strings.Split(s) will work in most cases, but does not handle
|
||||
// escaped dots (\.) for instance.
|
||||
// s must be a syntactically valid domain name, see IsDomainName.
|
||||
func SplitDomainName(s string) (labels []string) {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
fqdnEnd := 0 // offset of the final '.' or the length of the name
|
||||
idx := Split(s)
|
||||
begin := 0
|
||||
if IsFqdn(s) {
|
||||
fqdnEnd = len(s) - 1
|
||||
} else {
|
||||
fqdnEnd = len(s)
|
||||
}
|
||||
|
||||
switch len(idx) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
// no-op
|
||||
default:
|
||||
for _, end := range idx[1:] {
|
||||
labels = append(labels, s[begin:end-1])
|
||||
begin = end
|
||||
}
|
||||
}
|
||||
|
||||
return append(labels, s[begin:fqdnEnd])
|
||||
}
|
||||
|
||||
// CompareDomainName compares the names s1 and s2 and
|
||||
// returns how many labels they have in common starting from the *right*.
|
||||
// The comparison stops at the first inequality. The names are downcased
|
||||
// before the comparison.
|
||||
//
|
||||
// www.miek.nl. and miek.nl. have two labels in common: miek and nl
|
||||
// www.miek.nl. and www.bla.nl. have one label in common: nl
|
||||
//
|
||||
// s1 and s2 must be syntactically valid domain names.
|
||||
func CompareDomainName(s1, s2 string) (n int) {
|
||||
// the first check: root label
|
||||
if s1 == "." || s2 == "." {
|
||||
return 0
|
||||
}
|
||||
|
||||
l1 := Split(s1)
|
||||
l2 := Split(s2)
|
||||
|
||||
j1 := len(l1) - 1 // end
|
||||
i1 := len(l1) - 2 // start
|
||||
j2 := len(l2) - 1
|
||||
i2 := len(l2) - 2
|
||||
// the second check can be done here: last/only label
|
||||
// before we fall through into the for-loop below
|
||||
if equal(s1[l1[j1]:], s2[l2[j2]:]) {
|
||||
n++
|
||||
} else {
|
||||
return
|
||||
}
|
||||
for {
|
||||
if i1 < 0 || i2 < 0 {
|
||||
break
|
||||
}
|
||||
if equal(s1[l1[i1]:l1[j1]], s2[l2[i2]:l2[j2]]) {
|
||||
n++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
j1--
|
||||
i1--
|
||||
j2--
|
||||
i2--
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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 == "." {
|
||||
return
|
||||
}
|
||||
off := 0
|
||||
end := false
|
||||
for {
|
||||
off, end = NextLabel(s, off)
|
||||
labels++
|
||||
if end {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Split splits a name s into its label indexes.
|
||||
// www.miek.nl. returns []int{0, 4, 9}, www.miek.nl also returns []int{0, 4, 9}.
|
||||
// The root name (.) returns nil. Also see SplitDomainName.
|
||||
// s must be a syntactically valid domain name.
|
||||
func Split(s string) []int {
|
||||
if s == "." {
|
||||
return nil
|
||||
}
|
||||
idx := make([]int, 1, 3)
|
||||
off := 0
|
||||
end := false
|
||||
|
||||
for {
|
||||
off, end = NextLabel(s, off)
|
||||
if end {
|
||||
return idx
|
||||
}
|
||||
idx = append(idx, off)
|
||||
}
|
||||
}
|
||||
|
||||
// NextLabel returns the index of the start of the next label in the
|
||||
// string s starting at offset.
|
||||
// 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) {
|
||||
if s == "" {
|
||||
return 0, true
|
||||
}
|
||||
for i = offset; i < len(s)-1; i++ {
|
||||
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
|
||||
}
|
||||
|
||||
// PrevLabel returns the index of the label when starting from the right and
|
||||
// jumping n labels to the left.
|
||||
// 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
|
||||
}
|
||||
|
||||
l := len(s) - 1
|
||||
if s[l] == '.' {
|
||||
l--
|
||||
}
|
||||
|
||||
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 0, n > 1
|
||||
}
|
||||
|
||||
// equal compares a and b while ignoring case. It returns true when equal otherwise false.
|
||||
func equal(a, b string) bool {
|
||||
// might be lifted into API function.
|
||||
la := len(a)
|
||||
lb := len(b)
|
||||
if la != lb {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := la - 1; i >= 0; i-- {
|
||||
ai := a[i]
|
||||
bi := b[i]
|
||||
if ai >= 'A' && ai <= 'Z' {
|
||||
ai |= 'a' - 'A'
|
||||
}
|
||||
if bi >= 'A' && bi <= 'Z' {
|
||||
bi |= 'a' - 'A'
|
||||
}
|
||||
if ai != bi {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// +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,44 @@
|
|||
// +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)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,812 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// helper functions called from the generated zmsg.go
|
||||
|
||||
// These function are named after the tag to help pack/unpack, if there is no tag it is the name
|
||||
// of the type they pack/unpack (string, int, etc). We prefix all with unpackData or packData, so packDataA or
|
||||
// packDataDomainName.
|
||||
|
||||
func unpackDataA(msg []byte, off int) (net.IP, int, error) {
|
||||
if off+net.IPv4len > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking a"}
|
||||
}
|
||||
a := append(make(net.IP, 0, net.IPv4len), msg[off:off+net.IPv4len]...)
|
||||
off += net.IPv4len
|
||||
return a, off, nil
|
||||
}
|
||||
|
||||
func packDataA(a net.IP, msg []byte, off int) (int, error) {
|
||||
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:
|
||||
// Allowed, for dynamic updates.
|
||||
default:
|
||||
return len(msg), &Error{err: "overflow packing a"}
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func unpackDataAAAA(msg []byte, off int) (net.IP, int, error) {
|
||||
if off+net.IPv6len > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking aaaa"}
|
||||
}
|
||||
aaaa := append(make(net.IP, 0, net.IPv6len), msg[off:off+net.IPv6len]...)
|
||||
off += net.IPv6len
|
||||
return aaaa, off, nil
|
||||
}
|
||||
|
||||
func packDataAAAA(aaaa net.IP, msg []byte, off int) (int, error) {
|
||||
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:
|
||||
// Allowed, dynamic updates.
|
||||
default:
|
||||
return len(msg), &Error{err: "overflow packing aaaa"}
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
// unpackHeader unpacks an RR header, returning the offset to the end of the header and a
|
||||
// re-sliced msg according to the expected length of the RR.
|
||||
func unpackHeader(msg []byte, off int) (rr RR_Header, off1 int, truncmsg []byte, err error) {
|
||||
hdr := RR_Header{}
|
||||
if off == len(msg) {
|
||||
return hdr, off, msg, nil
|
||||
}
|
||||
|
||||
hdr.Name, off, err = UnpackDomainName(msg, off)
|
||||
if err != nil {
|
||||
return hdr, len(msg), msg, err
|
||||
}
|
||||
hdr.Rrtype, off, err = unpackUint16(msg, off)
|
||||
if err != nil {
|
||||
return hdr, len(msg), msg, err
|
||||
}
|
||||
hdr.Class, off, err = unpackUint16(msg, off)
|
||||
if err != nil {
|
||||
return hdr, len(msg), msg, err
|
||||
}
|
||||
hdr.Ttl, off, err = unpackUint32(msg, off)
|
||||
if err != nil {
|
||||
return hdr, len(msg), msg, err
|
||||
}
|
||||
hdr.Rdlength, off, err = unpackUint16(msg, off)
|
||||
if err != nil {
|
||||
return hdr, len(msg), msg, err
|
||||
}
|
||||
msg, err = truncateMsgFromRdlength(msg, off, hdr.Rdlength)
|
||||
return hdr, off, msg, err
|
||||
}
|
||||
|
||||
// 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) 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)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
off, err = packUint16(hdr.Rrtype, msg, off)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
off, err = packUint16(hdr.Class, msg, off)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
off, err = packUint32(hdr.Ttl, msg, off)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
off, err = packUint16(0, msg, off) // The RDLENGTH field will be set later in packRR.
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
// helper helper functions.
|
||||
|
||||
// truncateMsgFromRdLength truncates msg to match the expected length of the RR.
|
||||
// Returns an error if msg is smaller than the expected size.
|
||||
func truncateMsgFromRdlength(msg []byte, off int, rdlength uint16) (truncmsg []byte, err error) {
|
||||
lenrd := off + int(rdlength)
|
||||
if lenrd > len(msg) {
|
||||
return msg, &Error{err: "overflowing header size"}
|
||||
}
|
||||
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 := base32HexNoPadEncoding.DecodedLen(len(s))
|
||||
buf = make([]byte, buflen)
|
||||
n, err := base32HexNoPadEncoding.Decode(buf, s)
|
||||
buf = buf[:n]
|
||||
return
|
||||
}
|
||||
|
||||
func toBase32(b []byte) string {
|
||||
return base32HexNoPadEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
func fromBase64(s []byte) (buf []byte, err error) {
|
||||
buflen := base64.StdEncoding.DecodedLen(len(s))
|
||||
buf = make([]byte, buflen)
|
||||
n, err := base64.StdEncoding.Decode(buf, s)
|
||||
buf = buf[:n]
|
||||
return
|
||||
}
|
||||
|
||||
func toBase64(b []byte) string { return base64.StdEncoding.EncodeToString(b) }
|
||||
|
||||
// dynamicUpdate returns true if the Rdlength is zero.
|
||||
func noRdata(h RR_Header) bool { return h.Rdlength == 0 }
|
||||
|
||||
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 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] = i
|
||||
return off + 1, nil
|
||||
}
|
||||
|
||||
func unpackUint16(msg []byte, off int) (i uint16, off1 int, err error) {
|
||||
if off+2 > len(msg) {
|
||||
return 0, len(msg), &Error{err: "overflow unpacking uint16"}
|
||||
}
|
||||
return binary.BigEndian.Uint16(msg[off:]), off + 2, nil
|
||||
}
|
||||
|
||||
func packUint16(i uint16, msg []byte, off int) (off1 int, err error) {
|
||||
if off+2 > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing uint16"}
|
||||
}
|
||||
binary.BigEndian.PutUint16(msg[off:], i)
|
||||
return off + 2, nil
|
||||
}
|
||||
|
||||
func unpackUint32(msg []byte, off int) (i uint32, off1 int, err error) {
|
||||
if off+4 > len(msg) {
|
||||
return 0, len(msg), &Error{err: "overflow unpacking uint32"}
|
||||
}
|
||||
return binary.BigEndian.Uint32(msg[off:]), off + 4, nil
|
||||
}
|
||||
|
||||
func packUint32(i uint32, msg []byte, off int) (off1 int, err error) {
|
||||
if off+4 > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing uint32"}
|
||||
}
|
||||
binary.BigEndian.PutUint32(msg[off:], i)
|
||||
return off + 4, nil
|
||||
}
|
||||
|
||||
func unpackUint48(msg []byte, off int) (i uint64, off1 int, err error) {
|
||||
if off+6 > len(msg) {
|
||||
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(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
|
||||
}
|
||||
|
||||
func packUint48(i uint64, msg []byte, off int) (off1 int, err error) {
|
||||
if off+6 > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing uint64 as uint48"}
|
||||
}
|
||||
msg[off] = byte(i >> 40)
|
||||
msg[off+1] = byte(i >> 32)
|
||||
msg[off+2] = byte(i >> 24)
|
||||
msg[off+3] = byte(i >> 16)
|
||||
msg[off+4] = byte(i >> 8)
|
||||
msg[off+5] = byte(i)
|
||||
off += 6
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func unpackUint64(msg []byte, off int) (i uint64, off1 int, err error) {
|
||||
if off+8 > len(msg) {
|
||||
return 0, len(msg), &Error{err: "overflow unpacking uint64"}
|
||||
}
|
||||
return binary.BigEndian.Uint64(msg[off:]), off + 8, nil
|
||||
}
|
||||
|
||||
func packUint64(i uint64, msg []byte, off int) (off1 int, err error) {
|
||||
if off+8 > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing uint64"}
|
||||
}
|
||||
binary.BigEndian.PutUint64(msg[off:], i)
|
||||
off += 8
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func unpackString(msg []byte, off int) (string, int, error) {
|
||||
if off+1 > len(msg) {
|
||||
return "", off, &Error{err: "overflow unpacking txt"}
|
||||
}
|
||||
l := int(msg[off])
|
||||
off++
|
||||
if off+l > len(msg) {
|
||||
return "", off, &Error{err: "overflow unpacking txt"}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
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) {
|
||||
txtTmp := make([]byte, 256*4+1)
|
||||
off, err := packTxtString(s, msg, off, txtTmp)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func unpackStringBase32(msg []byte, off, end int) (string, int, error) {
|
||||
if end > len(msg) {
|
||||
return "", len(msg), &Error{err: "overflow unpacking base32"}
|
||||
}
|
||||
s := toBase32(msg[off:end])
|
||||
return s, end, nil
|
||||
}
|
||||
|
||||
func packStringBase32(s string, msg []byte, off int) (int, error) {
|
||||
b32, err := fromBase32([]byte(s))
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
if off+len(b32) > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing base32"}
|
||||
}
|
||||
copy(msg[off:off+len(b32)], b32)
|
||||
off += len(b32)
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func unpackStringBase64(msg []byte, off, end int) (string, int, error) {
|
||||
// Rest of the RR is base64 encoded value, so we don't need an explicit length
|
||||
// to be set. Thus far all RR's that have base64 encoded fields have those as their
|
||||
// last one. What we do need is the end of the RR!
|
||||
if end > len(msg) {
|
||||
return "", len(msg), &Error{err: "overflow unpacking base64"}
|
||||
}
|
||||
s := toBase64(msg[off:end])
|
||||
return s, end, nil
|
||||
}
|
||||
|
||||
func packStringBase64(s string, msg []byte, off int) (int, error) {
|
||||
b64, err := fromBase64([]byte(s))
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
if off+len(b64) > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing base64"}
|
||||
}
|
||||
copy(msg[off:off+len(b64)], b64)
|
||||
off += len(b64)
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func unpackStringHex(msg []byte, off, end int) (string, int, error) {
|
||||
// Rest of the RR is hex encoded value, so we don't need an explicit length
|
||||
// to be set. NSEC and TSIG have hex fields with a length field.
|
||||
// What we do need is the end of the RR!
|
||||
if end > len(msg) {
|
||||
return "", len(msg), &Error{err: "overflow unpacking hex"}
|
||||
}
|
||||
|
||||
s := hex.EncodeToString(msg[off:end])
|
||||
return s, end, nil
|
||||
}
|
||||
|
||||
func packStringHex(s string, msg []byte, off int) (int, error) {
|
||||
h, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
if off+len(h) > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing hex"}
|
||||
}
|
||||
copy(msg[off:off+len(h)], h)
|
||||
off += len(h)
|
||||
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 {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
return txt, off, nil
|
||||
}
|
||||
|
||||
func packStringTxt(s []string, msg []byte, off int) (int, error) {
|
||||
txtTmp := make([]byte, 256*4+1) // If the whole string consists out of \DDD we need this many.
|
||||
off, err := packTxt(s, msg, off, txtTmp)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func unpackDataOpt(msg []byte, off int) ([]EDNS0, int, error) {
|
||||
var edns []EDNS0
|
||||
Option:
|
||||
var code uint16
|
||||
if off+4 > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking opt"}
|
||||
}
|
||||
code = binary.BigEndian.Uint16(msg[off:])
|
||||
off += 2
|
||||
optlen := binary.BigEndian.Uint16(msg[off:])
|
||||
off += 2
|
||||
if off+int(optlen) > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking opt"}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
return edns, off, nil
|
||||
}
|
||||
|
||||
func packDataOpt(options []EDNS0, msg []byte, off int) (int, error) {
|
||||
for _, el := range options {
|
||||
b, err := el.pack()
|
||||
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) {
|
||||
return len(msg), &Error{err: "overflow packing opt"}
|
||||
}
|
||||
// Actual data
|
||||
copy(msg[off:off+len(b)], b)
|
||||
off += len(b)
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func unpackStringOctet(msg []byte, off int) (string, int, error) {
|
||||
s := string(msg[off:])
|
||||
return s, len(msg), nil
|
||||
}
|
||||
|
||||
func packStringOctet(s string, msg []byte, off int) (int, error) {
|
||||
txtTmp := make([]byte, 256*4+1)
|
||||
off, err := packOctetString(s, msg, off, txtTmp)
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
|
||||
var nsec []uint16
|
||||
length, window, lastwindow := 0, 0, -1
|
||||
for off < len(msg) {
|
||||
if off+2 > len(msg) {
|
||||
return nsec, len(msg), &Error{err: "overflow unpacking nsecx"}
|
||||
}
|
||||
window = int(msg[off])
|
||||
length = int(msg[off+1])
|
||||
off += 2
|
||||
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"}
|
||||
}
|
||||
if length == 0 {
|
||||
// RFC 4034: Blocks with no types present MUST NOT be included.
|
||||
return nsec, len(msg), &Error{err: "empty NSEC block"}
|
||||
}
|
||||
if length > 32 {
|
||||
return nsec, len(msg), &Error{err: "NSEC block too long"}
|
||||
}
|
||||
if off+length > len(msg) {
|
||||
return nsec, len(msg), &Error{err: "overflowing NSEC block"}
|
||||
}
|
||||
|
||||
// Walk the bytes in the window and extract the type bits
|
||||
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))
|
||||
}
|
||||
if b&0x40 == 0x40 {
|
||||
nsec = append(nsec, uint16(window*256+j*8+1))
|
||||
}
|
||||
if b&0x20 == 0x20 {
|
||||
nsec = append(nsec, uint16(window*256+j*8+2))
|
||||
}
|
||||
if b&0x10 == 0x10 {
|
||||
nsec = append(nsec, uint16(window*256+j*8+3))
|
||||
}
|
||||
if b&0x8 == 0x8 {
|
||||
nsec = append(nsec, uint16(window*256+j*8+4))
|
||||
}
|
||||
if b&0x4 == 0x4 {
|
||||
nsec = append(nsec, uint16(window*256+j*8+5))
|
||||
}
|
||||
if b&0x2 == 0x2 {
|
||||
nsec = append(nsec, uint16(window*256+j*8+6))
|
||||
}
|
||||
if b&0x1 == 0x1 {
|
||||
nsec = append(nsec, uint16(window*256+j*8+7))
|
||||
}
|
||||
}
|
||||
off += length
|
||||
lastwindow = window
|
||||
}
|
||||
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 _, t := range bitmap {
|
||||
window := t / 256
|
||||
length := (t-window*256)/8 + 1
|
||||
if window > lastwindow && lastlength != 0 { // New window, jump to the new offset
|
||||
off += int(lastlength) + 2
|
||||
lastlength = 0
|
||||
}
|
||||
if window < lastwindow || length < lastlength {
|
||||
return len(msg), &Error{err: "nsec bits out of order"}
|
||||
}
|
||||
if off+2+int(length) > len(msg) {
|
||||
return len(msg), &Error{err: "overflow packing nsec"}
|
||||
}
|
||||
// Setting the window #
|
||||
msg[off] = byte(window)
|
||||
// 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))
|
||||
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
|
||||
s string
|
||||
err error
|
||||
)
|
||||
if end > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking domain names"}
|
||||
}
|
||||
for off < end {
|
||||
s, off, err = UnpackDomainName(msg, off)
|
||||
if err != nil {
|
||||
return servers, len(msg), err
|
||||
}
|
||||
servers = append(servers, s)
|
||||
}
|
||||
return servers, off, nil
|
||||
}
|
||||
|
||||
func packDataDomainNames(names []string, msg []byte, off int, compression compressionMap, compress bool) (int, error) {
|
||||
var err error
|
||||
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,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,95 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
if ha != SHA1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
wireSalt := make([]byte, hex.DecodedLen(len(salt)))
|
||||
n, err := packStringHex(salt, wireSalt, 0)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
wireSalt = wireSalt[:n]
|
||||
|
||||
name := make([]byte, 255)
|
||||
off, err := PackDomainName(strings.ToLower(label), name, 0, nil, false)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
name = name[:off]
|
||||
|
||||
s := sha1.New()
|
||||
// k = 0
|
||||
s.Write(name)
|
||||
s.Write(wireSalt)
|
||||
nsec3 := s.Sum(nil)
|
||||
|
||||
// k > 0
|
||||
for k := uint16(0); k < iter; k++ {
|
||||
s.Reset()
|
||||
s.Write(nsec3)
|
||||
s.Write(wireSalt)
|
||||
nsec3 = s.Sum(nsec3[:0])
|
||||
}
|
||||
|
||||
return toBase32(nsec3)
|
||||
}
|
||||
|
||||
// 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)
|
||||
labelIndices := Split(owner)
|
||||
if len(labelIndices) < 2 {
|
||||
return false
|
||||
}
|
||||
ownerHash := owner[:labelIndices[1]-1]
|
||||
ownerZone := owner[labelIndices[1]:]
|
||||
if !IsSubDomain(ownerZone, strings.ToUpper(name)) { // name is outside owner zone
|
||||
return false
|
||||
}
|
||||
|
||||
nextHash := rr.NextDomain
|
||||
|
||||
// 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
|
||||
return true
|
||||
}
|
||||
return nameHash < nextHash // if nameHash is before beginning of zone it is covered
|
||||
}
|
||||
if nameHash < ownerHash { // nameHash is before ownerHash, not covered
|
||||
return false
|
||||
}
|
||||
return nameHash < nextHash // if nameHash is before nextHash is it covered (between ownerHash and nextHash)
|
||||
}
|
||||
|
||||
// Match returns true if a name matches the NSEC3 record
|
||||
func (rr *NSEC3) Match(name string) bool {
|
||||
nameHash := HashName(name, rr.Hash, rr.Iterations, rr.Salt)
|
||||
owner := strings.ToUpper(rr.Hdr.Name)
|
||||
labelIndices := Split(owner)
|
||||
if len(labelIndices) < 2 {
|
||||
return false
|
||||
}
|
||||
ownerHash := owner[:labelIndices[1]-1]
|
||||
ownerZone := owner[labelIndices[1]:]
|
||||
if !IsSubDomain(ownerZone, strings.ToUpper(name)) { // name is outside owner zone
|
||||
return false
|
||||
}
|
||||
if ownerHash == nameHash {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package dns
|
||||
|
||||
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 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.
|
||||
Unpack([]byte) (int, error)
|
||||
// Copy copies the Rdata into the PrivateRdata argument.
|
||||
Copy(PrivateRdata) error
|
||||
// Len returns the length in octets of the Rdata.
|
||||
Len() int
|
||||
}
|
||||
|
||||
// PrivateRR represents an RR that uses a PrivateRdata user-defined type.
|
||||
// It mocks normal RRs and implements dns.RR interface.
|
||||
type PrivateRR struct {
|
||||
Hdr RR_Header
|
||||
Data PrivateRdata
|
||||
|
||||
generator func() PrivateRdata // for copy
|
||||
}
|
||||
|
||||
// Header return the RR header of r.
|
||||
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(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 := &PrivateRR{r.Hdr, r.generator(), r.generator}
|
||||
|
||||
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 compressionMap, compress bool) (int, error) {
|
||||
n, err := r.Data.Pack(msg[off:])
|
||||
if err != nil {
|
||||
return len(msg), err
|
||||
}
|
||||
off += n
|
||||
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(), generator} }
|
||||
TypeToString[rtype] = rtypestr
|
||||
StringToType[rtypestr] = rtype
|
||||
}
|
||||
|
||||
// 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(StringToType, rtypestr)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package dns
|
||||
|
||||
// StringToType is the reverse of TypeToString, needed for string parsing.
|
||||
var StringToType = reverseInt16(TypeToString)
|
||||
|
||||
// StringToClass is the reverse of ClassToString, needed for string parsing.
|
||||
var StringToClass = reverseInt16(ClassToString)
|
||||
|
||||
// StringToOpcode is a map of opcodes to strings.
|
||||
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))
|
||||
for u, s := range m {
|
||||
n[s] = u
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func reverseInt16(m map[uint16]string) map[string]uint16 {
|
||||
n := make(map[string]uint16, len(m))
|
||||
for u, s := range m {
|
||||
n[s] = u
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func reverseInt(m map[int]string) map[string]int {
|
||||
n := make(map[string]int, len(m))
|
||||
for u, s := range m {
|
||||
n[s] = u
|
||||
}
|
||||
return n
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package dns
|
||||
|
||||
// Dedup removes identical RRs from rrs. It preserves the original ordering.
|
||||
// The lowest TTL of any duplicates is used in the remaining one. Dedup modifies
|
||||
// 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)
|
||||
}
|
||||
// Save the keys, so we don't have to call normalizedString twice.
|
||||
keys := make([]*string, 0, len(rrs))
|
||||
|
||||
for _, r := range rrs {
|
||||
key := normalizedString(r)
|
||||
keys = append(keys, &key)
|
||||
if mr, ok := m[key]; ok {
|
||||
// Shortest TTL wins.
|
||||
rh, mrh := r.Header(), mr.Header()
|
||||
if mrh.Ttl > rh.Ttl {
|
||||
mrh.Ttl = rh.Ttl
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
m[key] = r
|
||||
}
|
||||
// If the length of the result map equals the amount of RRs we got,
|
||||
// it means they were all different. We can then just return the original rrset.
|
||||
if len(m) == len(rrs) {
|
||||
return rrs
|
||||
}
|
||||
|
||||
j := 0
|
||||
for i, r := range rrs {
|
||||
// If keys[i] lives in the map, we should copy and remove it.
|
||||
if _, ok := m[*keys[i]]; ok {
|
||||
delete(m, *keys[i])
|
||||
rrs[j] = r
|
||||
j++
|
||||
}
|
||||
|
||||
if len(m) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return rrs[:j]
|
||||
}
|
||||
|
||||
// normalizedString returns a normalized string from r. The TTL
|
||||
// is removed and the domain name is lowercased. We go from this:
|
||||
// DomainName<TAB>TTL<TAB>CLASS<TAB>TYPE<TAB>RDATA to:
|
||||
// lowercasename<TAB>CLASS<TAB>TYPE...
|
||||
func normalizedString(r RR) string {
|
||||
// A string Go DNS makes has: domainname<TAB>TTL<TAB>...
|
||||
b := []byte(r.String())
|
||||
|
||||
// find the first non-escaped tab, then another, so we capture where the TTL lives.
|
||||
esc := false
|
||||
ttlStart, ttlEnd := 0, 0
|
||||
for i := 0; i < len(b) && ttlEnd == 0; i++ {
|
||||
switch {
|
||||
case b[i] == '\\':
|
||||
esc = !esc
|
||||
case b[i] == '\t' && !esc:
|
||||
if ttlStart == 0 {
|
||||
ttlStart = i
|
||||
continue
|
||||
}
|
||||
if ttlEnd == 0 {
|
||||
ttlEnd = i
|
||||
}
|
||||
case b[i] >= 'A' && b[i] <= 'Z' && !esc:
|
||||
b[i] += 32
|
||||
default:
|
||||
esc = false
|
||||
}
|
||||
}
|
||||
|
||||
// remove TTL.
|
||||
copy(b[ttlStart:], b[ttlEnd:])
|
||||
cut := ttlEnd - ttlStart
|
||||
return string(b[:len(b)-cut])
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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,836 @@
|
|||
// DNS server implementation.
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Default maximum number of TCP queries before we close the socket.
|
||||
const maxTCPQueries = 128
|
||||
|
||||
// aLongTimeAgo is a non-zero time, far in the past, used for
|
||||
// immediate cancelation of network operations.
|
||||
var aLongTimeAgo = time.Unix(1, 0)
|
||||
|
||||
// Handler is implemented by any value that implements ServeDNS.
|
||||
type Handler interface {
|
||||
ServeDNS(w ResponseWriter, r *Msg)
|
||||
}
|
||||
|
||||
// The HandlerFunc type is an adapter to allow the use of
|
||||
// ordinary functions as DNS handlers. If f is a function
|
||||
// with the appropriate signature, HandlerFunc(f) is a
|
||||
// Handler object that calls f.
|
||||
type HandlerFunc func(ResponseWriter, *Msg)
|
||||
|
||||
// ServeDNS calls f(w, r).
|
||||
func (f HandlerFunc) ServeDNS(w ResponseWriter, r *Msg) {
|
||||
f(w, r)
|
||||
}
|
||||
|
||||
// A ResponseWriter interface is used by an DNS handler to
|
||||
// construct an DNS response.
|
||||
type ResponseWriter interface {
|
||||
// LocalAddr returns the net.Addr of the server
|
||||
LocalAddr() net.Addr
|
||||
// RemoteAddr returns the net.Addr of the client that sent the current request.
|
||||
RemoteAddr() net.Addr
|
||||
// WriteMsg writes a reply back to the client.
|
||||
WriteMsg(*Msg) error
|
||||
// Write writes a raw buffer back to the client.
|
||||
Write([]byte) (int, error)
|
||||
// Close closes the connection.
|
||||
Close() error
|
||||
// TsigStatus returns the status of the Tsig.
|
||||
TsigStatus() error
|
||||
// TsigTimersOnly sets the tsig timers only boolean.
|
||||
TsigTimersOnly(bool)
|
||||
// Hijack lets the caller take over the connection.
|
||||
// After a call to Hijack(), the DNS package will not do anything with the connection.
|
||||
Hijack()
|
||||
}
|
||||
|
||||
// A ConnectionStater interface is used by a DNS Handler to access TLS connection state
|
||||
// when available.
|
||||
type ConnectionStater interface {
|
||||
ConnectionState() *tls.ConnectionState
|
||||
}
|
||||
|
||||
type response struct {
|
||||
closed bool // connection has been closed
|
||||
hijacked bool // connection has been hijacked by handler
|
||||
tsigTimersOnly bool
|
||||
tsigStatus error
|
||||
tsigRequestMAC string
|
||||
tsigProvider TsigProvider
|
||||
udp net.PacketConn // i/o connection if UDP was used
|
||||
tcp net.Conn // i/o connection if TCP was used
|
||||
udpSession *SessionUDP // oob data to get egress interface right
|
||||
pcSession net.Addr // address to use when writing to a generic net.PacketConn
|
||||
writer Writer // writer to output the raw DNS bits
|
||||
}
|
||||
|
||||
// handleRefused returns a HandlerFunc that returns REFUSED for every request it gets.
|
||||
func handleRefused(w ResponseWriter, r *Msg) {
|
||||
m := new(Msg)
|
||||
m.SetRcode(r, RcodeRefused)
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
// HandleFailed returns a HandlerFunc that returns SERVFAIL for every request it gets.
|
||||
// Deprecated: This function is going away.
|
||||
func HandleFailed(w ResponseWriter, r *Msg) {
|
||||
m := new(Msg)
|
||||
m.SetRcode(r, RcodeServerFailure)
|
||||
// does not matter if this write fails
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
// ListenAndServe Starts a server on address and network specified Invoke handler
|
||||
// for incoming queries.
|
||||
func ListenAndServe(addr string, network string, handler Handler) error {
|
||||
server := &Server{Addr: addr, Net: network, Handler: handler}
|
||||
return server.ListenAndServe()
|
||||
}
|
||||
|
||||
// ListenAndServeTLS acts like http.ListenAndServeTLS, more information in
|
||||
// http://golang.org/pkg/net/http/#ListenAndServeTLS
|
||||
func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config := tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
Addr: addr,
|
||||
Net: "tcp-tls",
|
||||
TLSConfig: &config,
|
||||
Handler: handler,
|
||||
}
|
||||
|
||||
return server.ListenAndServe()
|
||||
}
|
||||
|
||||
// ActivateAndServe activates a server with a listener from systemd,
|
||||
// l and p should not both be non-nil.
|
||||
// If both l and p are not nil only p will be used.
|
||||
// Invoke handler for incoming queries.
|
||||
func ActivateAndServe(l net.Listener, p net.PacketConn, handler Handler) error {
|
||||
server := &Server{Listener: l, PacketConn: p, Handler: handler}
|
||||
return server.ActivateAndServe()
|
||||
}
|
||||
|
||||
// Writer writes raw DNS messages; each call to Write should send an entire message.
|
||||
type Writer interface {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// Reader reads raw DNS messages; each call to ReadTCP or ReadUDP should return an entire message.
|
||||
type Reader interface {
|
||||
// ReadTCP reads a raw message from a TCP connection. Implementations may alter
|
||||
// connection properties, for example the read-deadline.
|
||||
ReadTCP(conn net.Conn, timeout time.Duration) ([]byte, error)
|
||||
// ReadUDP reads a raw message from a UDP connection. Implementations may alter
|
||||
// connection properties, for example the read-deadline.
|
||||
ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error)
|
||||
}
|
||||
|
||||
// PacketConnReader is an optional interface that Readers can implement to support using generic net.PacketConns.
|
||||
type PacketConnReader interface {
|
||||
Reader
|
||||
|
||||
// ReadPacketConn reads a raw message from a generic net.PacketConn UDP connection. Implementations may
|
||||
// alter connection properties, for example the read-deadline.
|
||||
ReadPacketConn(conn net.PacketConn, timeout time.Duration) ([]byte, net.Addr, error)
|
||||
}
|
||||
|
||||
// defaultReader is an adapter for the Server struct that implements the Reader and
|
||||
// PacketConnReader interfaces using the readTCP, readUDP and readPacketConn funcs
|
||||
// of the embedded Server.
|
||||
type defaultReader struct {
|
||||
*Server
|
||||
}
|
||||
|
||||
var _ PacketConnReader = defaultReader{}
|
||||
|
||||
func (dr defaultReader) ReadTCP(conn net.Conn, timeout time.Duration) ([]byte, error) {
|
||||
return dr.readTCP(conn, timeout)
|
||||
}
|
||||
|
||||
func (dr defaultReader) ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) {
|
||||
return dr.readUDP(conn, timeout)
|
||||
}
|
||||
|
||||
func (dr defaultReader) ReadPacketConn(conn net.PacketConn, timeout time.Duration) ([]byte, net.Addr, error) {
|
||||
return dr.readPacketConn(conn, timeout)
|
||||
}
|
||||
|
||||
// DecorateReader is a decorator hook for extending or supplanting the functionality of a Reader.
|
||||
// Implementations should never return a nil Reader.
|
||||
// Readers should also implement the optional PacketConnReader interface.
|
||||
// PacketConnReader is required to use a generic net.PacketConn.
|
||||
type DecorateReader func(Reader) Reader
|
||||
|
||||
// DecorateWriter is a decorator hook for extending or supplanting the functionality of a Writer.
|
||||
// Implementations should never return a nil Writer.
|
||||
type DecorateWriter func(Writer) Writer
|
||||
|
||||
// A Server defines parameters for running an DNS server.
|
||||
type Server struct {
|
||||
// Address to listen on, ":dns" if empty.
|
||||
Addr string
|
||||
// if "tcp" or "tcp-tls" (DNS over TLS) it will invoke a TCP listener, otherwise an UDP one
|
||||
Net string
|
||||
// TCP Listener to use, this is to aid in systemd's socket activation.
|
||||
Listener net.Listener
|
||||
// TLS connection configuration
|
||||
TLSConfig *tls.Config
|
||||
// UDP "Listener" to use, this is to aid in systemd's socket activation.
|
||||
PacketConn net.PacketConn
|
||||
// Handler to invoke, dns.DefaultServeMux if nil.
|
||||
Handler Handler
|
||||
// Default buffer size to use to read incoming UDP messages. If not set
|
||||
// it defaults to MinMsgSize (512 B).
|
||||
UDPSize int
|
||||
// The net.Conn.SetReadTimeout value for new connections, defaults to 2 * time.Second.
|
||||
ReadTimeout time.Duration
|
||||
// The net.Conn.SetWriteTimeout value for new connections, defaults to 2 * time.Second.
|
||||
WriteTimeout time.Duration
|
||||
// TCP idle timeout for multiple queries, if nil, defaults to 8 * time.Second (RFC 5966).
|
||||
IdleTimeout func() time.Duration
|
||||
// An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
|
||||
TsigProvider TsigProvider
|
||||
// Secret(s) for Tsig map[<zonename>]<base64 secret>. The zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2).
|
||||
TsigSecret map[string]string
|
||||
// If NotifyStartedFunc is set it is called once the server has started listening.
|
||||
NotifyStartedFunc func()
|
||||
// DecorateReader is optional, allows customization of the process that reads raw DNS messages.
|
||||
DecorateReader DecorateReader
|
||||
// DecorateWriter is optional, allows customization of the process that writes raw DNS messages.
|
||||
DecorateWriter DecorateWriter
|
||||
// Maximum number of TCP queries before we close the socket. Default is maxTCPQueries (unlimited if -1).
|
||||
MaxTCPQueries int
|
||||
// Whether to set the SO_REUSEPORT socket option, allowing multiple listeners to be bound to a single address.
|
||||
// It is only supported on go1.11+ and when using ListenAndServe.
|
||||
ReusePort bool
|
||||
// AcceptMsgFunc will check the incoming message and will reject it early in the process.
|
||||
// By default DefaultMsgAcceptFunc will be used.
|
||||
MsgAcceptFunc MsgAcceptFunc
|
||||
|
||||
// Shutdown handling
|
||||
lock sync.RWMutex
|
||||
started bool
|
||||
shutdown chan struct{}
|
||||
conns map[net.Conn]struct{}
|
||||
|
||||
// A pool for UDP message buffers.
|
||||
udpPool sync.Pool
|
||||
}
|
||||
|
||||
func (srv *Server) tsigProvider() TsigProvider {
|
||||
if srv.TsigProvider != nil {
|
||||
return srv.TsigProvider
|
||||
}
|
||||
if srv.TsigSecret != nil {
|
||||
return tsigSecretProvider(srv.TsigSecret)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) isStarted() bool {
|
||||
srv.lock.RLock()
|
||||
started := srv.started
|
||||
srv.lock.RUnlock()
|
||||
return started
|
||||
}
|
||||
|
||||
func makeUDPBuffer(size int) func() interface{} {
|
||||
return func() interface{} {
|
||||
return make([]byte, size)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Server) init() {
|
||||
srv.shutdown = make(chan struct{})
|
||||
srv.conns = make(map[net.Conn]struct{})
|
||||
|
||||
if srv.UDPSize == 0 {
|
||||
srv.UDPSize = MinMsgSize
|
||||
}
|
||||
if srv.MsgAcceptFunc == nil {
|
||||
srv.MsgAcceptFunc = DefaultMsgAcceptFunc
|
||||
}
|
||||
if srv.Handler == nil {
|
||||
srv.Handler = DefaultServeMux
|
||||
}
|
||||
|
||||
srv.udpPool.New = makeUDPBuffer(srv.UDPSize)
|
||||
}
|
||||
|
||||
func unlockOnce(l sync.Locker) func() {
|
||||
var once sync.Once
|
||||
return func() { once.Do(l.Unlock) }
|
||||
}
|
||||
|
||||
// ListenAndServe starts a nameserver on the configured address in *Server.
|
||||
func (srv *Server) ListenAndServe() error {
|
||||
unlock := unlockOnce(&srv.lock)
|
||||
srv.lock.Lock()
|
||||
defer unlock()
|
||||
|
||||
if srv.started {
|
||||
return &Error{err: "server already started"}
|
||||
}
|
||||
|
||||
addr := srv.Addr
|
||||
if addr == "" {
|
||||
addr = ":domain"
|
||||
}
|
||||
|
||||
srv.init()
|
||||
|
||||
switch srv.Net {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
l, err := listenTCP(srv.Net, addr, srv.ReusePort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srv.Listener = l
|
||||
srv.started = true
|
||||
unlock()
|
||||
return srv.serveTCP(l)
|
||||
case "tcp-tls", "tcp4-tls", "tcp6-tls":
|
||||
if srv.TLSConfig == nil || (len(srv.TLSConfig.Certificates) == 0 && srv.TLSConfig.GetCertificate == nil) {
|
||||
return errors.New("dns: neither Certificates nor GetCertificate set in Config")
|
||||
}
|
||||
network := strings.TrimSuffix(srv.Net, "-tls")
|
||||
l, err := listenTCP(network, addr, srv.ReusePort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l = tls.NewListener(l, srv.TLSConfig)
|
||||
srv.Listener = l
|
||||
srv.started = true
|
||||
unlock()
|
||||
return srv.serveTCP(l)
|
||||
case "udp", "udp4", "udp6":
|
||||
l, err := listenUDP(srv.Net, addr, srv.ReusePort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u := l.(*net.UDPConn)
|
||||
if e := setUDPSocketOptions(u); e != nil {
|
||||
u.Close()
|
||||
return e
|
||||
}
|
||||
srv.PacketConn = l
|
||||
srv.started = true
|
||||
unlock()
|
||||
return srv.serveUDP(u)
|
||||
}
|
||||
return &Error{err: "bad network"}
|
||||
}
|
||||
|
||||
// ActivateAndServe starts a nameserver with the PacketConn or Listener
|
||||
// configured in *Server. Its main use is to start a server from systemd.
|
||||
func (srv *Server) ActivateAndServe() error {
|
||||
unlock := unlockOnce(&srv.lock)
|
||||
srv.lock.Lock()
|
||||
defer unlock()
|
||||
|
||||
if srv.started {
|
||||
return &Error{err: "server already started"}
|
||||
}
|
||||
|
||||
srv.init()
|
||||
|
||||
if srv.PacketConn != nil {
|
||||
// Check PacketConn interface's type is valid and value
|
||||
// is not nil
|
||||
if t, ok := srv.PacketConn.(*net.UDPConn); ok && t != nil {
|
||||
if e := setUDPSocketOptions(t); e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
srv.started = true
|
||||
unlock()
|
||||
return srv.serveUDP(srv.PacketConn)
|
||||
}
|
||||
if srv.Listener != nil {
|
||||
srv.started = true
|
||||
unlock()
|
||||
return srv.serveTCP(srv.Listener)
|
||||
}
|
||||
return &Error{err: "bad listeners"}
|
||||
}
|
||||
|
||||
// Shutdown shuts down a server. After a call to Shutdown, ListenAndServe and
|
||||
// ActivateAndServe will return.
|
||||
func (srv *Server) Shutdown() error {
|
||||
return srv.ShutdownContext(context.Background())
|
||||
}
|
||||
|
||||
// ShutdownContext shuts down a server. After a call to ShutdownContext,
|
||||
// ListenAndServe and ActivateAndServe will return.
|
||||
//
|
||||
// A context.Context may be passed to limit how long to wait for connections
|
||||
// to terminate.
|
||||
func (srv *Server) ShutdownContext(ctx context.Context) error {
|
||||
srv.lock.Lock()
|
||||
if !srv.started {
|
||||
srv.lock.Unlock()
|
||||
return &Error{err: "server not started"}
|
||||
}
|
||||
|
||||
srv.started = false
|
||||
|
||||
if srv.PacketConn != nil {
|
||||
srv.PacketConn.SetReadDeadline(aLongTimeAgo) // Unblock reads
|
||||
}
|
||||
|
||||
if srv.Listener != nil {
|
||||
srv.Listener.Close()
|
||||
}
|
||||
|
||||
for rw := range srv.conns {
|
||||
rw.SetReadDeadline(aLongTimeAgo) // Unblock reads
|
||||
}
|
||||
|
||||
srv.lock.Unlock()
|
||||
|
||||
if testShutdownNotify != nil {
|
||||
testShutdownNotify.Broadcast()
|
||||
}
|
||||
|
||||
var ctxErr error
|
||||
select {
|
||||
case <-srv.shutdown:
|
||||
case <-ctx.Done():
|
||||
ctxErr = ctx.Err()
|
||||
}
|
||||
|
||||
if srv.PacketConn != nil {
|
||||
srv.PacketConn.Close()
|
||||
}
|
||||
|
||||
return ctxErr
|
||||
}
|
||||
|
||||
var testShutdownNotify *sync.Cond
|
||||
|
||||
// getReadTimeout is a helper func to use system timeout if server did not intend to change it.
|
||||
func (srv *Server) getReadTimeout() time.Duration {
|
||||
if srv.ReadTimeout != 0 {
|
||||
return srv.ReadTimeout
|
||||
}
|
||||
return dnsTimeout
|
||||
}
|
||||
|
||||
// serveTCP starts a TCP listener for the server.
|
||||
func (srv *Server) serveTCP(l net.Listener) error {
|
||||
defer l.Close()
|
||||
|
||||
if srv.NotifyStartedFunc != nil {
|
||||
srv.NotifyStartedFunc()
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer func() {
|
||||
wg.Wait()
|
||||
close(srv.shutdown)
|
||||
}()
|
||||
|
||||
for srv.isStarted() {
|
||||
rw, err := l.Accept()
|
||||
if err != nil {
|
||||
if !srv.isStarted() {
|
||||
return nil
|
||||
}
|
||||
if neterr, ok := err.(net.Error); ok && neterr.Temporary() {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
srv.lock.Lock()
|
||||
// Track the connection to allow unblocking reads on shutdown.
|
||||
srv.conns[rw] = struct{}{}
|
||||
srv.lock.Unlock()
|
||||
wg.Add(1)
|
||||
go srv.serveTCPConn(&wg, rw)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// serveUDP starts a UDP listener for the server.
|
||||
func (srv *Server) serveUDP(l net.PacketConn) error {
|
||||
defer l.Close()
|
||||
|
||||
reader := Reader(defaultReader{srv})
|
||||
if srv.DecorateReader != nil {
|
||||
reader = srv.DecorateReader(reader)
|
||||
}
|
||||
|
||||
lUDP, isUDP := l.(*net.UDPConn)
|
||||
readerPC, canPacketConn := reader.(PacketConnReader)
|
||||
if !isUDP && !canPacketConn {
|
||||
return &Error{err: "PacketConnReader was not implemented on Reader returned from DecorateReader but is required for net.PacketConn"}
|
||||
}
|
||||
|
||||
if srv.NotifyStartedFunc != nil {
|
||||
srv.NotifyStartedFunc()
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer func() {
|
||||
wg.Wait()
|
||||
close(srv.shutdown)
|
||||
}()
|
||||
|
||||
rtimeout := srv.getReadTimeout()
|
||||
// deadline is not used here
|
||||
for srv.isStarted() {
|
||||
var (
|
||||
m []byte
|
||||
sPC net.Addr
|
||||
sUDP *SessionUDP
|
||||
err error
|
||||
)
|
||||
if isUDP {
|
||||
m, sUDP, err = reader.ReadUDP(lUDP, rtimeout)
|
||||
} else {
|
||||
m, sPC, err = readerPC.ReadPacketConn(l, rtimeout)
|
||||
}
|
||||
if err != nil {
|
||||
if !srv.isStarted() {
|
||||
return nil
|
||||
}
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
if len(m) < headerSize {
|
||||
if cap(m) == srv.UDPSize {
|
||||
srv.udpPool.Put(m[:srv.UDPSize])
|
||||
}
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go srv.serveUDPPacket(&wg, m, l, sUDP, sPC)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serve a new TCP connection.
|
||||
func (srv *Server) serveTCPConn(wg *sync.WaitGroup, rw net.Conn) {
|
||||
w := &response{tsigProvider: srv.tsigProvider(), tcp: rw}
|
||||
if srv.DecorateWriter != nil {
|
||||
w.writer = srv.DecorateWriter(w)
|
||||
} else {
|
||||
w.writer = w
|
||||
}
|
||||
|
||||
reader := Reader(defaultReader{srv})
|
||||
if srv.DecorateReader != nil {
|
||||
reader = srv.DecorateReader(reader)
|
||||
}
|
||||
|
||||
idleTimeout := tcpIdleTimeout
|
||||
if srv.IdleTimeout != nil {
|
||||
idleTimeout = srv.IdleTimeout()
|
||||
}
|
||||
|
||||
timeout := srv.getReadTimeout()
|
||||
|
||||
limit := srv.MaxTCPQueries
|
||||
if limit == 0 {
|
||||
limit = maxTCPQueries
|
||||
}
|
||||
|
||||
for q := 0; (q < limit || limit == -1) && srv.isStarted(); q++ {
|
||||
m, err := reader.ReadTCP(w.tcp, timeout)
|
||||
if err != nil {
|
||||
// TODO(tmthrgd): handle error
|
||||
break
|
||||
}
|
||||
srv.serveDNS(m, w)
|
||||
if w.closed {
|
||||
break // Close() was called
|
||||
}
|
||||
if w.hijacked {
|
||||
break // client will call Close() themselves
|
||||
}
|
||||
// The first read uses the read timeout, the rest use the
|
||||
// idle timeout.
|
||||
timeout = idleTimeout
|
||||
}
|
||||
|
||||
if !w.hijacked {
|
||||
w.Close()
|
||||
}
|
||||
|
||||
srv.lock.Lock()
|
||||
delete(srv.conns, w.tcp)
|
||||
srv.lock.Unlock()
|
||||
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
// Serve a new UDP request.
|
||||
func (srv *Server) serveUDPPacket(wg *sync.WaitGroup, m []byte, u net.PacketConn, udpSession *SessionUDP, pcSession net.Addr) {
|
||||
w := &response{tsigProvider: srv.tsigProvider(), udp: u, udpSession: udpSession, pcSession: pcSession}
|
||||
if srv.DecorateWriter != nil {
|
||||
w.writer = srv.DecorateWriter(w)
|
||||
} else {
|
||||
w.writer = w
|
||||
}
|
||||
|
||||
srv.serveDNS(m, w)
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func (srv *Server) serveDNS(m []byte, w *response) {
|
||||
dh, off, err := unpackMsgHdr(m, 0)
|
||||
if err != nil {
|
||||
// Let client hang, they are sending crap; any reply can be used to amplify.
|
||||
return
|
||||
}
|
||||
|
||||
req := new(Msg)
|
||||
req.setHdr(dh)
|
||||
|
||||
switch action := srv.MsgAcceptFunc(dh); action {
|
||||
case MsgAccept:
|
||||
if req.unpack(dh, m, off) == nil {
|
||||
break
|
||||
}
|
||||
|
||||
fallthrough
|
||||
case MsgReject, MsgRejectNotImplemented:
|
||||
opcode := req.Opcode
|
||||
req.SetRcodeFormatError(req)
|
||||
req.Zero = false
|
||||
if action == MsgRejectNotImplemented {
|
||||
req.Opcode = opcode
|
||||
req.Rcode = RcodeNotImplemented
|
||||
}
|
||||
|
||||
// Are we allowed to delete any OPT records here?
|
||||
req.Ns, req.Answer, req.Extra = nil, nil, nil
|
||||
|
||||
w.WriteMsg(req)
|
||||
fallthrough
|
||||
case MsgIgnore:
|
||||
if w.udp != nil && cap(m) == srv.UDPSize {
|
||||
srv.udpPool.Put(m[:srv.UDPSize])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.tsigStatus = nil
|
||||
if w.tsigProvider != nil {
|
||||
if t := req.IsTsig(); t != nil {
|
||||
w.tsigStatus = tsigVerifyProvider(m, w.tsigProvider, "", false)
|
||||
w.tsigTimersOnly = false
|
||||
w.tsigRequestMAC = t.MAC
|
||||
}
|
||||
}
|
||||
|
||||
if w.udp != nil && cap(m) == srv.UDPSize {
|
||||
srv.udpPool.Put(m[:srv.UDPSize])
|
||||
}
|
||||
|
||||
srv.Handler.ServeDNS(w, req) // Writes back to the client
|
||||
}
|
||||
|
||||
func (srv *Server) readTCP(conn net.Conn, timeout time.Duration) ([]byte, error) {
|
||||
// If we race with ShutdownContext, the read deadline may
|
||||
// have been set in the distant past to unblock the read
|
||||
// below. We must not override it, otherwise we may block
|
||||
// ShutdownContext.
|
||||
srv.lock.RLock()
|
||||
if srv.started {
|
||||
conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
}
|
||||
srv.lock.RUnlock()
|
||||
|
||||
var length uint16
|
||||
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := make([]byte, length)
|
||||
if _, err := io.ReadFull(conn, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (srv *Server) readUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) {
|
||||
srv.lock.RLock()
|
||||
if srv.started {
|
||||
// See the comment in readTCP above.
|
||||
conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
}
|
||||
srv.lock.RUnlock()
|
||||
|
||||
m := srv.udpPool.Get().([]byte)
|
||||
n, s, err := ReadFromSessionUDP(conn, m)
|
||||
if err != nil {
|
||||
srv.udpPool.Put(m)
|
||||
return nil, nil, err
|
||||
}
|
||||
m = m[:n]
|
||||
return m, s, nil
|
||||
}
|
||||
|
||||
func (srv *Server) readPacketConn(conn net.PacketConn, timeout time.Duration) ([]byte, net.Addr, error) {
|
||||
srv.lock.RLock()
|
||||
if srv.started {
|
||||
// See the comment in readTCP above.
|
||||
conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
}
|
||||
srv.lock.RUnlock()
|
||||
|
||||
m := srv.udpPool.Get().([]byte)
|
||||
n, addr, err := conn.ReadFrom(m)
|
||||
if err != nil {
|
||||
srv.udpPool.Put(m)
|
||||
return nil, nil, err
|
||||
}
|
||||
m = m[:n]
|
||||
return m, addr, nil
|
||||
}
|
||||
|
||||
// WriteMsg implements the ResponseWriter.WriteMsg method.
|
||||
func (w *response) WriteMsg(m *Msg) (err error) {
|
||||
if w.closed {
|
||||
return &Error{err: "WriteMsg called after Close"}
|
||||
}
|
||||
|
||||
var data []byte
|
||||
if w.tsigProvider != nil { // if no provider, dont check for the tsig (which is a longer check)
|
||||
if t := m.IsTsig(); t != nil {
|
||||
data, w.tsigRequestMAC, err = tsigGenerateProvider(m, w.tsigProvider, w.tsigRequestMAC, w.tsigTimersOnly)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.writer.Write(data)
|
||||
return err
|
||||
}
|
||||
}
|
||||
data, err = m.Pack()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.writer.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
// Write implements the ResponseWriter.Write method.
|
||||
func (w *response) Write(m []byte) (int, error) {
|
||||
if w.closed {
|
||||
return 0, &Error{err: "Write called after Close"}
|
||||
}
|
||||
|
||||
switch {
|
||||
case w.udp != nil:
|
||||
if u, ok := w.udp.(*net.UDPConn); ok {
|
||||
return WriteToSessionUDP(u, m, w.udpSession)
|
||||
}
|
||||
return w.udp.WriteTo(m, w.pcSession)
|
||||
case w.tcp != nil:
|
||||
if len(m) > MaxMsgSize {
|
||||
return 0, &Error{err: "message too large"}
|
||||
}
|
||||
|
||||
msg := make([]byte, 2+len(m))
|
||||
binary.BigEndian.PutUint16(msg, uint16(len(m)))
|
||||
copy(msg[2:], m)
|
||||
return w.tcp.Write(msg)
|
||||
default:
|
||||
panic("dns: internal error: udp and tcp both nil")
|
||||
}
|
||||
}
|
||||
|
||||
// LocalAddr implements the ResponseWriter.LocalAddr method.
|
||||
func (w *response) LocalAddr() net.Addr {
|
||||
switch {
|
||||
case w.udp != nil:
|
||||
return w.udp.LocalAddr()
|
||||
case w.tcp != nil:
|
||||
return w.tcp.LocalAddr()
|
||||
default:
|
||||
panic("dns: internal error: udp and tcp both nil")
|
||||
}
|
||||
}
|
||||
|
||||
// RemoteAddr implements the ResponseWriter.RemoteAddr method.
|
||||
func (w *response) RemoteAddr() net.Addr {
|
||||
switch {
|
||||
case w.udpSession != nil:
|
||||
return w.udpSession.RemoteAddr()
|
||||
case w.pcSession != nil:
|
||||
return w.pcSession
|
||||
case w.tcp != nil:
|
||||
return w.tcp.RemoteAddr()
|
||||
default:
|
||||
panic("dns: internal error: udpSession, pcSession and tcp are all nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TsigStatus implements the ResponseWriter.TsigStatus method.
|
||||
func (w *response) TsigStatus() error { return w.tsigStatus }
|
||||
|
||||
// TsigTimersOnly implements the ResponseWriter.TsigTimersOnly method.
|
||||
func (w *response) TsigTimersOnly(b bool) { w.tsigTimersOnly = b }
|
||||
|
||||
// Hijack implements the ResponseWriter.Hijack method.
|
||||
func (w *response) Hijack() { w.hijacked = true }
|
||||
|
||||
// Close implements the ResponseWriter.Close method
|
||||
func (w *response) Close() error {
|
||||
if w.closed {
|
||||
return &Error{err: "connection already closed"}
|
||||
}
|
||||
w.closed = true
|
||||
|
||||
switch {
|
||||
case w.udp != nil:
|
||||
// Can't close the udp conn, as that is actually the listener.
|
||||
return nil
|
||||
case w.tcp != nil:
|
||||
return w.tcp.Close()
|
||||
default:
|
||||
panic("dns: internal error: udp and tcp both nil")
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionState() implements the ConnectionStater.ConnectionState() interface.
|
||||
func (w *response) ConnectionState() *tls.ConnectionState {
|
||||
type tlsConnectionStater interface {
|
||||
ConnectionState() tls.ConnectionState
|
||||
}
|
||||
if v, ok := w.tcp.(tlsConnectionStater); ok {
|
||||
t := v.ConnectionState()
|
||||
return &t
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Sign signs a dns.Msg. It fills the signature with the appropriate data.
|
||||
// The SIG record should have the SignerName, KeyTag, Algorithm, Inception
|
||||
// and Expiration set.
|
||||
func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) {
|
||||
if k == nil {
|
||||
return nil, ErrPrivKey
|
||||
}
|
||||
if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 {
|
||||
return nil, ErrKey
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if &buf[0] != &mbuf[0] {
|
||||
return nil, ErrBuf
|
||||
}
|
||||
off, err := PackRR(rr, buf, len(mbuf), nil, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = buf[:off:cap(buf)]
|
||||
|
||||
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write SIG rdata
|
||||
h.Write(buf[len(mbuf)+1+2+2+4+2:])
|
||||
// Write message
|
||||
h.Write(buf[:len(mbuf)])
|
||||
|
||||
signature, err := sign(k, h.Sum(nil), cryptohash, rr.Algorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rr.Signature = toBase64(signature)
|
||||
|
||||
buf = append(buf, signature...)
|
||||
if len(buf) > int(^uint16(0)) {
|
||||
return nil, ErrBuf
|
||||
}
|
||||
// Adjust sig data length
|
||||
rdoff := len(mbuf) + 1 + 2 + 2 + 4
|
||||
rdlen := binary.BigEndian.Uint16(buf[rdoff:])
|
||||
rdlen += uint16(len(signature))
|
||||
binary.BigEndian.PutUint16(buf[rdoff:], rdlen)
|
||||
// Adjust additional count
|
||||
adc := binary.BigEndian.Uint16(buf[10:])
|
||||
adc++
|
||||
binary.BigEndian.PutUint16(buf[10:], adc)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Verify validates the message buf using the key k.
|
||||
// It's assumed that buf is a valid message from which rr was unpacked.
|
||||
func (rr *SIG) Verify(k *KEY, buf []byte) error {
|
||||
if k == nil {
|
||||
return ErrKey
|
||||
}
|
||||
if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 {
|
||||
return ErrKey
|
||||
}
|
||||
|
||||
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 := headerSize
|
||||
for i := uint16(0); i < qdc && offset < buflen; i++ {
|
||||
_, offset, err = UnpackDomainName(buf, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Skip past Type and Class
|
||||
offset += 2 + 2
|
||||
}
|
||||
for i := uint16(1); i < anc+auc+adc && offset < buflen; i++ {
|
||||
_, offset, err = UnpackDomainName(buf, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Skip past Type, Class and TTL
|
||||
offset += 2 + 2 + 4
|
||||
if offset+1 >= buflen {
|
||||
continue
|
||||
}
|
||||
rdlen := binary.BigEndian.Uint16(buf[offset:])
|
||||
offset += 2
|
||||
offset += int(rdlen)
|
||||
}
|
||||
if offset >= buflen {
|
||||
return &Error{err: "overflowing unpacking signed message"}
|
||||
}
|
||||
|
||||
// offset should be just prior to SIG
|
||||
bodyend := offset
|
||||
// owner name SHOULD be root
|
||||
_, offset, err = UnpackDomainName(buf, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Skip Type, Class, TTL, RDLen
|
||||
offset += 2 + 2 + 4 + 2
|
||||
sigstart := offset
|
||||
// Skip Type Covered, Algorithm, Labels, Original TTL
|
||||
offset += 2 + 1 + 1 + 4
|
||||
if offset+4+4 >= buflen {
|
||||
return &Error{err: "overflow unpacking signed message"}
|
||||
}
|
||||
expire := binary.BigEndian.Uint32(buf[offset:])
|
||||
offset += 4
|
||||
incept := binary.BigEndian.Uint32(buf[offset:])
|
||||
offset += 4
|
||||
now := uint32(time.Now().Unix())
|
||||
if now < incept || now > expire {
|
||||
return ErrTime
|
||||
}
|
||||
// Skip key tag
|
||||
offset += 2
|
||||
var signername string
|
||||
signername, offset, err = UnpackDomainName(buf, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If key has come from the DNS name compression might
|
||||
// have mangled the case of the name
|
||||
if !strings.EqualFold(signername, k.Header().Name) {
|
||||
return &Error{err: "signer name doesn't match key name"}
|
||||
}
|
||||
sigend := offset
|
||||
h.Write(buf[sigstart:sigend])
|
||||
h.Write(buf[:10])
|
||||
h.Write([]byte{
|
||||
byte((adc - 1) << 8),
|
||||
byte(adc - 1),
|
||||
})
|
||||
h.Write(buf[12:bodyend])
|
||||
|
||||
hashed := h.Sum(nil)
|
||||
sig := buf[sigend:]
|
||||
switch k.Algorithm {
|
||||
case RSASHA1, RSASHA256, RSASHA512:
|
||||
pk := k.publicKeyRSA()
|
||||
if pk != nil {
|
||||
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 ED25519:
|
||||
pk := k.publicKeyED25519()
|
||||
if pk != nil {
|
||||
if ed25519.Verify(pk, hashed, sig) {
|
||||
return nil
|
||||
}
|
||||
return ErrSig
|
||||
}
|
||||
}
|
||||
return ErrKeyAlg
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Adapted for dns package usage by Miek Gieben.
|
||||
|
||||
package dns
|
||||
|
||||
import "sync"
|
||||
import "time"
|
||||
|
||||
// call is an in-flight or completed singleflight.Do call
|
||||
type call struct {
|
||||
wg sync.WaitGroup
|
||||
val *Msg
|
||||
rtt time.Duration
|
||||
err error
|
||||
dups int
|
||||
}
|
||||
|
||||
// singleflight represents a class of work and forms a namespace in
|
||||
// which units of work can be executed with duplicate suppression.
|
||||
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
|
||||
// sure that only one execution is in-flight for a given key at a
|
||||
// time. If a duplicate comes in, the duplicate caller waits for the
|
||||
// original to complete and receives the same results.
|
||||
// The return value shared indicates whether v was given to multiple callers.
|
||||
func (g *singleflight) Do(key string, fn func() (*Msg, time.Duration, error)) (v *Msg, rtt time.Duration, err error, shared bool) {
|
||||
g.Lock()
|
||||
if g.m == nil {
|
||||
g.m = make(map[string]*call)
|
||||
}
|
||||
if c, ok := g.m[key]; ok {
|
||||
c.dups++
|
||||
g.Unlock()
|
||||
c.wg.Wait()
|
||||
return c.val, c.rtt, c.err, true
|
||||
}
|
||||
c := new(call)
|
||||
c.wg.Add(1)
|
||||
g.m[key] = c
|
||||
g.Unlock()
|
||||
|
||||
c.val, c.rtt, c.err = fn()
|
||||
c.wg.Done()
|
||||
|
||||
if !g.dontDeleteForTesting {
|
||||
g.Lock()
|
||||
delete(g.m, key)
|
||||
g.Unlock()
|
||||
}
|
||||
|
||||
return c.val, c.rtt, c.err, c.dups > 0
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// Sign creates a SMIMEA record from an SSL certificate.
|
||||
func (r *SMIMEA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (err error) {
|
||||
r.Hdr.Rrtype = TypeSMIMEA
|
||||
r.Usage = uint8(usage)
|
||||
r.Selector = uint8(selector)
|
||||
r.MatchingType = uint8(matchingType)
|
||||
|
||||
r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert)
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify verifies a SMIMEA record against an SSL certificate. If it is OK
|
||||
// a nil error is returned.
|
||||
func (r *SMIMEA) Verify(cert *x509.Certificate) error {
|
||||
c, err := CertificateToDANE(r.Selector, r.MatchingType, cert)
|
||||
if err != nil {
|
||||
return err // Not also ErrSig?
|
||||
}
|
||||
if r.Certificate == c {
|
||||
return nil
|
||||
}
|
||||
return ErrSig // ErrSig, really?
|
||||
}
|
||||
|
||||
// SMIMEAName returns the ownername of a SMIMEA resource record as per the
|
||||
// format specified in RFC 'draft-ietf-dane-smime-12' Section 2 and 3
|
||||
func SMIMEAName(email, domain string) (string, error) {
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(email))
|
||||
|
||||
// RFC Section 3: "The local-part is hashed using the SHA2-256
|
||||
// algorithm with the hash truncated to 28 octets and
|
||||
// represented in its hexadecimal representation to become the
|
||||
// left-most label in the prepared domain name"
|
||||
return hex.EncodeToString(hasher.Sum(nil)[:28]) + "." + "_smimecert." + domain, nil
|
||||
}
|
|
@ -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 excaped.
|
||||
//
|
||||
// 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,44 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Sign creates a TLSA record from an SSL certificate.
|
||||
func (r *TLSA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (err error) {
|
||||
r.Hdr.Rrtype = TypeTLSA
|
||||
r.Usage = uint8(usage)
|
||||
r.Selector = uint8(selector)
|
||||
r.MatchingType = uint8(matchingType)
|
||||
|
||||
r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert)
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify verifies a TLSA record against an SSL certificate. If it is OK
|
||||
// a nil error is returned.
|
||||
func (r *TLSA) Verify(cert *x509.Certificate) error {
|
||||
c, err := CertificateToDANE(r.Selector, r.MatchingType, cert)
|
||||
if err != nil {
|
||||
return err // Not also ErrSig?
|
||||
}
|
||||
if r.Certificate == c {
|
||||
return nil
|
||||
}
|
||||
return ErrSig // ErrSig, really?
|
||||
}
|
||||
|
||||
// TLSAName returns the ownername of a TLSA resource record as per the
|
||||
// rules specified in RFC 6698, Section 3.
|
||||
func TLSAName(name, service, network string) (string, error) {
|
||||
if !IsFqdn(name) {
|
||||
return "", ErrFqdn
|
||||
}
|
||||
p, err := net.LookupPort(network, service)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "_" + strconv.Itoa(p) + "._" + network + "." + name, nil
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// +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"
|
|
@ -0,0 +1,456 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HMAC hashing codes. These are transmitted as domain names.
|
||||
const (
|
||||
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 {
|
||||
Hdr RR_Header
|
||||
Algorithm string `dns:"domain-name"`
|
||||
TimeSigned uint64 `dns:"uint48"`
|
||||
Fudge uint16
|
||||
MACSize uint16
|
||||
MAC string `dns:"size-hex:MACSize"`
|
||||
OrigId uint16
|
||||
Error uint16
|
||||
OtherLen uint16
|
||||
OtherData string `dns:"size-hex:OtherLen"`
|
||||
}
|
||||
|
||||
// TSIG has no official presentation format, but this will suffice.
|
||||
|
||||
func (rr *TSIG) String() string {
|
||||
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) +
|
||||
" " + strconv.Itoa(int(rr.Fudge)) +
|
||||
" " + strconv.Itoa(int(rr.MACSize)) +
|
||||
" " + strings.ToUpper(rr.MAC) +
|
||||
" " + strconv.Itoa(int(rr.OrigId)) +
|
||||
" " + strconv.Itoa(int(rr.Error)) + // BIND prints NOERROR
|
||||
" " + strconv.Itoa(int(rr.OtherLen)) +
|
||||
" " + rr.OtherData
|
||||
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 {
|
||||
// From RR_Header
|
||||
Name string `dns:"domain-name"`
|
||||
Class uint16
|
||||
Ttl uint32
|
||||
// Rdata of the TSIG
|
||||
Algorithm string `dns:"domain-name"`
|
||||
TimeSigned uint64 `dns:"uint48"`
|
||||
Fudge uint16
|
||||
// MACSize, MAC and OrigId excluded
|
||||
Error uint16
|
||||
OtherLen uint16
|
||||
OtherData string `dns:"size-hex:OtherLen"`
|
||||
}
|
||||
|
||||
// If we have the MAC use this type to convert it to wiredata. Section 3.4.3. Request MAC
|
||||
type macWireFmt struct {
|
||||
MACSize uint16
|
||||
MAC string `dns:"size-hex:MACSize"`
|
||||
}
|
||||
|
||||
// 3.3. Time values used in TSIG calculations
|
||||
type timerWireFmt struct {
|
||||
TimeSigned uint64 `dns:"uint48"`
|
||||
Fudge uint16
|
||||
}
|
||||
|
||||
// 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.
|
||||
func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) {
|
||||
return tsigGenerateProvider(m, tsigHMACProvider(secret), requestMAC, timersOnly)
|
||||
}
|
||||
|
||||
func tsigGenerateProvider(m *Msg, provider TsigProvider, requestMAC string, timersOnly bool) ([]byte, string, error) {
|
||||
if m.IsTsig() == nil {
|
||||
panic("dns: TSIG not last RR in additional")
|
||||
}
|
||||
|
||||
rr := m.Extra[len(m.Extra)-1].(*TSIG)
|
||||
m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg
|
||||
mbuf, err := m.Pack()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
buf, err := tsigBuffer(mbuf, rr, requestMAC, timersOnly)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
t := new(TSIG)
|
||||
// Copy all TSIG fields except MAC, 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.
|
||||
func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
|
||||
return tsigVerify(msg, tsigHMACProvider(secret), requestMAC, timersOnly, uint64(time.Now().Unix()))
|
||||
}
|
||||
|
||||
func tsigVerifyProvider(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool) error {
|
||||
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
|
||||
}
|
||||
|
||||
buf, err := tsigBuffer(stripped, tsig, requestMAC, timersOnly)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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.
|
||||
// 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
|
||||
}
|
||||
if uint64(tsig.Fudge) < ti {
|
||||
return ErrTime
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a wiredata buffer for the MAC calculation.
|
||||
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())
|
||||
}
|
||||
if rr.Fudge == 0 {
|
||||
rr.Fudge = 300 // Standard (RFC) default.
|
||||
}
|
||||
|
||||
// Replace message ID in header with original ID from TSIG
|
||||
binary.BigEndian.PutUint16(msgbuf[0:2], rr.OrigId)
|
||||
|
||||
if requestMAC != "" {
|
||||
m := new(macWireFmt)
|
||||
m.MACSize = uint16(len(requestMAC) / 2)
|
||||
m.MAC = requestMAC
|
||||
buf = make([]byte, len(requestMAC)) // long enough
|
||||
n, err := packMacWire(m, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = buf[:n]
|
||||
}
|
||||
|
||||
tsigvar := make([]byte, DefaultMsgSize)
|
||||
if timersOnly {
|
||||
tsig := new(timerWireFmt)
|
||||
tsig.TimeSigned = rr.TimeSigned
|
||||
tsig.Fudge = rr.Fudge
|
||||
n, err := packTimerWire(tsig, tsigvar)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tsigvar = tsigvar[:n]
|
||||
} else {
|
||||
tsig := new(tsigWireFmt)
|
||||
tsig.Name = CanonicalName(rr.Hdr.Name)
|
||||
tsig.Class = ClassANY
|
||||
tsig.Ttl = rr.Hdr.Ttl
|
||||
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, err := packTsigWire(tsig, tsigvar)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tsigvar = tsigvar[:n]
|
||||
}
|
||||
|
||||
if requestMAC != "" {
|
||||
x := append(buf, msgbuf...)
|
||||
buf = append(x, tsigvar...)
|
||||
} else {
|
||||
buf = append(msgbuf, tsigvar...)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Strip the TSIG from the raw message.
|
||||
func stripTsig(msg []byte) ([]byte, *TSIG, error) {
|
||||
// Copied from msg.go's Unpack() Header, but modified.
|
||||
var (
|
||||
dh Header
|
||||
err error
|
||||
)
|
||||
off, tsigoff := 0, 0
|
||||
|
||||
if dh, off, err = unpackMsgHdr(msg, off); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if dh.Arcount == 0 {
|
||||
return nil, nil, ErrNoSig
|
||||
}
|
||||
|
||||
// Rcode, see msg.go Unpack()
|
||||
if int(dh.Bits&0xF) == RcodeNotAuth {
|
||||
return nil, nil, ErrAuth
|
||||
}
|
||||
|
||||
for i := 0; i < int(dh.Qdcount); i++ {
|
||||
_, off, err = unpackQuestion(msg, off)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
_, off, err = unpackRRslice(int(dh.Ancount), msg, off)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
_, off, err = unpackRRslice(int(dh.Nscount), msg, off)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rr := new(TSIG)
|
||||
var extra RR
|
||||
for i := 0; i < int(dh.Arcount); i++ {
|
||||
tsigoff = off
|
||||
extra, off, err = UnpackRR(msg, off)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if extra.Header().Rrtype == TypeTSIG {
|
||||
rr = extra.(*TSIG)
|
||||
// Adjust Arcount.
|
||||
arcount := binary.BigEndian.Uint16(msg[10:])
|
||||
binary.BigEndian.PutUint16(msg[10:], arcount-1)
|
||||
break
|
||||
}
|
||||
}
|
||||
if rr == nil {
|
||||
return nil, nil, ErrNoSig
|
||||
}
|
||||
return msg[:tsigoff], rr, nil
|
||||
}
|
||||
|
||||
// Translate the TSIG time signed into a date. There is no
|
||||
// need for RFC1982 calculations as this date is 48 bits.
|
||||
func tsigTimeToString(t uint64) string {
|
||||
ti := time.Unix(int64(t), 0).UTC()
|
||||
return ti.Format("20060102150405")
|
||||
}
|
||||
|
||||
func packTsigWire(tw *tsigWireFmt, msg []byte) (int, error) {
|
||||
// copied from zmsg.go TSIG packing
|
||||
// RR_Header
|
||||
off, err := PackDomainName(tw.Name, msg, 0, nil, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint16(tw.Class, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint32(tw.Ttl, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
||||
off, err = PackDomainName(tw.Algorithm, msg, off, nil, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint48(tw.TimeSigned, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint16(tw.Fudge, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
||||
off, err = packUint16(tw.Error, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint16(tw.OtherLen, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packStringHex(tw.OtherData, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func packMacWire(mw *macWireFmt, msg []byte) (int, error) {
|
||||
off, err := packUint16(mw.MACSize, msg, 0)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packStringHex(mw.MAC, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func packTimerWire(tw *timerWireFmt, msg []byte) (int, error) {
|
||||
off, err := packUint48(tw.TimeSigned, msg, 0)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = packUint16(tw.Fudge, msg, off)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
return off, nil
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,102 @@
|
|||
// +build !windows
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
"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 {
|
||||
raddr *net.UDPAddr
|
||||
context []byte
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
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, udpOOBSize)
|
||||
n, oobn, _, raddr, err := conn.ReadMsgUDP(b, oob)
|
||||
if err != nil {
|
||||
return n, nil, err
|
||||
}
|
||||
return n, &SessionUDP{raddr, oob[:oobn]}, err
|
||||
}
|
||||
|
||||
// WriteToSessionUDP acts just like net.UDPConn.WriteTo(), but uses a *SessionUDP instead of a net.Addr.
|
||||
func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) {
|
||||
oob := correctSource(session.context)
|
||||
n, _, err := conn.WriteMsgUDP(b, oob, session.raddr)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func setUDPSocketOptions(conn *net.UDPConn) error {
|
||||
// Try setting the flags for both families and ignore the errors unless they
|
||||
// both error.
|
||||
err6 := ipv6.NewPacketConn(conn).SetControlMessage(ipv6.FlagDst|ipv6.FlagInterface, true)
|
||||
err4 := ipv4.NewPacketConn(conn).SetControlMessage(ipv4.FlagDst|ipv4.FlagInterface, true)
|
||||
if err6 != nil && err4 != nil {
|
||||
return err4
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseDstFromOOB takes oob data and returns the destination IP.
|
||||
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.
|
||||
cm6 := new(ipv6.ControlMessage)
|
||||
if cm6.Parse(oob) == nil && cm6.Dst != nil {
|
||||
return cm6.Dst
|
||||
}
|
||||
cm4 := new(ipv4.ControlMessage)
|
||||
if cm4.Parse(oob) == nil && cm4.Dst != nil {
|
||||
return cm4.Dst
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// correctSource takes oob data and returns new oob data with the Src equal to the Dst
|
||||
func correctSource(oob []byte) []byte {
|
||||
dst := parseDstFromOOB(oob)
|
||||
if dst == nil {
|
||||
return nil
|
||||
}
|
||||
// If the dst is definitely an IPv6, then use ipv6's ControlMessage to
|
||||
// respond otherwise use ipv4's because ipv6's marshal ignores ipv4
|
||||
// addresses.
|
||||
if dst.To4() == nil {
|
||||
cm := new(ipv6.ControlMessage)
|
||||
cm.Src = dst
|
||||
oob = cm.Marshal()
|
||||
} else {
|
||||
cm := new(ipv4.ControlMessage)
|
||||
cm.Src = dst
|
||||
oob = cm.Marshal()
|
||||
}
|
||||
return oob
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// +build windows
|
||||
|
||||
package dns
|
||||
|
||||
import "net"
|
||||
|
||||
// SessionUDP holds the remote address
|
||||
type SessionUDP struct {
|
||||
raddr *net.UDPAddr
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
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.
|
||||
// TODO(fastest963): Once go1.10 is released, use ReadMsgUDP.
|
||||
func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) {
|
||||
n, raddr, err := conn.ReadFrom(b)
|
||||
if err != nil {
|
||||
return n, nil, 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) {
|
||||
return conn.WriteTo(b, session.raddr)
|
||||
}
|
||||
|
||||
// TODO(fastest963): Once go1.10 is released and we can use *MsgUDP methods
|
||||
// use the standard method in udp.go for these.
|
||||
func setUDPSocketOptions(*net.UDPConn) error { return nil }
|
||||
func parseDstFromOOB([]byte, net.IP) net.IP { return nil }
|
|
@ -0,0 +1,112 @@
|
|||
package dns
|
||||
|
||||
// NameUsed sets the RRs in the prereq section to
|
||||
// "Name is in use" RRs. RFC 2136 section 2.4.4.
|
||||
func (u *Msg) NameUsed(rr []RR) {
|
||||
if u.Answer == nil {
|
||||
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: TypeANY, Class: ClassANY}})
|
||||
}
|
||||
}
|
||||
|
||||
// NameNotUsed sets the RRs in the prereq section to
|
||||
// "Name is in not use" RRs. RFC 2136 section 2.4.5.
|
||||
func (u *Msg) NameNotUsed(rr []RR) {
|
||||
if u.Answer == nil {
|
||||
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: TypeANY, Class: ClassNONE}})
|
||||
}
|
||||
}
|
||||
|
||||
// Used sets the RRs in the prereq section to
|
||||
// "RRset exists (value dependent -- with rdata)" RRs. RFC 2136 section 2.4.2.
|
||||
func (u *Msg) Used(rr []RR) {
|
||||
if len(u.Question) == 0 {
|
||||
panic("dns: empty question section")
|
||||
}
|
||||
if u.Answer == nil {
|
||||
u.Answer = make([]RR, 0, len(rr))
|
||||
}
|
||||
for _, r := range rr {
|
||||
hdr := r.Header()
|
||||
hdr.Class = u.Question[0].Qclass
|
||||
hdr.Ttl = 0
|
||||
u.Answer = append(u.Answer, r)
|
||||
}
|
||||
}
|
||||
|
||||
// RRsetUsed sets the RRs in the prereq section to
|
||||
// "RRset exists (value independent -- no rdata)" RRs. RFC 2136 section 2.4.1.
|
||||
func (u *Msg) RRsetUsed(rr []RR) {
|
||||
if u.Answer == nil {
|
||||
u.Answer = make([]RR, 0, len(rr))
|
||||
}
|
||||
for _, r := range rr {
|
||||
h := r.Header()
|
||||
u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: h.Name, Ttl: 0, Rrtype: h.Rrtype, Class: ClassANY}})
|
||||
}
|
||||
}
|
||||
|
||||
// RRsetNotUsed sets the RRs in the prereq section to
|
||||
// "RRset does not exist" RRs. RFC 2136 section 2.4.3.
|
||||
func (u *Msg) RRsetNotUsed(rr []RR) {
|
||||
if u.Answer == nil {
|
||||
u.Answer = make([]RR, 0, len(rr))
|
||||
}
|
||||
for _, r := range rr {
|
||||
h := r.Header()
|
||||
u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: h.Name, Ttl: 0, Rrtype: h.Rrtype, Class: ClassNONE}})
|
||||
}
|
||||
}
|
||||
|
||||
// Insert creates a dynamic update packet that adds an complete RRset, see RFC 2136 section 2.5.1.
|
||||
func (u *Msg) Insert(rr []RR) {
|
||||
if len(u.Question) == 0 {
|
||||
panic("dns: empty question section")
|
||||
}
|
||||
if u.Ns == nil {
|
||||
u.Ns = make([]RR, 0, len(rr))
|
||||
}
|
||||
for _, r := range rr {
|
||||
r.Header().Class = u.Question[0].Qclass
|
||||
u.Ns = append(u.Ns, r)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveRRset creates a dynamic update packet that deletes an RRset, see RFC 2136 section 2.5.2.
|
||||
func (u *Msg) RemoveRRset(rr []RR) {
|
||||
if u.Ns == nil {
|
||||
u.Ns = make([]RR, 0, len(rr))
|
||||
}
|
||||
for _, r := range rr {
|
||||
h := r.Header()
|
||||
u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: h.Name, Ttl: 0, Rrtype: h.Rrtype, Class: ClassANY}})
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveName creates a dynamic update packet that deletes all RRsets of a name, see RFC 2136 section 2.5.3
|
||||
func (u *Msg) RemoveName(rr []RR) {
|
||||
if u.Ns == nil {
|
||||
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: TypeANY, Class: ClassANY}})
|
||||
}
|
||||
}
|
||||
|
||||
// Remove creates a dynamic update packet deletes RR from a RRSset, see RFC 2136 section 2.5.4
|
||||
func (u *Msg) Remove(rr []RR) {
|
||||
if u.Ns == nil {
|
||||
u.Ns = make([]RR, 0, len(rr))
|
||||
}
|
||||
for _, r := range rr {
|
||||
h := r.Header()
|
||||
h.Class = ClassNONE
|
||||
h.Ttl = 0
|
||||
u.Ns = append(u.Ns, r)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package dns
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Version is current version of this library.
|
||||
var Version = v{1, 1, 49}
|
||||
|
||||
// v holds the version of this library.
|
||||
type v struct {
|
||||
Major, Minor, Patch int
|
||||
}
|
||||
|
||||
func (v v) String() string {
|
||||
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Envelope is used when doing a zone transfer with a remote server.
|
||||
type Envelope struct {
|
||||
RR []RR // The set of RRs in the answer section of the xfr reply message.
|
||||
Error error // If something went wrong, this contains the error.
|
||||
}
|
||||
|
||||
// A Transfer defines parameters that are used during a zone transfer.
|
||||
type Transfer struct {
|
||||
*Conn
|
||||
DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds
|
||||
ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds
|
||||
WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds
|
||||
TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
|
||||
TsigSecret map[string]string // Secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
|
||||
tsigTimersOnly bool
|
||||
}
|
||||
|
||||
func (t *Transfer) tsigProvider() TsigProvider {
|
||||
if t.TsigProvider != nil {
|
||||
return t.TsigProvider
|
||||
}
|
||||
if t.TsigSecret != nil {
|
||||
return tsigSecretProvider(t.TsigSecret)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Think we need to away to stop the transfer
|
||||
|
||||
// In performs an incoming transfer with the server in a.
|
||||
// If you would like to set the source IP, or some other attribute
|
||||
// of a Dialer for a Transfer, you can do so by specifying the attributes
|
||||
// in the Transfer.Conn:
|
||||
//
|
||||
// d := net.Dialer{LocalAddr: transfer_source}
|
||||
// con, err := d.Dial("tcp", master)
|
||||
// dnscon := &dns.Conn{Conn:con}
|
||||
// transfer = &dns.Transfer{Conn: dnscon}
|
||||
// channel, err := transfer.In(message, master)
|
||||
//
|
||||
func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
|
||||
switch q.Question[0].Qtype {
|
||||
case TypeAXFR, TypeIXFR:
|
||||
default:
|
||||
return nil, &Error{"unsupported question type"}
|
||||
}
|
||||
|
||||
timeout := dnsTimeout
|
||||
if t.DialTimeout != 0 {
|
||||
timeout = t.DialTimeout
|
||||
}
|
||||
|
||||
if t.Conn == nil {
|
||||
t.Conn, err = DialTimeout("tcp", a, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := t.WriteMsg(q); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
env = make(chan *Envelope)
|
||||
switch q.Question[0].Qtype {
|
||||
case TypeAXFR:
|
||||
go t.inAxfr(q, env)
|
||||
case TypeIXFR:
|
||||
go t.inIxfr(q, env)
|
||||
}
|
||||
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func (t *Transfer) inAxfr(q *Msg, c chan *Envelope) {
|
||||
first := true
|
||||
defer t.Close()
|
||||
defer close(c)
|
||||
timeout := dnsTimeout
|
||||
if t.ReadTimeout != 0 {
|
||||
timeout = t.ReadTimeout
|
||||
}
|
||||
for {
|
||||
t.Conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
in, err := t.ReadMsg()
|
||||
if err != nil {
|
||||
c <- &Envelope{nil, err}
|
||||
return
|
||||
}
|
||||
if q.Id != in.Id {
|
||||
c <- &Envelope{in.Answer, ErrId}
|
||||
return
|
||||
}
|
||||
if first {
|
||||
if in.Rcode != RcodeSuccess {
|
||||
c <- &Envelope{in.Answer, &Error{err: fmt.Sprintf(errXFR, in.Rcode)}}
|
||||
return
|
||||
}
|
||||
if !isSOAFirst(in) {
|
||||
c <- &Envelope{in.Answer, ErrSoa}
|
||||
return
|
||||
}
|
||||
first = !first
|
||||
// only one answer that is SOA, receive more
|
||||
if len(in.Answer) == 1 {
|
||||
t.tsigTimersOnly = true
|
||||
c <- &Envelope{in.Answer, nil}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !first {
|
||||
t.tsigTimersOnly = true // Subsequent envelopes use this.
|
||||
if isSOALast(in) {
|
||||
c <- &Envelope{in.Answer, nil}
|
||||
return
|
||||
}
|
||||
c <- &Envelope{in.Answer, nil}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transfer) inIxfr(q *Msg, c chan *Envelope) {
|
||||
var serial uint32 // The first serial seen is the current server serial
|
||||
axfr := true
|
||||
n := 0
|
||||
qser := q.Ns[0].(*SOA).Serial
|
||||
defer t.Close()
|
||||
defer close(c)
|
||||
timeout := dnsTimeout
|
||||
if t.ReadTimeout != 0 {
|
||||
timeout = t.ReadTimeout
|
||||
}
|
||||
for {
|
||||
t.SetReadDeadline(time.Now().Add(timeout))
|
||||
in, err := t.ReadMsg()
|
||||
if err != nil {
|
||||
c <- &Envelope{nil, err}
|
||||
return
|
||||
}
|
||||
if q.Id != in.Id {
|
||||
c <- &Envelope{in.Answer, ErrId}
|
||||
return
|
||||
}
|
||||
if in.Rcode != RcodeSuccess {
|
||||
c <- &Envelope{in.Answer, &Error{err: fmt.Sprintf(errXFR, in.Rcode)}}
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
// Check if the returned answer is ok
|
||||
if !isSOAFirst(in) {
|
||||
c <- &Envelope{in.Answer, ErrSoa}
|
||||
return
|
||||
}
|
||||
// This serial is important
|
||||
serial = in.Answer[0].(*SOA).Serial
|
||||
// Check if there are no changes in zone
|
||||
if qser >= serial {
|
||||
c <- &Envelope{in.Answer, nil}
|
||||
return
|
||||
}
|
||||
}
|
||||
// Now we need to check each message for SOA records, to see what we need to do
|
||||
t.tsigTimersOnly = true
|
||||
for _, rr := range in.Answer {
|
||||
if v, ok := rr.(*SOA); ok {
|
||||
if v.Serial == serial {
|
||||
n++
|
||||
// quit if it's a full axfr or the the servers' SOA is repeated the third time
|
||||
if axfr && n == 2 || n == 3 {
|
||||
c <- &Envelope{in.Answer, nil}
|
||||
return
|
||||
}
|
||||
} else if axfr {
|
||||
// it's an ixfr
|
||||
axfr = false
|
||||
}
|
||||
}
|
||||
}
|
||||
c <- &Envelope{in.Answer, nil}
|
||||
}
|
||||
}
|
||||
|
||||
// Out performs an outgoing transfer with the client connecting in w.
|
||||
// Basic use pattern:
|
||||
//
|
||||
// ch := make(chan *dns.Envelope)
|
||||
// tr := new(dns.Transfer)
|
||||
// var wg sync.WaitGroup
|
||||
// go func() {
|
||||
// tr.Out(w, r, ch)
|
||||
// wg.Done()
|
||||
// }()
|
||||
// ch <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}}
|
||||
// close(ch)
|
||||
// wg.Wait() // wait until everything is written out
|
||||
// w.Close() // close connection
|
||||
//
|
||||
// The server is responsible for sending the correct sequence of RRs through the channel ch.
|
||||
func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error {
|
||||
for x := range ch {
|
||||
r := new(Msg)
|
||||
// Compress?
|
||||
r.SetReply(q)
|
||||
r.Authoritative = true
|
||||
// assume it fits TODO(miek): fix
|
||||
r.Answer = append(r.Answer, x.RR...)
|
||||
if tsig := q.IsTsig(); tsig != nil && w.TsigStatus() == nil {
|
||||
r.SetTsig(tsig.Hdr.Name, tsig.Algorithm, tsig.Fudge, time.Now().Unix())
|
||||
}
|
||||
if err := w.WriteMsg(r); err != nil {
|
||||
return err
|
||||
}
|
||||
w.TsigTimersOnly(true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadMsg reads a message from the transfer connection t.
|
||||
func (t *Transfer) ReadMsg() (*Msg, error) {
|
||||
m := new(Msg)
|
||||
p := make([]byte, MaxMsgSize)
|
||||
n, err := t.Read(p)
|
||||
if err != nil && n == 0 {
|
||||
return nil, err
|
||||
}
|
||||
p = p[:n]
|
||||
if err := m.Unpack(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ts, tp := m.IsTsig(), t.tsigProvider(); ts != nil && tp != nil {
|
||||
// Need to work on the original message p, as that was used to calculate the tsig.
|
||||
err = tsigVerifyProvider(p, tp, t.tsigRequestMAC, t.tsigTimersOnly)
|
||||
t.tsigRequestMAC = ts.MAC
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
// WriteMsg writes a message through the transfer connection t.
|
||||
func (t *Transfer) WriteMsg(m *Msg) (err error) {
|
||||
var out []byte
|
||||
if ts, tp := m.IsTsig(), t.tsigProvider(); ts != nil && tp != nil {
|
||||
out, t.tsigRequestMAC, err = tsigGenerateProvider(m, tp, t.tsigRequestMAC, t.tsigTimersOnly)
|
||||
} else {
|
||||
out, err = m.Pack()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = t.Write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func isSOAFirst(in *Msg) bool {
|
||||
return len(in.Answer) > 0 &&
|
||||
in.Answer[0].Header().Rrtype == TypeSOA
|
||||
}
|
||||
|
||||
func isSOALast(in *Msg) bool {
|
||||
return len(in.Answer) > 0 &&
|
||||
in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA
|
||||
}
|
||||
|
||||
const errXFR = "bad xfr rcode: %d"
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,952 @@
|
|||
// Code generated by "go run types_generate.go"; DO NOT EDIT.
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net"
|
||||
)
|
||||
|
||||
// TypeToRR is a map of constructors for each RR type.
|
||||
var TypeToRR = map[uint16]func() RR{
|
||||
TypeA: func() RR { return new(A) },
|
||||
TypeAAAA: func() RR { return new(AAAA) },
|
||||
TypeAFSDB: func() RR { return new(AFSDB) },
|
||||
TypeANY: func() RR { return new(ANY) },
|
||||
TypeAPL: func() RR { return new(APL) },
|
||||
TypeAVC: func() RR { return new(AVC) },
|
||||
TypeCAA: func() RR { return new(CAA) },
|
||||
TypeCDNSKEY: func() RR { return new(CDNSKEY) },
|
||||
TypeCDS: func() RR { return new(CDS) },
|
||||
TypeCERT: func() RR { return new(CERT) },
|
||||
TypeCNAME: func() RR { return new(CNAME) },
|
||||
TypeCSYNC: func() RR { return new(CSYNC) },
|
||||
TypeDHCID: func() RR { return new(DHCID) },
|
||||
TypeDLV: func() RR { return new(DLV) },
|
||||
TypeDNAME: func() RR { return new(DNAME) },
|
||||
TypeDNSKEY: func() RR { return new(DNSKEY) },
|
||||
TypeDS: func() RR { return new(DS) },
|
||||
TypeEID: func() RR { return new(EID) },
|
||||
TypeEUI48: func() RR { return new(EUI48) },
|
||||
TypeEUI64: func() RR { return new(EUI64) },
|
||||
TypeGID: func() RR { return new(GID) },
|
||||
TypeGPOS: func() RR { return new(GPOS) },
|
||||
TypeHINFO: func() RR { return new(HINFO) },
|
||||
TypeHIP: func() RR { return new(HIP) },
|
||||
TypeHTTPS: func() RR { return new(HTTPS) },
|
||||
TypeKEY: func() RR { return new(KEY) },
|
||||
TypeKX: func() RR { return new(KX) },
|
||||
TypeL32: func() RR { return new(L32) },
|
||||
TypeL64: func() RR { return new(L64) },
|
||||
TypeLOC: func() RR { return new(LOC) },
|
||||
TypeLP: func() RR { return new(LP) },
|
||||
TypeMB: func() RR { return new(MB) },
|
||||
TypeMD: func() RR { return new(MD) },
|
||||
TypeMF: func() RR { return new(MF) },
|
||||
TypeMG: func() RR { return new(MG) },
|
||||
TypeMINFO: func() RR { return new(MINFO) },
|
||||
TypeMR: func() RR { return new(MR) },
|
||||
TypeMX: func() RR { return new(MX) },
|
||||
TypeNAPTR: func() RR { return new(NAPTR) },
|
||||
TypeNID: func() RR { return new(NID) },
|
||||
TypeNIMLOC: func() RR { return new(NIMLOC) },
|
||||
TypeNINFO: func() RR { return new(NINFO) },
|
||||
TypeNS: func() RR { return new(NS) },
|
||||
TypeNSAPPTR: func() RR { return new(NSAPPTR) },
|
||||
TypeNSEC: func() RR { return new(NSEC) },
|
||||
TypeNSEC3: func() RR { return new(NSEC3) },
|
||||
TypeNSEC3PARAM: func() RR { return new(NSEC3PARAM) },
|
||||
TypeNULL: func() RR { return new(NULL) },
|
||||
TypeOPENPGPKEY: func() RR { return new(OPENPGPKEY) },
|
||||
TypeOPT: func() RR { return new(OPT) },
|
||||
TypePTR: func() RR { return new(PTR) },
|
||||
TypePX: func() RR { return new(PX) },
|
||||
TypeRKEY: func() RR { return new(RKEY) },
|
||||
TypeRP: func() RR { return new(RP) },
|
||||
TypeRRSIG: func() RR { return new(RRSIG) },
|
||||
TypeRT: func() RR { return new(RT) },
|
||||
TypeSIG: func() RR { return new(SIG) },
|
||||
TypeSMIMEA: func() RR { return new(SMIMEA) },
|
||||
TypeSOA: func() RR { return new(SOA) },
|
||||
TypeSPF: func() RR { return new(SPF) },
|
||||
TypeSRV: func() RR { return new(SRV) },
|
||||
TypeSSHFP: func() RR { return new(SSHFP) },
|
||||
TypeSVCB: func() RR { return new(SVCB) },
|
||||
TypeTA: func() RR { return new(TA) },
|
||||
TypeTALINK: func() RR { return new(TALINK) },
|
||||
TypeTKEY: func() RR { return new(TKEY) },
|
||||
TypeTLSA: func() RR { return new(TLSA) },
|
||||
TypeTSIG: func() RR { return new(TSIG) },
|
||||
TypeTXT: func() RR { return new(TXT) },
|
||||
TypeUID: func() RR { return new(UID) },
|
||||
TypeUINFO: func() RR { return new(UINFO) },
|
||||
TypeURI: func() RR { return new(URI) },
|
||||
TypeX25: func() RR { return new(X25) },
|
||||
TypeZONEMD: func() RR { return new(ZONEMD) },
|
||||
}
|
||||
|
||||
// TypeToString is a map of strings for each RR type.
|
||||
var TypeToString = map[uint16]string{
|
||||
TypeA: "A",
|
||||
TypeAAAA: "AAAA",
|
||||
TypeAFSDB: "AFSDB",
|
||||
TypeANY: "ANY",
|
||||
TypeAPL: "APL",
|
||||
TypeATMA: "ATMA",
|
||||
TypeAVC: "AVC",
|
||||
TypeAXFR: "AXFR",
|
||||
TypeCAA: "CAA",
|
||||
TypeCDNSKEY: "CDNSKEY",
|
||||
TypeCDS: "CDS",
|
||||
TypeCERT: "CERT",
|
||||
TypeCNAME: "CNAME",
|
||||
TypeCSYNC: "CSYNC",
|
||||
TypeDHCID: "DHCID",
|
||||
TypeDLV: "DLV",
|
||||
TypeDNAME: "DNAME",
|
||||
TypeDNSKEY: "DNSKEY",
|
||||
TypeDS: "DS",
|
||||
TypeEID: "EID",
|
||||
TypeEUI48: "EUI48",
|
||||
TypeEUI64: "EUI64",
|
||||
TypeGID: "GID",
|
||||
TypeGPOS: "GPOS",
|
||||
TypeHINFO: "HINFO",
|
||||
TypeHIP: "HIP",
|
||||
TypeHTTPS: "HTTPS",
|
||||
TypeISDN: "ISDN",
|
||||
TypeIXFR: "IXFR",
|
||||
TypeKEY: "KEY",
|
||||
TypeKX: "KX",
|
||||
TypeL32: "L32",
|
||||
TypeL64: "L64",
|
||||
TypeLOC: "LOC",
|
||||
TypeLP: "LP",
|
||||
TypeMAILA: "MAILA",
|
||||
TypeMAILB: "MAILB",
|
||||
TypeMB: "MB",
|
||||
TypeMD: "MD",
|
||||
TypeMF: "MF",
|
||||
TypeMG: "MG",
|
||||
TypeMINFO: "MINFO",
|
||||
TypeMR: "MR",
|
||||
TypeMX: "MX",
|
||||
TypeNAPTR: "NAPTR",
|
||||
TypeNID: "NID",
|
||||
TypeNIMLOC: "NIMLOC",
|
||||
TypeNINFO: "NINFO",
|
||||
TypeNS: "NS",
|
||||
TypeNSEC: "NSEC",
|
||||
TypeNSEC3: "NSEC3",
|
||||
TypeNSEC3PARAM: "NSEC3PARAM",
|
||||
TypeNULL: "NULL",
|
||||
TypeNXT: "NXT",
|
||||
TypeNone: "None",
|
||||
TypeOPENPGPKEY: "OPENPGPKEY",
|
||||
TypeOPT: "OPT",
|
||||
TypePTR: "PTR",
|
||||
TypePX: "PX",
|
||||
TypeRKEY: "RKEY",
|
||||
TypeRP: "RP",
|
||||
TypeRRSIG: "RRSIG",
|
||||
TypeRT: "RT",
|
||||
TypeReserved: "Reserved",
|
||||
TypeSIG: "SIG",
|
||||
TypeSMIMEA: "SMIMEA",
|
||||
TypeSOA: "SOA",
|
||||
TypeSPF: "SPF",
|
||||
TypeSRV: "SRV",
|
||||
TypeSSHFP: "SSHFP",
|
||||
TypeSVCB: "SVCB",
|
||||
TypeTA: "TA",
|
||||
TypeTALINK: "TALINK",
|
||||
TypeTKEY: "TKEY",
|
||||
TypeTLSA: "TLSA",
|
||||
TypeTSIG: "TSIG",
|
||||
TypeTXT: "TXT",
|
||||
TypeUID: "UID",
|
||||
TypeUINFO: "UINFO",
|
||||
TypeUNSPEC: "UNSPEC",
|
||||
TypeURI: "URI",
|
||||
TypeX25: "X25",
|
||||
TypeZONEMD: "ZONEMD",
|
||||
TypeNSAPPTR: "NSAP-PTR",
|
||||
}
|
||||
|
||||
func (rr *A) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *AAAA) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *AFSDB) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *ANY) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *APL) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *AVC) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *CAA) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *CDNSKEY) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *CDS) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *CERT) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *CNAME) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *CSYNC) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *DHCID) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *DLV) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *DNAME) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *DNSKEY) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *DS) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *EID) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *EUI48) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *EUI64) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *GID) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *GPOS) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *HINFO) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *HIP) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *HTTPS) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *KEY) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *KX) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *L32) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *L64) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *LOC) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *LP) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *MB) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *MD) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *MF) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *MG) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *MINFO) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *MR) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *MX) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *NAPTR) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *NID) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *NIMLOC) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *NINFO) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *NS) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *NSAPPTR) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *NSEC) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *NSEC3) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *NSEC3PARAM) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *NULL) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *OPENPGPKEY) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *OPT) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *PTR) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *PX) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *RFC3597) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *RKEY) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *RP) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *RRSIG) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *RT) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *SIG) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *SMIMEA) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *SOA) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *SPF) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *SRV) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *SSHFP) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *SVCB) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *TA) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *TALINK) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *TKEY) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *TLSA) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *TSIG) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *TXT) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *UID) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *UINFO) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *URI) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *X25) Header() *RR_Header { return &rr.Hdr }
|
||||
func (rr *ZONEMD) Header() *RR_Header { return &rr.Hdr }
|
||||
|
||||
// len() functions
|
||||
func (rr *A) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
if len(rr.A) != 0 {
|
||||
l += net.IPv4len
|
||||
}
|
||||
return l
|
||||
}
|
||||
func (rr *AAAA) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
if len(rr.AAAA) != 0 {
|
||||
l += net.IPv6len
|
||||
}
|
||||
return l
|
||||
}
|
||||
func (rr *AFSDB) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Subtype
|
||||
l += domainNameLen(rr.Hostname, off+l, compression, false)
|
||||
return l
|
||||
}
|
||||
func (rr *ANY) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
return l
|
||||
}
|
||||
func (rr *APL) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
for _, x := range rr.Prefixes {
|
||||
l += x.len()
|
||||
}
|
||||
return l
|
||||
}
|
||||
func (rr *AVC) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
for _, x := range rr.Txt {
|
||||
l += len(x) + 1
|
||||
}
|
||||
return l
|
||||
}
|
||||
func (rr *CAA) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l++ // Flag
|
||||
l += len(rr.Tag) + 1
|
||||
l += len(rr.Value)
|
||||
return l
|
||||
}
|
||||
func (rr *CERT) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Type
|
||||
l += 2 // KeyTag
|
||||
l++ // Algorithm
|
||||
l += base64.StdEncoding.DecodedLen(len(rr.Certificate))
|
||||
return l
|
||||
}
|
||||
func (rr *CNAME) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Target, off+l, compression, true)
|
||||
return l
|
||||
}
|
||||
func (rr *DHCID) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += base64.StdEncoding.DecodedLen(len(rr.Digest))
|
||||
return l
|
||||
}
|
||||
func (rr *DNAME) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Target, off+l, compression, false)
|
||||
return l
|
||||
}
|
||||
func (rr *DNSKEY) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Flags
|
||||
l++ // Protocol
|
||||
l++ // Algorithm
|
||||
l += base64.StdEncoding.DecodedLen(len(rr.PublicKey))
|
||||
return l
|
||||
}
|
||||
func (rr *DS) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // KeyTag
|
||||
l++ // Algorithm
|
||||
l++ // DigestType
|
||||
l += len(rr.Digest) / 2
|
||||
return l
|
||||
}
|
||||
func (rr *EID) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += len(rr.Endpoint) / 2
|
||||
return l
|
||||
}
|
||||
func (rr *EUI48) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 6 // Address
|
||||
return l
|
||||
}
|
||||
func (rr *EUI64) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 8 // Address
|
||||
return l
|
||||
}
|
||||
func (rr *GID) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 4 // Gid
|
||||
return l
|
||||
}
|
||||
func (rr *GPOS) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += len(rr.Longitude) + 1
|
||||
l += len(rr.Latitude) + 1
|
||||
l += len(rr.Altitude) + 1
|
||||
return l
|
||||
}
|
||||
func (rr *HINFO) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += len(rr.Cpu) + 1
|
||||
l += len(rr.Os) + 1
|
||||
return l
|
||||
}
|
||||
func (rr *HIP) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l++ // HitLength
|
||||
l++ // PublicKeyAlgorithm
|
||||
l += 2 // PublicKeyLength
|
||||
l += len(rr.Hit) / 2
|
||||
l += base64.StdEncoding.DecodedLen(len(rr.PublicKey))
|
||||
for _, x := range rr.RendezvousServers {
|
||||
l += domainNameLen(x, off+l, compression, false)
|
||||
}
|
||||
return l
|
||||
}
|
||||
func (rr *KX) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Preference
|
||||
l += domainNameLen(rr.Exchanger, off+l, compression, false)
|
||||
return l
|
||||
}
|
||||
func (rr *L32) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Preference
|
||||
if len(rr.Locator32) != 0 {
|
||||
l += net.IPv4len
|
||||
}
|
||||
return l
|
||||
}
|
||||
func (rr *L64) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Preference
|
||||
l += 8 // Locator64
|
||||
return l
|
||||
}
|
||||
func (rr *LOC) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l++ // Version
|
||||
l++ // Size
|
||||
l++ // HorizPre
|
||||
l++ // VertPre
|
||||
l += 4 // Latitude
|
||||
l += 4 // Longitude
|
||||
l += 4 // Altitude
|
||||
return l
|
||||
}
|
||||
func (rr *LP) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Preference
|
||||
l += domainNameLen(rr.Fqdn, off+l, compression, false)
|
||||
return l
|
||||
}
|
||||
func (rr *MB) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Mb, off+l, compression, true)
|
||||
return l
|
||||
}
|
||||
func (rr *MD) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Md, off+l, compression, true)
|
||||
return l
|
||||
}
|
||||
func (rr *MF) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Mf, off+l, compression, true)
|
||||
return l
|
||||
}
|
||||
func (rr *MG) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Mg, off+l, compression, true)
|
||||
return l
|
||||
}
|
||||
func (rr *MINFO) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Rmail, off+l, compression, true)
|
||||
l += domainNameLen(rr.Email, off+l, compression, true)
|
||||
return l
|
||||
}
|
||||
func (rr *MR) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Mr, off+l, compression, true)
|
||||
return l
|
||||
}
|
||||
func (rr *MX) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Preference
|
||||
l += domainNameLen(rr.Mx, off+l, compression, true)
|
||||
return l
|
||||
}
|
||||
func (rr *NAPTR) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Order
|
||||
l += 2 // Preference
|
||||
l += len(rr.Flags) + 1
|
||||
l += len(rr.Service) + 1
|
||||
l += len(rr.Regexp) + 1
|
||||
l += domainNameLen(rr.Replacement, off+l, compression, false)
|
||||
return l
|
||||
}
|
||||
func (rr *NID) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Preference
|
||||
l += 8 // NodeID
|
||||
return l
|
||||
}
|
||||
func (rr *NIMLOC) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += len(rr.Locator) / 2
|
||||
return l
|
||||
}
|
||||
func (rr *NINFO) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
for _, x := range rr.ZSData {
|
||||
l += len(x) + 1
|
||||
}
|
||||
return l
|
||||
}
|
||||
func (rr *NS) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Ns, off+l, compression, true)
|
||||
return l
|
||||
}
|
||||
func (rr *NSAPPTR) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Ptr, off+l, compression, false)
|
||||
return l
|
||||
}
|
||||
func (rr *NSEC3PARAM) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l++ // Hash
|
||||
l++ // Flags
|
||||
l += 2 // Iterations
|
||||
l++ // SaltLength
|
||||
l += len(rr.Salt) / 2
|
||||
return l
|
||||
}
|
||||
func (rr *NULL) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += len(rr.Data)
|
||||
return l
|
||||
}
|
||||
func (rr *OPENPGPKEY) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += base64.StdEncoding.DecodedLen(len(rr.PublicKey))
|
||||
return l
|
||||
}
|
||||
func (rr *PTR) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Ptr, off+l, compression, true)
|
||||
return l
|
||||
}
|
||||
func (rr *PX) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Preference
|
||||
l += domainNameLen(rr.Map822, off+l, compression, false)
|
||||
l += domainNameLen(rr.Mapx400, off+l, compression, false)
|
||||
return l
|
||||
}
|
||||
func (rr *RFC3597) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += len(rr.Rdata) / 2
|
||||
return l
|
||||
}
|
||||
func (rr *RKEY) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Flags
|
||||
l++ // Protocol
|
||||
l++ // Algorithm
|
||||
l += base64.StdEncoding.DecodedLen(len(rr.PublicKey))
|
||||
return l
|
||||
}
|
||||
func (rr *RP) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Mbox, off+l, compression, false)
|
||||
l += domainNameLen(rr.Txt, off+l, compression, false)
|
||||
return l
|
||||
}
|
||||
func (rr *RRSIG) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // TypeCovered
|
||||
l++ // Algorithm
|
||||
l++ // Labels
|
||||
l += 4 // OrigTtl
|
||||
l += 4 // Expiration
|
||||
l += 4 // Inception
|
||||
l += 2 // KeyTag
|
||||
l += domainNameLen(rr.SignerName, off+l, compression, false)
|
||||
l += base64.StdEncoding.DecodedLen(len(rr.Signature))
|
||||
return l
|
||||
}
|
||||
func (rr *RT) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Preference
|
||||
l += domainNameLen(rr.Host, off+l, compression, false)
|
||||
return l
|
||||
}
|
||||
func (rr *SMIMEA) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l++ // Usage
|
||||
l++ // Selector
|
||||
l++ // MatchingType
|
||||
l += len(rr.Certificate) / 2
|
||||
return l
|
||||
}
|
||||
func (rr *SOA) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Ns, off+l, compression, true)
|
||||
l += domainNameLen(rr.Mbox, off+l, compression, true)
|
||||
l += 4 // Serial
|
||||
l += 4 // Refresh
|
||||
l += 4 // Retry
|
||||
l += 4 // Expire
|
||||
l += 4 // Minttl
|
||||
return l
|
||||
}
|
||||
func (rr *SPF) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
for _, x := range rr.Txt {
|
||||
l += len(x) + 1
|
||||
}
|
||||
return l
|
||||
}
|
||||
func (rr *SRV) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Priority
|
||||
l += 2 // Weight
|
||||
l += 2 // Port
|
||||
l += domainNameLen(rr.Target, off+l, compression, false)
|
||||
return l
|
||||
}
|
||||
func (rr *SSHFP) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l++ // Algorithm
|
||||
l++ // Type
|
||||
l += len(rr.FingerPrint) / 2
|
||||
return l
|
||||
}
|
||||
func (rr *SVCB) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Priority
|
||||
l += domainNameLen(rr.Target, off+l, compression, false)
|
||||
for _, x := range rr.Value {
|
||||
l += 4 + int(x.len())
|
||||
}
|
||||
return l
|
||||
}
|
||||
func (rr *TA) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // KeyTag
|
||||
l++ // Algorithm
|
||||
l++ // DigestType
|
||||
l += len(rr.Digest) / 2
|
||||
return l
|
||||
}
|
||||
func (rr *TALINK) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.PreviousName, off+l, compression, false)
|
||||
l += domainNameLen(rr.NextName, off+l, compression, false)
|
||||
return l
|
||||
}
|
||||
func (rr *TKEY) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Algorithm, off+l, compression, false)
|
||||
l += 4 // Inception
|
||||
l += 4 // Expiration
|
||||
l += 2 // Mode
|
||||
l += 2 // Error
|
||||
l += 2 // KeySize
|
||||
l += len(rr.Key) / 2
|
||||
l += 2 // OtherLen
|
||||
l += len(rr.OtherData) / 2
|
||||
return l
|
||||
}
|
||||
func (rr *TLSA) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l++ // Usage
|
||||
l++ // Selector
|
||||
l++ // MatchingType
|
||||
l += len(rr.Certificate) / 2
|
||||
return l
|
||||
}
|
||||
func (rr *TSIG) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += domainNameLen(rr.Algorithm, off+l, compression, false)
|
||||
l += 6 // TimeSigned
|
||||
l += 2 // Fudge
|
||||
l += 2 // MACSize
|
||||
l += len(rr.MAC) / 2
|
||||
l += 2 // OrigId
|
||||
l += 2 // Error
|
||||
l += 2 // OtherLen
|
||||
l += len(rr.OtherData) / 2
|
||||
return l
|
||||
}
|
||||
func (rr *TXT) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
for _, x := range rr.Txt {
|
||||
l += len(x) + 1
|
||||
}
|
||||
return l
|
||||
}
|
||||
func (rr *UID) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 4 // Uid
|
||||
return l
|
||||
}
|
||||
func (rr *UINFO) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += len(rr.Uinfo) + 1
|
||||
return l
|
||||
}
|
||||
func (rr *URI) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 2 // Priority
|
||||
l += 2 // Weight
|
||||
l += len(rr.Target)
|
||||
return l
|
||||
}
|
||||
func (rr *X25) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += len(rr.PSDNAddress) + 1
|
||||
return l
|
||||
}
|
||||
func (rr *ZONEMD) len(off int, compression map[string]struct{}) int {
|
||||
l := rr.Hdr.len(off, compression)
|
||||
l += 4 // Serial
|
||||
l++ // Scheme
|
||||
l++ // Hash
|
||||
l += len(rr.Digest) / 2
|
||||
return l
|
||||
}
|
||||
|
||||
// copy() functions
|
||||
func (rr *A) copy() RR {
|
||||
return &A{rr.Hdr, copyIP(rr.A)}
|
||||
}
|
||||
func (rr *AAAA) copy() RR {
|
||||
return &AAAA{rr.Hdr, copyIP(rr.AAAA)}
|
||||
}
|
||||
func (rr *AFSDB) copy() RR {
|
||||
return &AFSDB{rr.Hdr, rr.Subtype, rr.Hostname}
|
||||
}
|
||||
func (rr *ANY) copy() RR {
|
||||
return &ANY{rr.Hdr}
|
||||
}
|
||||
func (rr *APL) copy() RR {
|
||||
Prefixes := make([]APLPrefix, len(rr.Prefixes))
|
||||
for i, e := range rr.Prefixes {
|
||||
Prefixes[i] = e.copy()
|
||||
}
|
||||
return &APL{rr.Hdr, Prefixes}
|
||||
}
|
||||
func (rr *AVC) copy() RR {
|
||||
Txt := make([]string, len(rr.Txt))
|
||||
copy(Txt, rr.Txt)
|
||||
return &AVC{rr.Hdr, Txt}
|
||||
}
|
||||
func (rr *CAA) copy() RR {
|
||||
return &CAA{rr.Hdr, rr.Flag, rr.Tag, rr.Value}
|
||||
}
|
||||
func (rr *CDNSKEY) copy() RR {
|
||||
return &CDNSKEY{*rr.DNSKEY.copy().(*DNSKEY)}
|
||||
}
|
||||
func (rr *CDS) copy() RR {
|
||||
return &CDS{*rr.DS.copy().(*DS)}
|
||||
}
|
||||
func (rr *CERT) copy() RR {
|
||||
return &CERT{rr.Hdr, rr.Type, rr.KeyTag, rr.Algorithm, rr.Certificate}
|
||||
}
|
||||
func (rr *CNAME) copy() RR {
|
||||
return &CNAME{rr.Hdr, rr.Target}
|
||||
}
|
||||
func (rr *CSYNC) copy() RR {
|
||||
TypeBitMap := make([]uint16, len(rr.TypeBitMap))
|
||||
copy(TypeBitMap, rr.TypeBitMap)
|
||||
return &CSYNC{rr.Hdr, rr.Serial, rr.Flags, TypeBitMap}
|
||||
}
|
||||
func (rr *DHCID) copy() RR {
|
||||
return &DHCID{rr.Hdr, rr.Digest}
|
||||
}
|
||||
func (rr *DLV) copy() RR {
|
||||
return &DLV{*rr.DS.copy().(*DS)}
|
||||
}
|
||||
func (rr *DNAME) copy() RR {
|
||||
return &DNAME{rr.Hdr, rr.Target}
|
||||
}
|
||||
func (rr *DNSKEY) copy() RR {
|
||||
return &DNSKEY{rr.Hdr, rr.Flags, rr.Protocol, rr.Algorithm, rr.PublicKey}
|
||||
}
|
||||
func (rr *DS) copy() RR {
|
||||
return &DS{rr.Hdr, rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest}
|
||||
}
|
||||
func (rr *EID) copy() RR {
|
||||
return &EID{rr.Hdr, rr.Endpoint}
|
||||
}
|
||||
func (rr *EUI48) copy() RR {
|
||||
return &EUI48{rr.Hdr, rr.Address}
|
||||
}
|
||||
func (rr *EUI64) copy() RR {
|
||||
return &EUI64{rr.Hdr, rr.Address}
|
||||
}
|
||||
func (rr *GID) copy() RR {
|
||||
return &GID{rr.Hdr, rr.Gid}
|
||||
}
|
||||
func (rr *GPOS) copy() RR {
|
||||
return &GPOS{rr.Hdr, rr.Longitude, rr.Latitude, rr.Altitude}
|
||||
}
|
||||
func (rr *HINFO) copy() RR {
|
||||
return &HINFO{rr.Hdr, rr.Cpu, rr.Os}
|
||||
}
|
||||
func (rr *HIP) copy() RR {
|
||||
RendezvousServers := make([]string, len(rr.RendezvousServers))
|
||||
copy(RendezvousServers, rr.RendezvousServers)
|
||||
return &HIP{rr.Hdr, rr.HitLength, rr.PublicKeyAlgorithm, rr.PublicKeyLength, rr.Hit, rr.PublicKey, RendezvousServers}
|
||||
}
|
||||
func (rr *HTTPS) copy() RR {
|
||||
return &HTTPS{*rr.SVCB.copy().(*SVCB)}
|
||||
}
|
||||
func (rr *KEY) copy() RR {
|
||||
return &KEY{*rr.DNSKEY.copy().(*DNSKEY)}
|
||||
}
|
||||
func (rr *KX) copy() RR {
|
||||
return &KX{rr.Hdr, rr.Preference, rr.Exchanger}
|
||||
}
|
||||
func (rr *L32) copy() RR {
|
||||
return &L32{rr.Hdr, rr.Preference, copyIP(rr.Locator32)}
|
||||
}
|
||||
func (rr *L64) copy() RR {
|
||||
return &L64{rr.Hdr, rr.Preference, rr.Locator64}
|
||||
}
|
||||
func (rr *LOC) copy() RR {
|
||||
return &LOC{rr.Hdr, rr.Version, rr.Size, rr.HorizPre, rr.VertPre, rr.Latitude, rr.Longitude, rr.Altitude}
|
||||
}
|
||||
func (rr *LP) copy() RR {
|
||||
return &LP{rr.Hdr, rr.Preference, rr.Fqdn}
|
||||
}
|
||||
func (rr *MB) copy() RR {
|
||||
return &MB{rr.Hdr, rr.Mb}
|
||||
}
|
||||
func (rr *MD) copy() RR {
|
||||
return &MD{rr.Hdr, rr.Md}
|
||||
}
|
||||
func (rr *MF) copy() RR {
|
||||
return &MF{rr.Hdr, rr.Mf}
|
||||
}
|
||||
func (rr *MG) copy() RR {
|
||||
return &MG{rr.Hdr, rr.Mg}
|
||||
}
|
||||
func (rr *MINFO) copy() RR {
|
||||
return &MINFO{rr.Hdr, rr.Rmail, rr.Email}
|
||||
}
|
||||
func (rr *MR) copy() RR {
|
||||
return &MR{rr.Hdr, rr.Mr}
|
||||
}
|
||||
func (rr *MX) copy() RR {
|
||||
return &MX{rr.Hdr, rr.Preference, rr.Mx}
|
||||
}
|
||||
func (rr *NAPTR) copy() RR {
|
||||
return &NAPTR{rr.Hdr, rr.Order, rr.Preference, rr.Flags, rr.Service, rr.Regexp, rr.Replacement}
|
||||
}
|
||||
func (rr *NID) copy() RR {
|
||||
return &NID{rr.Hdr, rr.Preference, rr.NodeID}
|
||||
}
|
||||
func (rr *NIMLOC) copy() RR {
|
||||
return &NIMLOC{rr.Hdr, rr.Locator}
|
||||
}
|
||||
func (rr *NINFO) copy() RR {
|
||||
ZSData := make([]string, len(rr.ZSData))
|
||||
copy(ZSData, rr.ZSData)
|
||||
return &NINFO{rr.Hdr, ZSData}
|
||||
}
|
||||
func (rr *NS) copy() RR {
|
||||
return &NS{rr.Hdr, rr.Ns}
|
||||
}
|
||||
func (rr *NSAPPTR) copy() RR {
|
||||
return &NSAPPTR{rr.Hdr, rr.Ptr}
|
||||
}
|
||||
func (rr *NSEC) copy() RR {
|
||||
TypeBitMap := make([]uint16, len(rr.TypeBitMap))
|
||||
copy(TypeBitMap, rr.TypeBitMap)
|
||||
return &NSEC{rr.Hdr, rr.NextDomain, TypeBitMap}
|
||||
}
|
||||
func (rr *NSEC3) copy() RR {
|
||||
TypeBitMap := make([]uint16, len(rr.TypeBitMap))
|
||||
copy(TypeBitMap, rr.TypeBitMap)
|
||||
return &NSEC3{rr.Hdr, rr.Hash, rr.Flags, rr.Iterations, rr.SaltLength, rr.Salt, rr.HashLength, rr.NextDomain, TypeBitMap}
|
||||
}
|
||||
func (rr *NSEC3PARAM) copy() RR {
|
||||
return &NSEC3PARAM{rr.Hdr, rr.Hash, rr.Flags, rr.Iterations, rr.SaltLength, rr.Salt}
|
||||
}
|
||||
func (rr *NULL) copy() RR {
|
||||
return &NULL{rr.Hdr, rr.Data}
|
||||
}
|
||||
func (rr *OPENPGPKEY) copy() RR {
|
||||
return &OPENPGPKEY{rr.Hdr, rr.PublicKey}
|
||||
}
|
||||
func (rr *OPT) copy() RR {
|
||||
Option := make([]EDNS0, len(rr.Option))
|
||||
for i, e := range rr.Option {
|
||||
Option[i] = e.copy()
|
||||
}
|
||||
return &OPT{rr.Hdr, Option}
|
||||
}
|
||||
func (rr *PTR) copy() RR {
|
||||
return &PTR{rr.Hdr, rr.Ptr}
|
||||
}
|
||||
func (rr *PX) copy() RR {
|
||||
return &PX{rr.Hdr, rr.Preference, rr.Map822, rr.Mapx400}
|
||||
}
|
||||
func (rr *RFC3597) copy() RR {
|
||||
return &RFC3597{rr.Hdr, rr.Rdata}
|
||||
}
|
||||
func (rr *RKEY) copy() RR {
|
||||
return &RKEY{rr.Hdr, rr.Flags, rr.Protocol, rr.Algorithm, rr.PublicKey}
|
||||
}
|
||||
func (rr *RP) copy() RR {
|
||||
return &RP{rr.Hdr, rr.Mbox, rr.Txt}
|
||||
}
|
||||
func (rr *RRSIG) copy() RR {
|
||||
return &RRSIG{rr.Hdr, rr.TypeCovered, rr.Algorithm, rr.Labels, rr.OrigTtl, rr.Expiration, rr.Inception, rr.KeyTag, rr.SignerName, rr.Signature}
|
||||
}
|
||||
func (rr *RT) copy() RR {
|
||||
return &RT{rr.Hdr, rr.Preference, rr.Host}
|
||||
}
|
||||
func (rr *SIG) copy() RR {
|
||||
return &SIG{*rr.RRSIG.copy().(*RRSIG)}
|
||||
}
|
||||
func (rr *SMIMEA) copy() RR {
|
||||
return &SMIMEA{rr.Hdr, rr.Usage, rr.Selector, rr.MatchingType, rr.Certificate}
|
||||
}
|
||||
func (rr *SOA) copy() RR {
|
||||
return &SOA{rr.Hdr, rr.Ns, rr.Mbox, rr.Serial, rr.Refresh, rr.Retry, rr.Expire, rr.Minttl}
|
||||
}
|
||||
func (rr *SPF) copy() RR {
|
||||
Txt := make([]string, len(rr.Txt))
|
||||
copy(Txt, rr.Txt)
|
||||
return &SPF{rr.Hdr, Txt}
|
||||
}
|
||||
func (rr *SRV) copy() RR {
|
||||
return &SRV{rr.Hdr, rr.Priority, rr.Weight, rr.Port, rr.Target}
|
||||
}
|
||||
func (rr *SSHFP) copy() RR {
|
||||
return &SSHFP{rr.Hdr, rr.Algorithm, rr.Type, rr.FingerPrint}
|
||||
}
|
||||
func (rr *SVCB) copy() RR {
|
||||
Value := make([]SVCBKeyValue, len(rr.Value))
|
||||
for i, e := range rr.Value {
|
||||
Value[i] = e.copy()
|
||||
}
|
||||
return &SVCB{rr.Hdr, rr.Priority, rr.Target, Value}
|
||||
}
|
||||
func (rr *TA) copy() RR {
|
||||
return &TA{rr.Hdr, rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest}
|
||||
}
|
||||
func (rr *TALINK) copy() RR {
|
||||
return &TALINK{rr.Hdr, rr.PreviousName, rr.NextName}
|
||||
}
|
||||
func (rr *TKEY) copy() RR {
|
||||
return &TKEY{rr.Hdr, rr.Algorithm, rr.Inception, rr.Expiration, rr.Mode, rr.Error, rr.KeySize, rr.Key, rr.OtherLen, rr.OtherData}
|
||||
}
|
||||
func (rr *TLSA) copy() RR {
|
||||
return &TLSA{rr.Hdr, rr.Usage, rr.Selector, rr.MatchingType, rr.Certificate}
|
||||
}
|
||||
func (rr *TSIG) copy() RR {
|
||||
return &TSIG{rr.Hdr, rr.Algorithm, rr.TimeSigned, rr.Fudge, rr.MACSize, rr.MAC, rr.OrigId, rr.Error, rr.OtherLen, rr.OtherData}
|
||||
}
|
||||
func (rr *TXT) copy() RR {
|
||||
Txt := make([]string, len(rr.Txt))
|
||||
copy(Txt, rr.Txt)
|
||||
return &TXT{rr.Hdr, Txt}
|
||||
}
|
||||
func (rr *UID) copy() RR {
|
||||
return &UID{rr.Hdr, rr.Uid}
|
||||
}
|
||||
func (rr *UINFO) copy() RR {
|
||||
return &UINFO{rr.Hdr, rr.Uinfo}
|
||||
}
|
||||
func (rr *URI) copy() RR {
|
||||
return &URI{rr.Hdr, rr.Priority, rr.Weight, rr.Target}
|
||||
}
|
||||
func (rr *X25) copy() RR {
|
||||
return &X25{rr.Hdr, rr.PSDNAddress}
|
||||
}
|
||||
func (rr *ZONEMD) copy() RR {
|
||||
return &ZONEMD{rr.Hdr, rr.Serial, rr.Scheme, rr.Hash, rr.Digest}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bpf
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Assemble converts insts into raw instructions suitable for loading
|
||||
// into a BPF virtual machine.
|
||||
//
|
||||
// Currently, no optimization is attempted, the assembled program flow
|
||||
// is exactly as provided.
|
||||
func Assemble(insts []Instruction) ([]RawInstruction, error) {
|
||||
ret := make([]RawInstruction, len(insts))
|
||||
var err error
|
||||
for i, inst := range insts {
|
||||
ret[i], err = inst.Assemble()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("assembling instruction %d: %s", i+1, err)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Disassemble attempts to parse raw back into
|
||||
// Instructions. Unrecognized RawInstructions are assumed to be an
|
||||
// extension not implemented by this package, and are passed through
|
||||
// unchanged to the output. The allDecoded value reports whether insts
|
||||
// contains no RawInstructions.
|
||||
func Disassemble(raw []RawInstruction) (insts []Instruction, allDecoded bool) {
|
||||
insts = make([]Instruction, len(raw))
|
||||
allDecoded = true
|
||||
for i, r := range raw {
|
||||
insts[i] = r.Disassemble()
|
||||
if _, ok := insts[i].(RawInstruction); ok {
|
||||
allDecoded = false
|
||||
}
|
||||
}
|
||||
return insts, allDecoded
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bpf
|
||||
|
||||
// A Register is a register of the BPF virtual machine.
|
||||
type Register uint16
|
||||
|
||||
const (
|
||||
// RegA is the accumulator register. RegA is always the
|
||||
// destination register of ALU operations.
|
||||
RegA Register = iota
|
||||
// RegX is the indirection register, used by LoadIndirect
|
||||
// operations.
|
||||
RegX
|
||||
)
|
||||
|
||||
// An ALUOp is an arithmetic or logic operation.
|
||||
type ALUOp uint16
|
||||
|
||||
// ALU binary operation types.
|
||||
const (
|
||||
ALUOpAdd ALUOp = iota << 4
|
||||
ALUOpSub
|
||||
ALUOpMul
|
||||
ALUOpDiv
|
||||
ALUOpOr
|
||||
ALUOpAnd
|
||||
ALUOpShiftLeft
|
||||
ALUOpShiftRight
|
||||
aluOpNeg // Not exported because it's the only unary ALU operation, and gets its own instruction type.
|
||||
ALUOpMod
|
||||
ALUOpXor
|
||||
)
|
||||
|
||||
// A JumpTest is a comparison operator used in conditional jumps.
|
||||
type JumpTest uint16
|
||||
|
||||
// Supported operators for conditional jumps.
|
||||
// K can be RegX for JumpIfX
|
||||
const (
|
||||
// K == A
|
||||
JumpEqual JumpTest = iota
|
||||
// K != A
|
||||
JumpNotEqual
|
||||
// K > A
|
||||
JumpGreaterThan
|
||||
// K < A
|
||||
JumpLessThan
|
||||
// K >= A
|
||||
JumpGreaterOrEqual
|
||||
// K <= A
|
||||
JumpLessOrEqual
|
||||
// K & A != 0
|
||||
JumpBitsSet
|
||||
// K & A == 0
|
||||
JumpBitsNotSet
|
||||
)
|
||||
|
||||
// An Extension is a function call provided by the kernel that
|
||||
// performs advanced operations that are expensive or impossible
|
||||
// within the BPF virtual machine.
|
||||
//
|
||||
// Extensions are only implemented by the Linux kernel.
|
||||
//
|
||||
// TODO: should we prune this list? Some of these extensions seem
|
||||
// either broken or near-impossible to use correctly, whereas other
|
||||
// (len, random, ifindex) are quite useful.
|
||||
type Extension int
|
||||
|
||||
// Extension functions available in the Linux kernel.
|
||||
const (
|
||||
// extOffset is the negative maximum number of instructions used
|
||||
// to load instructions by overloading the K argument.
|
||||
extOffset = -0x1000
|
||||
// ExtLen returns the length of the packet.
|
||||
ExtLen Extension = 1
|
||||
// ExtProto returns the packet's L3 protocol type.
|
||||
ExtProto Extension = 0
|
||||
// ExtType returns the packet's type (skb->pkt_type in the kernel)
|
||||
//
|
||||
// TODO: better documentation. How nice an API do we want to
|
||||
// provide for these esoteric extensions?
|
||||
ExtType Extension = 4
|
||||
// ExtPayloadOffset returns the offset of the packet payload, or
|
||||
// the first protocol header that the kernel does not know how to
|
||||
// parse.
|
||||
ExtPayloadOffset Extension = 52
|
||||
// ExtInterfaceIndex returns the index of the interface on which
|
||||
// the packet was received.
|
||||
ExtInterfaceIndex Extension = 8
|
||||
// ExtNetlinkAttr returns the netlink attribute of type X at
|
||||
// offset A.
|
||||
ExtNetlinkAttr Extension = 12
|
||||
// ExtNetlinkAttrNested returns the nested netlink attribute of
|
||||
// type X at offset A.
|
||||
ExtNetlinkAttrNested Extension = 16
|
||||
// ExtMark returns the packet's mark value.
|
||||
ExtMark Extension = 20
|
||||
// ExtQueue returns the packet's assigned hardware queue.
|
||||
ExtQueue Extension = 24
|
||||
// ExtLinkLayerType returns the packet's hardware address type
|
||||
// (e.g. Ethernet, Infiniband).
|
||||
ExtLinkLayerType Extension = 28
|
||||
// ExtRXHash returns the packets receive hash.
|
||||
//
|
||||
// TODO: figure out what this rxhash actually is.
|
||||
ExtRXHash Extension = 32
|
||||
// ExtCPUID returns the ID of the CPU processing the current
|
||||
// packet.
|
||||
ExtCPUID Extension = 36
|
||||
// ExtVLANTag returns the packet's VLAN tag.
|
||||
ExtVLANTag Extension = 44
|
||||
// ExtVLANTagPresent returns non-zero if the packet has a VLAN
|
||||
// tag.
|
||||
//
|
||||
// TODO: I think this might be a lie: it reads bit 0x1000 of the
|
||||
// VLAN header, which changed meaning in recent revisions of the
|
||||
// spec - this extension may now return meaningless information.
|
||||
ExtVLANTagPresent Extension = 48
|
||||
// ExtVLANProto returns 0x8100 if the frame has a VLAN header,
|
||||
// 0x88a8 if the frame has a "Q-in-Q" double VLAN header, or some
|
||||
// other value if no VLAN information is present.
|
||||
ExtVLANProto Extension = 60
|
||||
// ExtRand returns a uniformly random uint32.
|
||||
ExtRand Extension = 56
|
||||
)
|
||||
|
||||
// The following gives names to various bit patterns used in opcode construction.
|
||||
|
||||
const (
|
||||
opMaskCls uint16 = 0x7
|
||||
// opClsLoad masks
|
||||
opMaskLoadDest = 0x01
|
||||
opMaskLoadWidth = 0x18
|
||||
opMaskLoadMode = 0xe0
|
||||
// opClsALU & opClsJump
|
||||
opMaskOperand = 0x08
|
||||
opMaskOperator = 0xf0
|
||||
)
|
||||
|
||||
const (
|
||||
// +---------------+-----------------+---+---+---+
|
||||
// | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 0 |
|
||||
// +---------------+-----------------+---+---+---+
|
||||
opClsLoadA uint16 = iota
|
||||
// +---------------+-----------------+---+---+---+
|
||||
// | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 1 |
|
||||
// +---------------+-----------------+---+---+---+
|
||||
opClsLoadX
|
||||
// +---+---+---+---+---+---+---+---+
|
||||
// | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
|
||||
// +---+---+---+---+---+---+---+---+
|
||||
opClsStoreA
|
||||
// +---+---+---+---+---+---+---+---+
|
||||
// | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
|
||||
// +---+---+---+---+---+---+---+---+
|
||||
opClsStoreX
|
||||
// +---------------+-----------------+---+---+---+
|
||||
// | Operator (4b) | OperandSrc (1b) | 1 | 0 | 0 |
|
||||
// +---------------+-----------------+---+---+---+
|
||||
opClsALU
|
||||
// +-----------------------------+---+---+---+---+
|
||||
// | TestOperator (4b) | 0 | 1 | 0 | 1 |
|
||||
// +-----------------------------+---+---+---+---+
|
||||
opClsJump
|
||||
// +---+-------------------------+---+---+---+---+
|
||||
// | 0 | 0 | 0 | RetSrc (1b) | 0 | 1 | 1 | 0 |
|
||||
// +---+-------------------------+---+---+---+---+
|
||||
opClsReturn
|
||||
// +---+-------------------------+---+---+---+---+
|
||||
// | 0 | 0 | 0 | TXAorTAX (1b) | 0 | 1 | 1 | 1 |
|
||||
// +---+-------------------------+---+---+---+---+
|
||||
opClsMisc
|
||||
)
|
||||
|
||||
const (
|
||||
opAddrModeImmediate uint16 = iota << 5
|
||||
opAddrModeAbsolute
|
||||
opAddrModeIndirect
|
||||
opAddrModeScratch
|
||||
opAddrModePacketLen // actually an extension, not an addressing mode.
|
||||
opAddrModeMemShift
|
||||
)
|
||||
|
||||
const (
|
||||
opLoadWidth4 uint16 = iota << 3
|
||||
opLoadWidth2
|
||||
opLoadWidth1
|
||||
)
|
||||
|
||||
// Operand for ALU and Jump instructions
|
||||
type opOperand uint16
|
||||
|
||||
// Supported operand sources.
|
||||
const (
|
||||
opOperandConstant opOperand = iota << 3
|
||||
opOperandX
|
||||
)
|
||||
|
||||
// An jumpOp is a conditional jump condition.
|
||||
type jumpOp uint16
|
||||
|
||||
// Supported jump conditions.
|
||||
const (
|
||||
opJumpAlways jumpOp = iota << 4
|
||||
opJumpEqual
|
||||
opJumpGT
|
||||
opJumpGE
|
||||
opJumpSet
|
||||
)
|
||||
|
||||
const (
|
||||
opRetSrcConstant uint16 = iota << 4
|
||||
opRetSrcA
|
||||
)
|
||||
|
||||
const (
|
||||
opMiscTAX = 0x00
|
||||
opMiscTXA = 0x80
|
||||
)
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package bpf implements marshaling and unmarshaling of programs for the
|
||||
Berkeley Packet Filter virtual machine, and provides a Go implementation
|
||||
of the virtual machine.
|
||||
|
||||
BPF's main use is to specify a packet filter for network taps, so that
|
||||
the kernel doesn't have to expensively copy every packet it sees to
|
||||
userspace. However, it's been repurposed to other areas where running
|
||||
user code in-kernel is needed. For example, Linux's seccomp uses BPF
|
||||
to apply security policies to system calls. For simplicity, this
|
||||
documentation refers only to packets, but other uses of BPF have their
|
||||
own data payloads.
|
||||
|
||||
BPF programs run in a restricted virtual machine. It has almost no
|
||||
access to kernel functions, and while conditional branches are
|
||||
allowed, they can only jump forwards, to guarantee that there are no
|
||||
infinite loops.
|
||||
|
||||
# The virtual machine
|
||||
|
||||
The BPF VM is an accumulator machine. Its main register, called
|
||||
register A, is an implicit source and destination in all arithmetic
|
||||
and logic operations. The machine also has 16 scratch registers for
|
||||
temporary storage, and an indirection register (register X) for
|
||||
indirect memory access. All registers are 32 bits wide.
|
||||
|
||||
Each run of a BPF program is given one packet, which is placed in the
|
||||
VM's read-only "main memory". LoadAbsolute and LoadIndirect
|
||||
instructions can fetch up to 32 bits at a time into register A for
|
||||
examination.
|
||||
|
||||
The goal of a BPF program is to produce and return a verdict (uint32),
|
||||
which tells the kernel what to do with the packet. In the context of
|
||||
packet filtering, the returned value is the number of bytes of the
|
||||
packet to forward to userspace, or 0 to ignore the packet. Other
|
||||
contexts like seccomp define their own return values.
|
||||
|
||||
In order to simplify programs, attempts to read past the end of the
|
||||
packet terminate the program execution with a verdict of 0 (ignore
|
||||
packet). This means that the vast majority of BPF programs don't need
|
||||
to do any explicit bounds checking.
|
||||
|
||||
In addition to the bytes of the packet, some BPF programs have access
|
||||
to extensions, which are essentially calls to kernel utility
|
||||
functions. Currently, the only extensions supported by this package
|
||||
are the Linux packet filter extensions.
|
||||
|
||||
# Examples
|
||||
|
||||
This packet filter selects all ARP packets.
|
||||
|
||||
bpf.Assemble([]bpf.Instruction{
|
||||
// Load "EtherType" field from the ethernet header.
|
||||
bpf.LoadAbsolute{Off: 12, Size: 2},
|
||||
// Skip over the next instruction if EtherType is not ARP.
|
||||
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x0806, SkipTrue: 1},
|
||||
// Verdict is "send up to 4k of the packet to userspace."
|
||||
bpf.RetConstant{Val: 4096},
|
||||
// Verdict is "ignore packet."
|
||||
bpf.RetConstant{Val: 0},
|
||||
})
|
||||
|
||||
This packet filter captures a random 1% sample of traffic.
|
||||
|
||||
bpf.Assemble([]bpf.Instruction{
|
||||
// Get a 32-bit random number from the Linux kernel.
|
||||
bpf.LoadExtension{Num: bpf.ExtRand},
|
||||
// 1% dice roll?
|
||||
bpf.JumpIf{Cond: bpf.JumpLessThan, Val: 2^32/100, SkipFalse: 1},
|
||||
// Capture.
|
||||
bpf.RetConstant{Val: 4096},
|
||||
// Ignore.
|
||||
bpf.RetConstant{Val: 0},
|
||||
})
|
||||
*/
|
||||
package bpf // import "golang.org/x/net/bpf"
|
|
@ -0,0 +1,726 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bpf
|
||||
|
||||
import "fmt"
|
||||
|
||||
// An Instruction is one instruction executed by the BPF virtual
|
||||
// machine.
|
||||
type Instruction interface {
|
||||
// Assemble assembles the Instruction into a RawInstruction.
|
||||
Assemble() (RawInstruction, error)
|
||||
}
|
||||
|
||||
// A RawInstruction is a raw BPF virtual machine instruction.
|
||||
type RawInstruction struct {
|
||||
// Operation to execute.
|
||||
Op uint16
|
||||
// For conditional jump instructions, the number of instructions
|
||||
// to skip if the condition is true/false.
|
||||
Jt uint8
|
||||
Jf uint8
|
||||
// Constant parameter. The meaning depends on the Op.
|
||||
K uint32
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (ri RawInstruction) Assemble() (RawInstruction, error) { return ri, nil }
|
||||
|
||||
// Disassemble parses ri into an Instruction and returns it. If ri is
|
||||
// not recognized by this package, ri itself is returned.
|
||||
func (ri RawInstruction) Disassemble() Instruction {
|
||||
switch ri.Op & opMaskCls {
|
||||
case opClsLoadA, opClsLoadX:
|
||||
reg := Register(ri.Op & opMaskLoadDest)
|
||||
sz := 0
|
||||
switch ri.Op & opMaskLoadWidth {
|
||||
case opLoadWidth4:
|
||||
sz = 4
|
||||
case opLoadWidth2:
|
||||
sz = 2
|
||||
case opLoadWidth1:
|
||||
sz = 1
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
switch ri.Op & opMaskLoadMode {
|
||||
case opAddrModeImmediate:
|
||||
if sz != 4 {
|
||||
return ri
|
||||
}
|
||||
return LoadConstant{Dst: reg, Val: ri.K}
|
||||
case opAddrModeScratch:
|
||||
if sz != 4 || ri.K > 15 {
|
||||
return ri
|
||||
}
|
||||
return LoadScratch{Dst: reg, N: int(ri.K)}
|
||||
case opAddrModeAbsolute:
|
||||
if ri.K > extOffset+0xffffffff {
|
||||
return LoadExtension{Num: Extension(-extOffset + ri.K)}
|
||||
}
|
||||
return LoadAbsolute{Size: sz, Off: ri.K}
|
||||
case opAddrModeIndirect:
|
||||
return LoadIndirect{Size: sz, Off: ri.K}
|
||||
case opAddrModePacketLen:
|
||||
if sz != 4 {
|
||||
return ri
|
||||
}
|
||||
return LoadExtension{Num: ExtLen}
|
||||
case opAddrModeMemShift:
|
||||
return LoadMemShift{Off: ri.K}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
|
||||
case opClsStoreA:
|
||||
if ri.Op != opClsStoreA || ri.K > 15 {
|
||||
return ri
|
||||
}
|
||||
return StoreScratch{Src: RegA, N: int(ri.K)}
|
||||
|
||||
case opClsStoreX:
|
||||
if ri.Op != opClsStoreX || ri.K > 15 {
|
||||
return ri
|
||||
}
|
||||
return StoreScratch{Src: RegX, N: int(ri.K)}
|
||||
|
||||
case opClsALU:
|
||||
switch op := ALUOp(ri.Op & opMaskOperator); op {
|
||||
case ALUOpAdd, ALUOpSub, ALUOpMul, ALUOpDiv, ALUOpOr, ALUOpAnd, ALUOpShiftLeft, ALUOpShiftRight, ALUOpMod, ALUOpXor:
|
||||
switch operand := opOperand(ri.Op & opMaskOperand); operand {
|
||||
case opOperandX:
|
||||
return ALUOpX{Op: op}
|
||||
case opOperandConstant:
|
||||
return ALUOpConstant{Op: op, Val: ri.K}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
case aluOpNeg:
|
||||
return NegateA{}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
|
||||
case opClsJump:
|
||||
switch op := jumpOp(ri.Op & opMaskOperator); op {
|
||||
case opJumpAlways:
|
||||
return Jump{Skip: ri.K}
|
||||
case opJumpEqual, opJumpGT, opJumpGE, opJumpSet:
|
||||
cond, skipTrue, skipFalse := jumpOpToTest(op, ri.Jt, ri.Jf)
|
||||
switch operand := opOperand(ri.Op & opMaskOperand); operand {
|
||||
case opOperandX:
|
||||
return JumpIfX{Cond: cond, SkipTrue: skipTrue, SkipFalse: skipFalse}
|
||||
case opOperandConstant:
|
||||
return JumpIf{Cond: cond, Val: ri.K, SkipTrue: skipTrue, SkipFalse: skipFalse}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
|
||||
case opClsReturn:
|
||||
switch ri.Op {
|
||||
case opClsReturn | opRetSrcA:
|
||||
return RetA{}
|
||||
case opClsReturn | opRetSrcConstant:
|
||||
return RetConstant{Val: ri.K}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
|
||||
case opClsMisc:
|
||||
switch ri.Op {
|
||||
case opClsMisc | opMiscTAX:
|
||||
return TAX{}
|
||||
case opClsMisc | opMiscTXA:
|
||||
return TXA{}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
|
||||
default:
|
||||
panic("unreachable") // switch is exhaustive on the bit pattern
|
||||
}
|
||||
}
|
||||
|
||||
func jumpOpToTest(op jumpOp, skipTrue uint8, skipFalse uint8) (JumpTest, uint8, uint8) {
|
||||
var test JumpTest
|
||||
|
||||
// Decode "fake" jump conditions that don't appear in machine code
|
||||
// Ensures the Assemble -> Disassemble stage recreates the same instructions
|
||||
// See https://github.com/golang/go/issues/18470
|
||||
if skipTrue == 0 {
|
||||
switch op {
|
||||
case opJumpEqual:
|
||||
test = JumpNotEqual
|
||||
case opJumpGT:
|
||||
test = JumpLessOrEqual
|
||||
case opJumpGE:
|
||||
test = JumpLessThan
|
||||
case opJumpSet:
|
||||
test = JumpBitsNotSet
|
||||
}
|
||||
|
||||
return test, skipFalse, 0
|
||||
}
|
||||
|
||||
switch op {
|
||||
case opJumpEqual:
|
||||
test = JumpEqual
|
||||
case opJumpGT:
|
||||
test = JumpGreaterThan
|
||||
case opJumpGE:
|
||||
test = JumpGreaterOrEqual
|
||||
case opJumpSet:
|
||||
test = JumpBitsSet
|
||||
}
|
||||
|
||||
return test, skipTrue, skipFalse
|
||||
}
|
||||
|
||||
// LoadConstant loads Val into register Dst.
|
||||
type LoadConstant struct {
|
||||
Dst Register
|
||||
Val uint32
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a LoadConstant) Assemble() (RawInstruction, error) {
|
||||
return assembleLoad(a.Dst, 4, opAddrModeImmediate, a.Val)
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a LoadConstant) String() string {
|
||||
switch a.Dst {
|
||||
case RegA:
|
||||
return fmt.Sprintf("ld #%d", a.Val)
|
||||
case RegX:
|
||||
return fmt.Sprintf("ldx #%d", a.Val)
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadScratch loads scratch[N] into register Dst.
|
||||
type LoadScratch struct {
|
||||
Dst Register
|
||||
N int // 0-15
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a LoadScratch) Assemble() (RawInstruction, error) {
|
||||
if a.N < 0 || a.N > 15 {
|
||||
return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
|
||||
}
|
||||
return assembleLoad(a.Dst, 4, opAddrModeScratch, uint32(a.N))
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a LoadScratch) String() string {
|
||||
switch a.Dst {
|
||||
case RegA:
|
||||
return fmt.Sprintf("ld M[%d]", a.N)
|
||||
case RegX:
|
||||
return fmt.Sprintf("ldx M[%d]", a.N)
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadAbsolute loads packet[Off:Off+Size] as an integer value into
|
||||
// register A.
|
||||
type LoadAbsolute struct {
|
||||
Off uint32
|
||||
Size int // 1, 2 or 4
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a LoadAbsolute) Assemble() (RawInstruction, error) {
|
||||
return assembleLoad(RegA, a.Size, opAddrModeAbsolute, a.Off)
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a LoadAbsolute) String() string {
|
||||
switch a.Size {
|
||||
case 1: // byte
|
||||
return fmt.Sprintf("ldb [%d]", a.Off)
|
||||
case 2: // half word
|
||||
return fmt.Sprintf("ldh [%d]", a.Off)
|
||||
case 4: // word
|
||||
if a.Off > extOffset+0xffffffff {
|
||||
return LoadExtension{Num: Extension(a.Off + 0x1000)}.String()
|
||||
}
|
||||
return fmt.Sprintf("ld [%d]", a.Off)
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadIndirect loads packet[X+Off:X+Off+Size] as an integer value
|
||||
// into register A.
|
||||
type LoadIndirect struct {
|
||||
Off uint32
|
||||
Size int // 1, 2 or 4
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a LoadIndirect) Assemble() (RawInstruction, error) {
|
||||
return assembleLoad(RegA, a.Size, opAddrModeIndirect, a.Off)
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a LoadIndirect) String() string {
|
||||
switch a.Size {
|
||||
case 1: // byte
|
||||
return fmt.Sprintf("ldb [x + %d]", a.Off)
|
||||
case 2: // half word
|
||||
return fmt.Sprintf("ldh [x + %d]", a.Off)
|
||||
case 4: // word
|
||||
return fmt.Sprintf("ld [x + %d]", a.Off)
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadMemShift multiplies the first 4 bits of the byte at packet[Off]
|
||||
// by 4 and stores the result in register X.
|
||||
//
|
||||
// This instruction is mainly useful to load into X the length of an
|
||||
// IPv4 packet header in a single instruction, rather than have to do
|
||||
// the arithmetic on the header's first byte by hand.
|
||||
type LoadMemShift struct {
|
||||
Off uint32
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a LoadMemShift) Assemble() (RawInstruction, error) {
|
||||
return assembleLoad(RegX, 1, opAddrModeMemShift, a.Off)
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a LoadMemShift) String() string {
|
||||
return fmt.Sprintf("ldx 4*([%d]&0xf)", a.Off)
|
||||
}
|
||||
|
||||
// LoadExtension invokes a linux-specific extension and stores the
|
||||
// result in register A.
|
||||
type LoadExtension struct {
|
||||
Num Extension
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a LoadExtension) Assemble() (RawInstruction, error) {
|
||||
if a.Num == ExtLen {
|
||||
return assembleLoad(RegA, 4, opAddrModePacketLen, 0)
|
||||
}
|
||||
return assembleLoad(RegA, 4, opAddrModeAbsolute, uint32(extOffset+a.Num))
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a LoadExtension) String() string {
|
||||
switch a.Num {
|
||||
case ExtLen:
|
||||
return "ld #len"
|
||||
case ExtProto:
|
||||
return "ld #proto"
|
||||
case ExtType:
|
||||
return "ld #type"
|
||||
case ExtPayloadOffset:
|
||||
return "ld #poff"
|
||||
case ExtInterfaceIndex:
|
||||
return "ld #ifidx"
|
||||
case ExtNetlinkAttr:
|
||||
return "ld #nla"
|
||||
case ExtNetlinkAttrNested:
|
||||
return "ld #nlan"
|
||||
case ExtMark:
|
||||
return "ld #mark"
|
||||
case ExtQueue:
|
||||
return "ld #queue"
|
||||
case ExtLinkLayerType:
|
||||
return "ld #hatype"
|
||||
case ExtRXHash:
|
||||
return "ld #rxhash"
|
||||
case ExtCPUID:
|
||||
return "ld #cpu"
|
||||
case ExtVLANTag:
|
||||
return "ld #vlan_tci"
|
||||
case ExtVLANTagPresent:
|
||||
return "ld #vlan_avail"
|
||||
case ExtVLANProto:
|
||||
return "ld #vlan_tpid"
|
||||
case ExtRand:
|
||||
return "ld #rand"
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// StoreScratch stores register Src into scratch[N].
|
||||
type StoreScratch struct {
|
||||
Src Register
|
||||
N int // 0-15
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a StoreScratch) Assemble() (RawInstruction, error) {
|
||||
if a.N < 0 || a.N > 15 {
|
||||
return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
|
||||
}
|
||||
var op uint16
|
||||
switch a.Src {
|
||||
case RegA:
|
||||
op = opClsStoreA
|
||||
case RegX:
|
||||
op = opClsStoreX
|
||||
default:
|
||||
return RawInstruction{}, fmt.Errorf("invalid source register %v", a.Src)
|
||||
}
|
||||
|
||||
return RawInstruction{
|
||||
Op: op,
|
||||
K: uint32(a.N),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a StoreScratch) String() string {
|
||||
switch a.Src {
|
||||
case RegA:
|
||||
return fmt.Sprintf("st M[%d]", a.N)
|
||||
case RegX:
|
||||
return fmt.Sprintf("stx M[%d]", a.N)
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// ALUOpConstant executes A = A <Op> Val.
|
||||
type ALUOpConstant struct {
|
||||
Op ALUOp
|
||||
Val uint32
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a ALUOpConstant) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsALU | uint16(opOperandConstant) | uint16(a.Op),
|
||||
K: a.Val,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a ALUOpConstant) String() string {
|
||||
switch a.Op {
|
||||
case ALUOpAdd:
|
||||
return fmt.Sprintf("add #%d", a.Val)
|
||||
case ALUOpSub:
|
||||
return fmt.Sprintf("sub #%d", a.Val)
|
||||
case ALUOpMul:
|
||||
return fmt.Sprintf("mul #%d", a.Val)
|
||||
case ALUOpDiv:
|
||||
return fmt.Sprintf("div #%d", a.Val)
|
||||
case ALUOpMod:
|
||||
return fmt.Sprintf("mod #%d", a.Val)
|
||||
case ALUOpAnd:
|
||||
return fmt.Sprintf("and #%d", a.Val)
|
||||
case ALUOpOr:
|
||||
return fmt.Sprintf("or #%d", a.Val)
|
||||
case ALUOpXor:
|
||||
return fmt.Sprintf("xor #%d", a.Val)
|
||||
case ALUOpShiftLeft:
|
||||
return fmt.Sprintf("lsh #%d", a.Val)
|
||||
case ALUOpShiftRight:
|
||||
return fmt.Sprintf("rsh #%d", a.Val)
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// ALUOpX executes A = A <Op> X
|
||||
type ALUOpX struct {
|
||||
Op ALUOp
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a ALUOpX) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsALU | uint16(opOperandX) | uint16(a.Op),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a ALUOpX) String() string {
|
||||
switch a.Op {
|
||||
case ALUOpAdd:
|
||||
return "add x"
|
||||
case ALUOpSub:
|
||||
return "sub x"
|
||||
case ALUOpMul:
|
||||
return "mul x"
|
||||
case ALUOpDiv:
|
||||
return "div x"
|
||||
case ALUOpMod:
|
||||
return "mod x"
|
||||
case ALUOpAnd:
|
||||
return "and x"
|
||||
case ALUOpOr:
|
||||
return "or x"
|
||||
case ALUOpXor:
|
||||
return "xor x"
|
||||
case ALUOpShiftLeft:
|
||||
return "lsh x"
|
||||
case ALUOpShiftRight:
|
||||
return "rsh x"
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// NegateA executes A = -A.
|
||||
type NegateA struct{}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a NegateA) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsALU | uint16(aluOpNeg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a NegateA) String() string {
|
||||
return fmt.Sprintf("neg")
|
||||
}
|
||||
|
||||
// Jump skips the following Skip instructions in the program.
|
||||
type Jump struct {
|
||||
Skip uint32
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a Jump) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsJump | uint16(opJumpAlways),
|
||||
K: a.Skip,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a Jump) String() string {
|
||||
return fmt.Sprintf("ja %d", a.Skip)
|
||||
}
|
||||
|
||||
// JumpIf skips the following Skip instructions in the program if A
|
||||
// <Cond> Val is true.
|
||||
type JumpIf struct {
|
||||
Cond JumpTest
|
||||
Val uint32
|
||||
SkipTrue uint8
|
||||
SkipFalse uint8
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a JumpIf) Assemble() (RawInstruction, error) {
|
||||
return jumpToRaw(a.Cond, opOperandConstant, a.Val, a.SkipTrue, a.SkipFalse)
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a JumpIf) String() string {
|
||||
return jumpToString(a.Cond, fmt.Sprintf("#%d", a.Val), a.SkipTrue, a.SkipFalse)
|
||||
}
|
||||
|
||||
// JumpIfX skips the following Skip instructions in the program if A
|
||||
// <Cond> X is true.
|
||||
type JumpIfX struct {
|
||||
Cond JumpTest
|
||||
SkipTrue uint8
|
||||
SkipFalse uint8
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a JumpIfX) Assemble() (RawInstruction, error) {
|
||||
return jumpToRaw(a.Cond, opOperandX, 0, a.SkipTrue, a.SkipFalse)
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a JumpIfX) String() string {
|
||||
return jumpToString(a.Cond, "x", a.SkipTrue, a.SkipFalse)
|
||||
}
|
||||
|
||||
// jumpToRaw assembles a jump instruction into a RawInstruction
|
||||
func jumpToRaw(test JumpTest, operand opOperand, k uint32, skipTrue, skipFalse uint8) (RawInstruction, error) {
|
||||
var (
|
||||
cond jumpOp
|
||||
flip bool
|
||||
)
|
||||
switch test {
|
||||
case JumpEqual:
|
||||
cond = opJumpEqual
|
||||
case JumpNotEqual:
|
||||
cond, flip = opJumpEqual, true
|
||||
case JumpGreaterThan:
|
||||
cond = opJumpGT
|
||||
case JumpLessThan:
|
||||
cond, flip = opJumpGE, true
|
||||
case JumpGreaterOrEqual:
|
||||
cond = opJumpGE
|
||||
case JumpLessOrEqual:
|
||||
cond, flip = opJumpGT, true
|
||||
case JumpBitsSet:
|
||||
cond = opJumpSet
|
||||
case JumpBitsNotSet:
|
||||
cond, flip = opJumpSet, true
|
||||
default:
|
||||
return RawInstruction{}, fmt.Errorf("unknown JumpTest %v", test)
|
||||
}
|
||||
jt, jf := skipTrue, skipFalse
|
||||
if flip {
|
||||
jt, jf = jf, jt
|
||||
}
|
||||
return RawInstruction{
|
||||
Op: opClsJump | uint16(cond) | uint16(operand),
|
||||
Jt: jt,
|
||||
Jf: jf,
|
||||
K: k,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// jumpToString converts a jump instruction to assembler notation
|
||||
func jumpToString(cond JumpTest, operand string, skipTrue, skipFalse uint8) string {
|
||||
switch cond {
|
||||
// K == A
|
||||
case JumpEqual:
|
||||
return conditionalJump(operand, skipTrue, skipFalse, "jeq", "jneq")
|
||||
// K != A
|
||||
case JumpNotEqual:
|
||||
return fmt.Sprintf("jneq %s,%d", operand, skipTrue)
|
||||
// K > A
|
||||
case JumpGreaterThan:
|
||||
return conditionalJump(operand, skipTrue, skipFalse, "jgt", "jle")
|
||||
// K < A
|
||||
case JumpLessThan:
|
||||
return fmt.Sprintf("jlt %s,%d", operand, skipTrue)
|
||||
// K >= A
|
||||
case JumpGreaterOrEqual:
|
||||
return conditionalJump(operand, skipTrue, skipFalse, "jge", "jlt")
|
||||
// K <= A
|
||||
case JumpLessOrEqual:
|
||||
return fmt.Sprintf("jle %s,%d", operand, skipTrue)
|
||||
// K & A != 0
|
||||
case JumpBitsSet:
|
||||
if skipFalse > 0 {
|
||||
return fmt.Sprintf("jset %s,%d,%d", operand, skipTrue, skipFalse)
|
||||
}
|
||||
return fmt.Sprintf("jset %s,%d", operand, skipTrue)
|
||||
// K & A == 0, there is no assembler instruction for JumpBitNotSet, use JumpBitSet and invert skips
|
||||
case JumpBitsNotSet:
|
||||
return jumpToString(JumpBitsSet, operand, skipFalse, skipTrue)
|
||||
default:
|
||||
return fmt.Sprintf("unknown JumpTest %#v", cond)
|
||||
}
|
||||
}
|
||||
|
||||
func conditionalJump(operand string, skipTrue, skipFalse uint8, positiveJump, negativeJump string) string {
|
||||
if skipTrue > 0 {
|
||||
if skipFalse > 0 {
|
||||
return fmt.Sprintf("%s %s,%d,%d", positiveJump, operand, skipTrue, skipFalse)
|
||||
}
|
||||
return fmt.Sprintf("%s %s,%d", positiveJump, operand, skipTrue)
|
||||
}
|
||||
return fmt.Sprintf("%s %s,%d", negativeJump, operand, skipFalse)
|
||||
}
|
||||
|
||||
// RetA exits the BPF program, returning the value of register A.
|
||||
type RetA struct{}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a RetA) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsReturn | opRetSrcA,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a RetA) String() string {
|
||||
return fmt.Sprintf("ret a")
|
||||
}
|
||||
|
||||
// RetConstant exits the BPF program, returning a constant value.
|
||||
type RetConstant struct {
|
||||
Val uint32
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a RetConstant) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsReturn | opRetSrcConstant,
|
||||
K: a.Val,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a RetConstant) String() string {
|
||||
return fmt.Sprintf("ret #%d", a.Val)
|
||||
}
|
||||
|
||||
// TXA copies the value of register X to register A.
|
||||
type TXA struct{}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a TXA) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsMisc | opMiscTXA,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a TXA) String() string {
|
||||
return fmt.Sprintf("txa")
|
||||
}
|
||||
|
||||
// TAX copies the value of register A to register X.
|
||||
type TAX struct{}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a TAX) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsMisc | opMiscTAX,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a TAX) String() string {
|
||||
return fmt.Sprintf("tax")
|
||||
}
|
||||
|
||||
func assembleLoad(dst Register, loadSize int, mode uint16, k uint32) (RawInstruction, error) {
|
||||
var (
|
||||
cls uint16
|
||||
sz uint16
|
||||
)
|
||||
switch dst {
|
||||
case RegA:
|
||||
cls = opClsLoadA
|
||||
case RegX:
|
||||
cls = opClsLoadX
|
||||
default:
|
||||
return RawInstruction{}, fmt.Errorf("invalid target register %v", dst)
|
||||
}
|
||||
switch loadSize {
|
||||
case 1:
|
||||
sz = opLoadWidth1
|
||||
case 2:
|
||||
sz = opLoadWidth2
|
||||
case 4:
|
||||
sz = opLoadWidth4
|
||||
default:
|
||||
return RawInstruction{}, fmt.Errorf("invalid load byte length %d", sz)
|
||||
}
|
||||
return RawInstruction{
|
||||
Op: cls | sz | mode,
|
||||
K: k,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bpf
|
||||
|
||||
// A Setter is a type which can attach a compiled BPF filter to itself.
|
||||
type Setter interface {
|
||||
SetBPF(filter []RawInstruction) error
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bpf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A VM is an emulated BPF virtual machine.
|
||||
type VM struct {
|
||||
filter []Instruction
|
||||
}
|
||||
|
||||
// NewVM returns a new VM using the input BPF program.
|
||||
func NewVM(filter []Instruction) (*VM, error) {
|
||||
if len(filter) == 0 {
|
||||
return nil, errors.New("one or more Instructions must be specified")
|
||||
}
|
||||
|
||||
for i, ins := range filter {
|
||||
check := len(filter) - (i + 1)
|
||||
switch ins := ins.(type) {
|
||||
// Check for out-of-bounds jumps in instructions
|
||||
case Jump:
|
||||
if check <= int(ins.Skip) {
|
||||
return nil, fmt.Errorf("cannot jump %d instructions; jumping past program bounds", ins.Skip)
|
||||
}
|
||||
case JumpIf:
|
||||
if check <= int(ins.SkipTrue) {
|
||||
return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
|
||||
}
|
||||
if check <= int(ins.SkipFalse) {
|
||||
return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
|
||||
}
|
||||
case JumpIfX:
|
||||
if check <= int(ins.SkipTrue) {
|
||||
return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
|
||||
}
|
||||
if check <= int(ins.SkipFalse) {
|
||||
return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
|
||||
}
|
||||
// Check for division or modulus by zero
|
||||
case ALUOpConstant:
|
||||
if ins.Val != 0 {
|
||||
break
|
||||
}
|
||||
|
||||
switch ins.Op {
|
||||
case ALUOpDiv, ALUOpMod:
|
||||
return nil, errors.New("cannot divide by zero using ALUOpConstant")
|
||||
}
|
||||
// Check for unknown extensions
|
||||
case LoadExtension:
|
||||
switch ins.Num {
|
||||
case ExtLen:
|
||||
default:
|
||||
return nil, fmt.Errorf("extension %d not implemented", ins.Num)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure last instruction is a return instruction
|
||||
switch filter[len(filter)-1].(type) {
|
||||
case RetA, RetConstant:
|
||||
default:
|
||||
return nil, errors.New("BPF program must end with RetA or RetConstant")
|
||||
}
|
||||
|
||||
// Though our VM works using disassembled instructions, we
|
||||
// attempt to assemble the input filter anyway to ensure it is compatible
|
||||
// with an operating system VM.
|
||||
_, err := Assemble(filter)
|
||||
|
||||
return &VM{
|
||||
filter: filter,
|
||||
}, err
|
||||
}
|
||||
|
||||
// Run runs the VM's BPF program against the input bytes.
|
||||
// Run returns the number of bytes accepted by the BPF program, and any errors
|
||||
// which occurred while processing the program.
|
||||
func (v *VM) Run(in []byte) (int, error) {
|
||||
var (
|
||||
// Registers of the virtual machine
|
||||
regA uint32
|
||||
regX uint32
|
||||
regScratch [16]uint32
|
||||
|
||||
// OK is true if the program should continue processing the next
|
||||
// instruction, or false if not, causing the loop to break
|
||||
ok = true
|
||||
)
|
||||
|
||||
// TODO(mdlayher): implement:
|
||||
// - NegateA:
|
||||
// - would require a change from uint32 registers to int32
|
||||
// registers
|
||||
|
||||
// TODO(mdlayher): add interop tests that check signedness of ALU
|
||||
// operations against kernel implementation, and make sure Go
|
||||
// implementation matches behavior
|
||||
|
||||
for i := 0; i < len(v.filter) && ok; i++ {
|
||||
ins := v.filter[i]
|
||||
|
||||
switch ins := ins.(type) {
|
||||
case ALUOpConstant:
|
||||
regA = aluOpConstant(ins, regA)
|
||||
case ALUOpX:
|
||||
regA, ok = aluOpX(ins, regA, regX)
|
||||
case Jump:
|
||||
i += int(ins.Skip)
|
||||
case JumpIf:
|
||||
jump := jumpIf(ins, regA)
|
||||
i += jump
|
||||
case JumpIfX:
|
||||
jump := jumpIfX(ins, regA, regX)
|
||||
i += jump
|
||||
case LoadAbsolute:
|
||||
regA, ok = loadAbsolute(ins, in)
|
||||
case LoadConstant:
|
||||
regA, regX = loadConstant(ins, regA, regX)
|
||||
case LoadExtension:
|
||||
regA = loadExtension(ins, in)
|
||||
case LoadIndirect:
|
||||
regA, ok = loadIndirect(ins, in, regX)
|
||||
case LoadMemShift:
|
||||
regX, ok = loadMemShift(ins, in)
|
||||
case LoadScratch:
|
||||
regA, regX = loadScratch(ins, regScratch, regA, regX)
|
||||
case RetA:
|
||||
return int(regA), nil
|
||||
case RetConstant:
|
||||
return int(ins.Val), nil
|
||||
case StoreScratch:
|
||||
regScratch = storeScratch(ins, regScratch, regA, regX)
|
||||
case TAX:
|
||||
regX = regA
|
||||
case TXA:
|
||||
regA = regX
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown Instruction at index %d: %T", i, ins)
|
||||
}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bpf
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func aluOpConstant(ins ALUOpConstant, regA uint32) uint32 {
|
||||
return aluOpCommon(ins.Op, regA, ins.Val)
|
||||
}
|
||||
|
||||
func aluOpX(ins ALUOpX, regA uint32, regX uint32) (uint32, bool) {
|
||||
// Guard against division or modulus by zero by terminating
|
||||
// the program, as the OS BPF VM does
|
||||
if regX == 0 {
|
||||
switch ins.Op {
|
||||
case ALUOpDiv, ALUOpMod:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
return aluOpCommon(ins.Op, regA, regX), true
|
||||
}
|
||||
|
||||
func aluOpCommon(op ALUOp, regA uint32, value uint32) uint32 {
|
||||
switch op {
|
||||
case ALUOpAdd:
|
||||
return regA + value
|
||||
case ALUOpSub:
|
||||
return regA - value
|
||||
case ALUOpMul:
|
||||
return regA * value
|
||||
case ALUOpDiv:
|
||||
// Division by zero not permitted by NewVM and aluOpX checks
|
||||
return regA / value
|
||||
case ALUOpOr:
|
||||
return regA | value
|
||||
case ALUOpAnd:
|
||||
return regA & value
|
||||
case ALUOpShiftLeft:
|
||||
return regA << value
|
||||
case ALUOpShiftRight:
|
||||
return regA >> value
|
||||
case ALUOpMod:
|
||||
// Modulus by zero not permitted by NewVM and aluOpX checks
|
||||
return regA % value
|
||||
case ALUOpXor:
|
||||
return regA ^ value
|
||||
default:
|
||||
return regA
|
||||
}
|
||||
}
|
||||
|
||||
func jumpIf(ins JumpIf, regA uint32) int {
|
||||
return jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, regA, ins.Val)
|
||||
}
|
||||
|
||||
func jumpIfX(ins JumpIfX, regA uint32, regX uint32) int {
|
||||
return jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, regA, regX)
|
||||
}
|
||||
|
||||
func jumpIfCommon(cond JumpTest, skipTrue, skipFalse uint8, regA uint32, value uint32) int {
|
||||
var ok bool
|
||||
|
||||
switch cond {
|
||||
case JumpEqual:
|
||||
ok = regA == value
|
||||
case JumpNotEqual:
|
||||
ok = regA != value
|
||||
case JumpGreaterThan:
|
||||
ok = regA > value
|
||||
case JumpLessThan:
|
||||
ok = regA < value
|
||||
case JumpGreaterOrEqual:
|
||||
ok = regA >= value
|
||||
case JumpLessOrEqual:
|
||||
ok = regA <= value
|
||||
case JumpBitsSet:
|
||||
ok = (regA & value) != 0
|
||||
case JumpBitsNotSet:
|
||||
ok = (regA & value) == 0
|
||||
}
|
||||
|
||||
if ok {
|
||||
return int(skipTrue)
|
||||
}
|
||||
|
||||
return int(skipFalse)
|
||||
}
|
||||
|
||||
func loadAbsolute(ins LoadAbsolute, in []byte) (uint32, bool) {
|
||||
offset := int(ins.Off)
|
||||
size := int(ins.Size)
|
||||
|
||||
return loadCommon(in, offset, size)
|
||||
}
|
||||
|
||||
func loadConstant(ins LoadConstant, regA uint32, regX uint32) (uint32, uint32) {
|
||||
switch ins.Dst {
|
||||
case RegA:
|
||||
regA = ins.Val
|
||||
case RegX:
|
||||
regX = ins.Val
|
||||
}
|
||||
|
||||
return regA, regX
|
||||
}
|
||||
|
||||
func loadExtension(ins LoadExtension, in []byte) uint32 {
|
||||
switch ins.Num {
|
||||
case ExtLen:
|
||||
return uint32(len(in))
|
||||
default:
|
||||
panic(fmt.Sprintf("unimplemented extension: %d", ins.Num))
|
||||
}
|
||||
}
|
||||
|
||||
func loadIndirect(ins LoadIndirect, in []byte, regX uint32) (uint32, bool) {
|
||||
offset := int(ins.Off) + int(regX)
|
||||
size := int(ins.Size)
|
||||
|
||||
return loadCommon(in, offset, size)
|
||||
}
|
||||
|
||||
func loadMemShift(ins LoadMemShift, in []byte) (uint32, bool) {
|
||||
offset := int(ins.Off)
|
||||
|
||||
// Size of LoadMemShift is always 1 byte
|
||||
if !inBounds(len(in), offset, 1) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Mask off high 4 bits and multiply low 4 bits by 4
|
||||
return uint32(in[offset]&0x0f) * 4, true
|
||||
}
|
||||
|
||||
func inBounds(inLen int, offset int, size int) bool {
|
||||
return offset+size <= inLen
|
||||
}
|
||||
|
||||
func loadCommon(in []byte, offset int, size int) (uint32, bool) {
|
||||
if !inBounds(len(in), offset, size) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
switch size {
|
||||
case 1:
|
||||
return uint32(in[offset]), true
|
||||
case 2:
|
||||
return uint32(binary.BigEndian.Uint16(in[offset : offset+size])), true
|
||||
case 4:
|
||||
return uint32(binary.BigEndian.Uint32(in[offset : offset+size])), true
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid load size: %d", size))
|
||||
}
|
||||
}
|
||||
|
||||
func loadScratch(ins LoadScratch, regScratch [16]uint32, regA uint32, regX uint32) (uint32, uint32) {
|
||||
switch ins.Dst {
|
||||
case RegA:
|
||||
regA = regScratch[ins.N]
|
||||
case RegX:
|
||||
regX = regScratch[ins.N]
|
||||
}
|
||||
|
||||
return regA, regX
|
||||
}
|
||||
|
||||
func storeScratch(ins StoreScratch, regScratch [16]uint32, regA uint32, regX uint32) [16]uint32 {
|
||||
switch ins.Src {
|
||||
case RegA:
|
||||
regScratch[ins.N] = regA
|
||||
case RegX:
|
||||
regScratch[ins.N] = regX
|
||||
}
|
||||
|
||||
return regScratch
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
// go generate gen.go
|
||||
// Code generated by the command above; DO NOT EDIT.
|
||||
|
||||
// Package iana provides protocol number resources managed by the Internet Assigned Numbers Authority (IANA).
|
||||
package iana // import "golang.org/x/net/internal/iana"
|
||||
|
||||
// Differentiated Services Field Codepoints (DSCP), Updated: 2018-05-04
|
||||
const (
|
||||
DiffServCS0 = 0x00 // CS0
|
||||
DiffServCS1 = 0x20 // CS1
|
||||
DiffServCS2 = 0x40 // CS2
|
||||
DiffServCS3 = 0x60 // CS3
|
||||
DiffServCS4 = 0x80 // CS4
|
||||
DiffServCS5 = 0xa0 // CS5
|
||||
DiffServCS6 = 0xc0 // CS6
|
||||
DiffServCS7 = 0xe0 // CS7
|
||||
DiffServAF11 = 0x28 // AF11
|
||||
DiffServAF12 = 0x30 // AF12
|
||||
DiffServAF13 = 0x38 // AF13
|
||||
DiffServAF21 = 0x48 // AF21
|
||||
DiffServAF22 = 0x50 // AF22
|
||||
DiffServAF23 = 0x58 // AF23
|
||||
DiffServAF31 = 0x68 // AF31
|
||||
DiffServAF32 = 0x70 // AF32
|
||||
DiffServAF33 = 0x78 // AF33
|
||||
DiffServAF41 = 0x88 // AF41
|
||||
DiffServAF42 = 0x90 // AF42
|
||||
DiffServAF43 = 0x98 // AF43
|
||||
DiffServEF = 0xb8 // EF
|
||||
DiffServVOICEADMIT = 0xb0 // VOICE-ADMIT
|
||||
NotECNTransport = 0x00 // Not-ECT (Not ECN-Capable Transport)
|
||||
ECNTransport1 = 0x01 // ECT(1) (ECN-Capable Transport(1))
|
||||
ECNTransport0 = 0x02 // ECT(0) (ECN-Capable Transport(0))
|
||||
CongestionExperienced = 0x03 // CE (Congestion Experienced)
|
||||
)
|
||||
|
||||
// Protocol Numbers, Updated: 2017-10-13
|
||||
const (
|
||||
ProtocolIP = 0 // IPv4 encapsulation, pseudo protocol number
|
||||
ProtocolHOPOPT = 0 // IPv6 Hop-by-Hop Option
|
||||
ProtocolICMP = 1 // Internet Control Message
|
||||
ProtocolIGMP = 2 // Internet Group Management
|
||||
ProtocolGGP = 3 // Gateway-to-Gateway
|
||||
ProtocolIPv4 = 4 // IPv4 encapsulation
|
||||
ProtocolST = 5 // Stream
|
||||
ProtocolTCP = 6 // Transmission Control
|
||||
ProtocolCBT = 7 // CBT
|
||||
ProtocolEGP = 8 // Exterior Gateway Protocol
|
||||
ProtocolIGP = 9 // any private interior gateway (used by Cisco for their IGRP)
|
||||
ProtocolBBNRCCMON = 10 // BBN RCC Monitoring
|
||||
ProtocolNVPII = 11 // Network Voice Protocol
|
||||
ProtocolPUP = 12 // PUP
|
||||
ProtocolEMCON = 14 // EMCON
|
||||
ProtocolXNET = 15 // Cross Net Debugger
|
||||
ProtocolCHAOS = 16 // Chaos
|
||||
ProtocolUDP = 17 // User Datagram
|
||||
ProtocolMUX = 18 // Multiplexing
|
||||
ProtocolDCNMEAS = 19 // DCN Measurement Subsystems
|
||||
ProtocolHMP = 20 // Host Monitoring
|
||||
ProtocolPRM = 21 // Packet Radio Measurement
|
||||
ProtocolXNSIDP = 22 // XEROX NS IDP
|
||||
ProtocolTRUNK1 = 23 // Trunk-1
|
||||
ProtocolTRUNK2 = 24 // Trunk-2
|
||||
ProtocolLEAF1 = 25 // Leaf-1
|
||||
ProtocolLEAF2 = 26 // Leaf-2
|
||||
ProtocolRDP = 27 // Reliable Data Protocol
|
||||
ProtocolIRTP = 28 // Internet Reliable Transaction
|
||||
ProtocolISOTP4 = 29 // ISO Transport Protocol Class 4
|
||||
ProtocolNETBLT = 30 // Bulk Data Transfer Protocol
|
||||
ProtocolMFENSP = 31 // MFE Network Services Protocol
|
||||
ProtocolMERITINP = 32 // MERIT Internodal Protocol
|
||||
ProtocolDCCP = 33 // Datagram Congestion Control Protocol
|
||||
Protocol3PC = 34 // Third Party Connect Protocol
|
||||
ProtocolIDPR = 35 // Inter-Domain Policy Routing Protocol
|
||||
ProtocolXTP = 36 // XTP
|
||||
ProtocolDDP = 37 // Datagram Delivery Protocol
|
||||
ProtocolIDPRCMTP = 38 // IDPR Control Message Transport Proto
|
||||
ProtocolTPPP = 39 // TP++ Transport Protocol
|
||||
ProtocolIL = 40 // IL Transport Protocol
|
||||
ProtocolIPv6 = 41 // IPv6 encapsulation
|
||||
ProtocolSDRP = 42 // Source Demand Routing Protocol
|
||||
ProtocolIPv6Route = 43 // Routing Header for IPv6
|
||||
ProtocolIPv6Frag = 44 // Fragment Header for IPv6
|
||||
ProtocolIDRP = 45 // Inter-Domain Routing Protocol
|
||||
ProtocolRSVP = 46 // Reservation Protocol
|
||||
ProtocolGRE = 47 // Generic Routing Encapsulation
|
||||
ProtocolDSR = 48 // Dynamic Source Routing Protocol
|
||||
ProtocolBNA = 49 // BNA
|
||||
ProtocolESP = 50 // Encap Security Payload
|
||||
ProtocolAH = 51 // Authentication Header
|
||||
ProtocolINLSP = 52 // Integrated Net Layer Security TUBA
|
||||
ProtocolNARP = 54 // NBMA Address Resolution Protocol
|
||||
ProtocolMOBILE = 55 // IP Mobility
|
||||
ProtocolTLSP = 56 // Transport Layer Security Protocol using Kryptonet key management
|
||||
ProtocolSKIP = 57 // SKIP
|
||||
ProtocolIPv6ICMP = 58 // ICMP for IPv6
|
||||
ProtocolIPv6NoNxt = 59 // No Next Header for IPv6
|
||||
ProtocolIPv6Opts = 60 // Destination Options for IPv6
|
||||
ProtocolCFTP = 62 // CFTP
|
||||
ProtocolSATEXPAK = 64 // SATNET and Backroom EXPAK
|
||||
ProtocolKRYPTOLAN = 65 // Kryptolan
|
||||
ProtocolRVD = 66 // MIT Remote Virtual Disk Protocol
|
||||
ProtocolIPPC = 67 // Internet Pluribus Packet Core
|
||||
ProtocolSATMON = 69 // SATNET Monitoring
|
||||
ProtocolVISA = 70 // VISA Protocol
|
||||
ProtocolIPCV = 71 // Internet Packet Core Utility
|
||||
ProtocolCPNX = 72 // Computer Protocol Network Executive
|
||||
ProtocolCPHB = 73 // Computer Protocol Heart Beat
|
||||
ProtocolWSN = 74 // Wang Span Network
|
||||
ProtocolPVP = 75 // Packet Video Protocol
|
||||
ProtocolBRSATMON = 76 // Backroom SATNET Monitoring
|
||||
ProtocolSUNND = 77 // SUN ND PROTOCOL-Temporary
|
||||
ProtocolWBMON = 78 // WIDEBAND Monitoring
|
||||
ProtocolWBEXPAK = 79 // WIDEBAND EXPAK
|
||||
ProtocolISOIP = 80 // ISO Internet Protocol
|
||||
ProtocolVMTP = 81 // VMTP
|
||||
ProtocolSECUREVMTP = 82 // SECURE-VMTP
|
||||
ProtocolVINES = 83 // VINES
|
||||
ProtocolTTP = 84 // Transaction Transport Protocol
|
||||
ProtocolIPTM = 84 // Internet Protocol Traffic Manager
|
||||
ProtocolNSFNETIGP = 85 // NSFNET-IGP
|
||||
ProtocolDGP = 86 // Dissimilar Gateway Protocol
|
||||
ProtocolTCF = 87 // TCF
|
||||
ProtocolEIGRP = 88 // EIGRP
|
||||
ProtocolOSPFIGP = 89 // OSPFIGP
|
||||
ProtocolSpriteRPC = 90 // Sprite RPC Protocol
|
||||
ProtocolLARP = 91 // Locus Address Resolution Protocol
|
||||
ProtocolMTP = 92 // Multicast Transport Protocol
|
||||
ProtocolAX25 = 93 // AX.25 Frames
|
||||
ProtocolIPIP = 94 // IP-within-IP Encapsulation Protocol
|
||||
ProtocolSCCSP = 96 // Semaphore Communications Sec. Pro.
|
||||
ProtocolETHERIP = 97 // Ethernet-within-IP Encapsulation
|
||||
ProtocolENCAP = 98 // Encapsulation Header
|
||||
ProtocolGMTP = 100 // GMTP
|
||||
ProtocolIFMP = 101 // Ipsilon Flow Management Protocol
|
||||
ProtocolPNNI = 102 // PNNI over IP
|
||||
ProtocolPIM = 103 // Protocol Independent Multicast
|
||||
ProtocolARIS = 104 // ARIS
|
||||
ProtocolSCPS = 105 // SCPS
|
||||
ProtocolQNX = 106 // QNX
|
||||
ProtocolAN = 107 // Active Networks
|
||||
ProtocolIPComp = 108 // IP Payload Compression Protocol
|
||||
ProtocolSNP = 109 // Sitara Networks Protocol
|
||||
ProtocolCompaqPeer = 110 // Compaq Peer Protocol
|
||||
ProtocolIPXinIP = 111 // IPX in IP
|
||||
ProtocolVRRP = 112 // Virtual Router Redundancy Protocol
|
||||
ProtocolPGM = 113 // PGM Reliable Transport Protocol
|
||||
ProtocolL2TP = 115 // Layer Two Tunneling Protocol
|
||||
ProtocolDDX = 116 // D-II Data Exchange (DDX)
|
||||
ProtocolIATP = 117 // Interactive Agent Transfer Protocol
|
||||
ProtocolSTP = 118 // Schedule Transfer Protocol
|
||||
ProtocolSRP = 119 // SpectraLink Radio Protocol
|
||||
ProtocolUTI = 120 // UTI
|
||||
ProtocolSMP = 121 // Simple Message Protocol
|
||||
ProtocolPTP = 123 // Performance Transparency Protocol
|
||||
ProtocolISIS = 124 // ISIS over IPv4
|
||||
ProtocolFIRE = 125 // FIRE
|
||||
ProtocolCRTP = 126 // Combat Radio Transport Protocol
|
||||
ProtocolCRUDP = 127 // Combat Radio User Datagram
|
||||
ProtocolSSCOPMCE = 128 // SSCOPMCE
|
||||
ProtocolIPLT = 129 // IPLT
|
||||
ProtocolSPS = 130 // Secure Packet Shield
|
||||
ProtocolPIPE = 131 // Private IP Encapsulation within IP
|
||||
ProtocolSCTP = 132 // Stream Control Transmission Protocol
|
||||
ProtocolFC = 133 // Fibre Channel
|
||||
ProtocolRSVPE2EIGNORE = 134 // RSVP-E2E-IGNORE
|
||||
ProtocolMobilityHeader = 135 // Mobility Header
|
||||
ProtocolUDPLite = 136 // UDPLite
|
||||
ProtocolMPLSinIP = 137 // MPLS-in-IP
|
||||
ProtocolMANET = 138 // MANET Protocols
|
||||
ProtocolHIP = 139 // Host Identity Protocol
|
||||
ProtocolShim6 = 140 // Shim6 Protocol
|
||||
ProtocolWESP = 141 // Wrapped Encapsulating Security Payload
|
||||
ProtocolROHC = 142 // Robust Header Compression
|
||||
ProtocolReserved = 255 // Reserved
|
||||
)
|
||||
|
||||
// Address Family Numbers, Updated: 2018-04-02
|
||||
const (
|
||||
AddrFamilyIPv4 = 1 // IP (IP version 4)
|
||||
AddrFamilyIPv6 = 2 // IP6 (IP version 6)
|
||||
AddrFamilyNSAP = 3 // NSAP
|
||||
AddrFamilyHDLC = 4 // HDLC (8-bit multidrop)
|
||||
AddrFamilyBBN1822 = 5 // BBN 1822
|
||||
AddrFamily802 = 6 // 802 (includes all 802 media plus Ethernet "canonical format")
|
||||
AddrFamilyE163 = 7 // E.163
|
||||
AddrFamilyE164 = 8 // E.164 (SMDS, Frame Relay, ATM)
|
||||
AddrFamilyF69 = 9 // F.69 (Telex)
|
||||
AddrFamilyX121 = 10 // X.121 (X.25, Frame Relay)
|
||||
AddrFamilyIPX = 11 // IPX
|
||||
AddrFamilyAppletalk = 12 // Appletalk
|
||||
AddrFamilyDecnetIV = 13 // Decnet IV
|
||||
AddrFamilyBanyanVines = 14 // Banyan Vines
|
||||
AddrFamilyE164withSubaddress = 15 // E.164 with NSAP format subaddress
|
||||
AddrFamilyDNS = 16 // DNS (Domain Name System)
|
||||
AddrFamilyDistinguishedName = 17 // Distinguished Name
|
||||
AddrFamilyASNumber = 18 // AS Number
|
||||
AddrFamilyXTPoverIPv4 = 19 // XTP over IP version 4
|
||||
AddrFamilyXTPoverIPv6 = 20 // XTP over IP version 6
|
||||
AddrFamilyXTPnativemodeXTP = 21 // XTP native mode XTP
|
||||
AddrFamilyFibreChannelWorldWidePortName = 22 // Fibre Channel World-Wide Port Name
|
||||
AddrFamilyFibreChannelWorldWideNodeName = 23 // Fibre Channel World-Wide Node Name
|
||||
AddrFamilyGWID = 24 // GWID
|
||||
AddrFamilyL2VPN = 25 // AFI for L2VPN information
|
||||
AddrFamilyMPLSTPSectionEndpointID = 26 // MPLS-TP Section Endpoint Identifier
|
||||
AddrFamilyMPLSTPLSPEndpointID = 27 // MPLS-TP LSP Endpoint Identifier
|
||||
AddrFamilyMPLSTPPseudowireEndpointID = 28 // MPLS-TP Pseudowire Endpoint Identifier
|
||||
AddrFamilyMTIPv4 = 29 // MT IP: Multi-Topology IP version 4
|
||||
AddrFamilyMTIPv6 = 30 // MT IPv6: Multi-Topology IP version 6
|
||||
AddrFamilyEIGRPCommonServiceFamily = 16384 // EIGRP Common Service Family
|
||||
AddrFamilyEIGRPIPv4ServiceFamily = 16385 // EIGRP IPv4 Service Family
|
||||
AddrFamilyEIGRPIPv6ServiceFamily = 16386 // EIGRP IPv6 Service Family
|
||||
AddrFamilyLISPCanonicalAddressFormat = 16387 // LISP Canonical Address Format (LCAF)
|
||||
AddrFamilyBGPLS = 16388 // BGP-LS
|
||||
AddrFamily48bitMAC = 16389 // 48-bit MAC
|
||||
AddrFamily64bitMAC = 16390 // 64-bit MAC
|
||||
AddrFamilyOUI = 16391 // OUI
|
||||
AddrFamilyMACFinal24bits = 16392 // MAC/24
|
||||
AddrFamilyMACFinal40bits = 16393 // MAC/40
|
||||
AddrFamilyIPv6Initial64bits = 16394 // IPv6/64
|
||||
AddrFamilyRBridgePortID = 16395 // RBridge Port ID
|
||||
AddrFamilyTRILLNickname = 16396 // TRILL Nickname
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
|
||||
|
||||
package socket
|
||||
|
||||
func (h *cmsghdr) len() int { return int(h.Len) }
|
||||
func (h *cmsghdr) lvl() int { return int(h.Level) }
|
||||
func (h *cmsghdr) typ() int { return int(h.Type) }
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || netbsd || openbsd
|
||||
// +build aix darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package socket
|
||||
|
||||
func (h *cmsghdr) set(l, lvl, typ int) {
|
||||
h.Len = uint32(l)
|
||||
h.Level = int32(lvl)
|
||||
h.Type = int32(typ)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (arm || mips || mipsle || 386 || ppc) && linux
|
||||
// +build arm mips mipsle 386 ppc
|
||||
// +build linux
|
||||
|
||||
package socket
|
||||
|
||||
func (h *cmsghdr) set(l, lvl, typ int) {
|
||||
h.Len = uint32(l)
|
||||
h.Level = int32(lvl)
|
||||
h.Type = int32(typ)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (arm64 || amd64 || loong64 || ppc64 || ppc64le || mips64 || mips64le || riscv64 || s390x) && linux
|
||||
// +build arm64 amd64 loong64 ppc64 ppc64le mips64 mips64le riscv64 s390x
|
||||
// +build linux
|
||||
|
||||
package socket
|
||||
|
||||
func (h *cmsghdr) set(l, lvl, typ int) {
|
||||
h.Len = uint64(l)
|
||||
h.Level = int32(lvl)
|
||||
h.Type = int32(typ)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build amd64 && solaris
|
||||
// +build amd64,solaris
|
||||
|
||||
package socket
|
||||
|
||||
func (h *cmsghdr) set(l, lvl, typ int) {
|
||||
h.Len = uint32(l)
|
||||
h.Level = int32(lvl)
|
||||
h.Type = int32(typ)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !zos
|
||||
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!zos
|
||||
|
||||
package socket
|
||||
|
||||
func controlHeaderLen() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func controlMessageLen(dataLen int) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func controlMessageSpace(dataLen int) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
type cmsghdr struct{}
|
||||
|
||||
func (h *cmsghdr) len() int { return 0 }
|
||||
func (h *cmsghdr) lvl() int { return 0 }
|
||||
func (h *cmsghdr) typ() int { return 0 }
|
||||
|
||||
func (h *cmsghdr) set(l, lvl, typ int) {}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
|
||||
|
||||
package socket
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func controlHeaderLen() int {
|
||||
return unix.CmsgLen(0)
|
||||
}
|
||||
|
||||
func controlMessageLen(dataLen int) int {
|
||||
return unix.CmsgLen(dataLen)
|
||||
}
|
||||
|
||||
func controlMessageSpace(dataLen int) int {
|
||||
return unix.CmsgSpace(dataLen)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
func (h *cmsghdr) set(l, lvl, typ int) {
|
||||
h.Len = int32(l)
|
||||
h.Level = int32(lvl)
|
||||
h.Type = int32(typ)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ioComplete checks the flags and result of a syscall, to be used as return
|
||||
// value in a syscall.RawConn.Read or Write callback.
|
||||
func ioComplete(flags int, operr error) bool {
|
||||
if flags&syscall.MSG_DONTWAIT != 0 {
|
||||
// Caller explicitly said don't wait, so always return immediately.
|
||||
return true
|
||||
}
|
||||
if operr == syscall.EAGAIN || operr == syscall.EWOULDBLOCK {
|
||||
// No data available, block for I/O and try again.
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || windows || zos
|
||||
// +build aix windows zos
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ioComplete checks the flags and result of a syscall, to be used as return
|
||||
// value in a syscall.RawConn.Read or Write callback.
|
||||
func ioComplete(flags int, operr error) bool {
|
||||
if operr == syscall.EAGAIN || operr == syscall.EWOULDBLOCK {
|
||||
// No data available, block for I/O and try again.
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin && go1.12
|
||||
// +build darwin,go1.12
|
||||
|
||||
// This exists solely so we can linkname in symbols from syscall.
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
|
||||
|
||||
package socket
|
||||
|
||||
import "syscall"
|
||||
|
||||
var (
|
||||
errEAGAIN error = syscall.EAGAIN
|
||||
errEINVAL error = syscall.EINVAL
|
||||
errENOENT error = syscall.ENOENT
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent allocations
|
||||
// at runtime.
|
||||
func errnoErr(errno syscall.Errno) error {
|
||||
switch errno {
|
||||
case 0:
|
||||
return nil
|
||||
case syscall.EAGAIN:
|
||||
return errEAGAIN
|
||||
case syscall.EINVAL:
|
||||
return errEINVAL
|
||||
case syscall.ENOENT:
|
||||
return errENOENT
|
||||
}
|
||||
return errno
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
import "syscall"
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.ERROR_IO_PENDING
|
||||
errEINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent allocations
|
||||
// at runtime.
|
||||
func errnoErr(errno syscall.Errno) error {
|
||||
switch errno {
|
||||
case 0:
|
||||
return nil
|
||||
case syscall.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
case syscall.EINVAL:
|
||||
return errEINVAL
|
||||
}
|
||||
return errno
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (arm || mips || mipsle || 386 || ppc) && (darwin || dragonfly || freebsd || linux || netbsd || openbsd)
|
||||
// +build arm mips mipsle 386 ppc
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (v *iovec) set(b []byte) {
|
||||
l := len(b)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
v.Base = (*byte)(unsafe.Pointer(&b[0]))
|
||||
v.Len = uint32(l)
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (arm64 || amd64 || loong64 || ppc64 || ppc64le || mips64 || mips64le || riscv64 || s390x) && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || zos)
|
||||
// +build arm64 amd64 loong64 ppc64 ppc64le mips64 mips64le riscv64 s390x
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd zos
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (v *iovec) set(b []byte) {
|
||||
l := len(b)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
v.Base = (*byte)(unsafe.Pointer(&b[0]))
|
||||
v.Len = uint64(l)
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build amd64 && solaris
|
||||
// +build amd64,solaris
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (v *iovec) set(b []byte) {
|
||||
l := len(b)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
v.Base = (*int8)(unsafe.Pointer(&b[0]))
|
||||
v.Len = uint64(l)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !zos
|
||||
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!zos
|
||||
|
||||
package socket
|
||||
|
||||
type iovec struct{}
|
||||
|
||||
func (v *iovec) set(b []byte) {}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !aix && !linux && !netbsd
|
||||
// +build !aix,!linux,!netbsd
|
||||
|
||||
package socket
|
||||
|
||||
import "net"
|
||||
|
||||
type mmsghdr struct{}
|
||||
|
||||
type mmsghdrs []mmsghdr
|
||||
|
||||
func (hs mmsghdrs) pack(ms []Message, parseFn func([]byte, string) (net.Addr, error), marshalFn func(net.Addr) []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs mmsghdrs) unpack(ms []Message, parseFn func([]byte, string) (net.Addr, error), hint string) error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || linux || netbsd
|
||||
// +build aix linux netbsd
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type mmsghdrs []mmsghdr
|
||||
|
||||
func (hs mmsghdrs) unpack(ms []Message, parseFn func([]byte, string) (net.Addr, error), hint string) error {
|
||||
for i := range hs {
|
||||
ms[i].N = int(hs[i].Len)
|
||||
ms[i].NN = hs[i].Hdr.controllen()
|
||||
ms[i].Flags = hs[i].Hdr.flags()
|
||||
if parseFn != nil {
|
||||
var err error
|
||||
ms[i].Addr, err = parseFn(hs[i].Hdr.name(), hint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mmsghdrsPacker packs Message-slices into mmsghdrs (re-)using pre-allocated buffers.
|
||||
type mmsghdrsPacker struct {
|
||||
// hs are the pre-allocated mmsghdrs.
|
||||
hs mmsghdrs
|
||||
// sockaddrs is the pre-allocated buffer for the Hdr.Name buffers.
|
||||
// We use one large buffer for all messages and slice it up.
|
||||
sockaddrs []byte
|
||||
// vs are the pre-allocated iovecs.
|
||||
// We allocate one large buffer for all messages and slice it up. This allows to reuse the buffer
|
||||
// if the number of buffers per message is distributed differently between calls.
|
||||
vs []iovec
|
||||
}
|
||||
|
||||
func (p *mmsghdrsPacker) prepare(ms []Message) {
|
||||
n := len(ms)
|
||||
if n <= cap(p.hs) {
|
||||
p.hs = p.hs[:n]
|
||||
} else {
|
||||
p.hs = make(mmsghdrs, n)
|
||||
}
|
||||
if n*sizeofSockaddrInet6 <= cap(p.sockaddrs) {
|
||||
p.sockaddrs = p.sockaddrs[:n*sizeofSockaddrInet6]
|
||||
} else {
|
||||
p.sockaddrs = make([]byte, n*sizeofSockaddrInet6)
|
||||
}
|
||||
|
||||
nb := 0
|
||||
for _, m := range ms {
|
||||
nb += len(m.Buffers)
|
||||
}
|
||||
if nb <= cap(p.vs) {
|
||||
p.vs = p.vs[:nb]
|
||||
} else {
|
||||
p.vs = make([]iovec, nb)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *mmsghdrsPacker) pack(ms []Message, parseFn func([]byte, string) (net.Addr, error), marshalFn func(net.Addr, []byte) int) mmsghdrs {
|
||||
p.prepare(ms)
|
||||
hs := p.hs
|
||||
vsRest := p.vs
|
||||
saRest := p.sockaddrs
|
||||
for i := range hs {
|
||||
nvs := len(ms[i].Buffers)
|
||||
vs := vsRest[:nvs]
|
||||
vsRest = vsRest[nvs:]
|
||||
|
||||
var sa []byte
|
||||
if parseFn != nil {
|
||||
sa = saRest[:sizeofSockaddrInet6]
|
||||
saRest = saRest[sizeofSockaddrInet6:]
|
||||
} else if marshalFn != nil {
|
||||
n := marshalFn(ms[i].Addr, saRest)
|
||||
if n > 0 {
|
||||
sa = saRest[:n]
|
||||
saRest = saRest[n:]
|
||||
}
|
||||
}
|
||||
hs[i].Hdr.pack(vs, ms[i].Buffers, ms[i].OOB, sa)
|
||||
}
|
||||
return hs
|
||||
}
|
||||
|
||||
// syscaller is a helper to invoke recvmmsg and sendmmsg via the RawConn.Read/Write interface.
|
||||
// It is reusable, to amortize the overhead of allocating a closure for the function passed to
|
||||
// RawConn.Read/Write.
|
||||
type syscaller struct {
|
||||
n int
|
||||
operr error
|
||||
hs mmsghdrs
|
||||
flags int
|
||||
|
||||
boundRecvmmsgF func(uintptr) bool
|
||||
boundSendmmsgF func(uintptr) bool
|
||||
}
|
||||
|
||||
func (r *syscaller) init() {
|
||||
r.boundRecvmmsgF = r.recvmmsgF
|
||||
r.boundSendmmsgF = r.sendmmsgF
|
||||
}
|
||||
|
||||
func (r *syscaller) recvmmsg(c syscall.RawConn, hs mmsghdrs, flags int) (int, error) {
|
||||
r.n = 0
|
||||
r.operr = nil
|
||||
r.hs = hs
|
||||
r.flags = flags
|
||||
if err := c.Read(r.boundRecvmmsgF); err != nil {
|
||||
return r.n, err
|
||||
}
|
||||
if r.operr != nil {
|
||||
return r.n, os.NewSyscallError("recvmmsg", r.operr)
|
||||
}
|
||||
return r.n, nil
|
||||
}
|
||||
|
||||
func (r *syscaller) recvmmsgF(s uintptr) bool {
|
||||
r.n, r.operr = recvmmsg(s, r.hs, r.flags)
|
||||
return ioComplete(r.flags, r.operr)
|
||||
}
|
||||
|
||||
func (r *syscaller) sendmmsg(c syscall.RawConn, hs mmsghdrs, flags int) (int, error) {
|
||||
r.n = 0
|
||||
r.operr = nil
|
||||
r.hs = hs
|
||||
r.flags = flags
|
||||
if err := c.Write(r.boundSendmmsgF); err != nil {
|
||||
return r.n, err
|
||||
}
|
||||
if r.operr != nil {
|
||||
return r.n, os.NewSyscallError("sendmmsg", r.operr)
|
||||
}
|
||||
return r.n, nil
|
||||
}
|
||||
|
||||
func (r *syscaller) sendmmsgF(s uintptr) bool {
|
||||
r.n, r.operr = sendmmsg(s, r.hs, r.flags)
|
||||
return ioComplete(r.flags, r.operr)
|
||||
}
|
||||
|
||||
// mmsgTmps holds reusable temporary helpers for recvmmsg and sendmmsg.
|
||||
type mmsgTmps struct {
|
||||
packer mmsghdrsPacker
|
||||
syscaller syscaller
|
||||
}
|
||||
|
||||
var defaultMmsgTmpsPool = mmsgTmpsPool{
|
||||
p: sync.Pool{
|
||||
New: func() interface{} {
|
||||
tmps := new(mmsgTmps)
|
||||
tmps.syscaller.init()
|
||||
return tmps
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type mmsgTmpsPool struct {
|
||||
p sync.Pool
|
||||
}
|
||||
|
||||
func (p *mmsgTmpsPool) Get() *mmsgTmps {
|
||||
return p.p.Get().(*mmsgTmps)
|
||||
}
|
||||
|
||||
func (p *mmsgTmpsPool) Put(tmps *mmsgTmps) {
|
||||
p.p.Put(tmps)
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || netbsd || openbsd
|
||||
// +build aix darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (h *msghdr) pack(vs []iovec, bs [][]byte, oob []byte, sa []byte) {
|
||||
for i := range vs {
|
||||
vs[i].set(bs[i])
|
||||
}
|
||||
h.setIov(vs)
|
||||
if len(oob) > 0 {
|
||||
h.Control = (*byte)(unsafe.Pointer(&oob[0]))
|
||||
h.Controllen = uint32(len(oob))
|
||||
}
|
||||
if sa != nil {
|
||||
h.Name = (*byte)(unsafe.Pointer(&sa[0]))
|
||||
h.Namelen = uint32(len(sa))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *msghdr) name() []byte {
|
||||
if h.Name != nil && h.Namelen > 0 {
|
||||
return (*[sizeofSockaddrInet6]byte)(unsafe.Pointer(h.Name))[:h.Namelen]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *msghdr) controllen() int {
|
||||
return int(h.Controllen)
|
||||
}
|
||||
|
||||
func (h *msghdr) flags() int {
|
||||
return int(h.Flags)
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || netbsd
|
||||
// +build aix darwin dragonfly freebsd netbsd
|
||||
|
||||
package socket
|
||||
|
||||
func (h *msghdr) setIov(vs []iovec) {
|
||||
l := len(vs)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
h.Iov = &vs[0]
|
||||
h.Iovlen = int32(l)
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (h *msghdr) pack(vs []iovec, bs [][]byte, oob []byte, sa []byte) {
|
||||
for i := range vs {
|
||||
vs[i].set(bs[i])
|
||||
}
|
||||
h.setIov(vs)
|
||||
if len(oob) > 0 {
|
||||
h.setControl(oob)
|
||||
}
|
||||
if sa != nil {
|
||||
h.Name = (*byte)(unsafe.Pointer(&sa[0]))
|
||||
h.Namelen = uint32(len(sa))
|
||||
} else {
|
||||
h.Name = nil
|
||||
h.Namelen = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (h *msghdr) name() []byte {
|
||||
if h.Name != nil && h.Namelen > 0 {
|
||||
return (*[sizeofSockaddrInet6]byte)(unsafe.Pointer(h.Name))[:h.Namelen]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *msghdr) controllen() int {
|
||||
return int(h.Controllen)
|
||||
}
|
||||
|
||||
func (h *msghdr) flags() int {
|
||||
return int(h.Flags)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (arm || mips || mipsle || 386 || ppc) && linux
|
||||
// +build arm mips mipsle 386 ppc
|
||||
// +build linux
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (h *msghdr) setIov(vs []iovec) {
|
||||
l := len(vs)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
h.Iov = &vs[0]
|
||||
h.Iovlen = uint32(l)
|
||||
}
|
||||
|
||||
func (h *msghdr) setControl(b []byte) {
|
||||
h.Control = (*byte)(unsafe.Pointer(&b[0]))
|
||||
h.Controllen = uint32(len(b))
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (arm64 || amd64 || loong64 || ppc64 || ppc64le || mips64 || mips64le || riscv64 || s390x) && linux
|
||||
// +build arm64 amd64 loong64 ppc64 ppc64le mips64 mips64le riscv64 s390x
|
||||
// +build linux
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (h *msghdr) setIov(vs []iovec) {
|
||||
l := len(vs)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
h.Iov = &vs[0]
|
||||
h.Iovlen = uint64(l)
|
||||
}
|
||||
|
||||
func (h *msghdr) setControl(b []byte) {
|
||||
h.Control = (*byte)(unsafe.Pointer(&b[0]))
|
||||
h.Controllen = uint64(len(b))
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
func (h *msghdr) setIov(vs []iovec) {
|
||||
l := len(vs)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
h.Iov = &vs[0]
|
||||
h.Iovlen = uint32(l)
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build amd64 && solaris
|
||||
// +build amd64,solaris
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (h *msghdr) pack(vs []iovec, bs [][]byte, oob []byte, sa []byte) {
|
||||
for i := range vs {
|
||||
vs[i].set(bs[i])
|
||||
}
|
||||
if len(vs) > 0 {
|
||||
h.Iov = &vs[0]
|
||||
h.Iovlen = int32(len(vs))
|
||||
}
|
||||
if len(oob) > 0 {
|
||||
h.Accrights = (*int8)(unsafe.Pointer(&oob[0]))
|
||||
h.Accrightslen = int32(len(oob))
|
||||
}
|
||||
if sa != nil {
|
||||
h.Name = (*byte)(unsafe.Pointer(&sa[0]))
|
||||
h.Namelen = uint32(len(sa))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *msghdr) controllen() int {
|
||||
return int(h.Accrightslen)
|
||||
}
|
||||
|
||||
func (h *msghdr) flags() int {
|
||||
return int(NativeEndian.Uint32(h.Pad_cgo_2[:]))
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !zos
|
||||
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!zos
|
||||
|
||||
package socket
|
||||
|
||||
type msghdr struct{}
|
||||
|
||||
func (h *msghdr) pack(vs []iovec, bs [][]byte, oob []byte, sa []byte) {}
|
||||
func (h *msghdr) name() []byte { return nil }
|
||||
func (h *msghdr) controllen() int { return 0 }
|
||||
func (h *msghdr) flags() int { return 0 }
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build s390x && zos
|
||||
// +build s390x,zos
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (h *msghdr) pack(vs []iovec, bs [][]byte, oob []byte, sa []byte) {
|
||||
for i := range vs {
|
||||
vs[i].set(bs[i])
|
||||
}
|
||||
if len(vs) > 0 {
|
||||
h.Iov = &vs[0]
|
||||
h.Iovlen = int32(len(vs))
|
||||
}
|
||||
if len(oob) > 0 {
|
||||
h.Control = (*byte)(unsafe.Pointer(&oob[0]))
|
||||
h.Controllen = uint32(len(oob))
|
||||
}
|
||||
if sa != nil {
|
||||
h.Name = (*byte)(unsafe.Pointer(&sa[0]))
|
||||
h.Namelen = uint32(len(sa))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *msghdr) controllen() int {
|
||||
return int(h.Controllen)
|
||||
}
|
||||
|
||||
func (h *msghdr) flags() int {
|
||||
return int(h.Flags)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !race
|
||||
// +build !race
|
||||
|
||||
package socket
|
||||
|
||||
func (m *Message) raceRead() {
|
||||
}
|
||||
func (m *Message) raceWrite() {
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build race
|
||||
// +build race
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// This package reads and writes the Message buffers using a
|
||||
// direct system call, which the race detector can't see.
|
||||
// These functions tell the race detector what is going on during the syscall.
|
||||
|
||||
func (m *Message) raceRead() {
|
||||
for _, b := range m.Buffers {
|
||||
if len(b) > 0 {
|
||||
runtime.RaceReadRange(unsafe.Pointer(&b[0]), len(b))
|
||||
}
|
||||
}
|
||||
if b := m.OOB; len(b) > 0 {
|
||||
runtime.RaceReadRange(unsafe.Pointer(&b[0]), len(b))
|
||||
}
|
||||
}
|
||||
func (m *Message) raceWrite() {
|
||||
for _, b := range m.Buffers {
|
||||
if len(b) > 0 {
|
||||
runtime.RaceWriteRange(unsafe.Pointer(&b[0]), len(b))
|
||||
}
|
||||
}
|
||||
if b := m.OOB; len(b) > 0 {
|
||||
runtime.RaceWriteRange(unsafe.Pointer(&b[0]), len(b))
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue