Implement SVCB (#1067)
* Implement SVCB * Fix serialization and deserialization of double quotes * More effort (?) 4 months old commit * DEBUG * _ * Presentation format serialization/deserialization * _ Remove generated * Progress on presentation format parse & write * _ * Finish parsing presentation format * Regenerate * Pack unpack * Move to svcb.go Scan_rr.go and types.go should be untouched now * 🐛 Thanks ghedo * Definitions * TypeHTTPSSVC * Generated and isDuplicate * Goodbye lenient functions Now private key=value pairs have to be defined as structs too. They are no longer automatically named as KeyNNNNN * Encode/decode * Experimental svc * Read method * Implement some of the methods, use trick... to report where the error is while reading it. This should be applied to EDNS too. Todo: Find if case can only contain e := new(SVC_ALPN) and rest moved out Also fix two compile errors * Add SVC_LOCAL methods, reorder, remove alpn value, bugs * Errors * Alpn, make it build * Correct testsuite * Fully implement parser Change from keeping a state variable to reading in one iteration until the key=value pair is fully consumed * Simplify and document EDNS should be simplified too * Attempt to fix fuzzer And Alpn bug * A bug and change type values to match @ghedo's implementation * IP bug Also there are two ip duplicating patterns, one with copy, one with append. Maybe change it to be consistent. * Check for strictly increasing keys as required * Don't panic on invalid alpn * Redundant check, don't modify original array * Size calculation * Fix the fuzzer, match the style * 65535 is reserved too, don't delay errors * Check keyNNN, check for aliasform having values * IPvNHint is an array * Fix ipvNHint * Rename everything * Unrecognized keys according to the updated specification * Skip zero-length structs in generators. Fix CI * Doc cleanup * Off by one * Add parse tests * Check if private key doesn't collide with known key, invalid tests * Disallow IPv4 as IPv6. More tests. Related #1107 * Style fixes * More consistency, more tests * 🐛 Deep copy as in the documentation a := make([]net.IP, 1) a[0] = net.ParseIP("1.1.1.1").To4() b := append(make([]net.IP, 0, 1), a...) b[0] = net.ParseIP("3.1.1.1").To4() fmt.Println(a[0][0]) * Make tests readable * Move valid parse tests to different file * 🐛 One of previous commits not fully committed * Test binary single value encoding/decoding and full encode/decode * Add worst-case grows to builders, 🐛 Wrong visible character range, redundant tests * Testing improvements And don't convert to IPv4 twice * Doc update only * Document worst case allocations and ipv6 can be at most of length 39, not 40 * Redundant IP copy, consistent IPv6 behavior, fix deep copy * isDuplicate for SVCB * Optimizations * echoconfig * Svc => SVCB * Fix CI * Regenerate after REBASE (2) Rebased twice on 15th and 20th May * Rename svc, use escapeByte. * Fix parsing whitespaces between quotes, rename ECHOHOConfig * resolve Remove svcbFieldLen Use reverseInt Uppercase SVCB Rename key_value "invalid" => bad Alpn comments > 65535 check Unneeded slices * a little more read => parse IP array meaning Force pushed because forgot to change read in svcb_test.go * HTTPSSVC -> HTTPS * Use new values * mandatory code https://github.com/MikeBishop/dns-alt-svc/pull/205 * Resolve comments Rename svcb-pairs Remove SVCB_PRIVATE ranges Comment on SVCB_KEY65535 ParseError return l.token rename svcbKeyToString and svcbStringToKey privatize SVCBKeyToString, SVCBStringToKey * Refactor 1 Rename sorted, originalPairs Use append instead of copy Use svcb_RESERVED instead of 65535, with it now being private "type SVCBKey uint16" * Refactor 2 svcbKeyToString as method svcbStringToKey updated after key 0 🐛 mandatory has missing key Rename str idx < 0 * Refactor 3 Use l.token as z var key, value string Comment wrap 0: Sentences with '.' keyValue => kv * Refactor 4 * Refactor 5 len() int * Refactor 6 * Refactor 7 * Test remove parsing * Error messages * Rewrite two estimate comments * parse shouldn't modify original array 🐛 * Remove two unneeded comments * Address review comments Push 2 because can't build fuzzer python Push 3 to try again * Simplify argument duplication as per tmthrgd's suggestion And add the relevant test Force push edit: Make sorting code fit into one line * Rewrite ECHConfig and address the review * Remove the optional tab * Add To4() Check * More cleanup and fix mandatory not sorting bug
This commit is contained in:
parent
cec9156531
commit
0972db6834
|
@ -30,6 +30,9 @@ func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
if st.NumFields() == 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
||||||
return st, false
|
return st, false
|
||||||
}
|
}
|
||||||
|
@ -118,6 +121,14 @@ func main() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if st.Tag(i) == `dns:"pairs"` {
|
||||||
|
o2(`if !areSVCBPairArraysEqual(r1.%s, r2.%s) {
|
||||||
|
return false
|
||||||
|
}`)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
o3(`for i := 0; i < len(r1.%s); i++ {
|
o3(`for i := 0; i < len(r1.%s); i++ {
|
||||||
if r1.%s[i] != r2.%s[i] {
|
if r1.%s[i] != r2.%s[i] {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -34,6 +34,39 @@ func TestDuplicateTXT(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDuplicateSVCB(t *testing.T) {
|
||||||
|
a1, _ := NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1::3:3:3:3 key65300=\254\032\030\000\ \043,\;`)
|
||||||
|
a2, _ := NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1:0::3:3:3:3 key65300="\254\ \030\000 +\,;"`)
|
||||||
|
|
||||||
|
if !IsDuplicate(a1, a2) {
|
||||||
|
t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
a2, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1::3:3:3:3 key65300="\255\ \030\000 +\,;"`)
|
||||||
|
|
||||||
|
if IsDuplicate(a1, a2) {
|
||||||
|
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
a1, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1::3:3:3:3`)
|
||||||
|
|
||||||
|
if IsDuplicate(a1, a2) {
|
||||||
|
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
a2, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv4hint=1.1.1.1`)
|
||||||
|
|
||||||
|
if IsDuplicate(a1, a2) {
|
||||||
|
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
a1, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv4hint=1.1.1.1,1.0.2.1`)
|
||||||
|
|
||||||
|
if IsDuplicate(a1, a2) {
|
||||||
|
t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDuplicateOwner(t *testing.T) {
|
func TestDuplicateOwner(t *testing.T) {
|
||||||
a1, _ := NewRR("www.example.org. IN A 127.0.0.1")
|
a1, _ := NewRR("www.example.org. IN A 127.0.0.1")
|
||||||
a2, _ := NewRR("www.example.org. IN A 127.0.0.1")
|
a2, _ := NewRR("www.example.org. IN A 127.0.0.1")
|
||||||
|
|
|
@ -35,6 +35,9 @@ func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
if st.NumFields() == 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
||||||
return st, false
|
return st, false
|
||||||
}
|
}
|
||||||
|
@ -110,6 +113,8 @@ return off, err
|
||||||
o("off, err = packDataOpt(rr.%s, msg, off)\n")
|
o("off, err = packDataOpt(rr.%s, msg, off)\n")
|
||||||
case `dns:"nsec"`:
|
case `dns:"nsec"`:
|
||||||
o("off, err = packDataNsec(rr.%s, msg, off)\n")
|
o("off, err = packDataNsec(rr.%s, msg, off)\n")
|
||||||
|
case `dns:"pairs"`:
|
||||||
|
o("off, err = packDataSVCB(rr.%s, msg, off)\n")
|
||||||
case `dns:"domain-name"`:
|
case `dns:"domain-name"`:
|
||||||
o("off, err = packDataDomainNames(rr.%s, msg, off, compression, false)\n")
|
o("off, err = packDataDomainNames(rr.%s, msg, off, compression, false)\n")
|
||||||
case `dns:"apl"`:
|
case `dns:"apl"`:
|
||||||
|
@ -236,6 +241,8 @@ return off, err
|
||||||
o("rr.%s, off, err = unpackDataOpt(msg, off)\n")
|
o("rr.%s, off, err = unpackDataOpt(msg, off)\n")
|
||||||
case `dns:"nsec"`:
|
case `dns:"nsec"`:
|
||||||
o("rr.%s, off, err = unpackDataNsec(msg, off)\n")
|
o("rr.%s, off, err = unpackDataNsec(msg, off)\n")
|
||||||
|
case `dns:"pairs"`:
|
||||||
|
o("rr.%s, off, err = unpackDataSVCB(msg, off)\n")
|
||||||
case `dns:"domain-name"`:
|
case `dns:"domain-name"`:
|
||||||
o("rr.%s, off, err = unpackDataDomainNames(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
|
o("rr.%s, off, err = unpackDataDomainNames(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
|
||||||
case `dns:"apl"`:
|
case `dns:"apl"`:
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"net"
|
"net"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -612,6 +613,65 @@ func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) {
|
||||||
return off, nil
|
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) {
|
func unpackDataDomainNames(msg []byte, off, end int) ([]string, int, error) {
|
||||||
var (
|
var (
|
||||||
servers []string
|
servers []string
|
||||||
|
|
|
@ -1633,6 +1633,70 @@ func TestParseCSYNC(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseSVCB(t *testing.T) {
|
||||||
|
svcbs := map[string]string{
|
||||||
|
`example.com. 3600 IN SVCB 0 cloudflare.com.`: `example.com. 3600 IN SVCB 0 cloudflare.com.`,
|
||||||
|
`example.com. 3600 IN SVCB 65000 cloudflare.com. alpn=h2 ipv4hint=3.4.3.2`: `example.com. 3600 IN SVCB 65000 cloudflare.com. alpn="h2" ipv4hint="3.4.3.2"`,
|
||||||
|
`example.com. 3600 IN SVCB 65000 cloudflare.com. key65000=4\ 3 key65001="\" " key65002 key65003= key65004="" key65005== key65006==\"\" key65007=\254 key65008=\032`: `example.com. 3600 IN SVCB 65000 cloudflare.com. key65000="4\ 3" key65001="\"\ " key65002="" key65003="" key65004="" key65005="=" key65006="=\"\"" key65007="\254" key65008="\ "`,
|
||||||
|
}
|
||||||
|
for s, o := range svcbs {
|
||||||
|
rr, err := NewRR(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to parse RR: ", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rr.String() != o {
|
||||||
|
t.Errorf("`%s' should be equal to\n`%s', but is `%s'", s, o, rr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseBadSVCB(t *testing.T) {
|
||||||
|
header := `example.com. 3600 IN HTTPS `
|
||||||
|
evils := []string{
|
||||||
|
`0 . no-default-alpn`, // aliasform
|
||||||
|
`65536 . no-default-alpn`, // bad priority
|
||||||
|
`1 ..`, // bad domain
|
||||||
|
`1 . no-default-alpn=1`, // value illegal
|
||||||
|
`1 . key`, // invalid key
|
||||||
|
`1 . key=`, // invalid key
|
||||||
|
`1 . =`, // invalid key
|
||||||
|
`1 . ==`, // invalid key
|
||||||
|
`1 . =a`, // invalid key
|
||||||
|
`1 . ""`, // invalid key
|
||||||
|
`1 . ""=`, // invalid key
|
||||||
|
`1 . "a"`, // invalid key
|
||||||
|
`1 . "a"=`, // invalid key
|
||||||
|
`1 . key1=`, // we know that key
|
||||||
|
`1 . key65535`, // key reserved
|
||||||
|
`1 . key065534`, // key can't be padded
|
||||||
|
`1 . key65534="f`, // unterminated value
|
||||||
|
`1 . key65534="`, // unterminated value
|
||||||
|
`1 . key65534=\2`, // invalid numberic escape
|
||||||
|
`1 . key65534=\24`, // invalid numberic escape
|
||||||
|
`1 . key65534=\256`, // invalid numberic escape
|
||||||
|
`1 . key65534=\`, // invalid numberic escape
|
||||||
|
`1 . key65534=""alpn`, // zQuote ending needs whitespace
|
||||||
|
`1 . key65534="a"alpn`, // zQuote ending needs whitespace
|
||||||
|
`1 . ipv6hint=1.1.1.1`, // not ipv6
|
||||||
|
`1 . ipv6hint=1:1:1:1`, // not ipv6
|
||||||
|
`1 . ipv6hint=a`, // not ipv6
|
||||||
|
`1 . ipv4hint=1.1.1.1.1`, // not ipv4
|
||||||
|
`1 . ipv4hint=::fc`, // not ipv4
|
||||||
|
`1 . ipv4hint=..11`, // not ipv4
|
||||||
|
`1 . ipv4hint=a`, // not ipv4
|
||||||
|
`1 . port=`, // empty port
|
||||||
|
`1 . echconfig=YUd`, // bad base64
|
||||||
|
}
|
||||||
|
for _, o := range evils {
|
||||||
|
_, err := NewRR(header + o)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("failed to reject invalid RR: ", header+o)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseBadNAPTR(t *testing.T) {
|
func TestParseBadNAPTR(t *testing.T) {
|
||||||
// Should look like: mplus.ims.vodafone.com. 3600 IN NAPTR 10 100 "S" "SIP+D2U" "" _sip._udp.mplus.ims.vodafone.com.
|
// Should look like: mplus.ims.vodafone.com. 3600 IN NAPTR 10 100 "S" "SIP+D2U" "" _sip._udp.mplus.ims.vodafone.com.
|
||||||
naptr := `mplus.ims.vodafone.com. 3600 IN NAPTR 10 100 S SIP+D2U _sip._udp.mplus.ims.vodafone.com.`
|
naptr := `mplus.ims.vodafone.com. 3600 IN NAPTR 10 100 S SIP+D2U _sip._udp.mplus.ims.vodafone.com.`
|
||||||
|
|
|
@ -0,0 +1,773 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SVCBKey uint16
|
||||||
|
|
||||||
|
// Keys defined in draft-ietf-dnsop-svcb-https-02 Section 11.1.2
|
||||||
|
const (
|
||||||
|
SVCB_MANDATORY SVCBKey = 0
|
||||||
|
SVCB_ALPN SVCBKey = 1
|
||||||
|
SVCB_NO_DEFAULT_ALPN SVCBKey = 2
|
||||||
|
SVCB_PORT SVCBKey = 3
|
||||||
|
SVCB_IPV4HINT SVCBKey = 4
|
||||||
|
SVCB_ECHCONFIG SVCBKey = 5
|
||||||
|
SVCB_IPV6HINT SVCBKey = 6
|
||||||
|
svcb_RESERVED SVCBKey = 65535
|
||||||
|
)
|
||||||
|
|
||||||
|
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: "echconfig",
|
||||||
|
SVCB_IPV6HINT: "ipv6hint",
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
rr.Value = xs
|
||||||
|
if rr.Priority == 0 && len(xs) > 0 {
|
||||||
|
return &ParseError{l.token, "SVCB aliasform can't have values", l}
|
||||||
|
}
|
||||||
|
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_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-00)
|
||||||
|
|
||||||
|
// The one with the smallest priority should be given preference. Of those with
|
||||||
|
// equal priority, a random one should be preferred for load balancing.
|
||||||
|
type SVCB struct {
|
||||||
|
Hdr RR_Header
|
||||||
|
Priority uint16
|
||||||
|
Target string `dns:"domain-name"`
|
||||||
|
Value []SVCBKeyValue `dns:"pairs"` // This must be empty if Priority is non-zero
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
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 returns the numerical key code.
|
||||||
|
Key() SVCBKey
|
||||||
|
// pack returns the encoded value.
|
||||||
|
pack() ([]byte, error)
|
||||||
|
// unpack sets the value.
|
||||||
|
unpack([]byte) error
|
||||||
|
// String returns the string representation of the value.
|
||||||
|
String() string
|
||||||
|
// parse sets the value to the given string representation of the value.
|
||||||
|
parse(string) error
|
||||||
|
// copy returns a deep-copy of the pair.
|
||||||
|
copy() SVCBKeyValue
|
||||||
|
// len returns the length of value in the wire format.
|
||||||
|
len() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// SVCBMandatory pair adds to required keys that must be interpreted for the RR
|
||||||
|
// to be functional.
|
||||||
|
// Basic use pattern for creating a mandatory option:
|
||||||
|
//
|
||||||
|
// o := new(dns.SVCB)
|
||||||
|
// o.Hdr.Name = "."
|
||||||
|
// o.Hdr.Rrtype = dns.TypeSVCB
|
||||||
|
// e := new(dns.SVCBMandatory)
|
||||||
|
// e.Code = []uint16{65403}
|
||||||
|
// o.Value = append(o.Value, e)
|
||||||
|
// // Then add key-value pair for key65403
|
||||||
|
type SVCBMandatory struct {
|
||||||
|
Code []SVCBKey // Must not include mandatory
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
// 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:
|
||||||
|
//
|
||||||
|
// o := new(dns.HTTPS)
|
||||||
|
// o.Hdr.Name = "."
|
||||||
|
// o.Hdr.Rrtype = dns.TypeHTTPS
|
||||||
|
// e := new(dns.SVCBAlpn)
|
||||||
|
// e.Alpn = []string{"h2", "http/1.1"}
|
||||||
|
// o.Value = append(o.Value, e)
|
||||||
|
type SVCBAlpn struct {
|
||||||
|
Alpn []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN }
|
||||||
|
func (s *SVCBAlpn) String() string { return strings.Join(s.Alpn, ",") }
|
||||||
|
|
||||||
|
// The spec requires the alpn keys including \ or , to be escaped.
|
||||||
|
// In practice, no standard key including those exists.
|
||||||
|
// Therefore those characters are not escaped.
|
||||||
|
|
||||||
|
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 len(e) == 0 {
|
||||||
|
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 {
|
||||||
|
s.Alpn = strings.Split(b, ",")
|
||||||
|
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.
|
||||||
|
// Basic use pattern for creating a no-default-alpn option:
|
||||||
|
//
|
||||||
|
// o := new(dns.SVCB)
|
||||||
|
// o.Hdr.Name = "."
|
||||||
|
// o.Hdr.Rrtype = dns.SVCB
|
||||||
|
// e := new(dns.SVCBNoDefaultAlpn)
|
||||||
|
// o.Value = append(o.Value, e)
|
||||||
|
type SVCBNoDefaultAlpn struct {
|
||||||
|
// Empty
|
||||||
|
}
|
||||||
|
|
||||||
|
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 len(b) != 0 {
|
||||||
|
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:
|
||||||
|
//
|
||||||
|
// o := new(dns.SVCB)
|
||||||
|
// o.Hdr.Name = "."
|
||||||
|
// o.Hdr.Rrtype = dns.SVCB
|
||||||
|
// e := new(dns.SVCBPort)
|
||||||
|
// e.Port = 80
|
||||||
|
// o.Value = append(o.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:
|
||||||
|
//
|
||||||
|
// o := new(dns.HTTPS)
|
||||||
|
// o.Hdr.Name = "."
|
||||||
|
// o.Hdr.Rrtype = dns.HTTPS
|
||||||
|
// 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()}
|
||||||
|
// o.Value = append(o.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string form of s, it returns "<nil>" if s is invalid.
|
||||||
|
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 {
|
||||||
|
return &SVCBIPv4Hint{
|
||||||
|
append([]net.IP(nil), s.Hint...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx].
|
||||||
|
// Basic use pattern for creating an echconfig option:
|
||||||
|
//
|
||||||
|
// o := new(dns.HTTPS)
|
||||||
|
// o.Hdr.Name = "."
|
||||||
|
// o.Hdr.Rrtype = dns.HTTPS
|
||||||
|
// e := new(dns.SVCBECHConfig)
|
||||||
|
// e.ECH = "/wH...="
|
||||||
|
// o.Value = append(o.Value, e)
|
||||||
|
type SVCBECHConfig struct {
|
||||||
|
ECH []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
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: svcbechconfig: bad base64 echconfig")
|
||||||
|
}
|
||||||
|
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:
|
||||||
|
//
|
||||||
|
// o := new(dns.HTTPS)
|
||||||
|
// o.Hdr.Name = "."
|
||||||
|
// o.Hdr.Rrtype = dns.HTTPS
|
||||||
|
// e := new(dns.SVCBIPv6Hint)
|
||||||
|
// e.Hint = []net.IP{net.ParseIP("2001:db8::1")}
|
||||||
|
// o.Value = append(o.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string form of s, it returns "<nil>" if s is invalid.
|
||||||
|
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 {
|
||||||
|
if strings.Contains(b, ".") {
|
||||||
|
return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4")
|
||||||
|
}
|
||||||
|
str := strings.Split(b, ",")
|
||||||
|
dst := make([]net.IP, len(str))
|
||||||
|
for i, e := range str {
|
||||||
|
ip := net.ParseIP(e)
|
||||||
|
if ip == nil {
|
||||||
|
return errors.New("dns: svcbipv6hint: bad ip")
|
||||||
|
}
|
||||||
|
dst[i] = ip
|
||||||
|
}
|
||||||
|
s.Hint = dst
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
|
||||||
|
return &SVCBIPv6Hint{
|
||||||
|
append([]net.IP(nil), s.Hint...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
//
|
||||||
|
// o := new(dns.HTTPS)
|
||||||
|
// o.Hdr.Name = "."
|
||||||
|
// o.Hdr.Rrtype = dns.HTTPS
|
||||||
|
// e := new(dns.SVCBLocal)
|
||||||
|
// e.KeyCode = 65400
|
||||||
|
// e.Data = []byte("abc")
|
||||||
|
// o.Value = append(o.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) 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) String() string {
|
||||||
|
var str strings.Builder
|
||||||
|
str.Grow(4 * len(s.Data))
|
||||||
|
for _, e := range s.Data {
|
||||||
|
if ' ' <= e && e <= '~' {
|
||||||
|
switch e {
|
||||||
|
case '"', ';', ' ', '\\':
|
||||||
|
str.WriteByte('\\')
|
||||||
|
str.WriteByte(e)
|
||||||
|
default:
|
||||||
|
str.WriteByte(e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
str.WriteString(escapeByte(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SVCBLocal) parse(b string) error {
|
||||||
|
data := make([]byte, 0, len(b))
|
||||||
|
for i := 0; i < len(b); {
|
||||||
|
if b[i] != '\\' {
|
||||||
|
data = append(data, b[i])
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i+1 == len(b) {
|
||||||
|
return errors.New("dns: svcblocal: svcb private/experimental key escape unterminated")
|
||||||
|
}
|
||||||
|
if isDigit(b[i+1]) {
|
||||||
|
if i+3 < len(b) && isDigit(b[i+2]) && isDigit(b[i+3]) {
|
||||||
|
a, err := strconv.ParseUint(b[i+1:i+4], 10, 8)
|
||||||
|
if err == nil {
|
||||||
|
i += 4
|
||||||
|
data = append(data, byte(a))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("dns: svcblocal: svcb private/experimental key bad escaped octet")
|
||||||
|
} else {
|
||||||
|
data = append(data, b[i+1])
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This tests everything valid about SVCB but parsing.
|
||||||
|
// Parsing tests belong to parse_test.go.
|
||||||
|
func TestSVCB(t *testing.T) {
|
||||||
|
svcbs := []struct {
|
||||||
|
key string
|
||||||
|
data string
|
||||||
|
}{
|
||||||
|
{`mandatory`, `alpn,key65000`},
|
||||||
|
{`alpn`, `h2,h2c`},
|
||||||
|
{`port`, `499`},
|
||||||
|
{`ipv4hint`, `3.4.3.2,1.1.1.1`},
|
||||||
|
{`no-default-alpn`, ``},
|
||||||
|
{`ipv6hint`, `1::4:4:4:4,1::3:3:3:3`},
|
||||||
|
{`echconfig`, `YUdWc2JHOD0=`},
|
||||||
|
{`key65000`, `4\ 3`},
|
||||||
|
{`key65001`, `\"\ `},
|
||||||
|
{`key65002`, ``},
|
||||||
|
{`key65003`, `=\"\"`},
|
||||||
|
{`key65004`, `\254\ \ \030\000`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range svcbs {
|
||||||
|
keyCode := svcbStringToKey(o.key)
|
||||||
|
kv := makeSVCBKeyValue(keyCode)
|
||||||
|
if kv == nil {
|
||||||
|
t.Error("failed to parse svc key: ", o.key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if kv.Key() != keyCode {
|
||||||
|
t.Error("key constant is not in sync: ", keyCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := kv.parse(o.data)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to parse svc pair: ", o.key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b, err := kv.pack()
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to pack value of svc pair: ", o.key, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(b) != int(kv.len()) {
|
||||||
|
t.Errorf("expected packed svc value %s to be of length %d but got %d", o.key, int(kv.len()), len(b))
|
||||||
|
}
|
||||||
|
err = kv.unpack(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to unpack value of svc pair: ", o.key, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if str := kv.String(); str != o.data {
|
||||||
|
t.Errorf("`%s' should be equal to\n`%s', but is `%s'", o.key, o.data, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeBadSVCB(t *testing.T) {
|
||||||
|
svcbs := []struct {
|
||||||
|
key SVCBKey
|
||||||
|
data []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
key: SVCB_ALPN,
|
||||||
|
data: []byte{3, 0, 0}, // There aren't three octets after 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SVCB_NO_DEFAULT_ALPN,
|
||||||
|
data: []byte{0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SVCB_PORT,
|
||||||
|
data: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SVCB_IPV4HINT,
|
||||||
|
data: []byte{0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SVCB_IPV6HINT,
|
||||||
|
data: []byte{0, 0, 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, o := range svcbs {
|
||||||
|
err := makeSVCBKeyValue(SVCBKey(o.key)).unpack(o.data)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("accepted invalid svc value with key ", SVCBKey(o.key).String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompareSVCB(t *testing.T) {
|
||||||
|
val1 := []SVCBKeyValue{
|
||||||
|
&SVCBPort{
|
||||||
|
Port: 117,
|
||||||
|
},
|
||||||
|
&SVCBAlpn{
|
||||||
|
Alpn: []string{"h2", "h3"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
val2 := []SVCBKeyValue{
|
||||||
|
&SVCBAlpn{
|
||||||
|
Alpn: []string{"h2", "h3"},
|
||||||
|
},
|
||||||
|
&SVCBPort{
|
||||||
|
Port: 117,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !areSVCBPairArraysEqual(val1, val2) {
|
||||||
|
t.Error("svcb pairs were compared without sorting")
|
||||||
|
}
|
||||||
|
if val1[0].Key() != SVCB_PORT || val2[0].Key() != SVCB_ALPN {
|
||||||
|
t.Error("original svcb pairs were reordered during comparison")
|
||||||
|
}
|
||||||
|
}
|
2
types.go
2
types.go
|
@ -81,6 +81,8 @@ const (
|
||||||
TypeCDNSKEY uint16 = 60
|
TypeCDNSKEY uint16 = 60
|
||||||
TypeOPENPGPKEY uint16 = 61
|
TypeOPENPGPKEY uint16 = 61
|
||||||
TypeCSYNC uint16 = 62
|
TypeCSYNC uint16 = 62
|
||||||
|
TypeSVCB uint16 = 64
|
||||||
|
TypeHTTPS uint16 = 65
|
||||||
TypeSPF uint16 = 99
|
TypeSPF uint16 = 99
|
||||||
TypeUINFO uint16 = 100
|
TypeUINFO uint16 = 100
|
||||||
TypeUID uint16 = 101
|
TypeUID uint16 = 101
|
||||||
|
|
|
@ -72,6 +72,9 @@ func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
if st.NumFields() == 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
||||||
return st, false
|
return st, false
|
||||||
}
|
}
|
||||||
|
@ -181,6 +184,8 @@ func main() {
|
||||||
o("for _, x := range rr.%s { l += len(x) + 1 }\n")
|
o("for _, x := range rr.%s { l += len(x) + 1 }\n")
|
||||||
case `dns:"apl"`:
|
case `dns:"apl"`:
|
||||||
o("for _, x := range rr.%s { l += x.len() }\n")
|
o("for _, x := range rr.%s { l += x.len() }\n")
|
||||||
|
case `dns:"pairs"`:
|
||||||
|
o("for _, x := range rr.%s { l += 4 + int(x.len()) }\n")
|
||||||
default:
|
default:
|
||||||
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
||||||
}
|
}
|
||||||
|
@ -272,6 +277,12 @@ func main() {
|
||||||
fields = append(fields, f)
|
fields = append(fields, f)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if t == "SVCBKeyValue" {
|
||||||
|
fmt.Fprintf(b, "%s := make([]%s, len(rr.%s));\nfor i,e := range rr.%s {\n %s[i] = e.copy()\n}\n",
|
||||||
|
f, t, f, f, f)
|
||||||
|
fields = append(fields, f)
|
||||||
|
continue
|
||||||
|
}
|
||||||
fmt.Fprintf(b, "%s := make([]%s, len(rr.%s)); copy(%s, rr.%s)\n",
|
fmt.Fprintf(b, "%s := make([]%s, len(rr.%s)); copy(%s, rr.%s)\n",
|
||||||
f, t, f, f, f)
|
f, t, f, f, f)
|
||||||
fields = append(fields, f)
|
fields = append(fields, f)
|
||||||
|
|
|
@ -402,6 +402,27 @@ func (r1 *HIP) isDuplicate(_r2 RR) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r1 *HTTPS) isDuplicate(_r2 RR) bool {
|
||||||
|
r2, ok := _r2.(*HTTPS)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_ = r2
|
||||||
|
if r1.Priority != r2.Priority {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !isDuplicateName(r1.Target, r2.Target) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(r1.Value) != len(r2.Value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !areSVCBPairArraysEqual(r1.Value, r2.Value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (r1 *KEY) isDuplicate(_r2 RR) bool {
|
func (r1 *KEY) isDuplicate(_r2 RR) bool {
|
||||||
r2, ok := _r2.(*KEY)
|
r2, ok := _r2.(*KEY)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -1076,6 +1097,27 @@ func (r1 *SSHFP) isDuplicate(_r2 RR) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r1 *SVCB) isDuplicate(_r2 RR) bool {
|
||||||
|
r2, ok := _r2.(*SVCB)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_ = r2
|
||||||
|
if r1.Priority != r2.Priority {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !isDuplicateName(r1.Target, r2.Target) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(r1.Value) != len(r2.Value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !areSVCBPairArraysEqual(r1.Value, r2.Value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (r1 *TA) isDuplicate(_r2 RR) bool {
|
func (r1 *TA) isDuplicate(_r2 RR) bool {
|
||||||
r2, ok := _r2.(*TA)
|
r2, ok := _r2.(*TA)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
82
zmsg.go
82
zmsg.go
|
@ -316,6 +316,22 @@ func (rr *HIP) pack(msg []byte, off int, compression compressionMap, compress bo
|
||||||
return off, nil
|
return off, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rr *HTTPS) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
||||||
|
off, err = packUint16(rr.Priority, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packDomainName(rr.Target, msg, off, compression, false)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packDataSVCB(rr.Value, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (rr *KEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
func (rr *KEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
||||||
off, err = packUint16(rr.Flags, msg, off)
|
off, err = packUint16(rr.Flags, msg, off)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -906,6 +922,22 @@ func (rr *SSHFP) pack(msg []byte, off int, compression compressionMap, compress
|
||||||
return off, nil
|
return off, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rr *SVCB) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
||||||
|
off, err = packUint16(rr.Priority, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packDomainName(rr.Target, msg, off, compression, false)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packDataSVCB(rr.Value, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (rr *TA) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
func (rr *TA) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
||||||
off, err = packUint16(rr.KeyTag, msg, off)
|
off, err = packUint16(rr.KeyTag, msg, off)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1559,6 +1591,31 @@ func (rr *HIP) unpack(msg []byte, off int) (off1 int, err error) {
|
||||||
return off, nil
|
return off, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rr *HTTPS) unpack(msg []byte, off int) (off1 int, err error) {
|
||||||
|
rdStart := off
|
||||||
|
_ = rdStart
|
||||||
|
|
||||||
|
rr.Priority, off, err = unpackUint16(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
if off == len(msg) {
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
rr.Target, off, err = UnpackDomainName(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
if off == len(msg) {
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
rr.Value, off, err = unpackDataSVCB(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (rr *KEY) unpack(msg []byte, off int) (off1 int, err error) {
|
func (rr *KEY) unpack(msg []byte, off int) (off1 int, err error) {
|
||||||
rdStart := off
|
rdStart := off
|
||||||
_ = rdStart
|
_ = rdStart
|
||||||
|
@ -2461,6 +2518,31 @@ func (rr *SSHFP) unpack(msg []byte, off int) (off1 int, err error) {
|
||||||
return off, nil
|
return off, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rr *SVCB) unpack(msg []byte, off int) (off1 int, err error) {
|
||||||
|
rdStart := off
|
||||||
|
_ = rdStart
|
||||||
|
|
||||||
|
rr.Priority, off, err = unpackUint16(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
if off == len(msg) {
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
rr.Target, off, err = UnpackDomainName(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
if off == len(msg) {
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
rr.Value, off, err = unpackDataSVCB(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (rr *TA) unpack(msg []byte, off int) (off1 int, err error) {
|
func (rr *TA) unpack(msg []byte, off int) (off1 int, err error) {
|
||||||
rdStart := off
|
rdStart := off
|
||||||
_ = rdStart
|
_ = rdStart
|
||||||
|
|
25
ztypes.go
25
ztypes.go
|
@ -33,6 +33,7 @@ var TypeToRR = map[uint16]func() RR{
|
||||||
TypeGPOS: func() RR { return new(GPOS) },
|
TypeGPOS: func() RR { return new(GPOS) },
|
||||||
TypeHINFO: func() RR { return new(HINFO) },
|
TypeHINFO: func() RR { return new(HINFO) },
|
||||||
TypeHIP: func() RR { return new(HIP) },
|
TypeHIP: func() RR { return new(HIP) },
|
||||||
|
TypeHTTPS: func() RR { return new(HTTPS) },
|
||||||
TypeKEY: func() RR { return new(KEY) },
|
TypeKEY: func() RR { return new(KEY) },
|
||||||
TypeKX: func() RR { return new(KX) },
|
TypeKX: func() RR { return new(KX) },
|
||||||
TypeL32: func() RR { return new(L32) },
|
TypeL32: func() RR { return new(L32) },
|
||||||
|
@ -70,6 +71,7 @@ var TypeToRR = map[uint16]func() RR{
|
||||||
TypeSPF: func() RR { return new(SPF) },
|
TypeSPF: func() RR { return new(SPF) },
|
||||||
TypeSRV: func() RR { return new(SRV) },
|
TypeSRV: func() RR { return new(SRV) },
|
||||||
TypeSSHFP: func() RR { return new(SSHFP) },
|
TypeSSHFP: func() RR { return new(SSHFP) },
|
||||||
|
TypeSVCB: func() RR { return new(SVCB) },
|
||||||
TypeTA: func() RR { return new(TA) },
|
TypeTA: func() RR { return new(TA) },
|
||||||
TypeTALINK: func() RR { return new(TALINK) },
|
TypeTALINK: func() RR { return new(TALINK) },
|
||||||
TypeTKEY: func() RR { return new(TKEY) },
|
TypeTKEY: func() RR { return new(TKEY) },
|
||||||
|
@ -110,6 +112,7 @@ var TypeToString = map[uint16]string{
|
||||||
TypeGPOS: "GPOS",
|
TypeGPOS: "GPOS",
|
||||||
TypeHINFO: "HINFO",
|
TypeHINFO: "HINFO",
|
||||||
TypeHIP: "HIP",
|
TypeHIP: "HIP",
|
||||||
|
TypeHTTPS: "HTTPS",
|
||||||
TypeISDN: "ISDN",
|
TypeISDN: "ISDN",
|
||||||
TypeIXFR: "IXFR",
|
TypeIXFR: "IXFR",
|
||||||
TypeKEY: "KEY",
|
TypeKEY: "KEY",
|
||||||
|
@ -153,6 +156,7 @@ var TypeToString = map[uint16]string{
|
||||||
TypeSPF: "SPF",
|
TypeSPF: "SPF",
|
||||||
TypeSRV: "SRV",
|
TypeSRV: "SRV",
|
||||||
TypeSSHFP: "SSHFP",
|
TypeSSHFP: "SSHFP",
|
||||||
|
TypeSVCB: "SVCB",
|
||||||
TypeTA: "TA",
|
TypeTA: "TA",
|
||||||
TypeTALINK: "TALINK",
|
TypeTALINK: "TALINK",
|
||||||
TypeTKEY: "TKEY",
|
TypeTKEY: "TKEY",
|
||||||
|
@ -191,6 +195,7 @@ func (rr *GID) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *GPOS) 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 *HINFO) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *HIP) 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 *KEY) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *KX) 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 *L32) Header() *RR_Header { return &rr.Hdr }
|
||||||
|
@ -229,6 +234,7 @@ func (rr *SOA) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *SPF) 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 *SRV) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *SSHFP) 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 *TA) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *TALINK) 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 *TKEY) Header() *RR_Header { return &rr.Hdr }
|
||||||
|
@ -592,6 +598,15 @@ func (rr *SSHFP) len(off int, compression map[string]struct{}) int {
|
||||||
l += len(rr.FingerPrint) / 2
|
l += len(rr.FingerPrint) / 2
|
||||||
return l
|
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 {
|
func (rr *TA) len(off int, compression map[string]struct{}) int {
|
||||||
l := rr.Hdr.len(off, compression)
|
l := rr.Hdr.len(off, compression)
|
||||||
l += 2 // KeyTag
|
l += 2 // KeyTag
|
||||||
|
@ -753,6 +768,9 @@ func (rr *HIP) copy() RR {
|
||||||
copy(RendezvousServers, rr.RendezvousServers)
|
copy(RendezvousServers, rr.RendezvousServers)
|
||||||
return &HIP{rr.Hdr, rr.HitLength, rr.PublicKeyAlgorithm, rr.PublicKeyLength, rr.Hit, rr.PublicKey, 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 {
|
func (rr *KEY) copy() RR {
|
||||||
return &KEY{*rr.DNSKEY.copy().(*DNSKEY)}
|
return &KEY{*rr.DNSKEY.copy().(*DNSKEY)}
|
||||||
}
|
}
|
||||||
|
@ -879,6 +897,13 @@ func (rr *SRV) copy() RR {
|
||||||
func (rr *SSHFP) copy() RR {
|
func (rr *SSHFP) copy() RR {
|
||||||
return &SSHFP{rr.Hdr, rr.Algorithm, rr.Type, rr.FingerPrint}
|
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 {
|
func (rr *TA) copy() RR {
|
||||||
return &TA{rr.Hdr, rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest}
|
return &TA{rr.Hdr, rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue