Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: PTR Support #84

Merged
merged 8 commits into from
Jun 5, 2017
Merged
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
28 changes: 26 additions & 2 deletions pkg/js/js.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"io/ioutil"

"github.com/StackExchange/dnscontrol/models"
"github.com/StackExchange/dnscontrol/pkg/transform"

"github.com/robertkrimen/otto"
//load underscore js into vm by default

_ "github.com/robertkrimen/otto/underscore"
)

Expand All @@ -17,6 +19,7 @@ func ExecuteJavascript(script string, devMode bool) (*models.DNSConfig, error) {
vm := otto.New()

vm.Set("require", require)
vm.Set("REVERSE", reverse)

helperJs := GetHelpers(devMode)
// run helper script to prime vm and initialize variables
Expand Down Expand Up @@ -50,15 +53,36 @@ func GetHelpers(devMode bool) string {
}

func require(call otto.FunctionCall) otto.Value {
if len(call.ArgumentList) != 1 {
throw(call.Otto, "require takes exactly one argument")
}
file := call.Argument(0).String()
fmt.Printf("requiring: %s\n", file)
data, err := ioutil.ReadFile(file)
if err != nil {
panic(err)
throw(call.Otto, err.Error())
}
_, err = call.Otto.Run(string(data))
if err != nil {
panic(err)
throw(call.Otto, err.Error())
}
return otto.TrueValue()
}

func throw(vm *otto.Otto, str string) {
panic(vm.MakeCustomError("Error", str))
}

func reverse(call otto.FunctionCall) otto.Value {
if len(call.ArgumentList) != 1 {
throw(call.Otto, "REVERSE takes exactly one argument")
}
dom := call.Argument(0).String()
rev, err := transform.ReverseDomainName(dom)
fmt.Println(dom, rev, err)
if err != nil {
throw(call.Otto, err.Error())
}
v, _ := otto.ToValue(rev)
return v
}
1 change: 1 addition & 0 deletions pkg/js/js_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func TestErrors(t *testing.T) {
{"MX reversed", `D("foo.com","reg",MX("@","test.", 5))`},
{"CF_REDIRECT With comma", `D("foo.com","reg",CF_REDIRECT("foo.com,","baaa"))`},
{"CF_TEMP_REDIRECT With comma", `D("foo.com","reg",CF_TEMP_REDIRECT("foo.com","baa,a"))`},
{"Bad cidr", `D(reverse("foo.com"), "reg")`},
}
for _, tst := range tests {
t.Run(tst.desc, func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions pkg/js/parse_tests/009-reverse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
D(REVERSE("1.2.0.0/16"),"none");
13 changes: 13 additions & 0 deletions pkg/js/parse_tests/009-reverse.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"registrars": [],
"dns_providers": [],
"domains": [
{
"name": "2.1.in-addr.arpa",
"registrar": "none",
"dnsProviders": {},
"records": [],
"keepunknown": false
}
]
}
89 changes: 89 additions & 0 deletions pkg/transform/arpa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package transform

import (
"fmt"
"net"
"strings"
)

func ReverseDomainName(cidr string) (string, error) {
a, c, err := net.ParseCIDR(cidr)
if err != nil {
return "", err
}
base, err := reverseaddr(a.String())
if err != nil {
return "", err
}
base = strings.TrimRight(base, ".")
bits, total := c.Mask.Size()
var toTrim int
if bits == 0 {
return "", fmt.Errorf("Cannot use /0 in reverse cidr")
}
if total == 32 {
if bits%8 != 0 {
return "", fmt.Errorf("IPv4 mask must be multiple of 8 bits")
}
toTrim = (total - bits) / 8
} else if total == 128 {
if bits%4 != 0 {
return "", fmt.Errorf("IPv6 mask must be multiple of 4 bits")
}
toTrim = (total - bits) / 4
} else {
return "", fmt.Errorf("Invalid mask bit size: %d", total)
}

parts := strings.SplitN(base, ".", toTrim+1)
return parts[len(parts)-1], nil
}

// copied from go source.
// https://github.com/golang/go/blob/bfc164c64d33edfaf774b5c29b9bf5648a6447fb/src/net/dnsclient.go#L15

// reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
// address addr suitable for rDNS (PTR) record lookup 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 "", &net.DNSError{Err: "unrecognized address", Name: addr}
}
if ip.To4() != nil {
return uitoa(uint(ip[15])) + "." + uitoa(uint(ip[14])) + "." + uitoa(uint(ip[13])) + "." + uitoa(uint(ip[12])) + ".in-addr.arpa.", nil
}
// Must be IPv6
buf := make([]byte, 0, len(ip)*4+len("ip6.arpa."))
// Add it, in reverse, to the buffer
for i := len(ip) - 1; i >= 0; i-- {
v := ip[i]
buf = append(buf, hexDigit[v&0xF])
buf = append(buf, '.')
buf = append(buf, hexDigit[v>>4])
buf = append(buf, '.')
}
// Append "ip6.arpa." and return (buf already has the final .)
buf = append(buf, "ip6.arpa."...)
return string(buf), nil
}

// Convert unsigned integer to decimal string.
func uitoa(val uint) string {
if val == 0 { // avoid string allocation
return "0"
}
var buf [20]byte // big enough for 64bit value base 10
i := len(buf) - 1
for val >= 10 {
q := val / 10
buf[i] = byte('0' + val - q*10)
i--
val = q
}
// val < 10
buf[i] = byte('0' + val)
return string(buf[i:])
}

const hexDigit = "0123456789abcdef"
45 changes: 45 additions & 0 deletions pkg/transform/arpa_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package transform

import "testing"
import "fmt"

func TestReverse(t *testing.T) {
var tests = []struct {
in string
isError bool
out string
}{
{"174.136.107.0/24", false, "107.136.174.in-addr.arpa"},

{"174.136.0.0/16", false, "136.174.in-addr.arpa"},
{"174.136.43.0/16", false, "136.174.in-addr.arpa"}, //do bits set inside the masked range matter? Should this be invalid? Is there a shorter way to specify this?

{"174.0.0.0/8", false, "174.in-addr.arpa"},
{"174.136.43.0/8", false, "174.in-addr.arpa"},
{"174.136.43.0/8", false, "174.in-addr.arpa"},

{"2001::/16", false, "1.0.0.2.ip6.arpa"},
{"2001:0db8:0123:4567:89ab:cdef:1234:5670/124", false, "7.6.5.4.3.2.1.f.e.d.c.b.a.9.8.7.6.5.4.3.2.1.0.8.b.d.0.1.0.0.2.ip6.arpa"},

{"174.136.107.14/32", false, "14.107.136.174.in-addr.arpa"},
{"2001:0db8:0123:4567:89ab:cdef:1234:5678/128", false, "8.7.6.5.4.3.2.1.f.e.d.c.b.a.9.8.7.6.5.4.3.2.1.0.8.b.d.0.1.0.0.2.ip6.arpa"},

//Errror Cases:
{"0.0.0.0/0", true, ""},
{"2001::/0", true, ""},
{"4.5/16", true, ""},
{"foo.com", true, ""},
}
for i, tst := range tests {
t.Run(fmt.Sprintf("%d--%s", i, tst.in), func(t *testing.T) {
d, err := ReverseDomainName(tst.in)
if err != nil && !tst.isError {
t.Error("Should not have errored ", err)
} else if tst.isError && err == nil {
t.Errorf("Should have errored, but didn't. Got %s", d)
} else if d != tst.out {
t.Errorf("Expected '%s' but got '%s'", tst.out, d)
}
})
}
}