Merge branch 'pr-8299'
* pr-8299: Patch up website docs provider/dns: DNS dynamic updates (RFC 2136) vendor: Capture new dependency miekg-dns
This commit is contained in:
commit
be6ae20ac1
|
@ -0,0 +1,12 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/builtin/providers/dns"
|
||||||
|
"github.com/hashicorp/terraform/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
plugin.Serve(&plugin.ServeOpts{
|
||||||
|
ProviderFunc: dns.Provider,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package main
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
set -x
|
||||||
|
|
||||||
|
# Test domains
|
||||||
|
export DNS_DOMAIN_FORWARD="example.com."
|
||||||
|
export DNS_DOMAIN_REVERSE="1.168.192.in-addr.arpa."
|
||||||
|
|
||||||
|
# Run with no authentication
|
||||||
|
|
||||||
|
export DNS_UPDATE_SERVER=127.0.0.1
|
||||||
|
docker run -d -p 53:53/udp \
|
||||||
|
-e BIND_DOMAIN_FORWARD=${DNS_DOMAIN_FORWARD} \
|
||||||
|
-e BIND_DOMAIN_REVERSE=${DNS_DOMAIN_REVERSE} \
|
||||||
|
-e BIND_INSECURE=true \
|
||||||
|
--name bind_insecure drebes/bind
|
||||||
|
make testacc TEST=./builtin/providers/dns
|
||||||
|
docker stop bind_insecure
|
||||||
|
docker rm bind_insecure
|
||||||
|
|
||||||
|
# Run with authentication
|
||||||
|
|
||||||
|
export DNS_UPDATE_KEYNAME=${DNS_DOMAIN_FORWARD}
|
||||||
|
export DNS_UPDATE_KEYALGORITHM="hmac-md5"
|
||||||
|
export DNS_UPDATE_KEYSECRET="c3VwZXJzZWNyZXQ="
|
||||||
|
docker run -d -p 53:53/udp \
|
||||||
|
-e BIND_DOMAIN_FORWARD=${DNS_DOMAIN_FORWARD} \
|
||||||
|
-e BIND_DOMAIN_REVERSE=${DNS_DOMAIN_REVERSE} \
|
||||||
|
-e BIND_KEY_NAME=${DNS_UPDATE_KEYNAME} \
|
||||||
|
-e BIND_KEY_ALGORITHM=${DNS_UPDATE_KEYALGORITHM} \
|
||||||
|
-e BIND_KEY_SECRET=${DNS_UPDATE_KEYSECRET} \
|
||||||
|
--name bind_secure drebes/bind
|
||||||
|
make testacc TEST=./builtin/providers/dns
|
||||||
|
docker stop bind_secure
|
||||||
|
docker rm bind_secure
|
|
@ -0,0 +1,67 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
server string
|
||||||
|
port int
|
||||||
|
keyname string
|
||||||
|
keyalgo string
|
||||||
|
keysecret string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSClient struct {
|
||||||
|
c *dns.Client
|
||||||
|
srv_addr string
|
||||||
|
keyname string
|
||||||
|
keysecret string
|
||||||
|
keyalgo string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configures and returns a fully initialized DNSClient
|
||||||
|
func (c *Config) Client() (interface{}, error) {
|
||||||
|
log.Println("[INFO] Building DNSClient config structure")
|
||||||
|
|
||||||
|
var client DNSClient
|
||||||
|
client.srv_addr = fmt.Sprintf("%s:%d", c.server, c.port)
|
||||||
|
authCfgOk := false
|
||||||
|
if (c.keyname == "" && c.keysecret == "" && c.keyalgo == "") ||
|
||||||
|
(c.keyname != "" && c.keysecret != "" && c.keyalgo != "") {
|
||||||
|
authCfgOk = true
|
||||||
|
}
|
||||||
|
if !authCfgOk {
|
||||||
|
return nil, fmt.Errorf("Error configuring provider: when using authentication, \"key_name\", \"key_secret\" and \"key_algorithm\" should be non empty")
|
||||||
|
}
|
||||||
|
client.c = new(dns.Client)
|
||||||
|
if c.keyname != "" {
|
||||||
|
client.keyname = c.keyname
|
||||||
|
client.keysecret = c.keysecret
|
||||||
|
keyalgo, err := convertHMACAlgorithm(c.keyalgo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error configuring provider: %s", err)
|
||||||
|
}
|
||||||
|
client.keyalgo = keyalgo
|
||||||
|
client.c.TsigSecret = map[string]string{c.keyname: c.keysecret}
|
||||||
|
}
|
||||||
|
return &client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates and converts HMAC algorithm
|
||||||
|
func convertHMACAlgorithm(name string) (string, error) {
|
||||||
|
switch name {
|
||||||
|
case "hmac-md5":
|
||||||
|
return dns.HmacMD5, nil
|
||||||
|
case "hmac-sha1":
|
||||||
|
return dns.HmacSHA1, nil
|
||||||
|
case "hmac-sha256":
|
||||||
|
return dns.HmacSHA256, nil
|
||||||
|
case "hmac-sha512":
|
||||||
|
return dns.HmacSHA512, nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("Unknown HMAC algorithm: %s", name)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider returns a schema.Provider for DNS dynamic updates.
|
||||||
|
func Provider() terraform.ResourceProvider {
|
||||||
|
return &schema.Provider{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"update": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
MaxItems: 1,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"server": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("DNS_UPDATE_SERVER", nil),
|
||||||
|
},
|
||||||
|
"port": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Default: 53,
|
||||||
|
},
|
||||||
|
"key_name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("DNS_UPDATE_KEYNAME", nil),
|
||||||
|
},
|
||||||
|
"key_algorithm": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("DNS_UPDATE_KEYALGORITHM", nil),
|
||||||
|
},
|
||||||
|
"key_secret": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("DNS_UPDATE_KEYSECRET", nil),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
|
"dns_a_record_set": resourceDnsARecordSet(),
|
||||||
|
"dns_aaaa_record_set": resourceDnsAAAARecordSet(),
|
||||||
|
"dns_cname_record": resourceDnsCnameRecord(),
|
||||||
|
"dns_ptr_record": resourceDnsPtrRecord(),
|
||||||
|
},
|
||||||
|
|
||||||
|
ConfigureFunc: configureProvider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureProvider(d *schema.ResourceData) (interface{}, error) {
|
||||||
|
|
||||||
|
var server, keyname, keyalgo, keysecret string
|
||||||
|
var port int
|
||||||
|
|
||||||
|
// if the update block is missing, schema.EnvDefaultFunc is not called
|
||||||
|
if v, ok := d.GetOk("update"); ok {
|
||||||
|
update := v.([]interface{})[0].(map[string]interface{})
|
||||||
|
if val, ok := update["port"]; ok {
|
||||||
|
port = int(val.(int))
|
||||||
|
}
|
||||||
|
if val, ok := update["server"]; ok {
|
||||||
|
server = val.(string)
|
||||||
|
}
|
||||||
|
if val, ok := update["key_name"]; ok {
|
||||||
|
keyname = val.(string)
|
||||||
|
}
|
||||||
|
if val, ok := update["key_algorithm"]; ok {
|
||||||
|
keyalgo = val.(string)
|
||||||
|
}
|
||||||
|
if val, ok := update["key_secret"]; ok {
|
||||||
|
keysecret = val.(string)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(os.Getenv("DNS_UPDATE_SERVER")) > 0 {
|
||||||
|
server = os.Getenv("DNS_UPDATE_SERVER")
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
port = 53
|
||||||
|
if len(os.Getenv("DNS_UPDATE_KEYNAME")) > 0 {
|
||||||
|
keyname = os.Getenv("DNS_UPDATE_KEYNAME")
|
||||||
|
}
|
||||||
|
if len(os.Getenv("DNS_UPDATE_KEYALGORITHM")) > 0 {
|
||||||
|
keyalgo = os.Getenv("DNS_UPDATE_KEYALGORITHM")
|
||||||
|
}
|
||||||
|
if len(os.Getenv("DNS_UPDATE_KEYSECRET")) > 0 {
|
||||||
|
keysecret = os.Getenv("DNS_UPDATE_KEYSECRET")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config := Config{
|
||||||
|
server: server,
|
||||||
|
port: port,
|
||||||
|
keyname: keyname,
|
||||||
|
keyalgo: keyalgo,
|
||||||
|
keysecret: keysecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.Client()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAVal(record interface{}) (string, error) {
|
||||||
|
|
||||||
|
recstr := record.(*dns.A).String()
|
||||||
|
var name, ttl, class, typ, addr string
|
||||||
|
|
||||||
|
_, err := fmt.Sscanf(recstr, "%s\t%s\t%s\t%s\t%s", &name, &ttl, &class, &typ, &addr)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error parsing record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAAAAVal(record interface{}) (string, error) {
|
||||||
|
|
||||||
|
recstr := record.(*dns.AAAA).String()
|
||||||
|
var name, ttl, class, typ, addr string
|
||||||
|
|
||||||
|
_, err := fmt.Sscanf(recstr, "%s\t%s\t%s\t%s\t%s", &name, &ttl, &class, &typ, &addr)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error parsing record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCnameVal(record interface{}) (string, error) {
|
||||||
|
|
||||||
|
recstr := record.(*dns.CNAME).String()
|
||||||
|
var name, ttl, class, typ, cname string
|
||||||
|
|
||||||
|
_, err := fmt.Sscanf(recstr, "%s\t%s\t%s\t%s\t%s", &name, &ttl, &class, &typ, &cname)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error parsing record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cname, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPtrVal(record interface{}) (string, error) {
|
||||||
|
|
||||||
|
recstr := record.(*dns.PTR).String()
|
||||||
|
var name, ttl, class, typ, ptr string
|
||||||
|
|
||||||
|
_, err := fmt.Sscanf(recstr, "%s\t%s\t%s\t%s\t%s", &name, &ttl, &class, &typ, &ptr)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error parsing record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr, nil
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testAccProviders map[string]terraform.ResourceProvider
|
||||||
|
var testAccProvider *schema.Provider
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
testAccProvider = Provider().(*schema.Provider)
|
||||||
|
testAccProviders = map[string]terraform.ResourceProvider{
|
||||||
|
"dns": testAccProvider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider(t *testing.T) {
|
||||||
|
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider_impl(t *testing.T) {
|
||||||
|
var _ terraform.ResourceProvider = Provider()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccPreCheck(t *testing.T) {
|
||||||
|
v := os.Getenv("DNS_UPDATE_SERVER")
|
||||||
|
if v == "" {
|
||||||
|
t.Fatal("DNS_UPDATE_SERVER must be set for acceptance tests")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceDnsARecordSet() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceDnsARecordSetCreate,
|
||||||
|
Read: resourceDnsARecordSetRead,
|
||||||
|
Update: resourceDnsARecordSetUpdate,
|
||||||
|
Delete: resourceDnsARecordSetDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"zone": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"addresses": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Required: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
Set: schema.HashString,
|
||||||
|
},
|
||||||
|
"ttl": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Default: 3600,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsARecordSetCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error creating DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
d.SetId(rec_fqdn)
|
||||||
|
|
||||||
|
return resourceDnsARecordSetUpdate(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsARecordSetRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
if meta != nil {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetQuestion(rec_fqdn, dns.TypeA)
|
||||||
|
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses := schema.NewSet(schema.HashString, nil)
|
||||||
|
for _, record := range r.Answer {
|
||||||
|
addr, err := getAVal(record)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
addresses.Add(addr)
|
||||||
|
}
|
||||||
|
if !addresses.Equal(d.Get("addresses")) {
|
||||||
|
d.SetId("")
|
||||||
|
return fmt.Errorf("DNS record differs")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("update server is not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsARecordSetUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
if meta != nil {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
ttl := d.Get("ttl").(int)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
keyname := meta.(*DNSClient).keyname
|
||||||
|
keyalgo := meta.(*DNSClient).keyalgo
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
|
||||||
|
msg.SetUpdate(rec_zone)
|
||||||
|
|
||||||
|
if d.HasChange("addresses") {
|
||||||
|
o, n := d.GetChange("addresses")
|
||||||
|
os := o.(*schema.Set)
|
||||||
|
ns := n.(*schema.Set)
|
||||||
|
remove := os.Difference(ns).List()
|
||||||
|
add := ns.Difference(os).List()
|
||||||
|
|
||||||
|
// Loop through all the old addresses and remove them
|
||||||
|
for _, addr := range remove {
|
||||||
|
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s %d A %s", rec_fqdn, ttl, addr.(string)))
|
||||||
|
msg.Remove([]dns.RR{rr_remove})
|
||||||
|
}
|
||||||
|
// Loop through all the new addresses and insert them
|
||||||
|
for _, addr := range add {
|
||||||
|
rr_insert, _ := dns.NewRR(fmt.Sprintf("%s %d A %s", rec_fqdn, ttl, addr.(string)))
|
||||||
|
msg.Insert([]dns.RR{rr_insert})
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyname != "" {
|
||||||
|
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
d.SetId("")
|
||||||
|
return fmt.Errorf("Error updating DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
d.SetId("")
|
||||||
|
return fmt.Errorf("Error updating DNS record: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses := ns
|
||||||
|
d.Set("addresses", addresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceDnsARecordSetRead(d, meta)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("update server is not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsARecordSetDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
if meta != nil {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
keyname := meta.(*DNSClient).keyname
|
||||||
|
keyalgo := meta.(*DNSClient).keyalgo
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
|
||||||
|
msg.SetUpdate(rec_zone)
|
||||||
|
|
||||||
|
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s 0 A", rec_fqdn))
|
||||||
|
msg.RemoveRRset([]dns.RR{rr_remove})
|
||||||
|
|
||||||
|
if keyname != "" {
|
||||||
|
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
return fmt.Errorf("Error deleting DNS record: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("update server is not set")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccDnsARecordSet_basic(t *testing.T) {
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckDnsARecordSetDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDnsARecordSet_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("dns_a_record_set.foo", "addresses.#", "2"),
|
||||||
|
testAccCheckDnsARecordSetExists(t, "dns_a_record_set.foo", []interface{}{"192.168.0.2", "192.168.0.1"}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDnsARecordSet_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("dns_a_record_set.foo", "addresses.#", "3"),
|
||||||
|
testAccCheckDnsARecordSetExists(t, "dns_a_record_set.foo", []interface{}{"10.0.0.3", "10.0.0.2", "10.0.0.1"}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDnsARecordSetDestroy(s *terraform.State) error {
|
||||||
|
meta := testAccProvider.Meta()
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "dns_a_record_set" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_name := rs.Primary.Attributes["name"]
|
||||||
|
rec_zone := rs.Primary.Attributes["zone"]
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetQuestion(rec_fqdn, dns.TypeA)
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeNameError {
|
||||||
|
return fmt.Errorf("DNS record still exists: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDnsARecordSetExists(t *testing.T, n string, addr []interface{}) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_name := rs.Primary.Attributes["name"]
|
||||||
|
rec_zone := rs.Primary.Attributes["zone"]
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
meta := testAccProvider.Meta()
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetQuestion(rec_fqdn, dns.TypeA)
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
return fmt.Errorf("Error querying DNS record")
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses := schema.NewSet(schema.HashString, nil)
|
||||||
|
expected := schema.NewSet(schema.HashString, addr)
|
||||||
|
for _, record := range r.Answer {
|
||||||
|
addr, err := getAVal(record)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
addresses.Add(addr)
|
||||||
|
}
|
||||||
|
if !addresses.Equal(expected) {
|
||||||
|
return fmt.Errorf("DNS record differs: expected %v, found %v", expected, addresses)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccDnsARecordSet_basic = fmt.Sprintf(`
|
||||||
|
resource "dns_a_record_set" "foo" {
|
||||||
|
zone = "example.com."
|
||||||
|
name = "foo"
|
||||||
|
addresses = ["192.168.0.1", "192.168.0.2"]
|
||||||
|
ttl = 300
|
||||||
|
}`)
|
||||||
|
|
||||||
|
var testAccDnsARecordSet_update = fmt.Sprintf(`
|
||||||
|
resource "dns_a_record_set" "foo" {
|
||||||
|
zone = "example.com."
|
||||||
|
name = "foo"
|
||||||
|
addresses = ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
|
||||||
|
ttl = 300
|
||||||
|
}`)
|
|
@ -0,0 +1,212 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceDnsAAAARecordSet() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceDnsAAAARecordSetCreate,
|
||||||
|
Read: resourceDnsAAAARecordSetRead,
|
||||||
|
Update: resourceDnsAAAARecordSetUpdate,
|
||||||
|
Delete: resourceDnsAAAARecordSetDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"zone": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"addresses": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Required: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
Set: schema.HashString,
|
||||||
|
},
|
||||||
|
"ttl": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Default: 3600,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsAAAARecordSetCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error creating DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
d.SetId(rec_fqdn)
|
||||||
|
|
||||||
|
return resourceDnsAAAARecordSetUpdate(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsAAAARecordSetRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
if meta != nil {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetQuestion(rec_fqdn, dns.TypeAAAA)
|
||||||
|
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses := schema.NewSet(schema.HashString, nil)
|
||||||
|
for _, record := range r.Answer {
|
||||||
|
addr, err := getAAAAVal(record)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
addresses.Add(addr)
|
||||||
|
}
|
||||||
|
if !addresses.Equal(d.Get("addresses")) {
|
||||||
|
d.SetId("")
|
||||||
|
return fmt.Errorf("DNS record differs")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("update server is not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsAAAARecordSetUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
if meta != nil {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
ttl := d.Get("ttl").(int)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
keyname := meta.(*DNSClient).keyname
|
||||||
|
keyalgo := meta.(*DNSClient).keyalgo
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
|
||||||
|
msg.SetUpdate(rec_zone)
|
||||||
|
|
||||||
|
if d.HasChange("addresses") {
|
||||||
|
o, n := d.GetChange("addresses")
|
||||||
|
os := o.(*schema.Set)
|
||||||
|
ns := n.(*schema.Set)
|
||||||
|
remove := os.Difference(ns).List()
|
||||||
|
add := ns.Difference(os).List()
|
||||||
|
|
||||||
|
// Loop through all the old addresses and remove them
|
||||||
|
for _, addr := range remove {
|
||||||
|
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s %d AAAA %s", rec_fqdn, ttl, addr.(string)))
|
||||||
|
msg.Remove([]dns.RR{rr_remove})
|
||||||
|
}
|
||||||
|
// Loop through all the new addresses and insert them
|
||||||
|
for _, addr := range add {
|
||||||
|
rr_insert, _ := dns.NewRR(fmt.Sprintf("%s %d AAAA %s", rec_fqdn, ttl, addr.(string)))
|
||||||
|
msg.Insert([]dns.RR{rr_insert})
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyname != "" {
|
||||||
|
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
d.SetId("")
|
||||||
|
return fmt.Errorf("Error updating DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
d.SetId("")
|
||||||
|
return fmt.Errorf("Error updating DNS record: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses := ns
|
||||||
|
d.Set("addresses", addresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceDnsAAAARecordSetRead(d, meta)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("update server is not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsAAAARecordSetDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
if meta != nil {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
keyname := meta.(*DNSClient).keyname
|
||||||
|
keyalgo := meta.(*DNSClient).keyalgo
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
|
||||||
|
msg.SetUpdate(rec_zone)
|
||||||
|
|
||||||
|
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s 0 AAAA", rec_fqdn))
|
||||||
|
msg.RemoveRRset([]dns.RR{rr_remove})
|
||||||
|
|
||||||
|
if keyname != "" {
|
||||||
|
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
return fmt.Errorf("Error deleting DNS record: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("update server is not set")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccDnsAAAARecordSet_basic(t *testing.T) {
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckDnsAAAARecordSetDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDnsAAAARecordSet_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("dns_aaaa_record_set.bar", "addresses.#", "2"),
|
||||||
|
testAccCheckDnsAAAARecordSetExists(t, "dns_aaaa_record_set.bar", []interface{}{"fdd5:e282:43b8:5303:dead:beef:cafe:babe", "fdd5:e282:43b8:5303:cafe:babe:dead:beef"}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDnsAAAARecordSet_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("dns_aaaa_record_set.bar", "addresses.#", "2"),
|
||||||
|
testAccCheckDnsAAAARecordSetExists(t, "dns_aaaa_record_set.bar", []interface{}{"fdd5:e282:43b8:5303:beef:dead:babe:cafe", "fdd5:e282:43b8:5303:babe:cafe:beef:dead"}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDnsAAAARecordSetDestroy(s *terraform.State) error {
|
||||||
|
meta := testAccProvider.Meta()
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "dns_aaaa_record_set" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_name := rs.Primary.Attributes["name"]
|
||||||
|
rec_zone := rs.Primary.Attributes["zone"]
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetQuestion(rec_fqdn, dns.TypeAAAA)
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeNameError {
|
||||||
|
return fmt.Errorf("DNS record still exists: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDnsAAAARecordSetExists(t *testing.T, n string, addr []interface{}) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_name := rs.Primary.Attributes["name"]
|
||||||
|
rec_zone := rs.Primary.Attributes["zone"]
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
meta := testAccProvider.Meta()
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetQuestion(rec_fqdn, dns.TypeAAAA)
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
return fmt.Errorf("Error querying DNS record")
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses := schema.NewSet(schema.HashString, nil)
|
||||||
|
expected := schema.NewSet(schema.HashString, addr)
|
||||||
|
for _, record := range r.Answer {
|
||||||
|
addr, err := getAAAAVal(record)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
addresses.Add(addr)
|
||||||
|
}
|
||||||
|
if !addresses.Equal(expected) {
|
||||||
|
return fmt.Errorf("DNS record differs: expected %v, found %v", expected, addresses)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccDnsAAAARecordSet_basic = fmt.Sprintf(`
|
||||||
|
resource "dns_aaaa_record_set" "bar" {
|
||||||
|
zone = "example.com."
|
||||||
|
name = "bar"
|
||||||
|
addresses = ["fdd5:e282:43b8:5303:dead:beef:cafe:babe", "fdd5:e282:43b8:5303:cafe:babe:dead:beef"]
|
||||||
|
ttl = 300
|
||||||
|
}`)
|
||||||
|
|
||||||
|
var testAccDnsAAAARecordSet_update = fmt.Sprintf(`
|
||||||
|
resource "dns_aaaa_record_set" "bar" {
|
||||||
|
zone = "example.com."
|
||||||
|
name = "bar"
|
||||||
|
addresses = ["fdd5:e282:43b8:5303:beef:dead:babe:cafe", "fdd5:e282:43b8:5303:babe:cafe:beef:dead"]
|
||||||
|
ttl = 300
|
||||||
|
}`)
|
|
@ -0,0 +1,219 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceDnsCnameRecord() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceDnsCnameRecordCreate,
|
||||||
|
Read: resourceDnsCnameRecordRead,
|
||||||
|
Update: resourceDnsCnameRecordUpdate,
|
||||||
|
Delete: resourceDnsCnameRecordDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"zone": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"cname": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"ttl": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Default: 3600,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsCnameRecordCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
rec_cname := d.Get("cname").(string)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error creating DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rec_cname != dns.Fqdn(rec_cname) {
|
||||||
|
return fmt.Errorf("Error creating DNS record: \"cname\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
d.SetId(rec_fqdn)
|
||||||
|
|
||||||
|
return resourceDnsCnameRecordUpdate(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsCnameRecordRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
if meta != nil {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
rec_cname := d.Get("cname").(string)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rec_cname != dns.Fqdn(rec_cname) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"cname\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetQuestion(rec_fqdn, dns.TypeCNAME)
|
||||||
|
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Answer) > 1 {
|
||||||
|
return fmt.Errorf("Error querying DNS record: multiple responses received")
|
||||||
|
}
|
||||||
|
record := r.Answer[0]
|
||||||
|
cname, err := getCnameVal(record)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if rec_cname != cname {
|
||||||
|
d.SetId("")
|
||||||
|
return fmt.Errorf("DNS record differs")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("update server is not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsCnameRecordUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
if meta != nil {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
rec_cname := d.Get("cname").(string)
|
||||||
|
ttl := d.Get("ttl").(int)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rec_cname != dns.Fqdn(rec_cname) {
|
||||||
|
return fmt.Errorf("Error updating DNS record: \"cname\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
keyname := meta.(*DNSClient).keyname
|
||||||
|
keyalgo := meta.(*DNSClient).keyalgo
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
|
||||||
|
msg.SetUpdate(rec_zone)
|
||||||
|
|
||||||
|
if d.HasChange("cname") {
|
||||||
|
o, n := d.GetChange("cname")
|
||||||
|
|
||||||
|
if o != "" {
|
||||||
|
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s %d CNAME %s", rec_fqdn, ttl, o))
|
||||||
|
msg.Remove([]dns.RR{rr_remove})
|
||||||
|
}
|
||||||
|
if n != "" {
|
||||||
|
rr_insert, _ := dns.NewRR(fmt.Sprintf("%s %d CNAME %s", rec_fqdn, ttl, n))
|
||||||
|
msg.Insert([]dns.RR{rr_insert})
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyname != "" {
|
||||||
|
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
d.SetId("")
|
||||||
|
return fmt.Errorf("Error updating DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
d.SetId("")
|
||||||
|
return fmt.Errorf("Error updating DNS record: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
cname := n
|
||||||
|
d.Set("cname", cname)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceDnsCnameRecordRead(d, meta)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("update server is not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsCnameRecordDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
if meta != nil {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
keyname := meta.(*DNSClient).keyname
|
||||||
|
keyalgo := meta.(*DNSClient).keyalgo
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
|
||||||
|
msg.SetUpdate(rec_zone)
|
||||||
|
|
||||||
|
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s 0 CNAME", rec_fqdn))
|
||||||
|
msg.RemoveRRset([]dns.RR{rr_remove})
|
||||||
|
|
||||||
|
if keyname != "" {
|
||||||
|
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
return fmt.Errorf("Error deleting DNS record: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("update server is not set")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccDnsCnameRecord_basic(t *testing.T) {
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckDnsCnameRecordDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDnsCnameRecord_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDnsCnameRecordExists(t, "dns_cname_record.foo", "bar.example.com."),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDnsCnameRecord_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDnsCnameRecordExists(t, "dns_cname_record.foo", "baz.example.com."),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDnsCnameRecordDestroy(s *terraform.State) error {
|
||||||
|
meta := testAccProvider.Meta()
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "dns_cname_record" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_name := rs.Primary.Attributes["name"]
|
||||||
|
rec_zone := rs.Primary.Attributes["zone"]
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetQuestion(rec_fqdn, dns.TypeCNAME)
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeNameError {
|
||||||
|
return fmt.Errorf("DNS record still exists: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDnsCnameRecordExists(t *testing.T, n string, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_name := rs.Primary.Attributes["name"]
|
||||||
|
rec_zone := rs.Primary.Attributes["zone"]
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
meta := testAccProvider.Meta()
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetQuestion(rec_fqdn, dns.TypeCNAME)
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
return fmt.Errorf("Error querying DNS record")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Answer) > 1 {
|
||||||
|
return fmt.Errorf("Error querying DNS record: multiple responses received")
|
||||||
|
}
|
||||||
|
record := r.Answer[0]
|
||||||
|
cname, err := getCnameVal(record)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if expected != cname {
|
||||||
|
return fmt.Errorf("DNS record differs: expected %v, found %v", expected, cname)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccDnsCnameRecord_basic = fmt.Sprintf(`
|
||||||
|
resource "dns_cname_record" "foo" {
|
||||||
|
zone = "example.com."
|
||||||
|
name = "foo"
|
||||||
|
cname = "bar.example.com."
|
||||||
|
ttl = 300
|
||||||
|
}`)
|
||||||
|
|
||||||
|
var testAccDnsCnameRecord_update = fmt.Sprintf(`
|
||||||
|
resource "dns_cname_record" "foo" {
|
||||||
|
zone = "example.com."
|
||||||
|
name = "baz"
|
||||||
|
cname = "baz.example.com."
|
||||||
|
ttl = 300
|
||||||
|
}`)
|
|
@ -0,0 +1,219 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceDnsPtrRecord() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceDnsPtrRecordCreate,
|
||||||
|
Read: resourceDnsPtrRecordRead,
|
||||||
|
Update: resourceDnsPtrRecordUpdate,
|
||||||
|
Delete: resourceDnsPtrRecordDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"zone": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"ptr": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"ttl": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Default: 3600,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsPtrRecordCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
rec_ptr := d.Get("ptr").(string)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error creating DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rec_ptr != dns.Fqdn(rec_ptr) {
|
||||||
|
return fmt.Errorf("Error creating DNS record: \"ptr\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
d.SetId(rec_fqdn)
|
||||||
|
|
||||||
|
return resourceDnsPtrRecordUpdate(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsPtrRecordRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
if meta != nil {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
rec_ptr := d.Get("ptr").(string)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rec_ptr != dns.Fqdn(rec_ptr) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"ptr\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetQuestion(rec_fqdn, dns.TypePTR)
|
||||||
|
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Answer) > 1 {
|
||||||
|
return fmt.Errorf("Error querying DNS record: multiple responses received")
|
||||||
|
}
|
||||||
|
record := r.Answer[0]
|
||||||
|
ptr, err := getPtrVal(record)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if rec_ptr != ptr {
|
||||||
|
d.SetId("")
|
||||||
|
return fmt.Errorf("DNS record differs")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("update server is not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsPtrRecordUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
if meta != nil {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
rec_ptr := d.Get("ptr").(string)
|
||||||
|
ttl := d.Get("ttl").(int)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rec_ptr != dns.Fqdn(rec_ptr) {
|
||||||
|
return fmt.Errorf("Error updating DNS record: \"ptr\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
keyname := meta.(*DNSClient).keyname
|
||||||
|
keyalgo := meta.(*DNSClient).keyalgo
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
|
||||||
|
msg.SetUpdate(rec_zone)
|
||||||
|
|
||||||
|
if d.HasChange("ptr") {
|
||||||
|
o, n := d.GetChange("ptr")
|
||||||
|
|
||||||
|
if o != "" {
|
||||||
|
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s %d PTR %s", rec_fqdn, ttl, o))
|
||||||
|
msg.Remove([]dns.RR{rr_remove})
|
||||||
|
}
|
||||||
|
if n != "" {
|
||||||
|
rr_insert, _ := dns.NewRR(fmt.Sprintf("%s %d PTR %s", rec_fqdn, ttl, n))
|
||||||
|
msg.Insert([]dns.RR{rr_insert})
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyname != "" {
|
||||||
|
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
d.SetId("")
|
||||||
|
return fmt.Errorf("Error updating DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
d.SetId("")
|
||||||
|
return fmt.Errorf("Error updating DNS record: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := n
|
||||||
|
d.Set("ptr", ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceDnsPtrRecordRead(d, meta)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("update server is not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDnsPtrRecordDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
if meta != nil {
|
||||||
|
|
||||||
|
rec_name := d.Get("name").(string)
|
||||||
|
rec_zone := d.Get("zone").(string)
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
keyname := meta.(*DNSClient).keyname
|
||||||
|
keyalgo := meta.(*DNSClient).keyalgo
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
|
||||||
|
msg.SetUpdate(rec_zone)
|
||||||
|
|
||||||
|
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s 0 PTR", rec_fqdn))
|
||||||
|
msg.RemoveRRset([]dns.RR{rr_remove})
|
||||||
|
|
||||||
|
if keyname != "" {
|
||||||
|
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
return fmt.Errorf("Error deleting DNS record: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("update server is not set")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccDnsPtrRecord_basic(t *testing.T) {
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckDnsPtrRecordDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDnsPtrRecord_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDnsPtrRecordExists(t, "dns_ptr_record.foo", "bar.example.com."),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDnsPtrRecord_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDnsPtrRecordExists(t, "dns_ptr_record.foo", "baz.example.com."),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDnsPtrRecordDestroy(s *terraform.State) error {
|
||||||
|
meta := testAccProvider.Meta()
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "dns_ptr_record" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_name := rs.Primary.Attributes["name"]
|
||||||
|
rec_zone := rs.Primary.Attributes["zone"]
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetQuestion(rec_fqdn, dns.TypePTR)
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeNameError {
|
||||||
|
return fmt.Errorf("DNS record still exists: %v", r.Rcode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDnsPtrRecordExists(t *testing.T, n string, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_name := rs.Primary.Attributes["name"]
|
||||||
|
rec_zone := rs.Primary.Attributes["zone"]
|
||||||
|
|
||||||
|
if rec_zone != dns.Fqdn(rec_zone) {
|
||||||
|
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
|
||||||
|
|
||||||
|
meta := testAccProvider.Meta()
|
||||||
|
c := meta.(*DNSClient).c
|
||||||
|
srv_addr := meta.(*DNSClient).srv_addr
|
||||||
|
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetQuestion(rec_fqdn, dns.TypePTR)
|
||||||
|
r, _, err := c.Exchange(msg, srv_addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
return fmt.Errorf("Error querying DNS record")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Answer) > 1 {
|
||||||
|
return fmt.Errorf("Error querying DNS record: multiple responses received")
|
||||||
|
}
|
||||||
|
record := r.Answer[0]
|
||||||
|
ptr, err := getPtrVal(record)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error querying DNS record: %s", err)
|
||||||
|
}
|
||||||
|
if expected != ptr {
|
||||||
|
return fmt.Errorf("DNS record differs: expected %v, found %v", expected, ptr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccDnsPtrRecord_basic = fmt.Sprintf(`
|
||||||
|
resource "dns_ptr_record" "foo" {
|
||||||
|
zone = "example.com."
|
||||||
|
name = "r._dns-sd._udp"
|
||||||
|
ptr = "bar.example.com."
|
||||||
|
ttl = 300
|
||||||
|
}`)
|
||||||
|
|
||||||
|
var testAccDnsPtrRecord_update = fmt.Sprintf(`
|
||||||
|
resource "dns_ptr_record" "foo" {
|
||||||
|
zone = "example.com."
|
||||||
|
name = "r._dns-sd._udp"
|
||||||
|
ptr = "baz.example.com."
|
||||||
|
ttl = 300
|
||||||
|
}`)
|
|
@ -23,6 +23,7 @@ import (
|
||||||
datadogprovider "github.com/hashicorp/terraform/builtin/providers/datadog"
|
datadogprovider "github.com/hashicorp/terraform/builtin/providers/datadog"
|
||||||
digitaloceanprovider "github.com/hashicorp/terraform/builtin/providers/digitalocean"
|
digitaloceanprovider "github.com/hashicorp/terraform/builtin/providers/digitalocean"
|
||||||
dmeprovider "github.com/hashicorp/terraform/builtin/providers/dme"
|
dmeprovider "github.com/hashicorp/terraform/builtin/providers/dme"
|
||||||
|
dnsprovider "github.com/hashicorp/terraform/builtin/providers/dns"
|
||||||
dnsimpleprovider "github.com/hashicorp/terraform/builtin/providers/dnsimple"
|
dnsimpleprovider "github.com/hashicorp/terraform/builtin/providers/dnsimple"
|
||||||
dockerprovider "github.com/hashicorp/terraform/builtin/providers/docker"
|
dockerprovider "github.com/hashicorp/terraform/builtin/providers/docker"
|
||||||
dynprovider "github.com/hashicorp/terraform/builtin/providers/dyn"
|
dynprovider "github.com/hashicorp/terraform/builtin/providers/dyn"
|
||||||
|
@ -95,6 +96,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
|
||||||
"datadog": datadogprovider.Provider,
|
"datadog": datadogprovider.Provider,
|
||||||
"digitalocean": digitaloceanprovider.Provider,
|
"digitalocean": digitaloceanprovider.Provider,
|
||||||
"dme": dmeprovider.Provider,
|
"dme": dmeprovider.Provider,
|
||||||
|
"dns": dnsprovider.Provider,
|
||||||
"dnsimple": dnsimpleprovider.Provider,
|
"dnsimple": dnsimpleprovider.Provider,
|
||||||
"docker": dockerprovider.Provider,
|
"docker": dockerprovider.Provider,
|
||||||
"dyn": dynprovider.Provider,
|
"dyn": dynprovider.Provider,
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Miek Gieben <miek@miek.nl>
|
|
@ -0,0 +1,9 @@
|
||||||
|
Alex A. Skinner
|
||||||
|
Andrew Tunnell-Jones
|
||||||
|
Ask Bjørn Hansen
|
||||||
|
Dave Cheney
|
||||||
|
Dusty Wilson
|
||||||
|
Marek Majkowski
|
||||||
|
Peter van Dijk
|
||||||
|
Omri Bahumi
|
||||||
|
Alex Sergeyev
|
|
@ -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,32 @@
|
||||||
|
Extensions of the original work are copyright (c) 2011 Miek Gieben
|
||||||
|
|
||||||
|
As this is fork of the official Go code the same license applies:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
[![Build Status](https://travis-ci.org/miekg/dns.svg?branch=master)](https://travis-ci.org/miekg/dns)
|
||||||
|
|
||||||
|
# Alternative (more granular) approach to a DNS library
|
||||||
|
|
||||||
|
> Less is more.
|
||||||
|
|
||||||
|
Complete and usable DNS library. All widely used Resource Records are
|
||||||
|
supported, including the DNSSEC types. It follows a lean and mean philosophy.
|
||||||
|
If there is stuff you should know as a DNS programmer there isn't a convenience
|
||||||
|
function for it. Server side and client side programming is supported, i.e. you
|
||||||
|
can build servers and resolvers with it.
|
||||||
|
|
||||||
|
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, currently: 1.5 and 1.6.
|
||||||
|
|
||||||
|
# Goals
|
||||||
|
|
||||||
|
* KISS;
|
||||||
|
* Fast;
|
||||||
|
* Small API, if its 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://cloudflare.com
|
||||||
|
* https://github.com/abh/geodns
|
||||||
|
* 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://pulse.turbobytes.com/
|
||||||
|
* https://play.google.com/store/apps/details?id=com.turbobytes.dig
|
||||||
|
* https://github.com/fcambus/statzone
|
||||||
|
* https://github.com/benschw/dns-clb-go
|
||||||
|
* https://github.com/corny/dnscheck for http://public-dns.info/
|
||||||
|
* https://namesmith.io
|
||||||
|
* https://github.com/miekg/unbound
|
||||||
|
* https://github.com/miekg/exdns
|
||||||
|
* https://dnslookup.org
|
||||||
|
* https://github.com/looterz/grimd
|
||||||
|
* https://github.com/phamhongviet/serf-dns
|
||||||
|
|
||||||
|
Send pull request if you want to be listed here.
|
||||||
|
|
||||||
|
# Features
|
||||||
|
|
||||||
|
* UDP/TCP queries, IPv4 and IPv6;
|
||||||
|
* RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE (for all record types) are supported;
|
||||||
|
* Fast:
|
||||||
|
* Reply speed around ~ 80K qps (faster hardware results in more qps);
|
||||||
|
* Parsing RRs ~ 100K RR/s, that's 5M records in about 50 seconds;
|
||||||
|
* Server side programming (mimicking the net/http package);
|
||||||
|
* Client side programming;
|
||||||
|
* DNSSEC: signing, validating and key generation for DSA, RSA and ECDSA;
|
||||||
|
* EDNS0, NSID, Cookies;
|
||||||
|
* AXFR/IXFR;
|
||||||
|
* TSIG, SIG(0);
|
||||||
|
* DNS over TLS: optional encrypted connection between client and server;
|
||||||
|
* DNS name compression;
|
||||||
|
* Depends only on the standard library.
|
||||||
|
|
||||||
|
Have fun!
|
||||||
|
|
||||||
|
Miek Gieben - 2010-2012 - <miek@miek.nl>
|
||||||
|
|
||||||
|
# Building
|
||||||
|
|
||||||
|
Building is done with the `go` tool. If you have setup your GOPATH
|
||||||
|
correctly, 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
|
||||||
|
* 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
|
||||||
|
* 6975 - Algorithm Understanding in DNSSEC
|
||||||
|
* 7043 - EUI48/EUI64 records
|
||||||
|
* 7314 - DNS (EDNS) EXPIRE Option
|
||||||
|
* 7553 - URI record
|
||||||
|
* 7858 - DNS over TLS: Initiation and Performance Considerations (draft)
|
||||||
|
* 7873 - Domain Name System (DNS) Cookies (draft-ietf-dnsop-cookies)
|
||||||
|
* xxxx - EDNS0 DNS Update Lease (draft)
|
||||||
|
|
||||||
|
## Loosely based upon
|
||||||
|
|
||||||
|
* `ldns`
|
||||||
|
* `NSD`
|
||||||
|
* `Net::DNS`
|
||||||
|
* `GRONG`
|
|
@ -0,0 +1,455 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
// A client implementation.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dnsTimeout time.Duration = 2 * time.Second
|
||||||
|
const tcpIdleTimeout time.Duration = 8 * time.Second
|
||||||
|
|
||||||
|
// 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 fully qualified
|
||||||
|
rtt time.Duration
|
||||||
|
t time.Time
|
||||||
|
tsigRequestMAC string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
Timeout time.Duration // a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout and WriteTimeout when non-zero
|
||||||
|
DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds - 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 fully qualified
|
||||||
|
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 an 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) {
|
||||||
|
var co *Conn
|
||||||
|
co, err = DialTimeout("udp", a, dnsTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer co.Close()
|
||||||
|
|
||||||
|
opt := m.IsEdns0()
|
||||||
|
// If EDNS0 is used use that for size.
|
||||||
|
if opt != nil && opt.UDPSize() >= MinMsgSize {
|
||||||
|
co.UDPSize = opt.UDPSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
co.SetWriteDeadline(time.Now().Add(dnsTimeout))
|
||||||
|
if err = co.WriteMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
co.SetReadDeadline(time.Now().Add(dnsTimeout))
|
||||||
|
r, err = co.ReadMsg()
|
||||||
|
if err == nil && r.Id != m.Id {
|
||||||
|
err = ErrId
|
||||||
|
}
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExchangeConn performs a synchronous query. It sends the message m via the connection
|
||||||
|
// c and waits for a reply. The connection c is not closed by ExchangeConn.
|
||||||
|
// This function is going away, but can easily be mimicked:
|
||||||
|
//
|
||||||
|
// 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: 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange performs an synchronous query. It sends the message m to the address
|
||||||
|
// contained in a and waits for an 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. Messsages without an OPT RR will fallback to the historic limit
|
||||||
|
// of 512 bytes.
|
||||||
|
func (c *Client) Exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
|
||||||
|
if !c.SingleInflight {
|
||||||
|
return c.exchange(m, a)
|
||||||
|
}
|
||||||
|
// This adds a bunch of garbage, TODO(miek).
|
||||||
|
t := "nop"
|
||||||
|
if t1, ok := TypeToString[m.Question[0].Qtype]; ok {
|
||||||
|
t = t1
|
||||||
|
}
|
||||||
|
cl := "nop"
|
||||||
|
if cl1, ok := ClassToString[m.Question[0].Qclass]; ok {
|
||||||
|
cl = cl1
|
||||||
|
}
|
||||||
|
r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) {
|
||||||
|
return c.exchange(m, a)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return r, rtt, err
|
||||||
|
}
|
||||||
|
if shared {
|
||||||
|
return r.Copy(), rtt, nil
|
||||||
|
}
|
||||||
|
return r, rtt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
|
||||||
|
var co *Conn
|
||||||
|
network := "udp"
|
||||||
|
tls := false
|
||||||
|
|
||||||
|
switch c.Net {
|
||||||
|
case "tcp-tls":
|
||||||
|
network = "tcp"
|
||||||
|
tls = true
|
||||||
|
case "tcp4-tls":
|
||||||
|
network = "tcp4"
|
||||||
|
tls = true
|
||||||
|
case "tcp6-tls":
|
||||||
|
network = "tcp6"
|
||||||
|
tls = true
|
||||||
|
default:
|
||||||
|
if c.Net != "" {
|
||||||
|
network = c.Net
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var deadline time.Time
|
||||||
|
if c.Timeout != 0 {
|
||||||
|
deadline = time.Now().Add(c.Timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tls {
|
||||||
|
co, err = DialTimeoutWithTLS(network, a, c.TLSConfig, c.dialTimeout())
|
||||||
|
} else {
|
||||||
|
co, err = DialTimeout(network, a, c.dialTimeout())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
defer co.Close()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
co.TsigSecret = c.TsigSecret
|
||||||
|
co.SetWriteDeadline(deadlineOrTimeout(deadline, c.writeTimeout()))
|
||||||
|
if err = co.WriteMsg(m); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
co.SetReadDeadline(deadlineOrTimeout(deadline, c.readTimeout()))
|
||||||
|
r, err = co.ReadMsg()
|
||||||
|
if err == nil && r.Id != m.Id {
|
||||||
|
err = ErrId
|
||||||
|
}
|
||||||
|
return r, co.rtt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMsg reads a message from the connection co.
|
||||||
|
// If the received message contains a TSIG record the transaction
|
||||||
|
// signature is verified.
|
||||||
|
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 ErrTruncated 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 a truncated message
|
||||||
|
if err == ErrTruncated {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if t := m.IsTsig(); t != nil {
|
||||||
|
if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
|
||||||
|
return m, ErrSecret
|
||||||
|
}
|
||||||
|
// Need to work on the original message p, as that was used to calculate the tsig.
|
||||||
|
err = TsigVerify(p, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
switch t := co.Conn.(type) {
|
||||||
|
case *net.TCPConn, *tls.Conn:
|
||||||
|
r := t.(io.Reader)
|
||||||
|
|
||||||
|
// First two bytes specify the length of the entire message.
|
||||||
|
l, err := tcpMsgLen(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p = make([]byte, l)
|
||||||
|
n, err = tcpRead(r, p)
|
||||||
|
co.rtt = time.Since(co.t)
|
||||||
|
default:
|
||||||
|
if co.UDPSize > MinMsgSize {
|
||||||
|
p = make([]byte, co.UDPSize)
|
||||||
|
} else {
|
||||||
|
p = make([]byte, MinMsgSize)
|
||||||
|
}
|
||||||
|
n, err = co.Read(p)
|
||||||
|
co.rtt = time.Since(co.t)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// tcpMsgLen is a helper func to read first two bytes of stream as uint16 packet length.
|
||||||
|
func tcpMsgLen(t io.Reader) (int, error) {
|
||||||
|
p := []byte{0, 0}
|
||||||
|
n, err := t.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if n != 2 {
|
||||||
|
return 0, ErrShortRead
|
||||||
|
}
|
||||||
|
l := binary.BigEndian.Uint16(p)
|
||||||
|
if l == 0 {
|
||||||
|
return 0, ErrShortRead
|
||||||
|
}
|
||||||
|
return int(l), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tcpRead calls TCPConn.Read enough times to fill allocated buffer.
|
||||||
|
func tcpRead(t io.Reader, p []byte) (int, error) {
|
||||||
|
n, err := t.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
for n < len(p) {
|
||||||
|
j, err := t.Read(p[n:])
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n += j
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements the net.Conn read method.
|
||||||
|
func (co *Conn) Read(p []byte) (n int, err error) {
|
||||||
|
if co.Conn == nil {
|
||||||
|
return 0, ErrConnEmpty
|
||||||
|
}
|
||||||
|
if len(p) < 2 {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
switch t := co.Conn.(type) {
|
||||||
|
case *net.TCPConn, *tls.Conn:
|
||||||
|
r := t.(io.Reader)
|
||||||
|
|
||||||
|
l, err := tcpMsgLen(r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if l > len(p) {
|
||||||
|
return int(l), io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
return tcpRead(r, p[:l])
|
||||||
|
}
|
||||||
|
// UDP connection
|
||||||
|
n, err = co.Conn.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
mac := ""
|
||||||
|
if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
|
||||||
|
return ErrSecret
|
||||||
|
}
|
||||||
|
out, mac, err = TsigGenerate(m, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false)
|
||||||
|
// Set for the next read, although only used in zone transfers
|
||||||
|
co.tsigRequestMAC = mac
|
||||||
|
} else {
|
||||||
|
out, err = m.Pack()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
co.t = time.Now()
|
||||||
|
if _, err = co.Write(out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements the net.Conn Write method.
|
||||||
|
func (co *Conn) Write(p []byte) (n int, err error) {
|
||||||
|
switch t := co.Conn.(type) {
|
||||||
|
case *net.TCPConn, *tls.Conn:
|
||||||
|
w := t.(io.Writer)
|
||||||
|
|
||||||
|
lp := len(p)
|
||||||
|
if lp < 2 {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
if lp > MaxMsgSize {
|
||||||
|
return 0, &Error{err: "message too large"}
|
||||||
|
}
|
||||||
|
l := make([]byte, 2, lp+2)
|
||||||
|
binary.BigEndian.PutUint16(l, uint16(lp))
|
||||||
|
p = append(l, p...)
|
||||||
|
n, err := io.Copy(w, bytes.NewReader(p))
|
||||||
|
return int(n), err
|
||||||
|
}
|
||||||
|
n, err = co.Conn.(*net.UDPConn).Write(p)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialTimeout acts like Dial but takes a timeout.
|
||||||
|
func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) {
|
||||||
|
conn = new(Conn)
|
||||||
|
conn.Conn, err = net.DialTimeout(network, address, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialWithTLS connects to the address on the named network with TLS.
|
||||||
|
func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, err error) {
|
||||||
|
conn = new(Conn)
|
||||||
|
conn.Conn, err = tls.Dial(network, address, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialTimeoutWithTLS acts like DialWithTLS but takes a timeout.
|
||||||
|
func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout time.Duration) (conn *Conn, err error) {
|
||||||
|
var dialer net.Dialer
|
||||||
|
dialer.Timeout = timeout
|
||||||
|
|
||||||
|
conn = new(Conn)
|
||||||
|
conn.Conn, err = tls.DialWithDialer(&dialer, network, address, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deadlineOrTimeout(deadline time.Time, timeout time.Duration) time.Time {
|
||||||
|
if deadline.IsZero() {
|
||||||
|
return time.Now().Add(timeout)
|
||||||
|
}
|
||||||
|
return deadline
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"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()
|
||||||
|
c := new(ClientConfig)
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
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 = make([]string, len(f)-1)
|
||||||
|
for i := 0; i < len(c.Search); i++ {
|
||||||
|
c.Search[i] = f[i+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
case "options": // magic options
|
||||||
|
for i := 1; i < len(f); i++ {
|
||||||
|
s := f[i]
|
||||||
|
switch {
|
||||||
|
case len(s) >= 6 && s[:6] == "ndots:":
|
||||||
|
n, _ := strconv.Atoi(s[6:])
|
||||||
|
if n < 1 {
|
||||||
|
n = 1
|
||||||
|
}
|
||||||
|
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) >= 8 && s[:9] == "attempts:":
|
||||||
|
n, _ := strconv.Atoi(s[9:])
|
||||||
|
if n < 1 {
|
||||||
|
n = 1
|
||||||
|
}
|
||||||
|
c.Attempts = n
|
||||||
|
case s == "rotate":
|
||||||
|
/* not imp */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
|
@ -0,0 +1,282 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
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.RecursionDesired = request.RecursionDesired // Copy rd bit
|
||||||
|
dns.Response = true
|
||||||
|
dns.Opcode = OpcodeQuery
|
||||||
|
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, timesigned int64) *Msg {
|
||||||
|
t := new(TSIG)
|
||||||
|
t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0}
|
||||||
|
t.Algorithm = algo
|
||||||
|
t.Fudge = 300
|
||||||
|
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 {
|
||||||
|
// EDNS0 is at the end of the additional section, start there.
|
||||||
|
// We might want to change this to *only* look at the last two
|
||||||
|
// records. So we see TSIG and/or OPT - this a slightly bigger
|
||||||
|
// change though.
|
||||||
|
for i := len(dns.Extra) - 1; i >= 0; i-- {
|
||||||
|
if dns.Extra[i].Header().Rrtype == TypeOPT {
|
||||||
|
return dns.Extra[i].(*OPT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDomainName checks if s is a valid domain name, it returns the number of
|
||||||
|
// labels and true, when a domain name is valid. Note that non fully qualified
|
||||||
|
// domain name is considered valid, in this case the last label is counted in
|
||||||
|
// the number of labels. When false is returned the number of labels is not
|
||||||
|
// defined. Also note that this function is extremely liberal; almost any
|
||||||
|
// string is a valid domain name as the DNS is 8 bit protocol. It checks if each
|
||||||
|
// label fits in 63 characters, but there is no length check for the entire
|
||||||
|
// string s. I.e. a domain name longer than 255 characters is considered valid.
|
||||||
|
func IsDomainName(s string) (labels int, ok bool) {
|
||||||
|
_, labels, err := packDomainName(s, nil, 0, nil, false)
|
||||||
|
return labels, err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) < 12 {
|
||||||
|
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 {
|
||||||
|
l := len(s)
|
||||||
|
if l == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s[l-1] == '.'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 + "."
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 ip.To4() != nil {
|
||||||
|
return strconv.Itoa(int(ip[15])) + "." + strconv.Itoa(int(ip[14])) + "." + strconv.Itoa(int(ip[13])) + "." +
|
||||||
|
strconv.Itoa(int(ip[12])) + ".in-addr.arpa.", nil
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 c1, ok := ClassToString[uint16(c)]; ok {
|
||||||
|
return c1
|
||||||
|
}
|
||||||
|
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,104 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
const (
|
||||||
|
year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits.
|
||||||
|
defaultTtl = 3600 // Default internal TTL.
|
||||||
|
|
||||||
|
DefaultMsgSize = 4096 // DefaultMsgSize is the standard default for messages larger than 512 bytes.
|
||||||
|
MinMsgSize = 512 // MinMsgSize is the minimal size of a DNS packet.
|
||||||
|
MaxMsgSize = 65535 // MaxMsgSize is the largest possible DNS packet.
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 uncompressed RR in wire format.
|
||||||
|
len() int
|
||||||
|
// pack packs an RR into wire format.
|
||||||
|
pack([]byte, int, map[string]int, bool) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) copyHeader() *RR_Header {
|
||||||
|
r := new(RR_Header)
|
||||||
|
r.Name = h.Name
|
||||||
|
r.Rrtype = h.Rrtype
|
||||||
|
r.Class = h.Class
|
||||||
|
r.Ttl = h.Ttl
|
||||||
|
r.Rdlength = h.Rdlength
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RR_Header) String() string {
|
||||||
|
var s string
|
||||||
|
|
||||||
|
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() int {
|
||||||
|
l := len(h.Name) + 1
|
||||||
|
l += 10 // rrtype(2) + class(2) + ttl(4) + rdlength(2)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToRFC3597 converts a known RR to the unknown RR representation from RFC 3597.
|
||||||
|
func (rr *RFC3597) ToRFC3597(r RR) error {
|
||||||
|
buf := make([]byte, r.len()*2)
|
||||||
|
off, err := PackRR(r, buf, 0, nil, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf = buf[:off]
|
||||||
|
if int(r.Header().Rdlength) > off {
|
||||||
|
return ErrBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
rfc3597, _, err := unpackRFC3597(*r.Header(), buf, off-int(r.Header().Rdlength))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*rr = *rfc3597.(*RFC3597)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,721 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
_ "crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
_ "crypto/sha1"
|
||||||
|
_ "crypto/sha256"
|
||||||
|
_ "crypto/sha512"
|
||||||
|
"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
|
||||||
|
INDIRECT uint8 = 252
|
||||||
|
PRIVATEDNS uint8 = 253 // Private (experimental keys)
|
||||||
|
PRIVATEOID uint8 = 254
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map for 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",
|
||||||
|
INDIRECT: "INDIRECT",
|
||||||
|
PRIVATEDNS: "PRIVATEDNS",
|
||||||
|
PRIVATEOID: "PRIVATEOID",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map of algorithm strings.
|
||||||
|
var StringToAlgorithm = reverseInt8(AlgorithmToString)
|
||||||
|
|
||||||
|
// Map of algorithm crypto hashes.
|
||||||
|
var AlgorithmToHash = map[uint8]crypto.Hash{
|
||||||
|
RSAMD5: crypto.MD5, // Deprecated in RFC 6725
|
||||||
|
RSASHA1: crypto.SHA1,
|
||||||
|
RSASHA1NSEC3SHA1: crypto.SHA1,
|
||||||
|
RSASHA256: crypto.SHA256,
|
||||||
|
ECDSAP256SHA256: crypto.SHA256,
|
||||||
|
ECDSAP384SHA384: crypto.SHA384,
|
||||||
|
RSASHA512: crypto.SHA512,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSSEC hashing algorithm codes.
|
||||||
|
const (
|
||||||
|
_ uint8 = iota
|
||||||
|
SHA1 // RFC 4034
|
||||||
|
SHA256 // RFC 4509
|
||||||
|
GOST94 // RFC 5933
|
||||||
|
SHA384 // Experimental
|
||||||
|
SHA512 // Experimental
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map for hash names.
|
||||||
|
var HashToString = map[uint8]string{
|
||||||
|
SHA1: "SHA1",
|
||||||
|
SHA256: "SHA256",
|
||||||
|
GOST94: "GOST94",
|
||||||
|
SHA384: "SHA384",
|
||||||
|
SHA512: "SHA512",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map of hash strings.
|
||||||
|
var StringToHash = reverseInt8(HashToString)
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
// Look at the bottom two bytes of the modules, which the last
|
||||||
|
// item in the pubkey. We could do this faster by looking directly
|
||||||
|
// at the base64 values. But I'm lazy.
|
||||||
|
modulus, _ := fromBase64([]byte(k.PublicKey))
|
||||||
|
if len(modulus) > 1 {
|
||||||
|
x := binary.BigEndian.Uint16(modulus[len(modulus)-2:])
|
||||||
|
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(strings.ToLower(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.
|
||||||
|
|
||||||
|
// digest buffer
|
||||||
|
digest := append(owner, wire...) // another copy
|
||||||
|
|
||||||
|
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(digest)
|
||||||
|
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.copyHeader()
|
||||||
|
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.copyHeader()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
rr.Hdr.Rrtype = TypeRRSIG
|
||||||
|
rr.Hdr.Name = rrset[0].Header().Name
|
||||||
|
rr.Hdr.Class = rrset[0].Header().Class
|
||||||
|
if rr.OrigTtl == 0 { // If set don't override
|
||||||
|
rr.OrigTtl = rrset[0].Header().Ttl
|
||||||
|
}
|
||||||
|
rr.TypeCovered = rrset[0].Header().Rrtype
|
||||||
|
rr.Labels = uint8(CountLabel(rrset[0].Header().Name))
|
||||||
|
|
||||||
|
if strings.HasPrefix(rrset[0].Header().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 = strings.ToLower(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
|
||||||
|
}
|
||||||
|
signdata = append(signdata, wire...)
|
||||||
|
|
||||||
|
hash, ok := AlgorithmToHash[rr.Algorithm]
|
||||||
|
if !ok {
|
||||||
|
return ErrAlg
|
||||||
|
}
|
||||||
|
|
||||||
|
h := hash.New()
|
||||||
|
h.Write(signdata)
|
||||||
|
|
||||||
|
signature, err := sign(k, h.Sum(nil), hash, 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:
|
||||||
|
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
|
||||||
|
|
||||||
|
// There is no defined interface for what a DSA backed crypto.Signer returns
|
||||||
|
case DSA, DSANSEC3SHA1:
|
||||||
|
// t := divRoundUp(divRoundUp(p.PublicKey.Y.BitLen(), 8)-64, 8)
|
||||||
|
// signature := []byte{byte(t)}
|
||||||
|
// signature = append(signature, intToBytes(r1, 20)...)
|
||||||
|
// signature = append(signature, intToBytes(s1, 20)...)
|
||||||
|
// rr.Signature = signature
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
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.ToLower(rr.SignerName) != strings.ToLower(k.Hdr.Name) {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
if k.Protocol != 3 {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRRset checked that we have at least one RR and that the RRs in
|
||||||
|
// the set have consistent type, class, and name. Also check that type and
|
||||||
|
// class matches the RRSIG record.
|
||||||
|
if rrset[0].Header().Class != rr.Hdr.Class {
|
||||||
|
return ErrRRset
|
||||||
|
}
|
||||||
|
if rrset[0].Header().Rrtype != rr.TypeCovered {
|
||||||
|
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 = strings.ToLower(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
|
||||||
|
}
|
||||||
|
signeddata = append(signeddata, wire...)
|
||||||
|
|
||||||
|
sigbuf := rr.sigBuf() // Get the binary signature data
|
||||||
|
if rr.Algorithm == PRIVATEDNS { // PRIVATEOID
|
||||||
|
// TODO(miek)
|
||||||
|
// remove the domain name and assume its ours?
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, ok := AlgorithmToHash[rr.Algorithm]
|
||||||
|
if !ok {
|
||||||
|
return ErrAlg
|
||||||
|
}
|
||||||
|
|
||||||
|
switch rr.Algorithm {
|
||||||
|
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, RSAMD5:
|
||||||
|
// TODO(mg): this can be done quicker, ie. cache the pubkey data somewhere??
|
||||||
|
pubkey := k.publicKeyRSA() // Get the key
|
||||||
|
if pubkey == nil {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
|
||||||
|
h := hash.New()
|
||||||
|
h.Write(signeddata)
|
||||||
|
return rsa.VerifyPKCS1v15(pubkey, hash, 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 := hash.New()
|
||||||
|
h.Write(signeddata)
|
||||||
|
if ecdsa.Verify(pubkey, h.Sum(nil), r, s) {
|
||||||
|
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 encodedig 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
pubkey := new(rsa.PublicKey)
|
||||||
|
|
||||||
|
pubkey.N = big.NewInt(0)
|
||||||
|
shift := uint64((explen - 1) * 8)
|
||||||
|
expo := uint64(0)
|
||||||
|
for i := int(explen - 1); i > 0; i-- {
|
||||||
|
expo += uint64(keybuf[keyoff+i]) << shift
|
||||||
|
shift -= 8
|
||||||
|
}
|
||||||
|
// Remainder
|
||||||
|
expo += uint64(keybuf[keyoff])
|
||||||
|
if expo > 2<<31 {
|
||||||
|
// Larger expo than supported.
|
||||||
|
// println("dns: F5 primes (or larger) are not supported")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pubkey.E = int(expo)
|
||||||
|
|
||||||
|
pubkey.N.SetBytes(keybuf[keyoff+int(explen):])
|
||||||
|
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 = big.NewInt(0)
|
||||||
|
pubkey.X.SetBytes(keybuf[:len(keybuf)/2])
|
||||||
|
pubkey.Y = big.NewInt(0)
|
||||||
|
pubkey.Y.SetBytes(keybuf[len(keybuf)/2:])
|
||||||
|
return pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *DNSKEY) publicKeyDSA() *dsa.PublicKey {
|
||||||
|
keybuf, err := fromBase64([]byte(k.PublicKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(keybuf) < 22 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t, keybuf := int(keybuf[0]), keybuf[1:]
|
||||||
|
size := 64 + t*8
|
||||||
|
q, keybuf := keybuf[:20], keybuf[20:]
|
||||||
|
if len(keybuf) != 3*size {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p, keybuf := keybuf[:size], keybuf[size:]
|
||||||
|
g, y := keybuf[:size], keybuf[size:]
|
||||||
|
pubkey := new(dsa.PublicKey)
|
||||||
|
pubkey.Parameters.Q = big.NewInt(0).SetBytes(q)
|
||||||
|
pubkey.Parameters.P = big.NewInt(0).SetBytes(p)
|
||||||
|
pubkey.Parameters.G = big.NewInt(0).SetBytes(g)
|
||||||
|
pubkey.Y = big.NewInt(0).SetBytes(y)
|
||||||
|
return pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
r1.Header().Ttl = s.OrigTtl
|
||||||
|
labels := SplitDomainName(r1.Header().Name)
|
||||||
|
// 6.2. Canonical RR Form. (4) - wildcards
|
||||||
|
if len(labels) > int(s.Labels) {
|
||||||
|
// Wildcard
|
||||||
|
r1.Header().Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "."
|
||||||
|
}
|
||||||
|
// RFC 4034: 6.2. Canonical RR Form. (2) - domain name to lowercase
|
||||||
|
r1.Header().Name = strings.ToLower(r1.Header().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 = strings.ToLower(x.Ns)
|
||||||
|
case *CNAME:
|
||||||
|
x.Target = strings.ToLower(x.Target)
|
||||||
|
case *SOA:
|
||||||
|
x.Ns = strings.ToLower(x.Ns)
|
||||||
|
x.Mbox = strings.ToLower(x.Mbox)
|
||||||
|
case *MB:
|
||||||
|
x.Mb = strings.ToLower(x.Mb)
|
||||||
|
case *MG:
|
||||||
|
x.Mg = strings.ToLower(x.Mg)
|
||||||
|
case *MR:
|
||||||
|
x.Mr = strings.ToLower(x.Mr)
|
||||||
|
case *PTR:
|
||||||
|
x.Ptr = strings.ToLower(x.Ptr)
|
||||||
|
case *MINFO:
|
||||||
|
x.Rmail = strings.ToLower(x.Rmail)
|
||||||
|
x.Email = strings.ToLower(x.Email)
|
||||||
|
case *MX:
|
||||||
|
x.Mx = strings.ToLower(x.Mx)
|
||||||
|
case *NAPTR:
|
||||||
|
x.Replacement = strings.ToLower(x.Replacement)
|
||||||
|
case *KX:
|
||||||
|
x.Exchanger = strings.ToLower(x.Exchanger)
|
||||||
|
case *SRV:
|
||||||
|
x.Target = strings.ToLower(x.Target)
|
||||||
|
case *DNAME:
|
||||||
|
x.Target = strings.ToLower(x.Target)
|
||||||
|
}
|
||||||
|
// 6.2. Canonical RR Form. (5) - origTTL
|
||||||
|
wire := make([]byte, r1.len()+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,156 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"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 DSA, DSANSEC3SHA1:
|
||||||
|
if bits != 1024 {
|
||||||
|
return nil, ErrKeySize
|
||||||
|
}
|
||||||
|
case RSAMD5, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch k.Algorithm {
|
||||||
|
case DSA, DSANSEC3SHA1:
|
||||||
|
params := new(dsa.Parameters)
|
||||||
|
if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
priv := new(dsa.PrivateKey)
|
||||||
|
priv.PublicKey.Parameters = *params
|
||||||
|
err := dsa.GenerateKey(priv, rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
k.setPublicKeyDSA(params.Q, params.P, params.G, priv.PublicKey.Y)
|
||||||
|
return priv, nil
|
||||||
|
case RSAMD5, RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1:
|
||||||
|
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
|
||||||
|
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 DSA
|
||||||
|
func (k *DNSKEY) setPublicKeyDSA(_Q, _P, _G, _Y *big.Int) bool {
|
||||||
|
if _Q == nil || _P == nil || _G == nil || _Y == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
buf := dsaToBuf(_Q, _P, _G, _Y)
|
||||||
|
k.PublicKey = toBase64(buf)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the public key (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))
|
||||||
|
if len(i.Bytes()) < 256 {
|
||||||
|
buf = make([]byte, 1)
|
||||||
|
buf[0] = uint8(len(i.Bytes()))
|
||||||
|
} else {
|
||||||
|
buf = make([]byte, 3)
|
||||||
|
buf[0] = 0
|
||||||
|
buf[1] = uint8(len(i.Bytes()) >> 8)
|
||||||
|
buf[2] = uint8(len(i.Bytes()))
|
||||||
|
}
|
||||||
|
buf = append(buf, i.Bytes()...)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the public key for X and Y for Curve. The two
|
||||||
|
// values are just concatenated.
|
||||||
|
func dsaToBuf(_Q, _P, _G, _Y *big.Int) []byte {
|
||||||
|
t := divRoundUp(divRoundUp(_G.BitLen(), 8)-64, 8)
|
||||||
|
buf := []byte{byte(t)}
|
||||||
|
buf = append(buf, intToBytes(_Q, 20)...)
|
||||||
|
buf = append(buf, intToBytes(_P, 64+t*8)...)
|
||||||
|
buf = append(buf, intToBytes(_G, 64+t*8)...)
|
||||||
|
buf = append(buf, intToBytes(_Y, 64+t*8)...)
|
||||||
|
return buf
|
||||||
|
}
|
|
@ -0,0 +1,249 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"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[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.Atoi(strings.SplitN(m["algorithm"], " ", 2)[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrPrivKey
|
||||||
|
}
|
||||||
|
switch uint8(algo) {
|
||||||
|
case DSA:
|
||||||
|
priv, err := readPrivateKeyDSA(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pub := k.publicKeyDSA()
|
||||||
|
if pub == nil {
|
||||||
|
return nil, ErrKey
|
||||||
|
}
|
||||||
|
priv.PublicKey = *pub
|
||||||
|
return priv, nil
|
||||||
|
case RSAMD5:
|
||||||
|
fallthrough
|
||||||
|
case RSASHA1:
|
||||||
|
fallthrough
|
||||||
|
case RSASHA1NSEC3SHA1:
|
||||||
|
fallthrough
|
||||||
|
case RSASHA256:
|
||||||
|
fallthrough
|
||||||
|
case RSASHA512:
|
||||||
|
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 ECCGOST:
|
||||||
|
return nil, ErrPrivKey
|
||||||
|
case ECDSAP256SHA256:
|
||||||
|
fallthrough
|
||||||
|
case 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
|
||||||
|
default:
|
||||||
|
return nil, ErrPrivKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = big.NewInt(0)
|
||||||
|
p.PublicKey.N.SetBytes(v1)
|
||||||
|
case "publicexponent":
|
||||||
|
i := big.NewInt(0)
|
||||||
|
i.SetBytes(v1)
|
||||||
|
p.PublicKey.E = int(i.Int64()) // int64 should be large enough
|
||||||
|
case "privateexponent":
|
||||||
|
p.D = big.NewInt(0)
|
||||||
|
p.D.SetBytes(v1)
|
||||||
|
case "prime1":
|
||||||
|
p.Primes[0] = big.NewInt(0)
|
||||||
|
p.Primes[0].SetBytes(v1)
|
||||||
|
case "prime2":
|
||||||
|
p.Primes[1] = big.NewInt(0)
|
||||||
|
p.Primes[1].SetBytes(v1)
|
||||||
|
}
|
||||||
|
case "exponent1", "exponent2", "coefficient":
|
||||||
|
// not used in Go (yet)
|
||||||
|
case "created", "publish", "activate":
|
||||||
|
// not used in Go (yet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPrivateKeyDSA(m map[string]string) (*dsa.PrivateKey, error) {
|
||||||
|
p := new(dsa.PrivateKey)
|
||||||
|
p.X = big.NewInt(0)
|
||||||
|
for k, v := range m {
|
||||||
|
switch k {
|
||||||
|
case "private_value(x)":
|
||||||
|
v1, err := fromBase64([]byte(v))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.X.SetBytes(v1)
|
||||||
|
case "created", "publish", "activate":
|
||||||
|
/* not used in Go (yet) */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPrivateKeyECDSA(m map[string]string) (*ecdsa.PrivateKey, error) {
|
||||||
|
p := new(ecdsa.PrivateKey)
|
||||||
|
p.D = big.NewInt(0)
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseKey reads a private key from r. It returns a map[string]string,
|
||||||
|
// with the key-value pairs, or an error when the file is not correct.
|
||||||
|
func parseKey(r io.Reader, file string) (map[string]string, error) {
|
||||||
|
s := scanInit(r)
|
||||||
|
m := make(map[string]string)
|
||||||
|
c := make(chan lex)
|
||||||
|
k := ""
|
||||||
|
// Start the lexer
|
||||||
|
go klexer(s, c)
|
||||||
|
for l := range c {
|
||||||
|
// It should alternate
|
||||||
|
switch l.value {
|
||||||
|
case zKey:
|
||||||
|
k = l.token
|
||||||
|
case zValue:
|
||||||
|
if k == "" {
|
||||||
|
return nil, &ParseError{file, "no private key seen", l}
|
||||||
|
}
|
||||||
|
//println("Setting", strings.ToLower(k), "to", l.token, "b")
|
||||||
|
m[strings.ToLower(k)] = l.token
|
||||||
|
k = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// klexer scans the sourcefile and returns tokens on the channel c.
|
||||||
|
func klexer(s *scan, c chan lex) {
|
||||||
|
var l lex
|
||||||
|
str := "" // Hold the current read text
|
||||||
|
commt := false
|
||||||
|
key := true
|
||||||
|
x, err := s.tokenText()
|
||||||
|
defer close(c)
|
||||||
|
for err == nil {
|
||||||
|
l.column = s.position.Column
|
||||||
|
l.line = s.position.Line
|
||||||
|
switch x {
|
||||||
|
case ':':
|
||||||
|
if commt {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
l.token = str
|
||||||
|
if key {
|
||||||
|
l.value = zKey
|
||||||
|
c <- l
|
||||||
|
// Next token is a space, eat it
|
||||||
|
s.tokenText()
|
||||||
|
key = false
|
||||||
|
str = ""
|
||||||
|
} else {
|
||||||
|
l.value = zValue
|
||||||
|
}
|
||||||
|
case ';':
|
||||||
|
commt = true
|
||||||
|
case '\n':
|
||||||
|
if commt {
|
||||||
|
// Reset a comment
|
||||||
|
commt = false
|
||||||
|
}
|
||||||
|
l.value = zValue
|
||||||
|
l.token = str
|
||||||
|
c <- l
|
||||||
|
str = ""
|
||||||
|
commt = false
|
||||||
|
key = true
|
||||||
|
default:
|
||||||
|
if commt {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
str += string(x)
|
||||||
|
}
|
||||||
|
x, err = s.tokenText()
|
||||||
|
}
|
||||||
|
if len(str) > 0 {
|
||||||
|
// Send remainder
|
||||||
|
l.token = str
|
||||||
|
l.value = zValue
|
||||||
|
c <- l
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const format = "Private-key-format: v1.3\n"
|
||||||
|
|
||||||
|
// PrivateKeyString converts a PrivateKey to a string. This string has the same
|
||||||
|
// format as the private-key-file of BIND9 (Private-key-format: v1.3).
|
||||||
|
// It needs some info from the key (the algorithm), so its a method of the DNSKEY
|
||||||
|
// It supports rsa.PrivateKey, ecdsa.PrivateKey and dsa.PrivateKey
|
||||||
|
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
|
||||||
|
one := big.NewInt(1)
|
||||||
|
p1 := big.NewInt(0).Sub(p.Primes[0], one)
|
||||||
|
q1 := big.NewInt(0).Sub(p.Primes[1], one)
|
||||||
|
exp1 := big.NewInt(0).Mod(p.D, p1)
|
||||||
|
exp2 := big.NewInt(0).Mod(p.D, q1)
|
||||||
|
coeff := big.NewInt(0).ModInverse(p.Primes[1], p.Primes[0])
|
||||||
|
|
||||||
|
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 *dsa.PrivateKey:
|
||||||
|
T := divRoundUp(divRoundUp(p.PublicKey.Parameters.G.BitLen(), 8)-64, 8)
|
||||||
|
prime := toBase64(intToBytes(p.PublicKey.Parameters.P, 64+T*8))
|
||||||
|
subprime := toBase64(intToBytes(p.PublicKey.Parameters.Q, 20))
|
||||||
|
base := toBase64(intToBytes(p.PublicKey.Parameters.G, 64+T*8))
|
||||||
|
priv := toBase64(intToBytes(p.X, 20))
|
||||||
|
pub := toBase64(intToBytes(p.PublicKey.Y, 64+T*8))
|
||||||
|
return format +
|
||||||
|
"Algorithm: " + algorithm + "\n" +
|
||||||
|
"Prime(p): " + prime + "\n" +
|
||||||
|
"Subprime(q): " + subprime + "\n" +
|
||||||
|
"Base(g): " + base + "\n" +
|
||||||
|
"Private_value(x): " + priv + "\n" +
|
||||||
|
"Public_value(y): " + pub + "\n"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,251 @@
|
||||||
|
/*
|
||||||
|
Package dns implements a full featured interface to the Domain Name System.
|
||||||
|
Server- and client-side programming is supported.
|
||||||
|
The package allows complete control over what is send out to the DNS. The package
|
||||||
|
API follows the less-is-more principle, by presenting a small, clean interface.
|
||||||
|
|
||||||
|
The package dns 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 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 send.
|
||||||
|
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
|
||||||
|
|
||||||
|
If these "advanced" features are not needed, a simple UDP query can be send,
|
||||||
|
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: HmacMD5, 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==":
|
||||||
|
|
||||||
|
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.HmacMD5, 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.HmacMD5, 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.
|
||||||
|
|
||||||
|
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.HmacMD5, 300, time.Now().Unix())
|
||||||
|
} else {
|
||||||
|
// *Msg r has an TSIG records and it was not valided
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 http://miek.nl/posts/2014/Sep/21/Private%20RRs%20and%20IDN%20in%20Go%20DNS/ for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
EDNS0
|
||||||
|
|
||||||
|
EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated
|
||||||
|
by RFC 6891. It defines an new RR type, the OPT RR, which is then completely
|
||||||
|
abused.
|
||||||
|
Basic use pattern for creating an (empty) OPT RR:
|
||||||
|
|
||||||
|
o := new(dns.OPT)
|
||||||
|
o.Hdr.Name = "." // MUST be the root zone, per definition.
|
||||||
|
o.Hdr.Rrtype = dns.TypeOPT
|
||||||
|
|
||||||
|
The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891)
|
||||||
|
interfaces. Currently only a few have been standardized: EDNS0_NSID
|
||||||
|
(RFC 5001) and EDNS0_SUBNET (draft-vandergaast-edns-client-subnet-02). Note
|
||||||
|
that these options may be combined in an OPT RR.
|
||||||
|
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: DSA, ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256 and
|
||||||
|
RSASHA512.
|
||||||
|
|
||||||
|
Signing subsequent messages in multi-message sessions is not implemented.
|
||||||
|
*/
|
||||||
|
package dns
|
|
@ -0,0 +1,532 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"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 (RFC5001)
|
||||||
|
EDNS0DAU = 0x5 // DNSSEC Algorithm Understood
|
||||||
|
EDNS0DHU = 0x6 // DS Hash Understood
|
||||||
|
EDNS0N3U = 0x7 // NSEC3 Hash Understood
|
||||||
|
EDNS0SUBNET = 0x8 // client-subnet (RFC6891)
|
||||||
|
EDNS0EXPIRE = 0x9 // EDNS0 expire
|
||||||
|
EDNS0COOKIE = 0xa // EDNS0 Cookie
|
||||||
|
EDNS0SUBNETDRAFT = 0x50fa // Don't use! Use EDNS0SUBNET
|
||||||
|
EDNS0LOCALSTART = 0xFDE9 // Beginning of range reserved for local/experimental use (RFC6891)
|
||||||
|
EDNS0LOCALEND = 0xFFFE // End of range reserved for local/experimental use (RFC6891)
|
||||||
|
_DO = 1 << 15 // dnssec ok
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
if o.(*EDNS0_SUBNET).DraftOption {
|
||||||
|
s += " (draft)"
|
||||||
|
}
|
||||||
|
case *EDNS0_COOKIE:
|
||||||
|
s += "\n; COOKIE: " + 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *OPT) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
for i := 0; i < len(rr.Option); i++ {
|
||||||
|
l += 4 // Account for 2-byte option code and 2-byte option length.
|
||||||
|
lo, _ := rr.Option[i].pack()
|
||||||
|
l += len(lo)
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) + 15
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExtendedRcode sets the EDNS extended RCODE field.
|
||||||
|
func (rr *OPT) SetExtendedRcode(v uint8) {
|
||||||
|
if v < RcodeBadVers { // Smaller than 16.. Use the 4 bits you have!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | (uint32(v-15) << 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.
|
||||||
|
func (rr *OPT) SetDo() {
|
||||||
|
rr.Hdr.Ttl |= _DO
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// The nsid EDNS0 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_NSID) Option() uint16 { return EDNS0NSID }
|
||||||
|
func (e *EDNS0_NSID) unpack(b []byte) error { e.Nsid = hex.EncodeToString(b); return nil }
|
||||||
|
func (e *EDNS0_NSID) String() string { return string(e.Nsid) }
|
||||||
|
|
||||||
|
// EDNS0_SUBNET is the subnet option that is used to give the remote nameserver
|
||||||
|
// an idea of where the client lives. 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.NetMask = 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)
|
||||||
|
//
|
||||||
|
// Note: the spec (draft-ietf-dnsop-edns-client-subnet-00) has some insane logic
|
||||||
|
// for which netmask applies to the address. 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
|
||||||
|
DraftOption bool // Set to true if using the old (0x50fa) option code
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_SUBNET) Option() uint16 {
|
||||||
|
if e.DraftOption {
|
||||||
|
return EDNS0SUBNETDRAFT
|
||||||
|
}
|
||||||
|
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 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 1:
|
||||||
|
if e.SourceNetmask > net.IPv4len*8 || e.SourceScope > net.IPv4len*8 {
|
||||||
|
return errors.New("dns: bad netmask")
|
||||||
|
}
|
||||||
|
addr := make([]byte, net.IPv4len)
|
||||||
|
for i := 0; i < net.IPv4len && 4+i < len(b); i++ {
|
||||||
|
addr[i] = b[4+i]
|
||||||
|
}
|
||||||
|
e.Address = net.IPv4(addr[0], addr[1], addr[2], addr[3])
|
||||||
|
case 2:
|
||||||
|
if e.SourceNetmask > net.IPv6len*8 || e.SourceScope > net.IPv6len*8 {
|
||||||
|
return errors.New("dns: bad netmask")
|
||||||
|
}
|
||||||
|
addr := make([]byte, net.IPv6len)
|
||||||
|
for i := 0; i < net.IPv6len && 4+i < len(b); i++ {
|
||||||
|
addr[i] = b[4+i]
|
||||||
|
}
|
||||||
|
e.Address = net.IP{addr[0], addr[1], addr[2], addr[3], addr[4],
|
||||||
|
addr[5], addr[6], addr[7], addr[8], addr[9], addr[10],
|
||||||
|
addr[11], addr[12], addr[13], addr[14], addr[15]}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Cookie EDNS0 option
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }
|
||||||
|
|
||||||
|
// The EDNS0_UL (Update Lease) (draft RFC) option is used to tell the server to set
|
||||||
|
// an expiration on an update RR. This is helpful for clients that cannot clean
|
||||||
|
// up after themselves. This is a draft RFC and more information can be found at
|
||||||
|
// http://files.dns-sd.org/draft-sekar-dns-ul.txt
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_UL) Option() uint16 { return EDNS0UL }
|
||||||
|
func (e *EDNS0_UL) String() string { return strconv.FormatUint(uint64(e.Lease), 10) }
|
||||||
|
|
||||||
|
// Copied: http://golang.org/src/pkg/net/dnsmsg.go
|
||||||
|
func (e *EDNS0_UL) pack() ([]byte, error) {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(b, e.Lease)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_UL) unpack(b []byte) error {
|
||||||
|
if len(b) < 4 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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(uint64(e.Id), 10) +
|
||||||
|
" " + strconv.FormatUint(uint64(e.LeaseLife), 10)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type EDNS0_DAU struct {
|
||||||
|
Code uint16 // Always EDNS0DAU
|
||||||
|
AlgCode []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
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 i := 0; i < len(e.AlgCode); i++ {
|
||||||
|
if a, ok := AlgorithmToString[e.AlgCode[i]]; ok {
|
||||||
|
s += " " + a
|
||||||
|
} else {
|
||||||
|
s += " " + strconv.Itoa(int(e.AlgCode[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type EDNS0_DHU struct {
|
||||||
|
Code uint16 // Always EDNS0DHU
|
||||||
|
AlgCode []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
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 i := 0; i < len(e.AlgCode); i++ {
|
||||||
|
if a, ok := HashToString[e.AlgCode[i]]; ok {
|
||||||
|
s += " " + a
|
||||||
|
} else {
|
||||||
|
s += " " + strconv.Itoa(int(e.AlgCode[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type EDNS0_N3U struct {
|
||||||
|
Code uint16 // Always EDNS0N3U
|
||||||
|
AlgCode []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
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 i := 0; i < len(e.AlgCode); i++ {
|
||||||
|
if a, ok := HashToString[e.AlgCode[i]]; ok {
|
||||||
|
s += " " + a
|
||||||
|
} else {
|
||||||
|
s += " " + strconv.Itoa(int(e.AlgCode[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type EDNS0_EXPIRE struct {
|
||||||
|
Code uint16 // Always EDNS0EXPIRE
|
||||||
|
Expire uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE }
|
||||||
|
func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) }
|
||||||
|
|
||||||
|
func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
b[0] = byte(e.Expire >> 24)
|
||||||
|
b[1] = byte(e.Expire >> 16)
|
||||||
|
b[2] = byte(e.Expire >> 8)
|
||||||
|
b[3] = byte(e.Expire)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_EXPIRE) unpack(b []byte) error {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return ErrBuf
|
||||||
|
}
|
||||||
|
e.Expire = binary.BigEndian.Uint32(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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) 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
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
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 k := d.Kind(); k {
|
||||||
|
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.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"`:
|
||||||
|
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,159 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"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.
|
||||||
|
// Any error are returned as a string value, the empty string signals
|
||||||
|
// "no error".
|
||||||
|
func generate(l lex, c chan lex, t chan *Token, o string) string {
|
||||||
|
step := 1
|
||||||
|
if i := strings.IndexAny(l.token, "/"); i != -1 {
|
||||||
|
if i+1 == len(l.token) {
|
||||||
|
return "bad step in $GENERATE range"
|
||||||
|
}
|
||||||
|
if s, err := strconv.Atoi(l.token[i+1:]); err == nil {
|
||||||
|
if s < 0 {
|
||||||
|
return "bad step in $GENERATE range"
|
||||||
|
}
|
||||||
|
step = s
|
||||||
|
} else {
|
||||||
|
return "bad step in $GENERATE range"
|
||||||
|
}
|
||||||
|
l.token = l.token[:i]
|
||||||
|
}
|
||||||
|
sx := strings.SplitN(l.token, "-", 2)
|
||||||
|
if len(sx) != 2 {
|
||||||
|
return "bad start-stop in $GENERATE range"
|
||||||
|
}
|
||||||
|
start, err := strconv.Atoi(sx[0])
|
||||||
|
if err != nil {
|
||||||
|
return "bad start in $GENERATE range"
|
||||||
|
}
|
||||||
|
end, err := strconv.Atoi(sx[1])
|
||||||
|
if err != nil {
|
||||||
|
return "bad stop in $GENERATE range"
|
||||||
|
}
|
||||||
|
if end < 0 || start < 0 || end < start {
|
||||||
|
return "bad range in $GENERATE range"
|
||||||
|
}
|
||||||
|
|
||||||
|
<-c // _BLANK
|
||||||
|
// Create a complete new string, which we then parse again.
|
||||||
|
s := ""
|
||||||
|
BuildRR:
|
||||||
|
l = <-c
|
||||||
|
if l.value != zNewline && l.value != zEOF {
|
||||||
|
s += l.token
|
||||||
|
goto BuildRR
|
||||||
|
}
|
||||||
|
for i := start; i <= end; i += step {
|
||||||
|
var (
|
||||||
|
escape bool
|
||||||
|
dom bytes.Buffer
|
||||||
|
mod string
|
||||||
|
err error
|
||||||
|
offset int
|
||||||
|
)
|
||||||
|
|
||||||
|
for j := 0; j < len(s); j++ { // No 'range' because we need to jump around
|
||||||
|
switch s[j] {
|
||||||
|
case '\\':
|
||||||
|
if escape {
|
||||||
|
dom.WriteByte('\\')
|
||||||
|
escape = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
escape = true
|
||||||
|
case '$':
|
||||||
|
mod = "%d"
|
||||||
|
offset = 0
|
||||||
|
if escape {
|
||||||
|
dom.WriteByte('$')
|
||||||
|
escape = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
escape = false
|
||||||
|
if j+1 >= len(s) { // End of the string
|
||||||
|
dom.WriteString(fmt.Sprintf(mod, i+offset))
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
if s[j+1] == '$' {
|
||||||
|
dom.WriteByte('$')
|
||||||
|
j++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Search for { and }
|
||||||
|
if s[j+1] == '{' { // Modifier block
|
||||||
|
sep := strings.Index(s[j+2:], "}")
|
||||||
|
if sep == -1 {
|
||||||
|
return "bad modifier in $GENERATE"
|
||||||
|
}
|
||||||
|
mod, offset, err = modToPrintf(s[j+2 : j+2+sep])
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
j += 2 + sep // Jump to it
|
||||||
|
}
|
||||||
|
dom.WriteString(fmt.Sprintf(mod, i+offset))
|
||||||
|
default:
|
||||||
|
if escape { // Pretty useless here
|
||||||
|
escape = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dom.WriteByte(s[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Re-parse the RR and send it on the current channel t
|
||||||
|
rx, err := NewRR("$ORIGIN " + o + "\n" + dom.String())
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
t <- &Token{RR: rx}
|
||||||
|
// Its more efficient to first built the rrlist and then parse it in
|
||||||
|
// one go! But is this a problem?
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
|
||||||
|
func modToPrintf(s string) (string, int, error) {
|
||||||
|
xs := strings.SplitN(s, ",", 3)
|
||||||
|
if len(xs) != 3 {
|
||||||
|
return "", 0, errors.New("bad modifier in $GENERATE")
|
||||||
|
}
|
||||||
|
// xs[0] is offset, xs[1] is width, xs[2] is base
|
||||||
|
if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" {
|
||||||
|
return "", 0, errors.New("bad base in $GENERATE")
|
||||||
|
}
|
||||||
|
offset, err := strconv.Atoi(xs[0])
|
||||||
|
if err != nil || offset > 255 {
|
||||||
|
return "", 0, errors.New("bad offset in $GENERATE")
|
||||||
|
}
|
||||||
|
width, err := strconv.Atoi(xs[1])
|
||||||
|
if err != nil || width > 255 {
|
||||||
|
return "", offset, errors.New("bad width in $GENERATE")
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case width < 0:
|
||||||
|
return "", offset, errors.New("bad width in $GENERATE")
|
||||||
|
case width == 0:
|
||||||
|
return "%" + xs[1] + xs[2], offset, nil
|
||||||
|
}
|
||||||
|
return "%0" + xs[1] + xs[2], offset, nil
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
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 len(s) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fqdnEnd := 0 // offset of the final '.' or the length of the name
|
||||||
|
idx := Split(s)
|
||||||
|
begin := 0
|
||||||
|
if s[len(s)-1] == '.' {
|
||||||
|
fqdnEnd = len(s) - 1
|
||||||
|
} else {
|
||||||
|
fqdnEnd = len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(idx) {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 1:
|
||||||
|
// no-op
|
||||||
|
default:
|
||||||
|
end := 0
|
||||||
|
for i := 1; i < len(idx); i++ {
|
||||||
|
end = idx[i]
|
||||||
|
labels = append(labels, s[begin:end-1])
|
||||||
|
begin = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
labels = append(labels, s[begin:fqdnEnd])
|
||||||
|
return labels
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 not 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) {
|
||||||
|
s1 = Fqdn(s1)
|
||||||
|
s2 = Fqdn(s2)
|
||||||
|
l1 := Split(s1)
|
||||||
|
l2 := Split(s2)
|
||||||
|
|
||||||
|
// the first check: root label
|
||||||
|
if l1 == nil || l2 == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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 s1[l1[j1]:] == s2[l2[j2]:] {
|
||||||
|
n++
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if i1 < 0 || i2 < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if s1[l1[i1]:l1[j1]] == s2[l2[i2]:l2[j2]] {
|
||||||
|
n++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
j1--
|
||||||
|
i1--
|
||||||
|
j2--
|
||||||
|
i2--
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountLabel counts the 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) {
|
||||||
|
quote := false
|
||||||
|
for i = offset; i < len(s)-1; i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '\\':
|
||||||
|
quote = !quote
|
||||||
|
default:
|
||||||
|
quote = false
|
||||||
|
case '.':
|
||||||
|
if quote {
|
||||||
|
quote = !quote
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return i + 1, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 n == 0 {
|
||||||
|
return len(s), false
|
||||||
|
}
|
||||||
|
lab := Split(s)
|
||||||
|
if lab == nil {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if n > len(lab) {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
return lab[len(lab)-n], false
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,340 @@
|
||||||
|
//+build ignore
|
||||||
|
|
||||||
|
// msg_generate.go is meant to run with go generate. It will use
|
||||||
|
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||||
|
// it will generate pack/unpack methods based on the struct tags. The generated source is
|
||||||
|
// written to zmsg.go, and is meant to be checked into git.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"go/importer"
|
||||||
|
"go/types"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var packageHdr = `
|
||||||
|
// *** DO NOT MODIFY ***
|
||||||
|
// AUTOGENERATED BY go generate from msg_generate.go
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
// getTypeStruct will take a type and the package scope, and return the
|
||||||
|
// (innermost) struct if the type is considered a RR type (currently defined as
|
||||||
|
// those structs beginning with a RR_Header, could be redefined as implementing
|
||||||
|
// the RR interface). The bool return value indicates if embedded structs were
|
||||||
|
// resolved.
|
||||||
|
func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
|
||||||
|
st, ok := t.Underlying().(*types.Struct)
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
||||||
|
return st, false
|
||||||
|
}
|
||||||
|
if st.Field(0).Anonymous() {
|
||||||
|
st, _ := getTypeStruct(st.Field(0).Type(), scope)
|
||||||
|
return st, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Import and type-check the package
|
||||||
|
pkg, err := importer.Default().Import("github.com/miekg/dns")
|
||||||
|
fatalIfErr(err)
|
||||||
|
scope := pkg.Scope()
|
||||||
|
|
||||||
|
// Collect actual types (*X)
|
||||||
|
var namedTypes []string
|
||||||
|
for _, name := range scope.Names() {
|
||||||
|
o := scope.Lookup(name)
|
||||||
|
if o == nil || !o.Exported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if st, _ := getTypeStruct(o.Type(), scope); st == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if name == "PrivateRR" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if corresponding TypeX exists
|
||||||
|
if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" {
|
||||||
|
log.Fatalf("Constant Type%s does not exist.", o.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
namedTypes = append(namedTypes, o.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
b.WriteString(packageHdr)
|
||||||
|
|
||||||
|
fmt.Fprint(b, "// pack*() functions\n\n")
|
||||||
|
for _, name := range namedTypes {
|
||||||
|
o := scope.Lookup(name)
|
||||||
|
st, _ := getTypeStruct(o.Type(), scope)
|
||||||
|
|
||||||
|
fmt.Fprintf(b, "func (rr *%s) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) {\n", name)
|
||||||
|
fmt.Fprint(b, `off, err := rr.Hdr.pack(msg, off, compression, compress)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
headerEnd := off
|
||||||
|
`)
|
||||||
|
for i := 1; i < st.NumFields(); i++ {
|
||||||
|
o := func(s string) {
|
||||||
|
fmt.Fprintf(b, s, st.Field(i).Name())
|
||||||
|
fmt.Fprint(b, `if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := st.Field(i).Type().(*types.Slice); ok {
|
||||||
|
switch st.Tag(i) {
|
||||||
|
case `dns:"-"`: // ignored
|
||||||
|
case `dns:"txt"`:
|
||||||
|
o("off, err = packStringTxt(rr.%s, msg, off)\n")
|
||||||
|
case `dns:"opt"`:
|
||||||
|
o("off, err = packDataOpt(rr.%s, msg, off)\n")
|
||||||
|
case `dns:"nsec"`:
|
||||||
|
o("off, err = packDataNsec(rr.%s, msg, off)\n")
|
||||||
|
case `dns:"domain-name"`:
|
||||||
|
o("off, err = packDataDomainNames(rr.%s, msg, off, compression, compress)\n")
|
||||||
|
default:
|
||||||
|
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case st.Tag(i) == `dns:"-"`: // ignored
|
||||||
|
case st.Tag(i) == `dns:"cdomain-name"`:
|
||||||
|
fallthrough
|
||||||
|
case st.Tag(i) == `dns:"domain-name"`:
|
||||||
|
o("off, err = PackDomainName(rr.%s, msg, off, compression, compress)\n")
|
||||||
|
case st.Tag(i) == `dns:"a"`:
|
||||||
|
o("off, err = packDataA(rr.%s, msg, off)\n")
|
||||||
|
case st.Tag(i) == `dns:"aaaa"`:
|
||||||
|
o("off, err = packDataAAAA(rr.%s, msg, off)\n")
|
||||||
|
case st.Tag(i) == `dns:"uint48"`:
|
||||||
|
o("off, err = packUint48(rr.%s, msg, off)\n")
|
||||||
|
case st.Tag(i) == `dns:"txt"`:
|
||||||
|
o("off, err = packString(rr.%s, msg, off)\n")
|
||||||
|
|
||||||
|
case strings.HasPrefix(st.Tag(i), `dns:"size-base32`): // size-base32 can be packed just like base32
|
||||||
|
fallthrough
|
||||||
|
case st.Tag(i) == `dns:"base32"`:
|
||||||
|
o("off, err = packStringBase32(rr.%s, msg, off)\n")
|
||||||
|
|
||||||
|
case strings.HasPrefix(st.Tag(i), `dns:"size-base64`): // size-base64 can be packed just like base64
|
||||||
|
fallthrough
|
||||||
|
case st.Tag(i) == `dns:"base64"`:
|
||||||
|
o("off, err = packStringBase64(rr.%s, msg, off)\n")
|
||||||
|
|
||||||
|
case strings.HasPrefix(st.Tag(i), `dns:"size-hex:SaltLength`): // Hack to fix empty salt length for NSEC3
|
||||||
|
o("if rr.%s == \"-\" { /* do nothing, empty salt */ }\n")
|
||||||
|
continue
|
||||||
|
case strings.HasPrefix(st.Tag(i), `dns:"size-hex`): // size-hex can be packed just like hex
|
||||||
|
fallthrough
|
||||||
|
case st.Tag(i) == `dns:"hex"`:
|
||||||
|
o("off, err = packStringHex(rr.%s, msg, off)\n")
|
||||||
|
|
||||||
|
case st.Tag(i) == `dns:"octet"`:
|
||||||
|
o("off, err = packStringOctet(rr.%s, msg, off)\n")
|
||||||
|
case st.Tag(i) == "":
|
||||||
|
switch st.Field(i).Type().(*types.Basic).Kind() {
|
||||||
|
case types.Uint8:
|
||||||
|
o("off, err = packUint8(rr.%s, msg, off)\n")
|
||||||
|
case types.Uint16:
|
||||||
|
o("off, err = packUint16(rr.%s, msg, off)\n")
|
||||||
|
case types.Uint32:
|
||||||
|
o("off, err = packUint32(rr.%s, msg, off)\n")
|
||||||
|
case types.Uint64:
|
||||||
|
o("off, err = packUint64(rr.%s, msg, off)\n")
|
||||||
|
case types.String:
|
||||||
|
o("off, err = packString(rr.%s, msg, off)\n")
|
||||||
|
default:
|
||||||
|
log.Fatalln(name, st.Field(i).Name())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We have packed everything, only now we know the rdlength of this RR
|
||||||
|
fmt.Fprintln(b, "rr.Header().Rdlength = uint16(off-headerEnd)")
|
||||||
|
fmt.Fprintln(b, "return off, nil }\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(b, "// unpack*() functions\n\n")
|
||||||
|
for _, name := range namedTypes {
|
||||||
|
o := scope.Lookup(name)
|
||||||
|
st, _ := getTypeStruct(o.Type(), scope)
|
||||||
|
|
||||||
|
fmt.Fprintf(b, "func unpack%s(h RR_Header, msg []byte, off int) (RR, int, error) {\n", name)
|
||||||
|
fmt.Fprintf(b, "rr := new(%s)\n", name)
|
||||||
|
fmt.Fprint(b, "rr.Hdr = h\n")
|
||||||
|
fmt.Fprint(b, `if noRdata(h) {
|
||||||
|
return rr, off, nil
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
rdStart := off
|
||||||
|
_ = rdStart
|
||||||
|
|
||||||
|
`)
|
||||||
|
for i := 1; i < st.NumFields(); i++ {
|
||||||
|
o := func(s string) {
|
||||||
|
fmt.Fprintf(b, s, st.Field(i).Name())
|
||||||
|
fmt.Fprint(b, `if err != nil {
|
||||||
|
return rr, off, err
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// size-* are special, because they reference a struct member we should use for the length.
|
||||||
|
if strings.HasPrefix(st.Tag(i), `dns:"size-`) {
|
||||||
|
structMember := structMember(st.Tag(i))
|
||||||
|
structTag := structTag(st.Tag(i))
|
||||||
|
switch structTag {
|
||||||
|
case "hex":
|
||||||
|
fmt.Fprintf(b, "rr.%s, off, err = unpackStringHex(msg, off, off + int(rr.%s))\n", st.Field(i).Name(), structMember)
|
||||||
|
case "base32":
|
||||||
|
fmt.Fprintf(b, "rr.%s, off, err = unpackStringBase32(msg, off, off + int(rr.%s))\n", st.Field(i).Name(), structMember)
|
||||||
|
case "base64":
|
||||||
|
fmt.Fprintf(b, "rr.%s, off, err = unpackStringBase64(msg, off, off + int(rr.%s))\n", st.Field(i).Name(), structMember)
|
||||||
|
default:
|
||||||
|
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
||||||
|
}
|
||||||
|
fmt.Fprint(b, `if err != nil {
|
||||||
|
return rr, off, err
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := st.Field(i).Type().(*types.Slice); ok {
|
||||||
|
switch st.Tag(i) {
|
||||||
|
case `dns:"-"`: // ignored
|
||||||
|
case `dns:"txt"`:
|
||||||
|
o("rr.%s, off, err = unpackStringTxt(msg, off)\n")
|
||||||
|
case `dns:"opt"`:
|
||||||
|
o("rr.%s, off, err = unpackDataOpt(msg, off)\n")
|
||||||
|
case `dns:"nsec"`:
|
||||||
|
o("rr.%s, off, err = unpackDataNsec(msg, off)\n")
|
||||||
|
case `dns:"domain-name"`:
|
||||||
|
o("rr.%s, off, err = unpackDataDomainNames(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
|
||||||
|
default:
|
||||||
|
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch st.Tag(i) {
|
||||||
|
case `dns:"-"`: // ignored
|
||||||
|
case `dns:"cdomain-name"`:
|
||||||
|
fallthrough
|
||||||
|
case `dns:"domain-name"`:
|
||||||
|
o("rr.%s, off, err = UnpackDomainName(msg, off)\n")
|
||||||
|
case `dns:"a"`:
|
||||||
|
o("rr.%s, off, err = unpackDataA(msg, off)\n")
|
||||||
|
case `dns:"aaaa"`:
|
||||||
|
o("rr.%s, off, err = unpackDataAAAA(msg, off)\n")
|
||||||
|
case `dns:"uint48"`:
|
||||||
|
o("rr.%s, off, err = unpackUint48(msg, off)\n")
|
||||||
|
case `dns:"txt"`:
|
||||||
|
o("rr.%s, off, err = unpackString(msg, off)\n")
|
||||||
|
case `dns:"base32"`:
|
||||||
|
o("rr.%s, off, err = unpackStringBase32(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
|
||||||
|
case `dns:"base64"`:
|
||||||
|
o("rr.%s, off, err = unpackStringBase64(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
|
||||||
|
case `dns:"hex"`:
|
||||||
|
o("rr.%s, off, err = unpackStringHex(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
|
||||||
|
case `dns:"octet"`:
|
||||||
|
o("rr.%s, off, err = unpackStringOctet(msg, off)\n")
|
||||||
|
case "":
|
||||||
|
switch st.Field(i).Type().(*types.Basic).Kind() {
|
||||||
|
case types.Uint8:
|
||||||
|
o("rr.%s, off, err = unpackUint8(msg, off)\n")
|
||||||
|
case types.Uint16:
|
||||||
|
o("rr.%s, off, err = unpackUint16(msg, off)\n")
|
||||||
|
case types.Uint32:
|
||||||
|
o("rr.%s, off, err = unpackUint32(msg, off)\n")
|
||||||
|
case types.Uint64:
|
||||||
|
o("rr.%s, off, err = unpackUint64(msg, off)\n")
|
||||||
|
case types.String:
|
||||||
|
o("rr.%s, off, err = unpackString(msg, off)\n")
|
||||||
|
default:
|
||||||
|
log.Fatalln(name, st.Field(i).Name())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
||||||
|
}
|
||||||
|
// If we've hit len(msg) we return without error.
|
||||||
|
if i < st.NumFields()-1 {
|
||||||
|
fmt.Fprintf(b, `if off == len(msg) {
|
||||||
|
return rr, off, nil
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "return rr, off, err }\n\n")
|
||||||
|
}
|
||||||
|
// Generate typeToUnpack map
|
||||||
|
fmt.Fprintln(b, "var typeToUnpack = map[uint16]func(RR_Header, []byte, int) (RR, int, error){")
|
||||||
|
for _, name := range namedTypes {
|
||||||
|
if name == "RFC3597" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "Type%s: unpack%s,\n", name, name)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(b, "}\n")
|
||||||
|
|
||||||
|
// gofmt
|
||||||
|
res, err := format.Source(b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
b.WriteTo(os.Stderr)
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// write result
|
||||||
|
f, err := os.Create("zmsg.go")
|
||||||
|
fatalIfErr(err)
|
||||||
|
defer f.Close()
|
||||||
|
f.Write(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// structMember will take a tag like dns:"size-base32:SaltLength" and return the last part of this string.
|
||||||
|
func structMember(s string) string {
|
||||||
|
fields := strings.Split(s, ":")
|
||||||
|
if len(fields) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
f := fields[len(fields)-1]
|
||||||
|
// f should have a closing "
|
||||||
|
if len(f) > 1 {
|
||||||
|
return f[:len(f)-1]
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// structTag will take a tag like dns:"size-base32:SaltLength" and return base32.
|
||||||
|
func structTag(s string) string {
|
||||||
|
fields := strings.Split(s, ":")
|
||||||
|
if len(fields) < 2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fields[1][len("\"size-"):]
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatalIfErr(err error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,630 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base32"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// It must be a slice of 4, even if it is 16, we encode only the first 4
|
||||||
|
if off+net.IPv4len > len(msg) {
|
||||||
|
return len(msg), &Error{err: "overflow packing a"}
|
||||||
|
}
|
||||||
|
switch len(a) {
|
||||||
|
case net.IPv4len, net.IPv6len:
|
||||||
|
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) {
|
||||||
|
if off+net.IPv6len > len(msg) {
|
||||||
|
return len(msg), &Error{err: "overflow packing aaaa"}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(aaaa) {
|
||||||
|
case net.IPv6len:
|
||||||
|
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, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pack packs an RR header, returning the offset to the end of the header.
|
||||||
|
// See PackDomainName for documentation about the compression.
|
||||||
|
func (hdr RR_Header) pack(msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) {
|
||||||
|
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(hdr.Rdlength, msg, off)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromBase32(s []byte) (buf []byte, err error) {
|
||||||
|
buflen := base32.HexEncoding.DecodedLen(len(s))
|
||||||
|
buf = make([]byte, buflen)
|
||||||
|
n, err := base32.HexEncoding.Decode(buf, s)
|
||||||
|
buf = buf[:n]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func toBase32(b []byte) string { return base32.HexEncoding.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 uint8(msg[off]), off + 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func packUint8(i uint8, msg []byte, off int) (off1 int, err error) {
|
||||||
|
if off+1 > len(msg) {
|
||||||
|
return len(msg), &Error{err: "overflow packing uint8"}
|
||||||
|
}
|
||||||
|
msg[off] = byte(i)
|
||||||
|
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(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])
|
||||||
|
if off+l+1 > len(msg) {
|
||||||
|
return "", off, &Error{err: "overflow unpacking txt"}
|
||||||
|
}
|
||||||
|
s := make([]byte, 0, l)
|
||||||
|
for _, b := range msg[off+1 : off+1+l] {
|
||||||
|
switch b {
|
||||||
|
case '"', '\\':
|
||||||
|
s = append(s, '\\', b)
|
||||||
|
case '\t', '\r', '\n':
|
||||||
|
s = append(s, b)
|
||||||
|
default:
|
||||||
|
if b < 32 || b > 127 { // unprintable
|
||||||
|
var buf [3]byte
|
||||||
|
bufs := strconv.AppendInt(buf[:0], int64(b), 10)
|
||||||
|
s = append(s, '\\')
|
||||||
|
for i := 0; i < 3-len(bufs); i++ {
|
||||||
|
s = append(s, '0')
|
||||||
|
}
|
||||||
|
for _, r := range bufs {
|
||||||
|
s = append(s, r)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s = append(s, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
off += 1 + l
|
||||||
|
return string(s), off, 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 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:
|
||||||
|
code := uint16(0)
|
||||||
|
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"}
|
||||||
|
}
|
||||||
|
switch code {
|
||||||
|
case EDNS0NSID:
|
||||||
|
e := new(EDNS0_NSID)
|
||||||
|
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||||
|
return nil, len(msg), err
|
||||||
|
}
|
||||||
|
edns = append(edns, e)
|
||||||
|
off += int(optlen)
|
||||||
|
case EDNS0SUBNET, EDNS0SUBNETDRAFT:
|
||||||
|
e := new(EDNS0_SUBNET)
|
||||||
|
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||||
|
return nil, len(msg), err
|
||||||
|
}
|
||||||
|
edns = append(edns, e)
|
||||||
|
off += int(optlen)
|
||||||
|
if code == EDNS0SUBNETDRAFT {
|
||||||
|
e.DraftOption = true
|
||||||
|
}
|
||||||
|
case EDNS0COOKIE:
|
||||||
|
e := new(EDNS0_COOKIE)
|
||||||
|
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||||
|
return nil, len(msg), err
|
||||||
|
}
|
||||||
|
edns = append(edns, e)
|
||||||
|
off += int(optlen)
|
||||||
|
case EDNS0UL:
|
||||||
|
e := new(EDNS0_UL)
|
||||||
|
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||||
|
return nil, len(msg), err
|
||||||
|
}
|
||||||
|
edns = append(edns, e)
|
||||||
|
off += int(optlen)
|
||||||
|
case EDNS0LLQ:
|
||||||
|
e := new(EDNS0_LLQ)
|
||||||
|
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||||
|
return nil, len(msg), err
|
||||||
|
}
|
||||||
|
edns = append(edns, e)
|
||||||
|
off += int(optlen)
|
||||||
|
case EDNS0DAU:
|
||||||
|
e := new(EDNS0_DAU)
|
||||||
|
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||||
|
return nil, len(msg), err
|
||||||
|
}
|
||||||
|
edns = append(edns, e)
|
||||||
|
off += int(optlen)
|
||||||
|
case EDNS0DHU:
|
||||||
|
e := new(EDNS0_DHU)
|
||||||
|
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||||
|
return nil, len(msg), err
|
||||||
|
}
|
||||||
|
edns = append(edns, e)
|
||||||
|
off += int(optlen)
|
||||||
|
case EDNS0N3U:
|
||||||
|
e := new(EDNS0_N3U)
|
||||||
|
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||||
|
return nil, len(msg), err
|
||||||
|
}
|
||||||
|
edns = append(edns, e)
|
||||||
|
off += int(optlen)
|
||||||
|
default:
|
||||||
|
e := new(EDNS0_LOCAL)
|
||||||
|
e.Code = code
|
||||||
|
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||||
|
return nil, len(msg), err
|
||||||
|
}
|
||||||
|
edns = append(edns, e)
|
||||||
|
off += int(optlen)
|
||||||
|
}
|
||||||
|
|
||||||
|
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+3 > len(msg) {
|
||||||
|
return len(msg), &Error{err: "overflow packing opt"}
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint16(msg[off:], el.Option()) // Option code
|
||||||
|
binary.BigEndian.PutUint16(msg[off+2:], uint16(len(b))) // Length
|
||||||
|
off += 4
|
||||||
|
if off+len(b) > len(msg) {
|
||||||
|
copy(msg[off:], b)
|
||||||
|
off = len(msg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 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 := 0; j < length; j++ {
|
||||||
|
b := msg[off+j]
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) {
|
||||||
|
if len(bitmap) == 0 {
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
var lastwindow, lastlength uint16
|
||||||
|
for j := 0; j < len(bitmap); j++ {
|
||||||
|
t := bitmap[j]
|
||||||
|
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 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 map[string]int, compress bool) (int, error) {
|
||||||
|
var err error
|
||||||
|
for j := 0; j < len(names); j++ {
|
||||||
|
off, err = PackDomainName(names[j], msg, off, compression, false && compress)
|
||||||
|
if err != nil {
|
||||||
|
return len(msg), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return off, nil
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type saltWireFmt struct {
|
||||||
|
Salt string `dns:"size-hex"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashName hashes a string (label) according to RFC 5155. It returns the hashed string in uppercase.
|
||||||
|
func HashName(label string, ha uint8, iter uint16, salt string) string {
|
||||||
|
saltwire := new(saltWireFmt)
|
||||||
|
saltwire.Salt = salt
|
||||||
|
wire := make([]byte, DefaultMsgSize)
|
||||||
|
n, err := packSaltWire(saltwire, wire)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
wire = wire[:n]
|
||||||
|
name := make([]byte, 255)
|
||||||
|
off, err := PackDomainName(strings.ToLower(label), name, 0, nil, false)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
name = name[:off]
|
||||||
|
var s hash.Hash
|
||||||
|
switch ha {
|
||||||
|
case SHA1:
|
||||||
|
s = sha1.New()
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// k = 0
|
||||||
|
name = append(name, wire...)
|
||||||
|
io.WriteString(s, string(name))
|
||||||
|
nsec3 := s.Sum(nil)
|
||||||
|
// k > 0
|
||||||
|
for k := uint16(0); k < iter; k++ {
|
||||||
|
s.Reset()
|
||||||
|
nsec3 = append(nsec3, wire...)
|
||||||
|
io.WriteString(s, string(nsec3))
|
||||||
|
nsec3 = s.Sum(nil)
|
||||||
|
}
|
||||||
|
return toBase32(nsec3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Denialer is an interface that should be implemented by types that are used to denial
|
||||||
|
// answers in DNSSEC.
|
||||||
|
type Denialer interface {
|
||||||
|
// Cover will check if the (unhashed) name is being covered by this NSEC or NSEC3.
|
||||||
|
Cover(name string) bool
|
||||||
|
// Match will check if the ownername matches the (unhashed) name for this NSEC3 or NSEC3.
|
||||||
|
Match(name string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cover implements the Denialer interface.
|
||||||
|
func (rr *NSEC) Cover(name string) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match implements the Denialer interface.
|
||||||
|
func (rr *NSEC) Match(name string) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cover implements the Denialer interface.
|
||||||
|
func (rr *NSEC3) Cover(name string) bool {
|
||||||
|
// FIXME(miek): check if the zones match
|
||||||
|
// FIXME(miek): check if we're not dealing with parent nsec3
|
||||||
|
hname := HashName(name, rr.Hash, rr.Iterations, rr.Salt)
|
||||||
|
labels := Split(rr.Hdr.Name)
|
||||||
|
if len(labels) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
hash := strings.ToUpper(rr.Hdr.Name[labels[0] : labels[1]-1]) // -1 to remove the dot
|
||||||
|
if hash == rr.NextDomain {
|
||||||
|
return false // empty interval
|
||||||
|
}
|
||||||
|
if hash > rr.NextDomain { // last name, points to apex
|
||||||
|
// hname > hash
|
||||||
|
// hname > rr.NextDomain
|
||||||
|
// TODO(miek)
|
||||||
|
}
|
||||||
|
if hname <= hash {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if hname >= rr.NextDomain {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match implements the Denialer interface.
|
||||||
|
func (rr *NSEC3) Match(name string) bool {
|
||||||
|
// FIXME(miek): Check if we are in the same zone
|
||||||
|
hname := HashName(name, rr.Hash, rr.Iterations, rr.Salt)
|
||||||
|
labels := Split(rr.Hdr.Name)
|
||||||
|
if len(labels) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
hash := strings.ToUpper(rr.Hdr.Name[labels[0] : labels[1]-1]) // -1 to remove the .
|
||||||
|
if hash == hname {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func packSaltWire(sw *saltWireFmt, msg []byte) (int, error) {
|
||||||
|
off, err := packStringHex(sw.Salt, msg, 0)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
return off, nil
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrivateRdata is an interface used for implementing "Private Use" RR types, see
|
||||||
|
// RFC 6895. This allows one to experiment with new RR types, without requesting an
|
||||||
|
// official type code. Also see dns.PrivateHandle and dns.PrivateHandleRemove.
|
||||||
|
type PrivateRdata interface {
|
||||||
|
// String returns the text presentaton of the Rdata of the Private RR.
|
||||||
|
String() string
|
||||||
|
// Parse parses the Rdata of the private RR.
|
||||||
|
Parse([]string) error
|
||||||
|
// Pack is used when packing a private RR into a buffer.
|
||||||
|
Pack([]byte) (int, error)
|
||||||
|
// Unpack is used when unpacking a private RR from a buffer.
|
||||||
|
// TODO(miek): diff. signature than Pack, see edns0.go for instance.
|
||||||
|
Unpack([]byte) (int, error)
|
||||||
|
// Copy copies the Rdata.
|
||||||
|
Copy(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
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkPrivateRR(rrtype uint16) *PrivateRR {
|
||||||
|
// Panics if RR is not an instance of PrivateRR.
|
||||||
|
rrfunc, ok := TypeToRR[rrtype]
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("dns: invalid operation with Private RR type %d", rrtype))
|
||||||
|
}
|
||||||
|
|
||||||
|
anyrr := rrfunc()
|
||||||
|
switch rr := anyrr.(type) {
|
||||||
|
case *PrivateRR:
|
||||||
|
return rr
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("dns: RR is not a PrivateRR, TypeToRR[%d] generator returned %T", rrtype, anyrr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() int { return r.Hdr.len() + r.Data.Len() }
|
||||||
|
func (r *PrivateRR) copy() RR {
|
||||||
|
// make new RR like this:
|
||||||
|
rr := mkPrivateRR(r.Hdr.Rrtype)
|
||||||
|
newh := r.Hdr.copyHeader()
|
||||||
|
rr.Hdr = *newh
|
||||||
|
|
||||||
|
err := r.Data.Copy(rr.Data)
|
||||||
|
if err != nil {
|
||||||
|
panic("dns: got value that could not be used to copy Private rdata")
|
||||||
|
}
|
||||||
|
return rr
|
||||||
|
}
|
||||||
|
func (r *PrivateRR) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) {
|
||||||
|
off, err := r.Hdr.pack(msg, off, compression, compress)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
headerEnd := off
|
||||||
|
n, err := r.Data.Pack(msg[off:])
|
||||||
|
if err != nil {
|
||||||
|
return len(msg), err
|
||||||
|
}
|
||||||
|
off += n
|
||||||
|
r.Header().Rdlength = uint16(off - headerEnd)
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()} }
|
||||||
|
TypeToString[rtype] = rtypestr
|
||||||
|
StringToType[rtypestr] = rtype
|
||||||
|
|
||||||
|
typeToUnpack[rtype] = func(h RR_Header, msg []byte, off int) (RR, int, error) {
|
||||||
|
if noRdata(h) {
|
||||||
|
return &h, off, nil
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
rr := mkPrivateRR(h.Rrtype)
|
||||||
|
rr.Hdr = h
|
||||||
|
|
||||||
|
off1, err := rr.Data.Unpack(msg[off:])
|
||||||
|
off += off1
|
||||||
|
if err != nil {
|
||||||
|
return rr, off, err
|
||||||
|
}
|
||||||
|
return rr, off, err
|
||||||
|
}
|
||||||
|
|
||||||
|
setPrivateRR := func(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) {
|
||||||
|
rr := mkPrivateRR(h.Rrtype)
|
||||||
|
rr.Hdr = h
|
||||||
|
|
||||||
|
var l lex
|
||||||
|
text := make([]string, 0, 2) // could be 0..N elements, median is probably 1
|
||||||
|
Fetch:
|
||||||
|
for {
|
||||||
|
// TODO(miek): we could also be returning _QUOTE, this might or might not
|
||||||
|
// be an issue (basically parsing TXT becomes hard)
|
||||||
|
switch l = <-c; l.value {
|
||||||
|
case zNewline, zEOF:
|
||||||
|
break Fetch
|
||||||
|
case zString:
|
||||||
|
text = append(text, l.token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := rr.Data.Parse(text)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &ParseError{f, err.Error(), l}, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return rr, nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
typeToparserFunc[rtype] = parserFunc{setPrivateRR, true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivateHandleRemove removes defenitions required to support private RR type.
|
||||||
|
func PrivateHandleRemove(rtype uint16) {
|
||||||
|
rtypestr, ok := TypeToString[rtype]
|
||||||
|
if ok {
|
||||||
|
delete(TypeToRR, rtype)
|
||||||
|
delete(TypeToString, rtype)
|
||||||
|
delete(typeToparserFunc, rtype)
|
||||||
|
delete(StringToType, rtypestr)
|
||||||
|
delete(typeToUnpack, rtype)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
// rawSetRdlength sets the rdlength in the header of
|
||||||
|
// the RR. The offset 'off' must be positioned at the
|
||||||
|
// start of the header of the RR, 'end' must be the
|
||||||
|
// end of the RR.
|
||||||
|
func rawSetRdlength(msg []byte, off, end int) bool {
|
||||||
|
l := len(msg)
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
if off+1 > l {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c := int(msg[off])
|
||||||
|
off++
|
||||||
|
switch c & 0xC0 {
|
||||||
|
case 0x00:
|
||||||
|
if c == 0x00 {
|
||||||
|
// End of the domainname
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
if off+c > l {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
off += c
|
||||||
|
|
||||||
|
case 0xC0:
|
||||||
|
// pointer, next byte included, ends domainname
|
||||||
|
off++
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The domainname has been seen, we at the start of the fixed part in the header.
|
||||||
|
// Type is 2 bytes, class is 2 bytes, ttl 4 and then 2 bytes for the length.
|
||||||
|
off += 2 + 2 + 4
|
||||||
|
if off+2 > l {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
//off+1 is the end of the header, 'end' is the end of the rr
|
||||||
|
//so 'end' - 'off+2' is the length of the rdata
|
||||||
|
rdatalen := end - (off + 2)
|
||||||
|
if rdatalen > 0xFFFF {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint16(msg[off:], uint16(rdatalen))
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Map of opcodes strings.
|
||||||
|
var StringToOpcode = reverseInt(OpcodeToString)
|
||||||
|
|
||||||
|
// Map of rcodes strings.
|
||||||
|
var StringToRcode = reverseInt(RcodeToString)
|
||||||
|
|
||||||
|
// 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,84 @@
|
||||||
|
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 temporay. 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 _, ok := m[key]; ok {
|
||||||
|
// Shortest TTL wins.
|
||||||
|
if m[key].Header().Ttl > r.Header().Ttl {
|
||||||
|
m[key].Header().Ttl = r.Header().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])
|
||||||
|
}
|
|
@ -0,0 +1,974 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type debugging bool
|
||||||
|
|
||||||
|
const debug debugging = false
|
||||||
|
|
||||||
|
func (d debugging) Printf(format string, args ...interface{}) {
|
||||||
|
if d {
|
||||||
|
log.Printf(format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxTok = 2048 // Largest token we can return.
|
||||||
|
const maxUint16 = 1<<16 - 1
|
||||||
|
|
||||||
|
// Tokinize a RFC 1035 zone file. The tokenizer will normalize it:
|
||||||
|
// * Add ownernames if they are left blank;
|
||||||
|
// * Suppress sequences of spaces;
|
||||||
|
// * Make each RR fit on one line (_NEWLINE is send as last)
|
||||||
|
// * Handle comments: ;
|
||||||
|
// * Handle braces - anywhere.
|
||||||
|
const (
|
||||||
|
// Zonefile
|
||||||
|
zEOF = iota
|
||||||
|
zString
|
||||||
|
zBlank
|
||||||
|
zQuote
|
||||||
|
zNewline
|
||||||
|
zRrtpe
|
||||||
|
zOwner
|
||||||
|
zClass
|
||||||
|
zDirOrigin // $ORIGIN
|
||||||
|
zDirTtl // $TTL
|
||||||
|
zDirInclude // $INCLUDE
|
||||||
|
zDirGenerate // $GENERATE
|
||||||
|
|
||||||
|
// Privatekey file
|
||||||
|
zValue
|
||||||
|
zKey
|
||||||
|
|
||||||
|
zExpectOwnerDir // Ownername
|
||||||
|
zExpectOwnerBl // Whitespace after the ownername
|
||||||
|
zExpectAny // Expect rrtype, ttl or class
|
||||||
|
zExpectAnyNoClass // Expect rrtype or ttl
|
||||||
|
zExpectAnyNoClassBl // The whitespace after _EXPECT_ANY_NOCLASS
|
||||||
|
zExpectAnyNoTtl // Expect rrtype or class
|
||||||
|
zExpectAnyNoTtlBl // Whitespace after _EXPECT_ANY_NOTTL
|
||||||
|
zExpectRrtype // Expect rrtype
|
||||||
|
zExpectRrtypeBl // Whitespace BEFORE rrtype
|
||||||
|
zExpectRdata // The first element of the rdata
|
||||||
|
zExpectDirTtlBl // Space after directive $TTL
|
||||||
|
zExpectDirTtl // Directive $TTL
|
||||||
|
zExpectDirOriginBl // Space after directive $ORIGIN
|
||||||
|
zExpectDirOrigin // Directive $ORIGIN
|
||||||
|
zExpectDirIncludeBl // Space after directive $INCLUDE
|
||||||
|
zExpectDirInclude // Directive $INCLUDE
|
||||||
|
zExpectDirGenerate // Directive $GENERATE
|
||||||
|
zExpectDirGenerateBl // Space after directive $GENERATE
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseError is a parsing error. It contains the parse error and the location in the io.Reader
|
||||||
|
// where the error occurred.
|
||||||
|
type ParseError struct {
|
||||||
|
file string
|
||||||
|
err string
|
||||||
|
lex lex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ParseError) Error() (s string) {
|
||||||
|
if e.file != "" {
|
||||||
|
s = e.file + ": "
|
||||||
|
}
|
||||||
|
s += "dns: " + e.err + ": " + strconv.QuoteToASCII(e.lex.token) + " at line: " +
|
||||||
|
strconv.Itoa(e.lex.line) + ":" + strconv.Itoa(e.lex.column)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type lex struct {
|
||||||
|
token string // text of the token
|
||||||
|
tokenUpper string // uppercase text of the token
|
||||||
|
length int // length of the token
|
||||||
|
err bool // when true, token text has lexer error
|
||||||
|
value uint8 // value: zString, _BLANK, etc.
|
||||||
|
line int // line in the file
|
||||||
|
column int // column in the file
|
||||||
|
torc uint16 // type or class as parsed in the lexer, we only need to look this up in the grammar
|
||||||
|
comment string // any comment text seen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token holds the token that are returned when a zone file is parsed.
|
||||||
|
type Token struct {
|
||||||
|
// The scanned resource record when error is not nil.
|
||||||
|
RR
|
||||||
|
// When an error occurred, this has the error specifics.
|
||||||
|
Error *ParseError
|
||||||
|
// A potential comment positioned after the RR and on the same line.
|
||||||
|
Comment string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRR reads the RR contained in the string s. Only the first RR is
|
||||||
|
// returned. If s contains no RR, return nil with no error. The class
|
||||||
|
// defaults to IN and TTL defaults to 3600. The full zone file syntax
|
||||||
|
// like $TTL, $ORIGIN, etc. is supported. All fields of the returned
|
||||||
|
// RR are set, except RR.Header().Rdlength which is set to 0.
|
||||||
|
func NewRR(s string) (RR, error) {
|
||||||
|
if len(s) > 0 && s[len(s)-1] != '\n' { // We need a closing newline
|
||||||
|
return ReadRR(strings.NewReader(s+"\n"), "")
|
||||||
|
}
|
||||||
|
return ReadRR(strings.NewReader(s), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadRR reads the RR contained in q.
|
||||||
|
// See NewRR for more documentation.
|
||||||
|
func ReadRR(q io.Reader, filename string) (RR, error) {
|
||||||
|
r := <-parseZoneHelper(q, ".", filename, 1)
|
||||||
|
if r == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Error != nil {
|
||||||
|
return nil, r.Error
|
||||||
|
}
|
||||||
|
return r.RR, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseZone reads a RFC 1035 style zonefile from r. It returns *Tokens on the
|
||||||
|
// returned channel, which consist out the parsed RR, a potential comment or an error.
|
||||||
|
// If there is an error the RR is nil. The string file is only used
|
||||||
|
// in error reporting. The string origin is used as the initial origin, as
|
||||||
|
// if the file would start with: $ORIGIN origin .
|
||||||
|
// The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are supported.
|
||||||
|
// The channel t is closed by ParseZone when the end of r is reached.
|
||||||
|
//
|
||||||
|
// Basic usage pattern when reading from a string (z) containing the
|
||||||
|
// zone data:
|
||||||
|
//
|
||||||
|
// for x := range dns.ParseZone(strings.NewReader(z), "", "") {
|
||||||
|
// if x.Error != nil {
|
||||||
|
// // log.Println(x.Error)
|
||||||
|
// } else {
|
||||||
|
// // Do something with x.RR
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Comments specified after an RR (and on the same line!) are returned too:
|
||||||
|
//
|
||||||
|
// foo. IN A 10.0.0.1 ; this is a comment
|
||||||
|
//
|
||||||
|
// The text "; this is comment" is returned in Token.Comment. Comments inside the
|
||||||
|
// RR are discarded. Comments on a line by themselves are discarded too.
|
||||||
|
func ParseZone(r io.Reader, origin, file string) chan *Token {
|
||||||
|
return parseZoneHelper(r, origin, file, 10000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseZoneHelper(r io.Reader, origin, file string, chansize int) chan *Token {
|
||||||
|
t := make(chan *Token, chansize)
|
||||||
|
go parseZone(r, origin, file, t, 0)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseZone(r io.Reader, origin, f string, t chan *Token, include int) {
|
||||||
|
defer func() {
|
||||||
|
if include == 0 {
|
||||||
|
close(t)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s := scanInit(r)
|
||||||
|
c := make(chan lex)
|
||||||
|
// Start the lexer
|
||||||
|
go zlexer(s, c)
|
||||||
|
// 6 possible beginnings of a line, _ is a space
|
||||||
|
// 0. zRRTYPE -> all omitted until the rrtype
|
||||||
|
// 1. zOwner _ zRrtype -> class/ttl omitted
|
||||||
|
// 2. zOwner _ zString _ zRrtype -> class omitted
|
||||||
|
// 3. zOwner _ zString _ zClass _ zRrtype -> ttl/class
|
||||||
|
// 4. zOwner _ zClass _ zRrtype -> ttl omitted
|
||||||
|
// 5. zOwner _ zClass _ zString _ zRrtype -> class/ttl (reversed)
|
||||||
|
// After detecting these, we know the zRrtype so we can jump to functions
|
||||||
|
// handling the rdata for each of these types.
|
||||||
|
|
||||||
|
if origin == "" {
|
||||||
|
origin = "."
|
||||||
|
}
|
||||||
|
origin = Fqdn(origin)
|
||||||
|
if _, ok := IsDomainName(origin); !ok {
|
||||||
|
t <- &Token{Error: &ParseError{f, "bad initial origin name", lex{}}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
st := zExpectOwnerDir // initial state
|
||||||
|
var h RR_Header
|
||||||
|
var defttl uint32 = defaultTtl
|
||||||
|
var prevName string
|
||||||
|
for l := range c {
|
||||||
|
// Lexer spotted an error already
|
||||||
|
if l.err == true {
|
||||||
|
t <- &Token{Error: &ParseError{f, l.token, l}}
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
switch st {
|
||||||
|
case zExpectOwnerDir:
|
||||||
|
// We can also expect a directive, like $TTL or $ORIGIN
|
||||||
|
h.Ttl = defttl
|
||||||
|
h.Class = ClassINET
|
||||||
|
switch l.value {
|
||||||
|
case zNewline:
|
||||||
|
st = zExpectOwnerDir
|
||||||
|
case zOwner:
|
||||||
|
h.Name = l.token
|
||||||
|
if l.token[0] == '@' {
|
||||||
|
h.Name = origin
|
||||||
|
prevName = h.Name
|
||||||
|
st = zExpectOwnerBl
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if h.Name[l.length-1] != '.' {
|
||||||
|
h.Name = appendOrigin(h.Name, origin)
|
||||||
|
}
|
||||||
|
_, ok := IsDomainName(l.token)
|
||||||
|
if !ok {
|
||||||
|
t <- &Token{Error: &ParseError{f, "bad owner name", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
prevName = h.Name
|
||||||
|
st = zExpectOwnerBl
|
||||||
|
case zDirTtl:
|
||||||
|
st = zExpectDirTtlBl
|
||||||
|
case zDirOrigin:
|
||||||
|
st = zExpectDirOriginBl
|
||||||
|
case zDirInclude:
|
||||||
|
st = zExpectDirIncludeBl
|
||||||
|
case zDirGenerate:
|
||||||
|
st = zExpectDirGenerateBl
|
||||||
|
case zRrtpe:
|
||||||
|
h.Name = prevName
|
||||||
|
h.Rrtype = l.torc
|
||||||
|
st = zExpectRdata
|
||||||
|
case zClass:
|
||||||
|
h.Name = prevName
|
||||||
|
h.Class = l.torc
|
||||||
|
st = zExpectAnyNoClassBl
|
||||||
|
case zBlank:
|
||||||
|
// Discard, can happen when there is nothing on the
|
||||||
|
// line except the RR type
|
||||||
|
case zString:
|
||||||
|
ttl, ok := stringToTtl(l.token)
|
||||||
|
if !ok {
|
||||||
|
t <- &Token{Error: &ParseError{f, "not a TTL", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.Ttl = ttl
|
||||||
|
// Don't about the defttl, we should take the $TTL value
|
||||||
|
// defttl = ttl
|
||||||
|
st = zExpectAnyNoTtlBl
|
||||||
|
|
||||||
|
default:
|
||||||
|
t <- &Token{Error: &ParseError{f, "syntax error at beginning", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case zExpectDirIncludeBl:
|
||||||
|
if l.value != zBlank {
|
||||||
|
t <- &Token{Error: &ParseError{f, "no blank after $INCLUDE-directive", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st = zExpectDirInclude
|
||||||
|
case zExpectDirInclude:
|
||||||
|
if l.value != zString {
|
||||||
|
t <- &Token{Error: &ParseError{f, "expecting $INCLUDE value, not this...", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
neworigin := origin // There may be optionally a new origin set after the filename, if not use current one
|
||||||
|
l := <-c
|
||||||
|
switch l.value {
|
||||||
|
case zBlank:
|
||||||
|
l := <-c
|
||||||
|
if l.value == zString {
|
||||||
|
if _, ok := IsDomainName(l.token); !ok || l.length == 0 || l.err {
|
||||||
|
t <- &Token{Error: &ParseError{f, "bad origin name", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// a new origin is specified.
|
||||||
|
if l.token[l.length-1] != '.' {
|
||||||
|
if origin != "." { // Prevent .. endings
|
||||||
|
neworigin = l.token + "." + origin
|
||||||
|
} else {
|
||||||
|
neworigin = l.token + origin
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
neworigin = l.token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case zNewline, zEOF:
|
||||||
|
// Ok
|
||||||
|
default:
|
||||||
|
t <- &Token{Error: &ParseError{f, "garbage after $INCLUDE", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Start with the new file
|
||||||
|
r1, e1 := os.Open(l.token)
|
||||||
|
if e1 != nil {
|
||||||
|
t <- &Token{Error: &ParseError{f, "failed to open `" + l.token + "'", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if include+1 > 7 {
|
||||||
|
t <- &Token{Error: &ParseError{f, "too deeply nested $INCLUDE", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
parseZone(r1, l.token, neworigin, t, include+1)
|
||||||
|
st = zExpectOwnerDir
|
||||||
|
case zExpectDirTtlBl:
|
||||||
|
if l.value != zBlank {
|
||||||
|
t <- &Token{Error: &ParseError{f, "no blank after $TTL-directive", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st = zExpectDirTtl
|
||||||
|
case zExpectDirTtl:
|
||||||
|
if l.value != zString {
|
||||||
|
t <- &Token{Error: &ParseError{f, "expecting $TTL value, not this...", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e, _ := slurpRemainder(c, f); e != nil {
|
||||||
|
t <- &Token{Error: e}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ttl, ok := stringToTtl(l.token)
|
||||||
|
if !ok {
|
||||||
|
t <- &Token{Error: &ParseError{f, "expecting $TTL value, not this...", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defttl = ttl
|
||||||
|
st = zExpectOwnerDir
|
||||||
|
case zExpectDirOriginBl:
|
||||||
|
if l.value != zBlank {
|
||||||
|
t <- &Token{Error: &ParseError{f, "no blank after $ORIGIN-directive", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st = zExpectDirOrigin
|
||||||
|
case zExpectDirOrigin:
|
||||||
|
if l.value != zString {
|
||||||
|
t <- &Token{Error: &ParseError{f, "expecting $ORIGIN value, not this...", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e, _ := slurpRemainder(c, f); e != nil {
|
||||||
|
t <- &Token{Error: e}
|
||||||
|
}
|
||||||
|
if _, ok := IsDomainName(l.token); !ok {
|
||||||
|
t <- &Token{Error: &ParseError{f, "bad origin name", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if l.token[l.length-1] != '.' {
|
||||||
|
if origin != "." { // Prevent .. endings
|
||||||
|
origin = l.token + "." + origin
|
||||||
|
} else {
|
||||||
|
origin = l.token + origin
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
origin = l.token
|
||||||
|
}
|
||||||
|
st = zExpectOwnerDir
|
||||||
|
case zExpectDirGenerateBl:
|
||||||
|
if l.value != zBlank {
|
||||||
|
t <- &Token{Error: &ParseError{f, "no blank after $GENERATE-directive", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st = zExpectDirGenerate
|
||||||
|
case zExpectDirGenerate:
|
||||||
|
if l.value != zString {
|
||||||
|
t <- &Token{Error: &ParseError{f, "expecting $GENERATE value, not this...", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errMsg := generate(l, c, t, origin); errMsg != "" {
|
||||||
|
t <- &Token{Error: &ParseError{f, errMsg, l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st = zExpectOwnerDir
|
||||||
|
case zExpectOwnerBl:
|
||||||
|
if l.value != zBlank {
|
||||||
|
t <- &Token{Error: &ParseError{f, "no blank after owner", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st = zExpectAny
|
||||||
|
case zExpectAny:
|
||||||
|
switch l.value {
|
||||||
|
case zRrtpe:
|
||||||
|
h.Rrtype = l.torc
|
||||||
|
st = zExpectRdata
|
||||||
|
case zClass:
|
||||||
|
h.Class = l.torc
|
||||||
|
st = zExpectAnyNoClassBl
|
||||||
|
case zString:
|
||||||
|
ttl, ok := stringToTtl(l.token)
|
||||||
|
if !ok {
|
||||||
|
t <- &Token{Error: &ParseError{f, "not a TTL", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.Ttl = ttl
|
||||||
|
// defttl = ttl // don't set the defttl here
|
||||||
|
st = zExpectAnyNoTtlBl
|
||||||
|
default:
|
||||||
|
t <- &Token{Error: &ParseError{f, "expecting RR type, TTL or class, not this...", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case zExpectAnyNoClassBl:
|
||||||
|
if l.value != zBlank {
|
||||||
|
t <- &Token{Error: &ParseError{f, "no blank before class", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st = zExpectAnyNoClass
|
||||||
|
case zExpectAnyNoTtlBl:
|
||||||
|
if l.value != zBlank {
|
||||||
|
t <- &Token{Error: &ParseError{f, "no blank before TTL", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st = zExpectAnyNoTtl
|
||||||
|
case zExpectAnyNoTtl:
|
||||||
|
switch l.value {
|
||||||
|
case zClass:
|
||||||
|
h.Class = l.torc
|
||||||
|
st = zExpectRrtypeBl
|
||||||
|
case zRrtpe:
|
||||||
|
h.Rrtype = l.torc
|
||||||
|
st = zExpectRdata
|
||||||
|
default:
|
||||||
|
t <- &Token{Error: &ParseError{f, "expecting RR type or class, not this...", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case zExpectAnyNoClass:
|
||||||
|
switch l.value {
|
||||||
|
case zString:
|
||||||
|
ttl, ok := stringToTtl(l.token)
|
||||||
|
if !ok {
|
||||||
|
t <- &Token{Error: &ParseError{f, "not a TTL", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.Ttl = ttl
|
||||||
|
// defttl = ttl // don't set the def ttl anymore
|
||||||
|
st = zExpectRrtypeBl
|
||||||
|
case zRrtpe:
|
||||||
|
h.Rrtype = l.torc
|
||||||
|
st = zExpectRdata
|
||||||
|
default:
|
||||||
|
t <- &Token{Error: &ParseError{f, "expecting RR type or TTL, not this...", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case zExpectRrtypeBl:
|
||||||
|
if l.value != zBlank {
|
||||||
|
t <- &Token{Error: &ParseError{f, "no blank before RR type", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st = zExpectRrtype
|
||||||
|
case zExpectRrtype:
|
||||||
|
if l.value != zRrtpe {
|
||||||
|
t <- &Token{Error: &ParseError{f, "unknown RR type", l}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.Rrtype = l.torc
|
||||||
|
st = zExpectRdata
|
||||||
|
case zExpectRdata:
|
||||||
|
r, e, c1 := setRR(h, c, origin, f)
|
||||||
|
if e != nil {
|
||||||
|
// If e.lex is nil than we have encounter a unknown RR type
|
||||||
|
// in that case we substitute our current lex token
|
||||||
|
if e.lex.token == "" && e.lex.value == 0 {
|
||||||
|
e.lex = l // Uh, dirty
|
||||||
|
}
|
||||||
|
t <- &Token{Error: e}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t <- &Token{RR: r, Comment: c1}
|
||||||
|
st = zExpectOwnerDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we get here, we and the h.Rrtype is still zero, we haven't parsed anything, this
|
||||||
|
// is not an error, because an empty zone file is still a zone file.
|
||||||
|
}
|
||||||
|
|
||||||
|
// zlexer scans the sourcefile and returns tokens on the channel c.
|
||||||
|
func zlexer(s *scan, c chan lex) {
|
||||||
|
var l lex
|
||||||
|
str := make([]byte, maxTok) // Should be enough for any token
|
||||||
|
stri := 0 // Offset in str (0 means empty)
|
||||||
|
com := make([]byte, maxTok) // Hold comment text
|
||||||
|
comi := 0
|
||||||
|
quote := false
|
||||||
|
escape := false
|
||||||
|
space := false
|
||||||
|
commt := false
|
||||||
|
rrtype := false
|
||||||
|
owner := true
|
||||||
|
brace := 0
|
||||||
|
x, err := s.tokenText()
|
||||||
|
defer close(c)
|
||||||
|
for err == nil {
|
||||||
|
l.column = s.position.Column
|
||||||
|
l.line = s.position.Line
|
||||||
|
if stri >= maxTok {
|
||||||
|
l.token = "token length insufficient for parsing"
|
||||||
|
l.err = true
|
||||||
|
debug.Printf("[%+v]", l.token)
|
||||||
|
c <- l
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if comi >= maxTok {
|
||||||
|
l.token = "comment length insufficient for parsing"
|
||||||
|
l.err = true
|
||||||
|
debug.Printf("[%+v]", l.token)
|
||||||
|
c <- l
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch x {
|
||||||
|
case ' ', '\t':
|
||||||
|
if escape {
|
||||||
|
escape = false
|
||||||
|
str[stri] = x
|
||||||
|
stri++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if quote {
|
||||||
|
// Inside quotes this is legal
|
||||||
|
str[stri] = x
|
||||||
|
stri++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if commt {
|
||||||
|
com[comi] = x
|
||||||
|
comi++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if stri == 0 {
|
||||||
|
// Space directly in the beginning, handled in the grammar
|
||||||
|
} else if owner {
|
||||||
|
// If we have a string and its the first, make it an owner
|
||||||
|
l.value = zOwner
|
||||||
|
l.token = string(str[:stri])
|
||||||
|
l.tokenUpper = strings.ToUpper(l.token)
|
||||||
|
l.length = stri
|
||||||
|
// escape $... start with a \ not a $, so this will work
|
||||||
|
switch l.tokenUpper {
|
||||||
|
case "$TTL":
|
||||||
|
l.value = zDirTtl
|
||||||
|
case "$ORIGIN":
|
||||||
|
l.value = zDirOrigin
|
||||||
|
case "$INCLUDE":
|
||||||
|
l.value = zDirInclude
|
||||||
|
case "$GENERATE":
|
||||||
|
l.value = zDirGenerate
|
||||||
|
}
|
||||||
|
debug.Printf("[7 %+v]", l.token)
|
||||||
|
c <- l
|
||||||
|
} else {
|
||||||
|
l.value = zString
|
||||||
|
l.token = string(str[:stri])
|
||||||
|
l.tokenUpper = strings.ToUpper(l.token)
|
||||||
|
l.length = stri
|
||||||
|
if !rrtype {
|
||||||
|
if t, ok := StringToType[l.tokenUpper]; ok {
|
||||||
|
l.value = zRrtpe
|
||||||
|
l.torc = t
|
||||||
|
rrtype = true
|
||||||
|
} else {
|
||||||
|
if strings.HasPrefix(l.tokenUpper, "TYPE") {
|
||||||
|
t, ok := typeToInt(l.token)
|
||||||
|
if !ok {
|
||||||
|
l.token = "unknown RR type"
|
||||||
|
l.err = true
|
||||||
|
c <- l
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.value = zRrtpe
|
||||||
|
l.torc = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t, ok := StringToClass[l.tokenUpper]; ok {
|
||||||
|
l.value = zClass
|
||||||
|
l.torc = t
|
||||||
|
} else {
|
||||||
|
if strings.HasPrefix(l.tokenUpper, "CLASS") {
|
||||||
|
t, ok := classToInt(l.token)
|
||||||
|
if !ok {
|
||||||
|
l.token = "unknown class"
|
||||||
|
l.err = true
|
||||||
|
c <- l
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.value = zClass
|
||||||
|
l.torc = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug.Printf("[6 %+v]", l.token)
|
||||||
|
c <- l
|
||||||
|
}
|
||||||
|
stri = 0
|
||||||
|
// I reverse space stuff here
|
||||||
|
if !space && !commt {
|
||||||
|
l.value = zBlank
|
||||||
|
l.token = " "
|
||||||
|
l.length = 1
|
||||||
|
debug.Printf("[5 %+v]", l.token)
|
||||||
|
c <- l
|
||||||
|
}
|
||||||
|
owner = false
|
||||||
|
space = true
|
||||||
|
case ';':
|
||||||
|
if escape {
|
||||||
|
escape = false
|
||||||
|
str[stri] = x
|
||||||
|
stri++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if quote {
|
||||||
|
// Inside quotes this is legal
|
||||||
|
str[stri] = x
|
||||||
|
stri++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if stri > 0 {
|
||||||
|
l.value = zString
|
||||||
|
l.token = string(str[:stri])
|
||||||
|
l.length = stri
|
||||||
|
debug.Printf("[4 %+v]", l.token)
|
||||||
|
c <- l
|
||||||
|
stri = 0
|
||||||
|
}
|
||||||
|
commt = true
|
||||||
|
com[comi] = ';'
|
||||||
|
comi++
|
||||||
|
case '\r':
|
||||||
|
escape = false
|
||||||
|
if quote {
|
||||||
|
str[stri] = x
|
||||||
|
stri++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// discard if outside of quotes
|
||||||
|
case '\n':
|
||||||
|
escape = false
|
||||||
|
// Escaped newline
|
||||||
|
if quote {
|
||||||
|
str[stri] = x
|
||||||
|
stri++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// inside quotes this is legal
|
||||||
|
if commt {
|
||||||
|
// Reset a comment
|
||||||
|
commt = false
|
||||||
|
rrtype = false
|
||||||
|
stri = 0
|
||||||
|
// If not in a brace this ends the comment AND the RR
|
||||||
|
if brace == 0 {
|
||||||
|
owner = true
|
||||||
|
owner = true
|
||||||
|
l.value = zNewline
|
||||||
|
l.token = "\n"
|
||||||
|
l.length = 1
|
||||||
|
l.comment = string(com[:comi])
|
||||||
|
debug.Printf("[3 %+v %+v]", l.token, l.comment)
|
||||||
|
c <- l
|
||||||
|
l.comment = ""
|
||||||
|
comi = 0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
com[comi] = ' ' // convert newline to space
|
||||||
|
comi++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if brace == 0 {
|
||||||
|
// If there is previous text, we should output it here
|
||||||
|
if stri != 0 {
|
||||||
|
l.value = zString
|
||||||
|
l.token = string(str[:stri])
|
||||||
|
l.tokenUpper = strings.ToUpper(l.token)
|
||||||
|
|
||||||
|
l.length = stri
|
||||||
|
if !rrtype {
|
||||||
|
if t, ok := StringToType[l.tokenUpper]; ok {
|
||||||
|
l.value = zRrtpe
|
||||||
|
l.torc = t
|
||||||
|
rrtype = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug.Printf("[2 %+v]", l.token)
|
||||||
|
c <- l
|
||||||
|
}
|
||||||
|
l.value = zNewline
|
||||||
|
l.token = "\n"
|
||||||
|
l.length = 1
|
||||||
|
debug.Printf("[1 %+v]", l.token)
|
||||||
|
c <- l
|
||||||
|
stri = 0
|
||||||
|
commt = false
|
||||||
|
rrtype = false
|
||||||
|
owner = true
|
||||||
|
comi = 0
|
||||||
|
}
|
||||||
|
case '\\':
|
||||||
|
// comments do not get escaped chars, everything is copied
|
||||||
|
if commt {
|
||||||
|
com[comi] = x
|
||||||
|
comi++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// something already escaped must be in string
|
||||||
|
if escape {
|
||||||
|
str[stri] = x
|
||||||
|
stri++
|
||||||
|
escape = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// something escaped outside of string gets added to string
|
||||||
|
str[stri] = x
|
||||||
|
stri++
|
||||||
|
escape = true
|
||||||
|
case '"':
|
||||||
|
if commt {
|
||||||
|
com[comi] = x
|
||||||
|
comi++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if escape {
|
||||||
|
str[stri] = x
|
||||||
|
stri++
|
||||||
|
escape = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
space = false
|
||||||
|
// send previous gathered text and the quote
|
||||||
|
if stri != 0 {
|
||||||
|
l.value = zString
|
||||||
|
l.token = string(str[:stri])
|
||||||
|
l.length = stri
|
||||||
|
|
||||||
|
debug.Printf("[%+v]", l.token)
|
||||||
|
c <- l
|
||||||
|
stri = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// send quote itself as separate token
|
||||||
|
l.value = zQuote
|
||||||
|
l.token = "\""
|
||||||
|
l.length = 1
|
||||||
|
c <- l
|
||||||
|
quote = !quote
|
||||||
|
case '(', ')':
|
||||||
|
if commt {
|
||||||
|
com[comi] = x
|
||||||
|
comi++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if escape {
|
||||||
|
str[stri] = x
|
||||||
|
stri++
|
||||||
|
escape = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if quote {
|
||||||
|
str[stri] = x
|
||||||
|
stri++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch x {
|
||||||
|
case ')':
|
||||||
|
brace--
|
||||||
|
if brace < 0 {
|
||||||
|
l.token = "extra closing brace"
|
||||||
|
l.err = true
|
||||||
|
debug.Printf("[%+v]", l.token)
|
||||||
|
c <- l
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case '(':
|
||||||
|
brace++
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
escape = false
|
||||||
|
if commt {
|
||||||
|
com[comi] = x
|
||||||
|
comi++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
str[stri] = x
|
||||||
|
stri++
|
||||||
|
space = false
|
||||||
|
}
|
||||||
|
x, err = s.tokenText()
|
||||||
|
}
|
||||||
|
if stri > 0 {
|
||||||
|
// Send remainder
|
||||||
|
l.token = string(str[:stri])
|
||||||
|
l.length = stri
|
||||||
|
l.value = zString
|
||||||
|
debug.Printf("[%+v]", l.token)
|
||||||
|
c <- l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the class number from CLASSxx
|
||||||
|
func classToInt(token string) (uint16, bool) {
|
||||||
|
offset := 5
|
||||||
|
if len(token) < offset+1 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
class, ok := strconv.Atoi(token[offset:])
|
||||||
|
if ok != nil || class > maxUint16 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return uint16(class), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the rr number from TYPExxx
|
||||||
|
func typeToInt(token string) (uint16, bool) {
|
||||||
|
offset := 4
|
||||||
|
if len(token) < offset+1 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
typ, ok := strconv.Atoi(token[offset:])
|
||||||
|
if ok != nil || typ > maxUint16 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return uint16(typ), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse things like 2w, 2m, etc, Return the time in seconds.
|
||||||
|
func stringToTtl(token string) (uint32, bool) {
|
||||||
|
s := uint32(0)
|
||||||
|
i := uint32(0)
|
||||||
|
for _, c := range token {
|
||||||
|
switch c {
|
||||||
|
case 's', 'S':
|
||||||
|
s += i
|
||||||
|
i = 0
|
||||||
|
case 'm', 'M':
|
||||||
|
s += i * 60
|
||||||
|
i = 0
|
||||||
|
case 'h', 'H':
|
||||||
|
s += i * 60 * 60
|
||||||
|
i = 0
|
||||||
|
case 'd', 'D':
|
||||||
|
s += i * 60 * 60 * 24
|
||||||
|
i = 0
|
||||||
|
case 'w', 'W':
|
||||||
|
s += i * 60 * 60 * 24 * 7
|
||||||
|
i = 0
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
i *= 10
|
||||||
|
i += uint32(c) - '0'
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s + i, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse LOC records' <digits>[.<digits>][mM] into a
|
||||||
|
// mantissa exponent format. Token should contain the entire
|
||||||
|
// string (i.e. no spaces allowed)
|
||||||
|
func stringToCm(token string) (e, m uint8, ok bool) {
|
||||||
|
if token[len(token)-1] == 'M' || token[len(token)-1] == 'm' {
|
||||||
|
token = token[0 : len(token)-1]
|
||||||
|
}
|
||||||
|
s := strings.SplitN(token, ".", 2)
|
||||||
|
var meters, cmeters, val int
|
||||||
|
var err error
|
||||||
|
switch len(s) {
|
||||||
|
case 2:
|
||||||
|
if cmeters, err = strconv.Atoi(s[1]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
if meters, err = strconv.Atoi(s[0]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case 0:
|
||||||
|
// huh?
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
ok = true
|
||||||
|
if meters > 0 {
|
||||||
|
e = 2
|
||||||
|
val = meters
|
||||||
|
} else {
|
||||||
|
e = 0
|
||||||
|
val = cmeters
|
||||||
|
}
|
||||||
|
for val > 10 {
|
||||||
|
e++
|
||||||
|
val /= 10
|
||||||
|
}
|
||||||
|
if e > 9 {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
m = uint8(val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendOrigin(name, origin string) string {
|
||||||
|
if origin == "." {
|
||||||
|
return name + origin
|
||||||
|
}
|
||||||
|
return name + "." + origin
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOC record helper function
|
||||||
|
func locCheckNorth(token string, latitude uint32) (uint32, bool) {
|
||||||
|
switch token {
|
||||||
|
case "n", "N":
|
||||||
|
return LOC_EQUATOR + latitude, true
|
||||||
|
case "s", "S":
|
||||||
|
return LOC_EQUATOR - latitude, true
|
||||||
|
}
|
||||||
|
return latitude, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOC record helper function
|
||||||
|
func locCheckEast(token string, longitude uint32) (uint32, bool) {
|
||||||
|
switch token {
|
||||||
|
case "e", "E":
|
||||||
|
return LOC_EQUATOR + longitude, true
|
||||||
|
case "w", "W":
|
||||||
|
return LOC_EQUATOR - longitude, true
|
||||||
|
}
|
||||||
|
return longitude, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Eat" the rest of the "line". Return potential comments
|
||||||
|
func slurpRemainder(c chan lex, f string) (*ParseError, string) {
|
||||||
|
l := <-c
|
||||||
|
com := ""
|
||||||
|
switch l.value {
|
||||||
|
case zBlank:
|
||||||
|
l = <-c
|
||||||
|
com = l.comment
|
||||||
|
if l.value != zNewline && l.value != zEOF {
|
||||||
|
return &ParseError{f, "garbage after rdata", l}, ""
|
||||||
|
}
|
||||||
|
case zNewline:
|
||||||
|
com = l.comment
|
||||||
|
case zEOF:
|
||||||
|
default:
|
||||||
|
return &ParseError{f, "garbage after rdata", l}, ""
|
||||||
|
}
|
||||||
|
return nil, com
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a 64 bit-like ipv6 address: "0014:4fff:ff20:ee64"
|
||||||
|
// Used for NID and L64 record.
|
||||||
|
func stringToNodeID(l lex) (uint64, *ParseError) {
|
||||||
|
if len(l.token) < 19 {
|
||||||
|
return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
|
||||||
|
}
|
||||||
|
// There must be three colons at fixes postitions, if not its a parse error
|
||||||
|
if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' {
|
||||||
|
return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
|
||||||
|
}
|
||||||
|
s := l.token[0:4] + l.token[5:9] + l.token[10:14] + l.token[15:19]
|
||||||
|
u, err := strconv.ParseUint(s, 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,43 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
// Implement a simple scanner, return a byte stream from an io reader.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"text/scanner"
|
||||||
|
)
|
||||||
|
|
||||||
|
type scan struct {
|
||||||
|
src *bufio.Reader
|
||||||
|
position scanner.Position
|
||||||
|
eof bool // Have we just seen a eof
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanInit(r io.Reader) *scan {
|
||||||
|
s := new(scan)
|
||||||
|
s.src = bufio.NewReader(r)
|
||||||
|
s.position.Line = 1
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenText returns the next byte from the input
|
||||||
|
func (s *scan) tokenText() (byte, error) {
|
||||||
|
c, err := s.src.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
// delay the newline handling until the next token is delivered,
|
||||||
|
// fixes off-by-one errors when reporting a parse error.
|
||||||
|
if s.eof == true {
|
||||||
|
s.position.Line++
|
||||||
|
s.position.Column = 0
|
||||||
|
s.eof = false
|
||||||
|
}
|
||||||
|
if c == '\n' {
|
||||||
|
s.eof = true
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
s.position.Column++
|
||||||
|
return c, nil
|
||||||
|
}
|
|
@ -0,0 +1,732 @@
|
||||||
|
// DNS server implementation.
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Maximum number of TCP queries before we close the socket.
|
||||||
|
const maxTCPQueries = 128
|
||||||
|
|
||||||
|
// Handler is implemented by any value that implements ServeDNS.
|
||||||
|
type Handler interface {
|
||||||
|
ServeDNS(w ResponseWriter, r *Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
hijacked bool // connection has been hijacked by handler
|
||||||
|
tsigStatus error
|
||||||
|
tsigTimersOnly bool
|
||||||
|
tsigRequestMAC string
|
||||||
|
tsigSecret map[string]string // the tsig secrets
|
||||||
|
udp *net.UDPConn // 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
|
||||||
|
remoteAddr net.Addr // address of the client
|
||||||
|
writer Writer // writer to output the raw DNS bits
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
type ServeMux struct {
|
||||||
|
z map[string]Handler
|
||||||
|
m *sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServeMux allocates and returns a new ServeMux.
|
||||||
|
func NewServeMux() *ServeMux { return &ServeMux{z: make(map[string]Handler), m: new(sync.RWMutex)} }
|
||||||
|
|
||||||
|
// DefaultServeMux is the default ServeMux used by Serve.
|
||||||
|
var DefaultServeMux = NewServeMux()
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleFailed returns a HandlerFunc that returns SERVFAIL for every request it gets.
|
||||||
|
func HandleFailed(w ResponseWriter, r *Msg) {
|
||||||
|
m := new(Msg)
|
||||||
|
m.SetRcode(r, RcodeServerFailure)
|
||||||
|
// does not matter if this write fails
|
||||||
|
w.WriteMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func failedHandler() Handler { return HandlerFunc(HandleFailed) }
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mux *ServeMux) match(q string, t uint16) Handler {
|
||||||
|
mux.m.RLock()
|
||||||
|
defer mux.m.RUnlock()
|
||||||
|
var handler Handler
|
||||||
|
b := make([]byte, len(q)) // worst case, one label of length q
|
||||||
|
off := 0
|
||||||
|
end := false
|
||||||
|
for {
|
||||||
|
l := len(q[off:])
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
b[i] = q[off+i]
|
||||||
|
if b[i] >= 'A' && b[i] <= 'Z' {
|
||||||
|
b[i] |= ('a' - 'A')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if h, ok := mux.z[string(b[:l])]; ok { // 'causes garbage, might want to change the map key
|
||||||
|
if t != TypeDS {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
// Continue for DS to see if we have a parent too, if so delegeate to the parent
|
||||||
|
handler = h
|
||||||
|
}
|
||||||
|
off, end = NextLabel(q, off)
|
||||||
|
if end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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()
|
||||||
|
mux.z[Fqdn(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 deregistrars 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, Fqdn(pattern))
|
||||||
|
mux.m.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeDNS dispatches the request to the handler whose
|
||||||
|
// pattern most closely matches the request message. If DefaultServeMux
|
||||||
|
// is used the correct thing for DS queries is done: a possible parent
|
||||||
|
// is sought.
|
||||||
|
// If no handler is found a standard SERVFAIL message is returned
|
||||||
|
// If the request message does not have exactly one question in the
|
||||||
|
// question section a SERVFAIL is returned, unlesss Unsafe is true.
|
||||||
|
func (mux *ServeMux) ServeDNS(w ResponseWriter, request *Msg) {
|
||||||
|
var h Handler
|
||||||
|
if len(request.Question) < 1 { // allow more than one question
|
||||||
|
h = failedHandler()
|
||||||
|
} else {
|
||||||
|
if h = mux.match(request.Question[0].Name, request.Question[0].Qtype); h == nil {
|
||||||
|
h = failedHandler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.ServeDNS(w, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultReader is an adapter for the Server struct that implements the Reader interface
|
||||||
|
// using the readTCP and readUDP func of the embedded Server.
|
||||||
|
type defaultReader struct {
|
||||||
|
*Server
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecorateReader is a decorator hook for extending or supplanting the functionality of a Reader.
|
||||||
|
// Implementations should never return a nil Reader.
|
||||||
|
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
|
||||||
|
// Secret(s) for Tsig map[<zonename>]<base64 secret>.
|
||||||
|
TsigSecret map[string]string
|
||||||
|
// Unsafe instructs the server to disregard any sanity checks and directly hand the message to
|
||||||
|
// the handler. It will specifically not check if the query has the QR bit not set.
|
||||||
|
Unsafe bool
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// Graceful shutdown handling
|
||||||
|
|
||||||
|
inFlight sync.WaitGroup
|
||||||
|
|
||||||
|
lock sync.RWMutex
|
||||||
|
started bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe starts a nameserver on the configured address in *Server.
|
||||||
|
func (srv *Server) ListenAndServe() error {
|
||||||
|
srv.lock.Lock()
|
||||||
|
defer srv.lock.Unlock()
|
||||||
|
if srv.started {
|
||||||
|
return &Error{err: "server already started"}
|
||||||
|
}
|
||||||
|
addr := srv.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":domain"
|
||||||
|
}
|
||||||
|
if srv.UDPSize == 0 {
|
||||||
|
srv.UDPSize = MinMsgSize
|
||||||
|
}
|
||||||
|
switch srv.Net {
|
||||||
|
case "tcp", "tcp4", "tcp6":
|
||||||
|
a, err := net.ResolveTCPAddr(srv.Net, addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l, err := net.ListenTCP(srv.Net, a)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
srv.Listener = l
|
||||||
|
srv.started = true
|
||||||
|
srv.lock.Unlock()
|
||||||
|
err = srv.serveTCP(l)
|
||||||
|
srv.lock.Lock() // to satisfy the defer at the top
|
||||||
|
return err
|
||||||
|
case "tcp-tls", "tcp4-tls", "tcp6-tls":
|
||||||
|
network := "tcp"
|
||||||
|
if srv.Net == "tcp4-tls" {
|
||||||
|
network = "tcp4"
|
||||||
|
} else if srv.Net == "tcp6" {
|
||||||
|
network = "tcp6"
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := tls.Listen(network, addr, srv.TLSConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
srv.Listener = l
|
||||||
|
srv.started = true
|
||||||
|
srv.lock.Unlock()
|
||||||
|
err = srv.serveTCP(l)
|
||||||
|
srv.lock.Lock() // to satisfy the defer at the top
|
||||||
|
return err
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
a, err := net.ResolveUDPAddr(srv.Net, addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l, err := net.ListenUDP(srv.Net, a)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e := setUDPSocketOptions(l); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
srv.PacketConn = l
|
||||||
|
srv.started = true
|
||||||
|
srv.lock.Unlock()
|
||||||
|
err = srv.serveUDP(l)
|
||||||
|
srv.lock.Lock() // to satisfy the defer at the top
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
srv.lock.Lock()
|
||||||
|
defer srv.lock.Unlock()
|
||||||
|
if srv.started {
|
||||||
|
return &Error{err: "server already started"}
|
||||||
|
}
|
||||||
|
pConn := srv.PacketConn
|
||||||
|
l := srv.Listener
|
||||||
|
if pConn != nil {
|
||||||
|
if srv.UDPSize == 0 {
|
||||||
|
srv.UDPSize = MinMsgSize
|
||||||
|
}
|
||||||
|
if t, ok := pConn.(*net.UDPConn); ok {
|
||||||
|
if e := setUDPSocketOptions(t); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
srv.started = true
|
||||||
|
srv.lock.Unlock()
|
||||||
|
e := srv.serveUDP(t)
|
||||||
|
srv.lock.Lock() // to satisfy the defer at the top
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l != nil {
|
||||||
|
srv.started = true
|
||||||
|
srv.lock.Unlock()
|
||||||
|
e := srv.serveTCP(l)
|
||||||
|
srv.lock.Lock() // to satisfy the defer at the top
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return &Error{err: "bad listeners"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown gracefully shuts down a server. After a call to Shutdown, ListenAndServe and
|
||||||
|
// ActivateAndServe will return. All in progress queries are completed before the server
|
||||||
|
// is taken down. If the Shutdown is taking longer than the reading timeout an error
|
||||||
|
// is returned.
|
||||||
|
func (srv *Server) Shutdown() error {
|
||||||
|
srv.lock.Lock()
|
||||||
|
if !srv.started {
|
||||||
|
srv.lock.Unlock()
|
||||||
|
return &Error{err: "server not started"}
|
||||||
|
}
|
||||||
|
srv.started = false
|
||||||
|
srv.lock.Unlock()
|
||||||
|
|
||||||
|
if srv.PacketConn != nil {
|
||||||
|
srv.PacketConn.Close()
|
||||||
|
}
|
||||||
|
if srv.Listener != nil {
|
||||||
|
srv.Listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
fin := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
srv.inFlight.Wait()
|
||||||
|
fin <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-time.After(srv.getReadTimeout()):
|
||||||
|
return &Error{err: "server shutdown is pending"}
|
||||||
|
case <-fin:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getReadTimeout is a helper func to use system timeout if server did not intend to change it.
|
||||||
|
func (srv *Server) getReadTimeout() time.Duration {
|
||||||
|
rtimeout := dnsTimeout
|
||||||
|
if srv.ReadTimeout != 0 {
|
||||||
|
rtimeout = srv.ReadTimeout
|
||||||
|
}
|
||||||
|
return rtimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// serveTCP starts a TCP listener for the server.
|
||||||
|
// Each request is handled in a separate goroutine.
|
||||||
|
func (srv *Server) serveTCP(l net.Listener) error {
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
if srv.NotifyStartedFunc != nil {
|
||||||
|
srv.NotifyStartedFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := Reader(&defaultReader{srv})
|
||||||
|
if srv.DecorateReader != nil {
|
||||||
|
reader = srv.DecorateReader(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := srv.Handler
|
||||||
|
if handler == nil {
|
||||||
|
handler = DefaultServeMux
|
||||||
|
}
|
||||||
|
rtimeout := srv.getReadTimeout()
|
||||||
|
// deadline is not used here
|
||||||
|
for {
|
||||||
|
rw, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if neterr, ok := err.(net.Error); ok && neterr.Temporary() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m, err := reader.ReadTCP(rw, rtimeout)
|
||||||
|
srv.lock.RLock()
|
||||||
|
if !srv.started {
|
||||||
|
srv.lock.RUnlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
srv.lock.RUnlock()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
srv.inFlight.Add(1)
|
||||||
|
go srv.serve(rw.RemoteAddr(), handler, m, nil, nil, rw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// serveUDP starts a UDP listener for the server.
|
||||||
|
// Each request is handled in a separate goroutine.
|
||||||
|
func (srv *Server) serveUDP(l *net.UDPConn) error {
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
if srv.NotifyStartedFunc != nil {
|
||||||
|
srv.NotifyStartedFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := Reader(&defaultReader{srv})
|
||||||
|
if srv.DecorateReader != nil {
|
||||||
|
reader = srv.DecorateReader(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := srv.Handler
|
||||||
|
if handler == nil {
|
||||||
|
handler = DefaultServeMux
|
||||||
|
}
|
||||||
|
rtimeout := srv.getReadTimeout()
|
||||||
|
// deadline is not used here
|
||||||
|
for {
|
||||||
|
m, s, err := reader.ReadUDP(l, rtimeout)
|
||||||
|
srv.lock.RLock()
|
||||||
|
if !srv.started {
|
||||||
|
srv.lock.RUnlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
srv.lock.RUnlock()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
srv.inFlight.Add(1)
|
||||||
|
go srv.serve(s.RemoteAddr(), handler, m, l, s, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve a new connection.
|
||||||
|
func (srv *Server) serve(a net.Addr, h Handler, m []byte, u *net.UDPConn, s *SessionUDP, t net.Conn) {
|
||||||
|
defer srv.inFlight.Done()
|
||||||
|
|
||||||
|
w := &response{tsigSecret: srv.TsigSecret, udp: u, tcp: t, remoteAddr: a, udpSession: s}
|
||||||
|
if srv.DecorateWriter != nil {
|
||||||
|
w.writer = srv.DecorateWriter(w)
|
||||||
|
} else {
|
||||||
|
w.writer = w
|
||||||
|
}
|
||||||
|
|
||||||
|
q := 0 // counter for the amount of TCP queries we get
|
||||||
|
|
||||||
|
reader := Reader(&defaultReader{srv})
|
||||||
|
if srv.DecorateReader != nil {
|
||||||
|
reader = srv.DecorateReader(reader)
|
||||||
|
}
|
||||||
|
Redo:
|
||||||
|
req := new(Msg)
|
||||||
|
err := req.Unpack(m)
|
||||||
|
if err != nil { // Send a FormatError back
|
||||||
|
x := new(Msg)
|
||||||
|
x.SetRcodeFormatError(req)
|
||||||
|
w.WriteMsg(x)
|
||||||
|
goto Exit
|
||||||
|
}
|
||||||
|
if !srv.Unsafe && req.Response {
|
||||||
|
goto Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
w.tsigStatus = nil
|
||||||
|
if w.tsigSecret != nil {
|
||||||
|
if t := req.IsTsig(); t != nil {
|
||||||
|
secret := t.Hdr.Name
|
||||||
|
if _, ok := w.tsigSecret[secret]; !ok {
|
||||||
|
w.tsigStatus = ErrKeyAlg
|
||||||
|
}
|
||||||
|
w.tsigStatus = TsigVerify(m, w.tsigSecret[secret], "", false)
|
||||||
|
w.tsigTimersOnly = false
|
||||||
|
w.tsigRequestMAC = req.Extra[len(req.Extra)-1].(*TSIG).MAC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.ServeDNS(w, req) // Writes back to the client
|
||||||
|
|
||||||
|
Exit:
|
||||||
|
if w.tcp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO(miek): make this number configurable?
|
||||||
|
if q > maxTCPQueries { // close socket after this many queries
|
||||||
|
w.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.hijacked {
|
||||||
|
return // client calls Close()
|
||||||
|
}
|
||||||
|
if u != nil { // UDP, "close" and return
|
||||||
|
w.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
idleTimeout := tcpIdleTimeout
|
||||||
|
if srv.IdleTimeout != nil {
|
||||||
|
idleTimeout = srv.IdleTimeout()
|
||||||
|
}
|
||||||
|
m, err = reader.ReadTCP(w.tcp, idleTimeout)
|
||||||
|
if err == nil {
|
||||||
|
q++
|
||||||
|
goto Redo
|
||||||
|
}
|
||||||
|
w.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) readTCP(conn net.Conn, timeout time.Duration) ([]byte, error) {
|
||||||
|
conn.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
l := make([]byte, 2)
|
||||||
|
n, err := conn.Read(l)
|
||||||
|
if err != nil || n != 2 {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, ErrShortRead
|
||||||
|
}
|
||||||
|
length := binary.BigEndian.Uint16(l)
|
||||||
|
if length == 0 {
|
||||||
|
return nil, ErrShortRead
|
||||||
|
}
|
||||||
|
m := make([]byte, int(length))
|
||||||
|
n, err = conn.Read(m[:int(length)])
|
||||||
|
if err != nil || n == 0 {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, ErrShortRead
|
||||||
|
}
|
||||||
|
i := n
|
||||||
|
for i < int(length) {
|
||||||
|
j, err := conn.Read(m[i:int(length)])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
i += j
|
||||||
|
}
|
||||||
|
n = i
|
||||||
|
m = m[:n]
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) readUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) {
|
||||||
|
conn.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
m := make([]byte, srv.UDPSize)
|
||||||
|
n, s, err := ReadFromSessionUDP(conn, m)
|
||||||
|
if err != nil || n == 0 {
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return nil, nil, ErrShortRead
|
||||||
|
}
|
||||||
|
m = m[:n]
|
||||||
|
return m, s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg implements the ResponseWriter.WriteMsg method.
|
||||||
|
func (w *response) WriteMsg(m *Msg) (err error) {
|
||||||
|
var data []byte
|
||||||
|
if w.tsigSecret != nil { // if no secrets, dont check for the tsig (which is a longer check)
|
||||||
|
if t := m.IsTsig(); t != nil {
|
||||||
|
data, w.tsigRequestMAC, err = TsigGenerate(m, w.tsigSecret[t.Hdr.Name], 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) {
|
||||||
|
switch {
|
||||||
|
case w.udp != nil:
|
||||||
|
n, err := WriteToSessionUDP(w.udp, m, w.udpSession)
|
||||||
|
return n, err
|
||||||
|
case w.tcp != nil:
|
||||||
|
lm := len(m)
|
||||||
|
if lm < 2 {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
if lm > MaxMsgSize {
|
||||||
|
return 0, &Error{err: "message too large"}
|
||||||
|
}
|
||||||
|
l := make([]byte, 2, 2+lm)
|
||||||
|
binary.BigEndian.PutUint16(l, uint16(lm))
|
||||||
|
m = append(l, m...)
|
||||||
|
|
||||||
|
n, err := io.Copy(w.tcp, bytes.NewReader(m))
|
||||||
|
return int(n), err
|
||||||
|
}
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr implements the ResponseWriter.LocalAddr method.
|
||||||
|
func (w *response) LocalAddr() net.Addr {
|
||||||
|
if w.tcp != nil {
|
||||||
|
return w.tcp.LocalAddr()
|
||||||
|
}
|
||||||
|
return w.udp.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr implements the ResponseWriter.RemoteAddr method.
|
||||||
|
func (w *response) RemoteAddr() net.Addr { return w.remoteAddr }
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// Can't close the udp conn, as that is actually the listener.
|
||||||
|
if w.tcp != nil {
|
||||||
|
e := w.tcp.Close()
|
||||||
|
w.tcp = nil
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,219 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"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 || len(rr.SignerName) == 0 || rr.Algorithm == 0 {
|
||||||
|
return nil, ErrKey
|
||||||
|
}
|
||||||
|
rr.Header().Rrtype = TypeSIG
|
||||||
|
rr.Header().Class = ClassANY
|
||||||
|
rr.Header().Ttl = 0
|
||||||
|
rr.Header().Name = "."
|
||||||
|
rr.OrigTtl = 0
|
||||||
|
rr.TypeCovered = 0
|
||||||
|
rr.Labels = 0
|
||||||
|
|
||||||
|
buf := make([]byte, m.Len()+rr.len())
|
||||||
|
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)]
|
||||||
|
|
||||||
|
hash, ok := AlgorithmToHash[rr.Algorithm]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrAlg
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := hash.New()
|
||||||
|
// Write SIG rdata
|
||||||
|
hasher.Write(buf[len(mbuf)+1+2+2+4+2:])
|
||||||
|
// Write message
|
||||||
|
hasher.Write(buf[:len(mbuf)])
|
||||||
|
|
||||||
|
signature, err := sign(k, hasher.Sum(nil), hash, rr.Algorithm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rr.Signature = toBase64(signature)
|
||||||
|
sig := string(signature)
|
||||||
|
|
||||||
|
buf = append(buf, sig...)
|
||||||
|
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(sig))
|
||||||
|
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 || len(rr.SignerName) == 0 || rr.Algorithm == 0 {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash crypto.Hash
|
||||||
|
switch rr.Algorithm {
|
||||||
|
case DSA, RSASHA1:
|
||||||
|
hash = crypto.SHA1
|
||||||
|
case RSASHA256, ECDSAP256SHA256:
|
||||||
|
hash = crypto.SHA256
|
||||||
|
case ECDSAP384SHA384:
|
||||||
|
hash = crypto.SHA384
|
||||||
|
case RSASHA512:
|
||||||
|
hash = crypto.SHA512
|
||||||
|
default:
|
||||||
|
return ErrAlg
|
||||||
|
}
|
||||||
|
hasher := hash.New()
|
||||||
|
|
||||||
|
buflen := len(buf)
|
||||||
|
qdc := binary.BigEndian.Uint16(buf[4:])
|
||||||
|
anc := binary.BigEndian.Uint16(buf[6:])
|
||||||
|
auc := binary.BigEndian.Uint16(buf[8:])
|
||||||
|
adc := binary.BigEndian.Uint16(buf[10:])
|
||||||
|
offset := 12
|
||||||
|
var err error
|
||||||
|
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
|
||||||
|
}
|
||||||
|
var rdlen uint16
|
||||||
|
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.ToLower(signername) != strings.ToLower(k.Header().Name) {
|
||||||
|
return &Error{err: "signer name doesn't match key name"}
|
||||||
|
}
|
||||||
|
sigend := offset
|
||||||
|
hasher.Write(buf[sigstart:sigend])
|
||||||
|
hasher.Write(buf[:10])
|
||||||
|
hasher.Write([]byte{
|
||||||
|
byte((adc - 1) << 8),
|
||||||
|
byte(adc - 1),
|
||||||
|
})
|
||||||
|
hasher.Write(buf[12:bodyend])
|
||||||
|
|
||||||
|
hashed := hasher.Sum(nil)
|
||||||
|
sig := buf[sigend:]
|
||||||
|
switch k.Algorithm {
|
||||||
|
case DSA:
|
||||||
|
pk := k.publicKeyDSA()
|
||||||
|
sig = sig[1:]
|
||||||
|
r := big.NewInt(0)
|
||||||
|
r.SetBytes(sig[:len(sig)/2])
|
||||||
|
s := big.NewInt(0)
|
||||||
|
s.SetBytes(sig[len(sig)/2:])
|
||||||
|
if pk != nil {
|
||||||
|
if dsa.Verify(pk, hashed, r, s) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrSig
|
||||||
|
}
|
||||||
|
case RSASHA1, RSASHA256, RSASHA512:
|
||||||
|
pk := k.publicKeyRSA()
|
||||||
|
if pk != nil {
|
||||||
|
return rsa.VerifyPKCS1v15(pk, hash, hashed, sig)
|
||||||
|
}
|
||||||
|
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||||
|
pk := k.publicKeyECDSA()
|
||||||
|
r := big.NewInt(0)
|
||||||
|
r.SetBytes(sig[:len(sig)/2])
|
||||||
|
s := big.NewInt(0)
|
||||||
|
s.SetBytes(sig[len(sig)/2:])
|
||||||
|
if pk != nil {
|
||||||
|
if ecdsa.Verify(pk, hashed, r, s) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrSig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ErrKeyAlg
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
g.Lock()
|
||||||
|
delete(g.m, key)
|
||||||
|
g.Unlock()
|
||||||
|
|
||||||
|
return c.val, c.rtt, c.err, c.dups > 0
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CertificateToDANE converts a certificate to a hex string as used in the TLSA record.
|
||||||
|
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:
|
||||||
|
io.WriteString(h, string(cert.Raw))
|
||||||
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||||||
|
case 1:
|
||||||
|
io.WriteString(h, string(cert.RawSubjectPublicKeyInfo))
|
||||||
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
h := sha512.New()
|
||||||
|
switch selector {
|
||||||
|
case 0:
|
||||||
|
io.WriteString(h, string(cert.Raw))
|
||||||
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||||||
|
case 1:
|
||||||
|
io.WriteString(h, string(cert.RawSubjectPublicKeyInfo))
|
||||||
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("dns: bad TLSA MatchingType or TLSA Selector")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,384 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HMAC hashing codes. These are transmitted as domain names.
|
||||||
|
const (
|
||||||
|
HmacMD5 = "hmac-md5.sig-alg.reg.int."
|
||||||
|
HmacSHA1 = "hmac-sha1."
|
||||||
|
HmacSHA256 = "hmac-sha256."
|
||||||
|
HmacSHA512 = "hmac-sha512."
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
if m.IsTsig() == nil {
|
||||||
|
panic("dns: TSIG not last RR in additional")
|
||||||
|
}
|
||||||
|
// If we barf here, the caller is to blame
|
||||||
|
rawsecret, err := fromBase64([]byte(secret))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
rr := m.Extra[len(m.Extra)-1].(*TSIG)
|
||||||
|
m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg
|
||||||
|
mbuf, err := m.Pack()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
buf := tsigBuffer(mbuf, rr, requestMAC, timersOnly)
|
||||||
|
|
||||||
|
t := new(TSIG)
|
||||||
|
var h hash.Hash
|
||||||
|
switch strings.ToLower(rr.Algorithm) {
|
||||||
|
case HmacMD5:
|
||||||
|
h = hmac.New(md5.New, []byte(rawsecret))
|
||||||
|
case HmacSHA1:
|
||||||
|
h = hmac.New(sha1.New, []byte(rawsecret))
|
||||||
|
case HmacSHA256:
|
||||||
|
h = hmac.New(sha256.New, []byte(rawsecret))
|
||||||
|
case HmacSHA512:
|
||||||
|
h = hmac.New(sha512.New, []byte(rawsecret))
|
||||||
|
default:
|
||||||
|
return nil, "", ErrKeyAlg
|
||||||
|
}
|
||||||
|
io.WriteString(h, string(buf))
|
||||||
|
t.MAC = hex.EncodeToString(h.Sum(nil))
|
||||||
|
t.MACSize = uint16(len(t.MAC) / 2) // Size is half!
|
||||||
|
|
||||||
|
t.Hdr = RR_Header{Name: rr.Hdr.Name, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0}
|
||||||
|
t.Fudge = rr.Fudge
|
||||||
|
t.TimeSigned = rr.TimeSigned
|
||||||
|
t.Algorithm = rr.Algorithm
|
||||||
|
t.OrigId = m.Id
|
||||||
|
|
||||||
|
tbuf := make([]byte, t.len())
|
||||||
|
if off, err := PackRR(t, tbuf, 0, nil, false); err == nil {
|
||||||
|
tbuf = tbuf[:off] // reset to actual size used
|
||||||
|
} else {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
mbuf = append(mbuf, tbuf...)
|
||||||
|
// 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 {
|
||||||
|
rawsecret, err := fromBase64([]byte(secret))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Strip the TSIG from the incoming msg
|
||||||
|
stripped, tsig, err := stripTsig(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
msgMAC, err := hex.DecodeString(tsig.MAC)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := tsigBuffer(stripped, tsig, requestMAC, timersOnly)
|
||||||
|
|
||||||
|
// Fudge factor works both ways. A message can arrive before it was signed because
|
||||||
|
// of clock skew.
|
||||||
|
now := uint64(time.Now().Unix())
|
||||||
|
ti := now - tsig.TimeSigned
|
||||||
|
if now < tsig.TimeSigned {
|
||||||
|
ti = tsig.TimeSigned - now
|
||||||
|
}
|
||||||
|
if uint64(tsig.Fudge) < ti {
|
||||||
|
return ErrTime
|
||||||
|
}
|
||||||
|
|
||||||
|
var h hash.Hash
|
||||||
|
switch strings.ToLower(tsig.Algorithm) {
|
||||||
|
case HmacMD5:
|
||||||
|
h = hmac.New(md5.New, rawsecret)
|
||||||
|
case HmacSHA1:
|
||||||
|
h = hmac.New(sha1.New, rawsecret)
|
||||||
|
case HmacSHA256:
|
||||||
|
h = hmac.New(sha256.New, rawsecret)
|
||||||
|
case HmacSHA512:
|
||||||
|
h = hmac.New(sha512.New, rawsecret)
|
||||||
|
default:
|
||||||
|
return ErrKeyAlg
|
||||||
|
}
|
||||||
|
h.Write(buf)
|
||||||
|
if !hmac.Equal(h.Sum(nil), msgMAC) {
|
||||||
|
return ErrSig
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a wiredata buffer for the MAC calculation.
|
||||||
|
func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []byte {
|
||||||
|
var buf []byte
|
||||||
|
if rr.TimeSigned == 0 {
|
||||||
|
rr.TimeSigned = uint64(time.Now().Unix())
|
||||||
|
}
|
||||||
|
if rr.Fudge == 0 {
|
||||||
|
rr.Fudge = 300 // Standard (RFC) default.
|
||||||
|
}
|
||||||
|
|
||||||
|
if requestMAC != "" {
|
||||||
|
m := new(macWireFmt)
|
||||||
|
m.MACSize = uint16(len(requestMAC) / 2)
|
||||||
|
m.MAC = requestMAC
|
||||||
|
buf = make([]byte, len(requestMAC)) // long enough
|
||||||
|
n, _ := packMacWire(m, buf)
|
||||||
|
buf = buf[:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
tsigvar := make([]byte, DefaultMsgSize)
|
||||||
|
if timersOnly {
|
||||||
|
tsig := new(timerWireFmt)
|
||||||
|
tsig.TimeSigned = rr.TimeSigned
|
||||||
|
tsig.Fudge = rr.Fudge
|
||||||
|
n, _ := packTimerWire(tsig, tsigvar)
|
||||||
|
tsigvar = tsigvar[:n]
|
||||||
|
} else {
|
||||||
|
tsig := new(tsigWireFmt)
|
||||||
|
tsig.Name = strings.ToLower(rr.Hdr.Name)
|
||||||
|
tsig.Class = ClassANY
|
||||||
|
tsig.Ttl = rr.Hdr.Ttl
|
||||||
|
tsig.Algorithm = strings.ToLower(rr.Algorithm)
|
||||||
|
tsig.TimeSigned = rr.TimeSigned
|
||||||
|
tsig.Fudge = rr.Fudge
|
||||||
|
tsig.Error = rr.Error
|
||||||
|
tsig.OtherLen = rr.OtherLen
|
||||||
|
tsig.OtherData = rr.OtherData
|
||||||
|
n, _ := packTsigWire(tsig, tsigvar)
|
||||||
|
tsigvar = tsigvar[:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
if requestMAC != "" {
|
||||||
|
x := append(buf, msgbuf...)
|
||||||
|
buf = append(x, tsigvar...)
|
||||||
|
} else {
|
||||||
|
buf = append(msgbuf, tsigvar...)
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,271 @@
|
||||||
|
//+build ignore
|
||||||
|
|
||||||
|
// types_generate.go is meant to run with go generate. It will use
|
||||||
|
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||||
|
// it will generate conversion tables (TypeToRR and TypeToString) and banal
|
||||||
|
// methods (len, Header, copy) based on the struct tags. The generated source is
|
||||||
|
// written to ztypes.go, and is meant to be checked into git.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"go/importer"
|
||||||
|
"go/types"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
var skipLen = map[string]struct{}{
|
||||||
|
"NSEC": {},
|
||||||
|
"NSEC3": {},
|
||||||
|
"OPT": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
var packageHdr = `
|
||||||
|
// *** DO NOT MODIFY ***
|
||||||
|
// AUTOGENERATED BY go generate from type_generate.go
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
var TypeToRR = template.Must(template.New("TypeToRR").Parse(`
|
||||||
|
// TypeToRR is a map of constructors for each RR type.
|
||||||
|
var TypeToRR = map[uint16]func() RR{
|
||||||
|
{{range .}}{{if ne . "RFC3597"}} Type{{.}}: func() RR { return new({{.}}) },
|
||||||
|
{{end}}{{end}} }
|
||||||
|
|
||||||
|
`))
|
||||||
|
|
||||||
|
var typeToString = template.Must(template.New("typeToString").Parse(`
|
||||||
|
// TypeToString is a map of strings for each RR type.
|
||||||
|
var TypeToString = map[uint16]string{
|
||||||
|
{{range .}}{{if ne . "NSAPPTR"}} Type{{.}}: "{{.}}",
|
||||||
|
{{end}}{{end}} TypeNSAPPTR: "NSAP-PTR",
|
||||||
|
}
|
||||||
|
|
||||||
|
`))
|
||||||
|
|
||||||
|
var headerFunc = template.Must(template.New("headerFunc").Parse(`
|
||||||
|
// Header() functions
|
||||||
|
{{range .}} func (rr *{{.}}) Header() *RR_Header { return &rr.Hdr }
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
`))
|
||||||
|
|
||||||
|
// getTypeStruct will take a type and the package scope, and return the
|
||||||
|
// (innermost) struct if the type is considered a RR type (currently defined as
|
||||||
|
// those structs beginning with a RR_Header, could be redefined as implementing
|
||||||
|
// the RR interface). The bool return value indicates if embedded structs were
|
||||||
|
// resolved.
|
||||||
|
func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
|
||||||
|
st, ok := t.Underlying().(*types.Struct)
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
||||||
|
return st, false
|
||||||
|
}
|
||||||
|
if st.Field(0).Anonymous() {
|
||||||
|
st, _ := getTypeStruct(st.Field(0).Type(), scope)
|
||||||
|
return st, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Import and type-check the package
|
||||||
|
pkg, err := importer.Default().Import("github.com/miekg/dns")
|
||||||
|
fatalIfErr(err)
|
||||||
|
scope := pkg.Scope()
|
||||||
|
|
||||||
|
// Collect constants like TypeX
|
||||||
|
var numberedTypes []string
|
||||||
|
for _, name := range scope.Names() {
|
||||||
|
o := scope.Lookup(name)
|
||||||
|
if o == nil || !o.Exported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b, ok := o.Type().(*types.Basic)
|
||||||
|
if !ok || b.Kind() != types.Uint16 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(o.Name(), "Type") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := strings.TrimPrefix(o.Name(), "Type")
|
||||||
|
if name == "PrivateRR" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
numberedTypes = append(numberedTypes, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect actual types (*X)
|
||||||
|
var namedTypes []string
|
||||||
|
for _, name := range scope.Names() {
|
||||||
|
o := scope.Lookup(name)
|
||||||
|
if o == nil || !o.Exported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if st, _ := getTypeStruct(o.Type(), scope); st == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if name == "PrivateRR" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if corresponding TypeX exists
|
||||||
|
if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" {
|
||||||
|
log.Fatalf("Constant Type%s does not exist.", o.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
namedTypes = append(namedTypes, o.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
b.WriteString(packageHdr)
|
||||||
|
|
||||||
|
// Generate TypeToRR
|
||||||
|
fatalIfErr(TypeToRR.Execute(b, namedTypes))
|
||||||
|
|
||||||
|
// Generate typeToString
|
||||||
|
fatalIfErr(typeToString.Execute(b, numberedTypes))
|
||||||
|
|
||||||
|
// Generate headerFunc
|
||||||
|
fatalIfErr(headerFunc.Execute(b, namedTypes))
|
||||||
|
|
||||||
|
// Generate len()
|
||||||
|
fmt.Fprint(b, "// len() functions\n")
|
||||||
|
for _, name := range namedTypes {
|
||||||
|
if _, ok := skipLen[name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
o := scope.Lookup(name)
|
||||||
|
st, isEmbedded := getTypeStruct(o.Type(), scope)
|
||||||
|
if isEmbedded {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "func (rr *%s) len() int {\n", name)
|
||||||
|
fmt.Fprintf(b, "l := rr.Hdr.len()\n")
|
||||||
|
for i := 1; i < st.NumFields(); i++ {
|
||||||
|
o := func(s string) { fmt.Fprintf(b, s, st.Field(i).Name()) }
|
||||||
|
|
||||||
|
if _, ok := st.Field(i).Type().(*types.Slice); ok {
|
||||||
|
switch st.Tag(i) {
|
||||||
|
case `dns:"-"`:
|
||||||
|
// ignored
|
||||||
|
case `dns:"cdomain-name"`, `dns:"domain-name"`, `dns:"txt"`:
|
||||||
|
o("for _, x := range rr.%s { l += len(x) + 1 }\n")
|
||||||
|
default:
|
||||||
|
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case st.Tag(i) == `dns:"-"`:
|
||||||
|
// ignored
|
||||||
|
case st.Tag(i) == `dns:"cdomain-name"`, st.Tag(i) == `dns:"domain-name"`:
|
||||||
|
o("l += len(rr.%s) + 1\n")
|
||||||
|
case st.Tag(i) == `dns:"octet"`:
|
||||||
|
o("l += len(rr.%s)\n")
|
||||||
|
case strings.HasPrefix(st.Tag(i), `dns:"size-base64`):
|
||||||
|
fallthrough
|
||||||
|
case st.Tag(i) == `dns:"base64"`:
|
||||||
|
o("l += base64.StdEncoding.DecodedLen(len(rr.%s))\n")
|
||||||
|
case strings.HasPrefix(st.Tag(i), `dns:"size-hex`):
|
||||||
|
fallthrough
|
||||||
|
case st.Tag(i) == `dns:"hex"`:
|
||||||
|
o("l += len(rr.%s)/2 + 1\n")
|
||||||
|
case st.Tag(i) == `dns:"a"`:
|
||||||
|
o("l += net.IPv4len // %s\n")
|
||||||
|
case st.Tag(i) == `dns:"aaaa"`:
|
||||||
|
o("l += net.IPv6len // %s\n")
|
||||||
|
case st.Tag(i) == `dns:"txt"`:
|
||||||
|
o("for _, t := range rr.%s { l += len(t) + 1 }\n")
|
||||||
|
case st.Tag(i) == `dns:"uint48"`:
|
||||||
|
o("l += 6 // %s\n")
|
||||||
|
case st.Tag(i) == "":
|
||||||
|
switch st.Field(i).Type().(*types.Basic).Kind() {
|
||||||
|
case types.Uint8:
|
||||||
|
o("l += 1 // %s\n")
|
||||||
|
case types.Uint16:
|
||||||
|
o("l += 2 // %s\n")
|
||||||
|
case types.Uint32:
|
||||||
|
o("l += 4 // %s\n")
|
||||||
|
case types.Uint64:
|
||||||
|
o("l += 8 // %s\n")
|
||||||
|
case types.String:
|
||||||
|
o("l += len(rr.%s) + 1\n")
|
||||||
|
default:
|
||||||
|
log.Fatalln(name, st.Field(i).Name())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "return l }\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate copy()
|
||||||
|
fmt.Fprint(b, "// copy() functions\n")
|
||||||
|
for _, name := range namedTypes {
|
||||||
|
o := scope.Lookup(name)
|
||||||
|
st, isEmbedded := getTypeStruct(o.Type(), scope)
|
||||||
|
if isEmbedded {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "func (rr *%s) copy() RR {\n", name)
|
||||||
|
fields := []string{"*rr.Hdr.copyHeader()"}
|
||||||
|
for i := 1; i < st.NumFields(); i++ {
|
||||||
|
f := st.Field(i).Name()
|
||||||
|
if sl, ok := st.Field(i).Type().(*types.Slice); ok {
|
||||||
|
t := sl.Underlying().String()
|
||||||
|
t = strings.TrimPrefix(t, "[]")
|
||||||
|
if strings.Contains(t, ".") {
|
||||||
|
splits := strings.Split(t, ".")
|
||||||
|
t = splits[len(splits)-1]
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "%s := make([]%s, len(rr.%s)); copy(%s, rr.%s)\n",
|
||||||
|
f, t, f, f, f)
|
||||||
|
fields = append(fields, f)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if st.Field(i).Type().String() == "net.IP" {
|
||||||
|
fields = append(fields, "copyIP(rr."+f+")")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fields = append(fields, "rr."+f)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "return &%s{%s}\n", name, strings.Join(fields, ","))
|
||||||
|
fmt.Fprintf(b, "}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// gofmt
|
||||||
|
res, err := format.Source(b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
b.WriteTo(os.Stderr)
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// write result
|
||||||
|
f, err := os.Create("ztypes.go")
|
||||||
|
fatalIfErr(err)
|
||||||
|
defer f.Close()
|
||||||
|
f.Write(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatalIfErr(err error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
// +build !windows,!plan9
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 }
|
||||||
|
|
||||||
|
// setUDPSocketOptions sets the UDP socket options.
|
||||||
|
// This function is implemented on a per platform basis. See udp_*.go for more details
|
||||||
|
func setUDPSocketOptions(conn *net.UDPConn) error {
|
||||||
|
sa, err := getUDPSocketName(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch sa.(type) {
|
||||||
|
case *syscall.SockaddrInet6:
|
||||||
|
v6only, err := getUDPSocketOptions6Only(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
setUDPSocketOptions6(conn)
|
||||||
|
if !v6only {
|
||||||
|
setUDPSocketOptions4(conn)
|
||||||
|
}
|
||||||
|
case *syscall.SockaddrInet4:
|
||||||
|
setUDPSocketOptions4(conn)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a
|
||||||
|
// net.UDPAddr.
|
||||||
|
func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) {
|
||||||
|
oob := make([]byte, 40)
|
||||||
|
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.WritetTo(), but uses a *SessionUDP instead of a net.Addr.
|
||||||
|
func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) {
|
||||||
|
n, _, err := conn.WriteMsgUDP(b, session.context, session.raddr)
|
||||||
|
return n, err
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
// See:
|
||||||
|
// * http://stackoverflow.com/questions/3062205/setting-the-source-ip-for-a-udp-socket and
|
||||||
|
// * http://blog.powerdns.com/2012/10/08/on-binding-datagram-udp-sockets-to-the-any-addresses/
|
||||||
|
//
|
||||||
|
// Why do we need this: When listening on 0.0.0.0 with UDP so kernel decides what is the outgoing
|
||||||
|
// interface, this might not always be the correct one. This code will make sure the egress
|
||||||
|
// packet's interface matched the ingress' one.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setUDPSocketOptions4 prepares the v4 socket for sessions.
|
||||||
|
func setUDPSocketOptions4(conn *net.UDPConn) error {
|
||||||
|
file, err := conn.File()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IP, syscall.IP_PKTINFO, 1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Calling File() above results in the connection becoming blocking, we must fix that.
|
||||||
|
// See https://github.com/miekg/dns/issues/279
|
||||||
|
err = syscall.SetNonblock(int(file.Fd()), true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setUDPSocketOptions6 prepares the v6 socket for sessions.
|
||||||
|
func setUDPSocketOptions6(conn *net.UDPConn) error {
|
||||||
|
file, err := conn.File()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = syscall.SetNonblock(int(file.Fd()), true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUDPSocketOption6Only return true if the socket is v6 only and false when it is v4/v6 combined
|
||||||
|
// (dualstack).
|
||||||
|
func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) {
|
||||||
|
file, err := conn.File()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
// dual stack. See http://stackoverflow.com/questions/1618240/how-to-support-both-ipv4-and-ipv6-connections
|
||||||
|
v6only, err := syscall.GetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return v6only == 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) {
|
||||||
|
file, err := conn.File()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return syscall.Getsockname(int(file.Fd()))
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
// +build !linux,!plan9
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These do nothing. See udp_linux.go for an example of how to implement this.
|
||||||
|
|
||||||
|
// We tried to adhire to some kind of naming scheme.
|
||||||
|
|
||||||
|
func setUDPSocketOptions4(conn *net.UDPConn) error { return nil }
|
||||||
|
func setUDPSocketOptions6(conn *net.UDPConn) error { return nil }
|
||||||
|
func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { return false, nil }
|
||||||
|
func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) { return nil, nil }
|
|
@ -0,0 +1,34 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setUDPSocketOptions(conn *net.UDPConn) error { return nil }
|
||||||
|
|
||||||
|
// 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, 40)
|
||||||
|
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.WritetTo(), but uses a *SessionUDP instead of a net.Addr.
|
||||||
|
func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) {
|
||||||
|
n, _, err := conn.WriteMsgUDP(b, session.context, session.raddr)
|
||||||
|
return n, err
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
type SessionUDP struct {
|
||||||
|
raddr *net.UDPAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
n, raddr, err := conn.ReadFrom(b)
|
||||||
|
if err != nil {
|
||||||
|
return n, nil, err
|
||||||
|
}
|
||||||
|
session := &SessionUDP{raddr.(*net.UDPAddr)}
|
||||||
|
return n, session, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToSessionUDP acts just like net.UDPConn.WritetTo(), but uses a *SessionUDP instead of a net.Addr.
|
||||||
|
func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) {
|
||||||
|
n, err := conn.WriteTo(b, session.raddr)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr }
|
||||||
|
|
||||||
|
// setUDPSocketOptions sets the UDP socket options.
|
||||||
|
// This function is implemented on a per platform basis. See udp_*.go for more details
|
||||||
|
func setUDPSocketOptions(conn *net.UDPConn) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
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 {
|
||||||
|
r.Header().Class = u.Question[0].Qclass
|
||||||
|
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 {
|
||||||
|
u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().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 {
|
||||||
|
u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().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 {
|
||||||
|
u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().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 {
|
||||||
|
r.Header().Class = ClassNONE
|
||||||
|
r.Header().Ttl = 0
|
||||||
|
u.Ns = append(u.Ns, r)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,244 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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
|
||||||
|
TsigSecret map[string]string // Secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be fully qualified
|
||||||
|
tsigTimersOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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)
|
||||||
|
go func() {
|
||||||
|
if q.Question[0].Qtype == TypeAXFR {
|
||||||
|
go t.inAxfr(q.Id, env)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if q.Question[0].Qtype == TypeIXFR {
|
||||||
|
go t.inIxfr(q.Id, env)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return env, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transfer) inAxfr(id uint16, 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 id != in.Id {
|
||||||
|
c <- &Envelope{in.Answer, ErrId}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if first {
|
||||||
|
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(id uint16, c chan *Envelope) {
|
||||||
|
serial := uint32(0) // The first serial seen is the current server serial
|
||||||
|
first := true
|
||||||
|
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 id != in.Id {
|
||||||
|
c <- &Envelope{in.Answer, ErrId}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if first {
|
||||||
|
// A single SOA RR signals "no changes"
|
||||||
|
if len(in.Answer) == 1 && isSOAFirst(in) {
|
||||||
|
c <- &Envelope{in.Answer, nil}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
first = !first
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we need to check each message for SOA records, to see what we need to do
|
||||||
|
if !first {
|
||||||
|
t.tsigTimersOnly = true
|
||||||
|
// If the last record in the IXFR contains the servers' SOA, we should quit
|
||||||
|
if v, ok := in.Answer[len(in.Answer)-1].(*SOA); ok {
|
||||||
|
if v.Serial == serial {
|
||||||
|
c <- &Envelope{in.Answer, nil}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
// go tr.Out(w, r, ch)
|
||||||
|
// ch <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}}
|
||||||
|
// close(ch)
|
||||||
|
// w.Hijack()
|
||||||
|
// // w.Close() // Client closes 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 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 := m.IsTsig(); ts != nil && t.TsigSecret != nil {
|
||||||
|
if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
|
||||||
|
return m, ErrSecret
|
||||||
|
}
|
||||||
|
// Need to work on the original message p, as that was used to calculate the tsig.
|
||||||
|
err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], 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 := m.IsTsig(); ts != nil && t.TsigSecret != nil {
|
||||||
|
if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
|
||||||
|
return ErrSecret
|
||||||
|
}
|
||||||
|
out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
|
||||||
|
} else {
|
||||||
|
out, err = m.Pack()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = t.Write(out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSOAFirst(in *Msg) bool {
|
||||||
|
if len(in.Answer) > 0 {
|
||||||
|
return in.Answer[0].Header().Rrtype == TypeSOA
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSOALast(in *Msg) bool {
|
||||||
|
if len(in.Answer) > 0 {
|
||||||
|
return in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,828 @@
|
||||||
|
// *** DO NOT MODIFY ***
|
||||||
|
// AUTOGENERATED BY go generate from type_generate.go
|
||||||
|
|
||||||
|
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) },
|
||||||
|
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) },
|
||||||
|
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) },
|
||||||
|
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) },
|
||||||
|
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) },
|
||||||
|
TypeSOA: func() RR { return new(SOA) },
|
||||||
|
TypeSPF: func() RR { return new(SPF) },
|
||||||
|
TypeSRV: func() RR { return new(SRV) },
|
||||||
|
TypeSSHFP: func() RR { return new(SSHFP) },
|
||||||
|
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) },
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeToString is a map of strings for each RR type.
|
||||||
|
var TypeToString = map[uint16]string{
|
||||||
|
TypeA: "A",
|
||||||
|
TypeAAAA: "AAAA",
|
||||||
|
TypeAFSDB: "AFSDB",
|
||||||
|
TypeANY: "ANY",
|
||||||
|
TypeATMA: "ATMA",
|
||||||
|
TypeAXFR: "AXFR",
|
||||||
|
TypeCAA: "CAA",
|
||||||
|
TypeCDNSKEY: "CDNSKEY",
|
||||||
|
TypeCDS: "CDS",
|
||||||
|
TypeCERT: "CERT",
|
||||||
|
TypeCNAME: "CNAME",
|
||||||
|
TypeDHCID: "DHCID",
|
||||||
|
TypeDLV: "DLV",
|
||||||
|
TypeDNAME: "DNAME",
|
||||||
|
TypeDNSKEY: "DNSKEY",
|
||||||
|
TypeDS: "DS",
|
||||||
|
TypeEID: "EID",
|
||||||
|
TypeEUI48: "EUI48",
|
||||||
|
TypeEUI64: "EUI64",
|
||||||
|
TypeGID: "GID",
|
||||||
|
TypeGPOS: "GPOS",
|
||||||
|
TypeHINFO: "HINFO",
|
||||||
|
TypeHIP: "HIP",
|
||||||
|
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",
|
||||||
|
TypeSOA: "SOA",
|
||||||
|
TypeSPF: "SPF",
|
||||||
|
TypeSRV: "SRV",
|
||||||
|
TypeSSHFP: "SSHFP",
|
||||||
|
TypeTA: "TA",
|
||||||
|
TypeTALINK: "TALINK",
|
||||||
|
TypeTKEY: "TKEY",
|
||||||
|
TypeTLSA: "TLSA",
|
||||||
|
TypeTSIG: "TSIG",
|
||||||
|
TypeTXT: "TXT",
|
||||||
|
TypeUID: "UID",
|
||||||
|
TypeUINFO: "UINFO",
|
||||||
|
TypeUNSPEC: "UNSPEC",
|
||||||
|
TypeURI: "URI",
|
||||||
|
TypeX25: "X25",
|
||||||
|
TypeNSAPPTR: "NSAP-PTR",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header() functions
|
||||||
|
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 *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 *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 *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 *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 *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 *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 }
|
||||||
|
|
||||||
|
// len() functions
|
||||||
|
func (rr *A) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += net.IPv4len // A
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *AAAA) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += net.IPv6len // AAAA
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *AFSDB) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Subtype
|
||||||
|
l += len(rr.Hostname) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *ANY) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *CAA) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 1 // Flag
|
||||||
|
l += len(rr.Tag) + 1
|
||||||
|
l += len(rr.Value)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *CERT) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Type
|
||||||
|
l += 2 // KeyTag
|
||||||
|
l += 1 // Algorithm
|
||||||
|
l += base64.StdEncoding.DecodedLen(len(rr.Certificate))
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *CNAME) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Target) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *DHCID) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += base64.StdEncoding.DecodedLen(len(rr.Digest))
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *DNAME) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Target) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *DNSKEY) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Flags
|
||||||
|
l += 1 // Protocol
|
||||||
|
l += 1 // Algorithm
|
||||||
|
l += base64.StdEncoding.DecodedLen(len(rr.PublicKey))
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *DS) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // KeyTag
|
||||||
|
l += 1 // Algorithm
|
||||||
|
l += 1 // DigestType
|
||||||
|
l += len(rr.Digest)/2 + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *EID) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Endpoint)/2 + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *EUI48) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 6 // Address
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *EUI64) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 8 // Address
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *GID) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 4 // Gid
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *GPOS) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Longitude) + 1
|
||||||
|
l += len(rr.Latitude) + 1
|
||||||
|
l += len(rr.Altitude) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *HINFO) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Cpu) + 1
|
||||||
|
l += len(rr.Os) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *HIP) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 1 // HitLength
|
||||||
|
l += 1 // PublicKeyAlgorithm
|
||||||
|
l += 2 // PublicKeyLength
|
||||||
|
l += len(rr.Hit)/2 + 1
|
||||||
|
l += base64.StdEncoding.DecodedLen(len(rr.PublicKey))
|
||||||
|
for _, x := range rr.RendezvousServers {
|
||||||
|
l += len(x) + 1
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *KX) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Preference
|
||||||
|
l += len(rr.Exchanger) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *L32) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Preference
|
||||||
|
l += net.IPv4len // Locator32
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *L64) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Preference
|
||||||
|
l += 8 // Locator64
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *LOC) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 1 // Version
|
||||||
|
l += 1 // Size
|
||||||
|
l += 1 // HorizPre
|
||||||
|
l += 1 // VertPre
|
||||||
|
l += 4 // Latitude
|
||||||
|
l += 4 // Longitude
|
||||||
|
l += 4 // Altitude
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *LP) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Preference
|
||||||
|
l += len(rr.Fqdn) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *MB) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Mb) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *MD) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Md) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *MF) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Mf) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *MG) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Mg) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *MINFO) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Rmail) + 1
|
||||||
|
l += len(rr.Email) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *MR) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Mr) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *MX) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Preference
|
||||||
|
l += len(rr.Mx) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *NAPTR) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Order
|
||||||
|
l += 2 // Preference
|
||||||
|
l += len(rr.Flags) + 1
|
||||||
|
l += len(rr.Service) + 1
|
||||||
|
l += len(rr.Regexp) + 1
|
||||||
|
l += len(rr.Replacement) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *NID) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Preference
|
||||||
|
l += 8 // NodeID
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *NIMLOC) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Locator)/2 + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *NINFO) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
for _, x := range rr.ZSData {
|
||||||
|
l += len(x) + 1
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *NS) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Ns) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *NSAPPTR) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Ptr) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *NSEC3PARAM) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 1 // Hash
|
||||||
|
l += 1 // Flags
|
||||||
|
l += 2 // Iterations
|
||||||
|
l += 1 // SaltLength
|
||||||
|
l += len(rr.Salt)/2 + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *OPENPGPKEY) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += base64.StdEncoding.DecodedLen(len(rr.PublicKey))
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *PTR) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Ptr) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *PX) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Preference
|
||||||
|
l += len(rr.Map822) + 1
|
||||||
|
l += len(rr.Mapx400) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *RFC3597) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Rdata)/2 + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *RKEY) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Flags
|
||||||
|
l += 1 // Protocol
|
||||||
|
l += 1 // Algorithm
|
||||||
|
l += base64.StdEncoding.DecodedLen(len(rr.PublicKey))
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *RP) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Mbox) + 1
|
||||||
|
l += len(rr.Txt) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *RRSIG) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // TypeCovered
|
||||||
|
l += 1 // Algorithm
|
||||||
|
l += 1 // Labels
|
||||||
|
l += 4 // OrigTtl
|
||||||
|
l += 4 // Expiration
|
||||||
|
l += 4 // Inception
|
||||||
|
l += 2 // KeyTag
|
||||||
|
l += len(rr.SignerName) + 1
|
||||||
|
l += base64.StdEncoding.DecodedLen(len(rr.Signature))
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *RT) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Preference
|
||||||
|
l += len(rr.Host) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *SOA) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Ns) + 1
|
||||||
|
l += len(rr.Mbox) + 1
|
||||||
|
l += 4 // Serial
|
||||||
|
l += 4 // Refresh
|
||||||
|
l += 4 // Retry
|
||||||
|
l += 4 // Expire
|
||||||
|
l += 4 // Minttl
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *SPF) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
for _, x := range rr.Txt {
|
||||||
|
l += len(x) + 1
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *SRV) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Priority
|
||||||
|
l += 2 // Weight
|
||||||
|
l += 2 // Port
|
||||||
|
l += len(rr.Target) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *SSHFP) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 1 // Algorithm
|
||||||
|
l += 1 // Type
|
||||||
|
l += len(rr.FingerPrint)/2 + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *TA) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // KeyTag
|
||||||
|
l += 1 // Algorithm
|
||||||
|
l += 1 // DigestType
|
||||||
|
l += len(rr.Digest)/2 + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *TALINK) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.PreviousName) + 1
|
||||||
|
l += len(rr.NextName) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *TKEY) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Algorithm) + 1
|
||||||
|
l += 4 // Inception
|
||||||
|
l += 4 // Expiration
|
||||||
|
l += 2 // Mode
|
||||||
|
l += 2 // Error
|
||||||
|
l += 2 // KeySize
|
||||||
|
l += len(rr.Key) + 1
|
||||||
|
l += 2 // OtherLen
|
||||||
|
l += len(rr.OtherData) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *TLSA) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 1 // Usage
|
||||||
|
l += 1 // Selector
|
||||||
|
l += 1 // MatchingType
|
||||||
|
l += len(rr.Certificate)/2 + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *TSIG) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Algorithm) + 1
|
||||||
|
l += 6 // TimeSigned
|
||||||
|
l += 2 // Fudge
|
||||||
|
l += 2 // MACSize
|
||||||
|
l += len(rr.MAC)/2 + 1
|
||||||
|
l += 2 // OrigId
|
||||||
|
l += 2 // Error
|
||||||
|
l += 2 // OtherLen
|
||||||
|
l += len(rr.OtherData)/2 + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *TXT) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
for _, x := range rr.Txt {
|
||||||
|
l += len(x) + 1
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *UID) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 4 // Uid
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *UINFO) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.Uinfo) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *URI) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += 2 // Priority
|
||||||
|
l += 2 // Weight
|
||||||
|
l += len(rr.Target)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (rr *X25) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
l += len(rr.PSDNAddress) + 1
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy() functions
|
||||||
|
func (rr *A) copy() RR {
|
||||||
|
return &A{*rr.Hdr.copyHeader(), copyIP(rr.A)}
|
||||||
|
}
|
||||||
|
func (rr *AAAA) copy() RR {
|
||||||
|
return &AAAA{*rr.Hdr.copyHeader(), copyIP(rr.AAAA)}
|
||||||
|
}
|
||||||
|
func (rr *AFSDB) copy() RR {
|
||||||
|
return &AFSDB{*rr.Hdr.copyHeader(), rr.Subtype, rr.Hostname}
|
||||||
|
}
|
||||||
|
func (rr *ANY) copy() RR {
|
||||||
|
return &ANY{*rr.Hdr.copyHeader()}
|
||||||
|
}
|
||||||
|
func (rr *CAA) copy() RR {
|
||||||
|
return &CAA{*rr.Hdr.copyHeader(), rr.Flag, rr.Tag, rr.Value}
|
||||||
|
}
|
||||||
|
func (rr *CERT) copy() RR {
|
||||||
|
return &CERT{*rr.Hdr.copyHeader(), rr.Type, rr.KeyTag, rr.Algorithm, rr.Certificate}
|
||||||
|
}
|
||||||
|
func (rr *CNAME) copy() RR {
|
||||||
|
return &CNAME{*rr.Hdr.copyHeader(), rr.Target}
|
||||||
|
}
|
||||||
|
func (rr *DHCID) copy() RR {
|
||||||
|
return &DHCID{*rr.Hdr.copyHeader(), rr.Digest}
|
||||||
|
}
|
||||||
|
func (rr *DNAME) copy() RR {
|
||||||
|
return &DNAME{*rr.Hdr.copyHeader(), rr.Target}
|
||||||
|
}
|
||||||
|
func (rr *DNSKEY) copy() RR {
|
||||||
|
return &DNSKEY{*rr.Hdr.copyHeader(), rr.Flags, rr.Protocol, rr.Algorithm, rr.PublicKey}
|
||||||
|
}
|
||||||
|
func (rr *DS) copy() RR {
|
||||||
|
return &DS{*rr.Hdr.copyHeader(), rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest}
|
||||||
|
}
|
||||||
|
func (rr *EID) copy() RR {
|
||||||
|
return &EID{*rr.Hdr.copyHeader(), rr.Endpoint}
|
||||||
|
}
|
||||||
|
func (rr *EUI48) copy() RR {
|
||||||
|
return &EUI48{*rr.Hdr.copyHeader(), rr.Address}
|
||||||
|
}
|
||||||
|
func (rr *EUI64) copy() RR {
|
||||||
|
return &EUI64{*rr.Hdr.copyHeader(), rr.Address}
|
||||||
|
}
|
||||||
|
func (rr *GID) copy() RR {
|
||||||
|
return &GID{*rr.Hdr.copyHeader(), rr.Gid}
|
||||||
|
}
|
||||||
|
func (rr *GPOS) copy() RR {
|
||||||
|
return &GPOS{*rr.Hdr.copyHeader(), rr.Longitude, rr.Latitude, rr.Altitude}
|
||||||
|
}
|
||||||
|
func (rr *HINFO) copy() RR {
|
||||||
|
return &HINFO{*rr.Hdr.copyHeader(), rr.Cpu, rr.Os}
|
||||||
|
}
|
||||||
|
func (rr *HIP) copy() RR {
|
||||||
|
RendezvousServers := make([]string, len(rr.RendezvousServers))
|
||||||
|
copy(RendezvousServers, rr.RendezvousServers)
|
||||||
|
return &HIP{*rr.Hdr.copyHeader(), rr.HitLength, rr.PublicKeyAlgorithm, rr.PublicKeyLength, rr.Hit, rr.PublicKey, RendezvousServers}
|
||||||
|
}
|
||||||
|
func (rr *KX) copy() RR {
|
||||||
|
return &KX{*rr.Hdr.copyHeader(), rr.Preference, rr.Exchanger}
|
||||||
|
}
|
||||||
|
func (rr *L32) copy() RR {
|
||||||
|
return &L32{*rr.Hdr.copyHeader(), rr.Preference, copyIP(rr.Locator32)}
|
||||||
|
}
|
||||||
|
func (rr *L64) copy() RR {
|
||||||
|
return &L64{*rr.Hdr.copyHeader(), rr.Preference, rr.Locator64}
|
||||||
|
}
|
||||||
|
func (rr *LOC) copy() RR {
|
||||||
|
return &LOC{*rr.Hdr.copyHeader(), rr.Version, rr.Size, rr.HorizPre, rr.VertPre, rr.Latitude, rr.Longitude, rr.Altitude}
|
||||||
|
}
|
||||||
|
func (rr *LP) copy() RR {
|
||||||
|
return &LP{*rr.Hdr.copyHeader(), rr.Preference, rr.Fqdn}
|
||||||
|
}
|
||||||
|
func (rr *MB) copy() RR {
|
||||||
|
return &MB{*rr.Hdr.copyHeader(), rr.Mb}
|
||||||
|
}
|
||||||
|
func (rr *MD) copy() RR {
|
||||||
|
return &MD{*rr.Hdr.copyHeader(), rr.Md}
|
||||||
|
}
|
||||||
|
func (rr *MF) copy() RR {
|
||||||
|
return &MF{*rr.Hdr.copyHeader(), rr.Mf}
|
||||||
|
}
|
||||||
|
func (rr *MG) copy() RR {
|
||||||
|
return &MG{*rr.Hdr.copyHeader(), rr.Mg}
|
||||||
|
}
|
||||||
|
func (rr *MINFO) copy() RR {
|
||||||
|
return &MINFO{*rr.Hdr.copyHeader(), rr.Rmail, rr.Email}
|
||||||
|
}
|
||||||
|
func (rr *MR) copy() RR {
|
||||||
|
return &MR{*rr.Hdr.copyHeader(), rr.Mr}
|
||||||
|
}
|
||||||
|
func (rr *MX) copy() RR {
|
||||||
|
return &MX{*rr.Hdr.copyHeader(), rr.Preference, rr.Mx}
|
||||||
|
}
|
||||||
|
func (rr *NAPTR) copy() RR {
|
||||||
|
return &NAPTR{*rr.Hdr.copyHeader(), rr.Order, rr.Preference, rr.Flags, rr.Service, rr.Regexp, rr.Replacement}
|
||||||
|
}
|
||||||
|
func (rr *NID) copy() RR {
|
||||||
|
return &NID{*rr.Hdr.copyHeader(), rr.Preference, rr.NodeID}
|
||||||
|
}
|
||||||
|
func (rr *NIMLOC) copy() RR {
|
||||||
|
return &NIMLOC{*rr.Hdr.copyHeader(), rr.Locator}
|
||||||
|
}
|
||||||
|
func (rr *NINFO) copy() RR {
|
||||||
|
ZSData := make([]string, len(rr.ZSData))
|
||||||
|
copy(ZSData, rr.ZSData)
|
||||||
|
return &NINFO{*rr.Hdr.copyHeader(), ZSData}
|
||||||
|
}
|
||||||
|
func (rr *NS) copy() RR {
|
||||||
|
return &NS{*rr.Hdr.copyHeader(), rr.Ns}
|
||||||
|
}
|
||||||
|
func (rr *NSAPPTR) copy() RR {
|
||||||
|
return &NSAPPTR{*rr.Hdr.copyHeader(), rr.Ptr}
|
||||||
|
}
|
||||||
|
func (rr *NSEC) copy() RR {
|
||||||
|
TypeBitMap := make([]uint16, len(rr.TypeBitMap))
|
||||||
|
copy(TypeBitMap, rr.TypeBitMap)
|
||||||
|
return &NSEC{*rr.Hdr.copyHeader(), rr.NextDomain, TypeBitMap}
|
||||||
|
}
|
||||||
|
func (rr *NSEC3) copy() RR {
|
||||||
|
TypeBitMap := make([]uint16, len(rr.TypeBitMap))
|
||||||
|
copy(TypeBitMap, rr.TypeBitMap)
|
||||||
|
return &NSEC3{*rr.Hdr.copyHeader(), rr.Hash, rr.Flags, rr.Iterations, rr.SaltLength, rr.Salt, rr.HashLength, rr.NextDomain, TypeBitMap}
|
||||||
|
}
|
||||||
|
func (rr *NSEC3PARAM) copy() RR {
|
||||||
|
return &NSEC3PARAM{*rr.Hdr.copyHeader(), rr.Hash, rr.Flags, rr.Iterations, rr.SaltLength, rr.Salt}
|
||||||
|
}
|
||||||
|
func (rr *OPENPGPKEY) copy() RR {
|
||||||
|
return &OPENPGPKEY{*rr.Hdr.copyHeader(), rr.PublicKey}
|
||||||
|
}
|
||||||
|
func (rr *OPT) copy() RR {
|
||||||
|
Option := make([]EDNS0, len(rr.Option))
|
||||||
|
copy(Option, rr.Option)
|
||||||
|
return &OPT{*rr.Hdr.copyHeader(), Option}
|
||||||
|
}
|
||||||
|
func (rr *PTR) copy() RR {
|
||||||
|
return &PTR{*rr.Hdr.copyHeader(), rr.Ptr}
|
||||||
|
}
|
||||||
|
func (rr *PX) copy() RR {
|
||||||
|
return &PX{*rr.Hdr.copyHeader(), rr.Preference, rr.Map822, rr.Mapx400}
|
||||||
|
}
|
||||||
|
func (rr *RFC3597) copy() RR {
|
||||||
|
return &RFC3597{*rr.Hdr.copyHeader(), rr.Rdata}
|
||||||
|
}
|
||||||
|
func (rr *RKEY) copy() RR {
|
||||||
|
return &RKEY{*rr.Hdr.copyHeader(), rr.Flags, rr.Protocol, rr.Algorithm, rr.PublicKey}
|
||||||
|
}
|
||||||
|
func (rr *RP) copy() RR {
|
||||||
|
return &RP{*rr.Hdr.copyHeader(), rr.Mbox, rr.Txt}
|
||||||
|
}
|
||||||
|
func (rr *RRSIG) copy() RR {
|
||||||
|
return &RRSIG{*rr.Hdr.copyHeader(), 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.copyHeader(), rr.Preference, rr.Host}
|
||||||
|
}
|
||||||
|
func (rr *SOA) copy() RR {
|
||||||
|
return &SOA{*rr.Hdr.copyHeader(), 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.copyHeader(), Txt}
|
||||||
|
}
|
||||||
|
func (rr *SRV) copy() RR {
|
||||||
|
return &SRV{*rr.Hdr.copyHeader(), rr.Priority, rr.Weight, rr.Port, rr.Target}
|
||||||
|
}
|
||||||
|
func (rr *SSHFP) copy() RR {
|
||||||
|
return &SSHFP{*rr.Hdr.copyHeader(), rr.Algorithm, rr.Type, rr.FingerPrint}
|
||||||
|
}
|
||||||
|
func (rr *TA) copy() RR {
|
||||||
|
return &TA{*rr.Hdr.copyHeader(), rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest}
|
||||||
|
}
|
||||||
|
func (rr *TALINK) copy() RR {
|
||||||
|
return &TALINK{*rr.Hdr.copyHeader(), rr.PreviousName, rr.NextName}
|
||||||
|
}
|
||||||
|
func (rr *TKEY) copy() RR {
|
||||||
|
return &TKEY{*rr.Hdr.copyHeader(), 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.copyHeader(), rr.Usage, rr.Selector, rr.MatchingType, rr.Certificate}
|
||||||
|
}
|
||||||
|
func (rr *TSIG) copy() RR {
|
||||||
|
return &TSIG{*rr.Hdr.copyHeader(), 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.copyHeader(), Txt}
|
||||||
|
}
|
||||||
|
func (rr *UID) copy() RR {
|
||||||
|
return &UID{*rr.Hdr.copyHeader(), rr.Uid}
|
||||||
|
}
|
||||||
|
func (rr *UINFO) copy() RR {
|
||||||
|
return &UINFO{*rr.Hdr.copyHeader(), rr.Uinfo}
|
||||||
|
}
|
||||||
|
func (rr *URI) copy() RR {
|
||||||
|
return &URI{*rr.Hdr.copyHeader(), rr.Priority, rr.Weight, rr.Target}
|
||||||
|
}
|
||||||
|
func (rr *X25) copy() RR {
|
||||||
|
return &X25{*rr.Hdr.copyHeader(), rr.PSDNAddress}
|
||||||
|
}
|
|
@ -2143,6 +2143,12 @@
|
||||||
"path": "github.com/maximilien/softlayer-go/softlayer",
|
"path": "github.com/maximilien/softlayer-go/softlayer",
|
||||||
"revision": "85659debe44fab5792fc92cf755c04b115b9dc19"
|
"revision": "85659debe44fab5792fc92cf755c04b115b9dc19"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "OUZ1FFXyKs+Cfg9M9rmXqqweQck=",
|
||||||
|
"path": "github.com/miekg/dns",
|
||||||
|
"revision": "db96a2b759cdef4f11a34506a42eb8d1290c598e",
|
||||||
|
"revisionTime": "2016-07-26T03:20:27Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "GSum+utW01N3KeMdvAPnsW0TemM=",
|
"checksumSHA1": "GSum+utW01N3KeMdvAPnsW0TemM=",
|
||||||
"path": "github.com/michaelklishin/rabbit-hole",
|
"path": "github.com/michaelklishin/rabbit-hole",
|
||||||
|
|
|
@ -25,6 +25,7 @@ body.layout-consul,
|
||||||
body.layout-datadog,
|
body.layout-datadog,
|
||||||
body.layout-digitalocean,
|
body.layout-digitalocean,
|
||||||
body.layout-dme,
|
body.layout-dme,
|
||||||
|
body.layout-dns,
|
||||||
body.layout-dnsimple,
|
body.layout-dnsimple,
|
||||||
body.layout-docker,
|
body.layout-docker,
|
||||||
body.layout-dyn,
|
body.layout-dyn,
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
layout: "dns"
|
||||||
|
page_title: "Provider: DNS"
|
||||||
|
sidebar_current: "docs-dns-index"
|
||||||
|
description: |-
|
||||||
|
The DNS provider supports DNS updates (RFC 2136). Additionally, the provider can be configured with secret key based transaction authentication (RFC 2845).
|
||||||
|
---
|
||||||
|
|
||||||
|
# DNS Provider
|
||||||
|
|
||||||
|
The DNS provider supports DNS updates (RFC 2136). Additionally, the provider can be configured with secret key based transaction authentication (RFC 2845).
|
||||||
|
|
||||||
|
Use the navigation to the left to read about the available resources.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Configure the DNS Provider
|
||||||
|
provider "dns" {
|
||||||
|
update {
|
||||||
|
server = "192.168.0.1"
|
||||||
|
key_name = "example.com."
|
||||||
|
key_algorithm = "hmac-md5"
|
||||||
|
key_secret = "3VwZXJzZWNyZXQ="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a DNS A record set
|
||||||
|
resource "dns_a_record_set" "www" {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
`update` - (Optional) When the provider is used for DNS updates, this block is required. Structure is documented below.
|
||||||
|
|
||||||
|
The `update` block supports the following attributes:
|
||||||
|
|
||||||
|
* `server` - (Required) The IPv4 address of the DNS server to send updates to.
|
||||||
|
* `port` - (Optional) The target UDP port on the server where updates are sent to. Defaults to `53`.
|
||||||
|
* `key_name` - (Optional) The name of the TSIG key used to sign the DNS update messages.
|
||||||
|
* `key_algorithm` - (Optional; Required if `key_name` is set) When using TSIG authentication, the algorithm to use for HMAC. Valid values are `hmac-md5`, `hmac-sha1`, `hmac-sha256` or `hmac-sha512`.
|
||||||
|
* `key_secret` - (Optional; Required if `key_name` is set)
|
||||||
|
A Base64-encoded string containing the shared secret to be used for TSIG.
|
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
layout: "dns"
|
||||||
|
page_title: "DNS: dns_a_record_set"
|
||||||
|
sidebar_current: "docs-dns-a-record-set"
|
||||||
|
description: |-
|
||||||
|
Creates a A type DNS record set.
|
||||||
|
---
|
||||||
|
|
||||||
|
# dns\_a\_record\_set
|
||||||
|
|
||||||
|
Creates a A type DNS record set.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "dns_a_record_set" "www" {
|
||||||
|
zone = "example.com."
|
||||||
|
name = "www"
|
||||||
|
addresses = ["192.168.0.1", "192.168.0.2", "192.168.0.3"]
|
||||||
|
ttl = 300
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `zone` - (Required) DNS zone the record set belongs to. It must be an FQDN, that is, include the trailing dot.
|
||||||
|
* `name` - (Required) The name of the record set. The `zone` argument will be appended to this value to create the full record path.
|
||||||
|
* `addresses` - (Required) The IPv4 addresses this record set will point to.
|
||||||
|
* `ttl` - (Optional) The TTL of the record set. Defaults to `3600`.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `zone` - See Argument Reference above.
|
||||||
|
* `name` - See Argument Reference above.
|
||||||
|
* `addresses` - See Argument Reference above.
|
||||||
|
* `ttl` - See Argument Reference above.
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
layout: "dns"
|
||||||
|
page_title: "DNS: dns_aaaa_record_set"
|
||||||
|
sidebar_current: "docs-dns-aaaa-record-set"
|
||||||
|
description: |-
|
||||||
|
Creates a AAAA type DNS record set.
|
||||||
|
---
|
||||||
|
|
||||||
|
# dns\_aaaa\_record\_set
|
||||||
|
|
||||||
|
Creates a AAAA type DNS record set.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "dns_aaaa_record_set" "www" {
|
||||||
|
zone = "example.com."
|
||||||
|
name = "www"
|
||||||
|
addresses = ["fdd5:e282:43b8:5303:dead:beef:cafe:babe", "fdd5:e282:43b8:5303:cafe:babe:dead:beef"]
|
||||||
|
ttl = 300
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `zone` - (Required) DNS zone the record set belongs to. It must be an FQDN, that is, include the trailing dot.
|
||||||
|
* `name` - (Required) The name of the record set. The `zone` argument will be appended to this value to create the full record path.
|
||||||
|
* `addresses` - (Required) The IPv6 addresses this record set will point to.
|
||||||
|
* `ttl` - (Optional) The TTL of the record set. Defaults to `3600`.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `zone` - See Argument Reference above.
|
||||||
|
* `name` - See Argument Reference above.
|
||||||
|
* `addresses` - See Argument Reference above.
|
||||||
|
* `ttl` - See Argument Reference above.
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
layout: "dns"
|
||||||
|
page_title: "DNS: dns_cname_record"
|
||||||
|
sidebar_current: "docs-dns-cname-record"
|
||||||
|
description: |-
|
||||||
|
Creates a CNAME type DNS record.
|
||||||
|
---
|
||||||
|
|
||||||
|
# dns\_cname\_record
|
||||||
|
|
||||||
|
Creates a CNAME type DNS record.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "dns_cname_record" "foo" {
|
||||||
|
zone = "example.com."
|
||||||
|
name = "foo"
|
||||||
|
cname = "bar.example.com."
|
||||||
|
ttl = 300
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `zone` - (Required) DNS zone the record belongs to. It must be an FQDN, that is, include the trailing dot.
|
||||||
|
* `name` - (Required) The name of the record. The `zone` argument will be appended to this value to create the full record path.
|
||||||
|
* `cname` - (Required) The canonical name this record will point to.
|
||||||
|
* `ttl` - (Optional) The TTL of the record set. Defaults to `3600`.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `zone` - See Argument Reference above.
|
||||||
|
* `name` - See Argument Reference above.
|
||||||
|
* `cname` - See Argument Reference above.
|
||||||
|
* `ttl` - See Argument Reference above.
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
layout: "dns"
|
||||||
|
page_title: "DNS: dns_ptr_record"
|
||||||
|
sidebar_current: "docs-dns-ptr-record"
|
||||||
|
description: |-
|
||||||
|
Creates a PTR type DNS record.
|
||||||
|
---
|
||||||
|
|
||||||
|
# dns\_ptr\_record
|
||||||
|
|
||||||
|
Creates a PTR type DNS record.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "dns_ptr_record" "dns-sd" {
|
||||||
|
zone = "example.com."
|
||||||
|
name = "r._dns-sd"
|
||||||
|
ptr = "example.com."
|
||||||
|
ttl = 300
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `zone` - (Required) DNS zone the record belongs to. It must be an FQDN, that is, include the trailing dot.
|
||||||
|
* `name` - (Required) The name of the record. The `zone` argument will be appended to this value to create the full record path.
|
||||||
|
* `ptr` - (Required) The canonical name this record will point to.
|
||||||
|
* `ttl` - (Optional) The TTL of the record set. Defaults to `3600`.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `zone` - See Argument Reference above.
|
||||||
|
* `name` - See Argument Reference above.
|
||||||
|
* `ptr` - See Argument Reference above.
|
||||||
|
* `ttl` - See Argument Reference above.
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<% wrap_layout :inner do %>
|
||||||
|
<% content_for :sidebar do %>
|
||||||
|
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
||||||
|
<ul class="nav docs-sidenav">
|
||||||
|
<li<%= sidebar_current("docs-home") %>>
|
||||||
|
<a href="/docs/providers/index.html">« Documentation Home</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-dns-index") %>>
|
||||||
|
<a href="/docs/providers/dns/index.html">dns Provider</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current(/^docs-dns/) %>>
|
||||||
|
<a href="#">Resources</a>
|
||||||
|
<ul class="nav nav-visible">
|
||||||
|
<li<%= sidebar_current("docs-dns-a-record-set") %>>
|
||||||
|
<a href="/docs/providers/dns/r/dns_a_record_set.html">dns_a_record_set</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-dns-aaaa-record-set") %>>
|
||||||
|
<a href="/docs/providers/dns/r/dns_aaaa_record_set.html">dns_aaaa_record_set</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-dns-cname-record") %>>
|
||||||
|
<a href="/docs/providers/dns/r/dns_cname_record.html">dns_cname_record</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-dns-ptr-record") %>>
|
||||||
|
<a href="/docs/providers/dns/r/dns_ptr_record.html">dns_ptr_record</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= yield %>
|
||||||
|
<% end %>
|
|
@ -230,9 +230,13 @@
|
||||||
<a href="/docs/providers/datadog/index.html">Datadog</a>
|
<a href="/docs/providers/datadog/index.html">Datadog</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-providers-do") %>>
|
<li<%= sidebar_current("docs-providers-do") %>>
|
||||||
<a href="/docs/providers/do/index.html">DigitalOcean</a>
|
<a href="/docs/providers/do/index.html">DigitalOcean</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-providers-dns") %>>
|
||||||
|
<a href="/docs/providers/dns/index.html">DNS</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-providers-dme") %>>
|
<li<%= sidebar_current("docs-providers-dme") %>>
|
||||||
<a href="/docs/providers/dme/index.html">DNSMadeEasy</a>
|
<a href="/docs/providers/dme/index.html">DNSMadeEasy</a>
|
||||||
|
|
Loading…
Reference in New Issue