Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions integrationTest/helpers_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,9 +404,14 @@ func dname(name, target string) *models.RecordConfig {
}

func ds(name string, keyTag uint16, algorithm, digestType uint8, digest string) *models.RecordConfig {
r := makeRec(name, "", "DS")
panicOnErr(r.SetTargetDS(keyTag, algorithm, digestType, digest))
return r
rec, err := rtypecontrol.NewRecordConfigFromRaw(rtypecontrol.FromRawOpts{
Type: "DS",
TTL: 300,
Args: []any{name, keyTag, algorithm, digestType, digest},
DCN: globalDCN,
})
panicOnErr(err)
return rec
}

func dnskey(name string, flags uint16, protocol, algorithm uint8, publicKey string) *models.RecordConfig {
Expand Down
20 changes: 1 addition & 19 deletions pkg/js/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -470,25 +470,6 @@ var CAA = recordBuilder('CAA', {
// CNAME(name,target, recordModifiers...)
var CNAME = recordBuilder('CNAME');

// DS(name, keytag, algorithm, digestype, digest)
var DS = recordBuilder('DS', {
args: [
['name', _.isString],
['keytag', _.isNumber],
['algorithm', _.isNumber],
['digesttype', _.isNumber],
['digest', _.isString],
],
transform: function (record, args, modifiers) {
record.name = args.name;
record.dskeytag = args.keytag;
record.dsalgorithm = args.algorithm;
record.dsdigesttype = args.digesttype;
record.dsdigest = args.digest;
record.target = args.target;
},
});

// DHCID(name,target, recordModifiers...)
var DHCID = recordBuilder('DHCID');

Expand Down Expand Up @@ -2499,4 +2480,5 @@ function rawrecordBuilder(type) {
var CF_REDIRECT = rawrecordBuilder('CF_REDIRECT');
var CF_SINGLE_REDIRECT = rawrecordBuilder('CLOUDFLAREAPI_SINGLE_REDIRECT');
var CF_TEMP_REDIRECT = rawrecordBuilder('CF_TEMP_REDIRECT');
var DS = rawrecordBuilder('DS');
var RP = rawrecordBuilder('RP');
38 changes: 36 additions & 2 deletions pkg/js/parse_tests/027-ds.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,60 @@
"name": "foo.com",
"records": [
{
"comparable": "1 1 1 FFFF",
"dsalgorithm": 1,
"dsdigest": "FFFF",
"dsdigesttype": 1,
"dskeytag": 1,
"fields": {
"Algorithm": 1,
"Digest": "FFFF",
"DigestType": 1,
"Hdr": {
"Class": 0,
"Name": "",
"Rdlength": 0,
"Rrtype": 0,
"Ttl": 0
},
"KeyTag": 1
},
"filepos": "[line:3:5]",
"name": "@",
"name_raw": "@",
"name_unicode": "@",
"target": "",
"ttl": 300,
"type": "DS"
"type": "DS",
"zonfefilepartial": "1 1 1 FFFF"
},
{
"comparable": "1000 13 2 AABBCCDDEEFF",
"dsalgorithm": 13,
"dsdigest": "AABBCCDDEEFF",
"dsdigesttype": 2,
"dskeytag": 1000,
"fields": {
"Algorithm": 13,
"Digest": "AABBCCDDEEFF",
"DigestType": 2,
"Hdr": {
"Class": 0,
"Name": "",
"Rdlength": 0,
"Rrtype": 0,
"Ttl": 0
},
"KeyTag": 1000
},
"filepos": "[line:2:5]",
"name": "@",
"name_raw": "@",
"name_unicode": "@",
"target": "",
"ttl": 300,
"type": "DS"
"type": "DS",
"zonfefilepartial": "1000 13 2 AABBCCDDEEFF"
}
],
"registrar": "none",
Expand Down
61 changes: 61 additions & 0 deletions pkg/rtype/ds.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package rtype

import (
"fmt"

"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/domaintags"
"github.com/StackExchange/dnscontrol/v4/pkg/rtypecontrol"
"github.com/miekg/dns"
)

func init() {
rtypecontrol.Register(&DS{})
}

// DS RR.
type DS struct {
dns.DS
}

// Name returns the DNS record type as a string.
func (handle *DS) Name() string {
return "DS"
}

// FromArgs fills in the RecordConfig from []any, which is typically from a parsed config file.
func (handle *DS) FromArgs(dcn *domaintags.DomainNameVarieties, rec *models.RecordConfig, args []any) error {
if err := rtypecontrol.PaveArgs(args[1:], "wbbs"); err != nil {
return fmt.Errorf("ERROR: (%s) [DS(%q, %v)]: %w",
rec.FilePos,
rec.Name, rtypecontrol.StringifyQuoted(args[1:]),
err)
}
fields := &DS{
dns.DS{
KeyTag: args[1].(uint16),
Algorithm: args[2].(uint8),
DigestType: args[3].(uint8),
Digest: args[4].(string),
},
}

return handle.FromStruct(dcn, rec, args[0].(string), fields)
}

// FromStruct fills in the RecordConfig from a struct, typically from an API response.
func (handle *DS) FromStruct(dcn *domaintags.DomainNameVarieties, rec *models.RecordConfig, name string, fields any) error {
rec.F = fields

rec.ZonefilePartial = rec.GetTargetRFC1035Quoted()
rec.Comparable = rec.ZonefilePartial

handle.CopyToLegacyFields(rec)
return nil
}

// CopyToLegacyFields populates the legacy fields of the RecordConfig using the fields in .F.
func (handle *DS) CopyToLegacyFields(rec *models.RecordConfig) {
ds := rec.F.(*DS)
_ = rec.SetTargetDS(ds.KeyTag, ds.Algorithm, ds.DigestType, ds.Digest)
}
4 changes: 2 additions & 2 deletions pkg/rtype/rp.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ func (handle *RP) FromStruct(dcn *domaintags.DomainNameVarieties, rec *models.Re
rec.ZonefilePartial = rec.GetTargetRFC1035Quoted()
rec.Comparable = rec.ZonefilePartial

handle.CopyToLegacyFields(rec)
return nil
}

// CopyToLegacyFields populates the legacy fields of the RecordConfig using the fields in .F.
func (handle *RP) CopyToLegacyFields(rec *models.RecordConfig) {
rp := rec.F.(*RP)
_ = rec.SetTarget(rp.Mbox + " " + rp.Txt)
// RP, like all new RRs, does not have legacy fields. Even .target is deprecated.
}
91 changes: 79 additions & 12 deletions pkg/rtypecontrol/pave.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package rtypecontrol

import (
"fmt"
"math"
"strconv"
)

// PaveArgs converts each arg to its desired type, or returns an error if conversion fails or if the number of arguments is wrong.
// argTypes is a string where each rune specifies the desired type of the arg in the same position:
// 'i': uinet16 (will convert strings, truncate floats, etc)
// 's': Valid only if string.
// 'b': uint8 (will convert strings, truncate floats, etc)
// 'w': uint16 (will convert strings, truncate floats, etc)
// FUTURE 'd': uint32 (will convert strings, truncate floats, etc)
// FUTURE 'q': uint64 (will convert strings, truncate floats, etc)
// 's': string (will convert other types to string using %v)
func PaveArgs(args []any, argTypes string) error {
if len(args) != len(argTypes) {
return fmt.Errorf("wrong number of arguments. Expected %v, got %v", len(argTypes), len(args))
Expand All @@ -17,20 +21,80 @@ func PaveArgs(args []any, argTypes string) error {
for i, at := range argTypes {
arg := args[i]
switch at {
case 'i': // uint16
if s, ok := arg.(string); ok { // Is this a string-encoded int?
ni, err := strconv.Atoi(s)

case 'b': // uint8
switch v := arg.(type) {
case uint8:
// already correct type
case uint16:
if v > math.MaxUint8 {
return fmt.Errorf("value %q overflows uint8", arg)
}
args[i] = uint8(v)

case int16:
if v < 0 || v > math.MaxUint8 {
return fmt.Errorf("value %q overflows uint8", arg)
}
args[i] = uint8(v)
case uint:
if v > math.MaxUint8 {
return fmt.Errorf("value %q overflows uint8", arg)
}
args[i] = uint8(v)
case int:
if v < 0 || v > math.MaxUint8 {
return fmt.Errorf("value %q overflows uint8", arg)
}
args[i] = uint8(v)
case float64:
if v < 0 || v > math.MaxUint8 {
return fmt.Errorf("value %q overflows uint8", arg)
}
args[i] = uint8(v)
case string:
ni, err := strconv.ParseUint(arg.(string), 10, 8)
if err != nil {
return fmt.Errorf("value %q is not a number (uint8 wanted)", arg)
}
args[i] = uint8(ni)
default:
return fmt.Errorf("value %q is type %T, expected uint8", arg, arg)
}

case 'w': // uint16
switch v := arg.(type) {
case uint8:
args[i] = uint16(v)
case uint16:
// already correct type
case int16:
if v < 0 {
return fmt.Errorf("value %q overflows uint8", arg)
}
args[i] = uint16(v)
case uint:
if v > math.MaxUint16 {
return fmt.Errorf("value %q overflows uint8", arg)
}
args[i] = uint16(v)
case int:
if v < 0 || v > math.MaxUint16 {
return fmt.Errorf("value %q overflows uint8", arg)
}
args[i] = uint16(v)
case float64:
if v < 0 || v > math.MaxUint16 {
return fmt.Errorf("value %q overflows uint8", arg)
}
args[i] = uint16(v)
case string:
ni, err := strconv.ParseUint(arg.(string), 10, 16)
if err != nil {
return fmt.Errorf("value %q is not a number (uint16 wanted)", arg)
}
args[i] = uint16(ni)
} else if _, ok := arg.(float64); ok {
args[i] = uint16(arg.(float64))
} else if _, ok := arg.(uint16); ok {
args[i] = arg.(uint16)
} else if _, ok := arg.(int); ok {
args[i] = uint16(arg.(int))
} else {
default:
return fmt.Errorf("value %q is type %T, expected uint16", arg, arg)
}

Expand All @@ -40,6 +104,9 @@ func PaveArgs(args []any, argTypes string) error {
} else {
args[i] = fmt.Sprintf("%v", arg)
}

default:
return fmt.Errorf("unknown argType rune: %q", at)
}
}

Expand Down
Loading
Loading