├── .gitignore ├── Dockerfile ├── LICENSE.txt ├── README.md ├── Vagrantfile ├── docker ├── docker-compose.env.example └── docker-compose.yml ├── go.mod ├── go.sum ├── handler.go ├── lookup.go ├── main.go ├── record.go ├── response.go ├── scripts ├── docker-entrypoint.sh └── vagrant-up.sh ├── server.go └── upstart └── redis-dns-server.conf /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | redis-dns-server 3 | redis-dns-server.linux 4 | docker-compose.env 5 | .vagrant/ 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | ADD redis-dns-server.linux /redis-dns-server 3 | ADD scripts/docker-entrypoint.sh /docker-entrypoint.sh 4 | 5 | # ENV is not parsed in CMD/ENTRYPOINT? 6 | # ENTRYPOINT ["/go/src/github.com/dustacio/redis-dns-server/scripts/docker-entrypoint.sh"] 7 | ENTRYPOINT ["/docker-entrypoint.sh"] 8 | EXPOSE 53 -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dusty Jones 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redis Powered DNS Server in GoLang 2 | 3 | This is a DNS server that uses Redis as the backend. Redis records are stored 4 | according to the FQDN (with trailing dot) as the key, and a JSON payload as 5 | the value. 6 | 7 | ## JSON Payload: 8 | 9 | ```json 10 | { 11 | "cnames": ["foo-12345.example.com."], 12 | "fqdn": "foo-12345.example.com.", 13 | "id": 27469, 14 | "ipv4_public_ips": ["104.0.0.1"], 15 | "ipv6_public_ips": [], 16 | "mbox": "admin.example.com." 17 | "mx_servers": [], 18 | "name_servers": ["ns1.example.com", "ns2.example.com"], 19 | "soa": "example.com" 20 | "ttl": 300, 21 | } 22 | ``` 23 | * supply cnames or ipv4_public_ips, not both 24 | * mbox admin.example.com # Don't use '@' in DNS email addresses 25 | * wildcard records, eg. `www.foo-12345.example.com.` are supported. The Redis 26 | key for wildcards is `*.foo-12345.example.com.`. 27 | 28 | ## Usage: 29 | 30 | ``` 31 | ./redis-dns-server \ 32 | -redis-server-url redis://127.0.0.1:6379 \ 33 | -port 5300 34 | ``` 35 | 36 | Port `53` is the standard port. Using a port less than `1024` will require 37 | root privileges. 38 | 39 | 40 | ## Development 41 | 42 | ### Building 43 | 44 | General build steps: 45 | 46 | Download from github and build using golang 1.11 47 | Dependencies are automatically installed 48 | 49 | ``` 50 | 51 | $ go build 52 | $ ./redis-dns-server --help 53 | 54 | ``` 55 | 56 | ### Using Vagrant 57 | 58 | A simple `Vagrantfile` is provided to quickly spin up an Ubuntu 14.04 LTS box, 59 | that already has GoLang installed, as well as the latest version of Docker, 60 | and Docker Compose. 61 | 62 | ``` 63 | 64 | $ vagrant up 65 | $ vagrant ssh 66 | 67 | ``` 68 | 69 | Inside Vagrant, the current working project directory will be accessible at 70 | `/vagrant`. 71 | 72 | 73 | ### Using Docker and Docker Compose 74 | 75 | Either from your local machine running Docker, or from within the Vagrant box: 76 | 77 | ``` 78 | 79 | $ cp -a docker-compose.env.example docker-compose.env 80 | $ docker-compose up 81 | 82 | ``` 83 | 84 | To rebuild the image manually: 85 | 86 | ``` 87 | 88 | $ docker-compose build 89 | 90 | ``` 91 | 92 | 93 | ## Deployment 94 | 95 | ### Docker 96 | 97 | Reference the above Development section for using Docker Compose. 98 | Alternatively, Redis DNS Server can be deployed with Docker in the following 99 | fashion. 100 | 101 | #### Building 102 | 103 | ``` 104 | 105 | $ make linux 106 | $ docker build -t 'redis-dns-server:latest' . 107 | 108 | ``` 109 | 110 | #### Linking With a Redis Container 111 | 112 | ``` 113 | 114 | $ docker run -tid --name redis redis 115 | $ docker run -itd \ 116 | -e DOMAIN="example.com" \ 117 | -e HOSTNAME="myhostname.example.com" \ 118 | --link redis:db \ 119 | -p 53:53 \ 120 | redis-dns-server:latest 121 | 122 | ``` 123 | 124 | #### Linking With an External Redis Server 125 | 126 | ``` 127 | 128 | $ docker run -itd \ 129 | -e DOMAIN="example.com" \ 130 | -e HOSTNAME="myhostname.example.com" \ 131 | -e REDIS_HOST="redis.example.com" \ 132 | -p 53:53 \ 133 | redis-dns-server:latest 134 | 135 | ``` 136 | 137 | #### Using an Environment File 138 | 139 | You can load all `ENV` variables from an `ENV` file. An example `ENV` file 140 | can be found at `docker-compose.env.example`, and looks something like: 141 | 142 | ``` 143 | 144 | REDIS_HOST=redis.example.com 145 | REDIS_PORT=6379 146 | REDIS_DB=0 147 | REDIS_USERNAME=myuser 148 | REDIS_PASSWORD=mypassword 149 | DOMAIN=example.com 150 | DOMAIN_EMAIL=admin.example.com 151 | HOSTNAME=myhostname.example.com 152 | 153 | ``` 154 | 155 | Using it: 156 | 157 | ``` 158 | $ docker run -itd \ 159 | --env-file /path/to/myenv.file \ 160 | -p 53:53 \ 161 | redis-dns-server:latest 162 | ``` 163 | 164 | ## Inspiration: 165 | 166 | * https://github.com/ConradIrwin/aws-name-server 167 | * https://github.com/miekg/dns 168 | 169 | ## TODO List: 170 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | VAGRANTFILE_API_VERSION = "2" 5 | 6 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 7 | config.vm.synced_folder ".", "/vagrant" 8 | config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box" 9 | config.vm.box = "ubuntu-14.04-amd64" 10 | config.vm.network "private_network", type: :dhcp, auto_config: true 11 | config.vm.provision "shell" do |s| 12 | s.inline = "cd /vagrant && bash scripts/vagrant-up.sh" 13 | end 14 | 15 | config.vm.provider "virtualbox" do |v| 16 | v.memory = 1024 17 | v.cpus = 2 18 | v.gui = false 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /docker/docker-compose.env.example: -------------------------------------------------------------------------------- 1 | ### ONLY REQUIRED IF NOT LINKING WITH ANOTHER DOCKER CONTAINER OR OVERRIDING 2 | # REDIS_HOST=redis.example.com 3 | # REDIS_PORT=6379 4 | # REDIS_DB=0 5 | 6 | ### OPTIONAL AUTHENTICATION 7 | # REDIS_USERNAME=myuser 8 | # REDIS_PASSWORD=mypassword 9 | 10 | ### DNS SERVER HOSTNAME (OPTIONAL - DEFAULTS TO SYSTEM HOSTNAME) 11 | # HOSTNAME=example.com 12 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | redisdnsserver: 2 | build: . 3 | env_file: docker-compose.env 4 | links: 5 | - redis:db 6 | ports: 7 | - "5300:53" 8 | redis: 9 | image: redis:latest -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dustacio/redis-dns-server 2 | 3 | require ( 4 | github.com/elcuervo/redisurl v0.0.0-20140308042324-86d77c488bb8 5 | github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0 6 | github.com/miekg/dns v1.0.8 7 | golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b // indirect 8 | golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/elcuervo/redisurl v0.0.0-20140308042324-86d77c488bb8 h1:dHFiexLyaqYcMgsE698T1PeGyRhLg/d8Ikr8DZTnNzc= 2 | github.com/elcuervo/redisurl v0.0.0-20140308042324-86d77c488bb8/go.mod h1:j1patiTKl9w0QhBnOOhBdV7uFLrRhfDeQlXVMFspE4M= 3 | github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0 h1:mjZV3MTu2A5gwfT5G9IIiLGdwZNciyVq5qqnmJJZ2JI= 4 | github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0/go.mod h1:pMYMxVaKJqCDC1JUg/XbPJ4/fSazB25zORpFzqsIGIc= 5 | github.com/miekg/dns v1.0.8 h1:Zi8HNpze3NeRWH1PQV6O71YcvJRQ6j0lORO6DAEmAAI= 6 | github.com/miekg/dns v1.0.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 7 | golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ= 8 | golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 9 | golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3 h1:czFLhve3vsQetD6JOJ8NZZvGQIXlnN3/yXxbT6/awxI= 10 | golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 11 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/miekg/dns" 7 | ) 8 | 9 | func (s *RedisDNSServer) handleRequest(w dns.ResponseWriter, r *dns.Msg) { 10 | m := new(dns.Msg) 11 | m.SetReply(r) 12 | m.Authoritative = true 13 | m.RecursionAvailable = false 14 | 15 | for _, msg := range r.Question { 16 | log.Printf("%v %s", dns.TypeToString[msg.Qtype], msg.Name) 17 | 18 | answers := s.Answer(msg) 19 | 20 | if len(answers) > 0 { 21 | m.Answer = append(m.Answer, answers...) 22 | } else { 23 | log.Printf("Warning, No answers\n") 24 | } 25 | } 26 | 27 | err := w.WriteMsg(m) 28 | if err != nil { 29 | log.Printf("Error writing msg %s\n", err) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lookup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "strings" 7 | 8 | "github.com/hoisie/redis" 9 | ) 10 | 11 | // Lookup the record in Redis 12 | func Lookup(client redis.Client, key string) []byte { 13 | downcase := strings.ToLower(key) 14 | log.Printf("⌘ Lookup %s as %s\n", key, downcase) 15 | 16 | byteary, err := client.Get(downcase) 17 | log.Printf(" %s\n", string(byteary)) 18 | if err != nil { 19 | log.Printf("Error during Lookup %s: %s\n", key, err) 20 | return nil 21 | } 22 | return byteary 23 | } 24 | 25 | // *.key.domain or *-key.domain 26 | func wildcardKeys(lookupValue string) []string { 27 | keys := []string{} 28 | // *.key 29 | nameParts := strings.SplitAfterN(lookupValue, ".", 2) 30 | keys = append(keys, "*."+nameParts[1]) 31 | // *-key 32 | keys = append(keys, "*-"+lookupValue) 33 | return keys 34 | } 35 | 36 | // Get the record for key, apply wildcard if bare record doesn't work 37 | func (s *RedisDNSServer) Get(key string) *Record { 38 | keys := append([]string{key}, wildcardKeys(key)...) 39 | r := &Record{} 40 | 41 | for i := 0; i < len(keys); i++ { 42 | bAry := Lookup(s.redisClient, keys[i]) 43 | if bAry != nil { 44 | err := r.Parse(bAry) 45 | if err != nil { 46 | log.Printf("Error parsing JSON %s: %s\n", keys[i], err) 47 | return r 48 | } 49 | if r.TTL == 0 { 50 | r.TTL = TTL 51 | } 52 | return r 53 | } 54 | } 55 | return r // Nothing was found 56 | } 57 | 58 | // Parse value from Redis into Record 59 | func (r *Record) Parse(bAry []byte) error { 60 | err := json.Unmarshal(bAry, r) 61 | if err != nil { 62 | return err 63 | } 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net" 8 | "os" 9 | "strconv" 10 | 11 | "github.com/elcuervo/redisurl" 12 | "github.com/hoisie/redis" 13 | ) 14 | 15 | func main() { 16 | flag.Usage = func() { 17 | fmt.Printf("\nUsage: redis-dns-server -redis-server-url -mbox \n\n") 18 | flag.PrintDefaults() 19 | fmt.Printf("\nThe dns-server needs permission to bind to given port, default is 53.\n\n") 20 | } 21 | 22 | redisServerURLStr := flag.String("redis-server-url", "", "redis://[:password]@]host:port[/db-number][?option=value]") 23 | domain := flag.String("domain", "", "No longer used") 24 | hostname := flag.String("hostname", "", "Public hostname of *this* server") 25 | port := flag.Int("port", 53, "Port") 26 | help := flag.Bool("help", false, "Get help") 27 | mbox := flag.String("mbox", "", "No longer used") 28 | 29 | Header() 30 | flag.Parse() 31 | 32 | if *domain != "" { 33 | log.Println("Domain provided but no longer used", *domain) 34 | } 35 | 36 | if *mbox != "" { 37 | log.Println("Mbox provided but no longer used", *mbox) 38 | } 39 | 40 | if *help { 41 | flag.Usage() 42 | os.Exit(0) 43 | } else if *redisServerURLStr == "" { 44 | flag.Usage() 45 | log.Println(" -redis-server-url is a required parameter") 46 | os.Exit(1) 47 | } 48 | 49 | if *hostname == "" { 50 | *hostname, _ = os.Hostname() 51 | } 52 | 53 | redisClient := RedisClient(*redisServerURLStr) 54 | server := NewRedisDNSServer(*hostname, redisClient, *mbox) 55 | hostPort := net.JoinHostPort("0.0.0.0", strconv.Itoa(*port)) 56 | log.Printf("Serving DNS records from %s on %s", server.hostname, hostPort) 57 | 58 | go server.listenAndServe(hostPort, "udp") 59 | server.listenAndServe(hostPort, "tcp") 60 | } 61 | 62 | // Header fancy ascii art 63 | func Header() { 64 | head := ` 65 | ___ ___ ___ 66 | /\ \ _____ /\ \ /\__\ 67 | /::\ \ /::\ \ \:\ \ /:/ _/_ 68 | /:/\:\__\ /:/\:\ \ \:\ \ /:/ /\ \ 69 | /:/ /:/ / /:/ \:\__\ _____\:\ \ /:/ /::\ \ 70 | /:/_/:/__/___ /:/__/ \:|__| /::::::::\__\ /:/_/:/\:\__\ 71 | \:\/:::::/ / \:\ \ /:/ / \:\~~\~~\/__/ \:\/:/ /:/ / 72 | \::/~~/~~~~ \:\ /:/ / \:\ \ \::/ /:/ / 73 | \:\~~\ \:\/:/ / \:\ \ \/_/:/ / 74 | \:\__\ \::/ / \:\__\ /:/ / 75 | \/__/ \/__/ \/__/ \/__/ 76 | 77 | Redis DNS Server Version: Strange Days 1.0.100 78 | ` 79 | log.Println(head) 80 | 81 | } 82 | 83 | // RedisClient is a client to the Redis server given by urlStr 84 | func RedisClient(urlStr string) redis.Client { 85 | url := redisurl.Parse(urlStr) 86 | 87 | log.Printf("Redis Client Host: %s, Port: %d, DB: %d\n", url.Host, url.Port, url.Database) 88 | 89 | var client redis.Client 90 | address := net.JoinHostPort(url.Host, strconv.Itoa(url.Port)) 91 | client.Addr = address 92 | client.Db = url.Database 93 | client.Password = url.Password 94 | 95 | return client 96 | } 97 | -------------------------------------------------------------------------------- /record.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net" 4 | 5 | // Record is the json format message that is stored in Redis 6 | type Record struct { 7 | CNames []string `json:"cnames"` 8 | IPv4PublicIPs []net.IP `json:"ipv4_public_ips"` 9 | IPv6PublicIPs []net.IP `json:"ipv6_public_ips"` 10 | MBox string `json:"mbox"` 11 | MXServers []string `json:"mx_servers"` 12 | NameServers []string `json:"name_servers"` 13 | SOA string `json:"soa"` 14 | TTL uint32 `json:"ttl"` 15 | } 16 | -------------------------------------------------------------------------------- /response.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "strconv" 6 | 7 | "github.com/miekg/dns" 8 | ) 9 | 10 | // Answer crafts a response to the DNS Question 11 | func (s *RedisDNSServer) Answer(msg dns.Question) []dns.RR { 12 | var answers []dns.RR 13 | record := s.Get(msg.Name) 14 | 15 | // Bail out early if the record isn't found in the key store 16 | if record == nil { 17 | log.Printf("Error no record found for %s\n", msg.Name) 18 | return nil 19 | } 20 | 21 | switch msg.Qtype { 22 | case dns.TypeNS: 23 | answers = append(answers, NS(msg.Name, record)...) 24 | case dns.TypeSOA: 25 | if len(record.NameServers) > 0 { 26 | answers = append(answers, SOA(msg.Name, record, s.getSerialNumber())) 27 | } 28 | case dns.TypeA: 29 | answers = append(answers, A(msg.Name, record)...) 30 | case dns.TypeAAAA: 31 | answers = append(answers, AAAA(msg.Name, record)...) 32 | case dns.TypeCNAME: 33 | answers = append(answers, CNAME(msg.Name, record)...) 34 | case dns.TypeMX: 35 | answers = append(answers, MX(msg.Name, record)...) 36 | default: 37 | answers = append(answers, s.Host(msg.Name, record)...) 38 | } 39 | return answers 40 | } 41 | 42 | func (s *RedisDNSServer) getSerialNumber() uint32 { 43 | sn, err := s.redisClient.Get(serialNumberKey) 44 | if err != nil { 45 | log.Printf("Error reading SerialNumber: %s\n", err) 46 | return uint32(0) 47 | } 48 | 49 | x, _ := strconv.Atoi(string(sn)) 50 | log.Printf("Found SerialNumber %d\n", uint32(x)) 51 | return uint32(x) 52 | } 53 | 54 | // SOA returns the Server of Authority record response 55 | func SOA(name string, record *Record, serialNumber uint32) dns.RR { 56 | return &dns.SOA{ 57 | Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 60}, 58 | Ns: dns.Fqdn(record.NameServers[0]), 59 | Mbox: record.MBox, 60 | Serial: serialNumber, 61 | Refresh: 86400, 62 | Retry: 7200, 63 | Expire: 3600, // RFC1912 suggests 2-4 weeks 1209600-2419200 64 | Minttl: 60, 65 | } 66 | } 67 | 68 | // NS returns the Name Servers 69 | func NS(name string, record *Record) []dns.RR { 70 | var answers []dns.RR 71 | for i := 0; i < len(record.NameServers); i++ { 72 | r := new(dns.NS) 73 | r.Hdr = dns.RR_Header{Name: name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: record.TTL} 74 | r.Ns = dns.Fqdn(record.NameServers[i]) 75 | answers = append(answers, r) 76 | } 77 | return answers 78 | } 79 | 80 | // A returns A records 81 | func A(name string, record *Record) []dns.RR { 82 | var answers []dns.RR 83 | // handle A requests that have no A records 84 | 85 | if len(record.IPv4PublicIPs) == 0 && len(record.CNames) > 0 { 86 | return CNAME(name, record) 87 | } 88 | 89 | for i := 0; i < len(record.IPv4PublicIPs); i++ { 90 | r := new(dns.A) 91 | r.Hdr = dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: record.TTL} 92 | r.A = record.IPv4PublicIPs[i] 93 | answers = append(answers, r) 94 | } 95 | return answers 96 | } 97 | 98 | // AAAA returns IPv6 records 99 | func AAAA(name string, record *Record) []dns.RR { 100 | var answers []dns.RR 101 | for i := 0; i < len(record.IPv4PublicIPs); i++ { 102 | r := new(dns.AAAA) 103 | r.Hdr = dns.RR_Header{Name: name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: record.TTL} 104 | r.AAAA = record.IPv4PublicIPs[i] 105 | answers = append(answers, r) 106 | } 107 | return answers 108 | } 109 | 110 | // CNAME returns canonical names 111 | func CNAME(name string, record *Record) []dns.RR { 112 | var answers []dns.RR 113 | for i := 0; i < len(record.CNames); i++ { 114 | r := new(dns.CNAME) 115 | 116 | r.Hdr = dns.RR_Header{Name: name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: record.TTL} 117 | r.Target = dns.Fqdn(record.CNames[i]) 118 | answers = append(answers, r) 119 | } 120 | return answers 121 | } 122 | 123 | // MX returns mail records 124 | func MX(name string, record *Record) []dns.RR { 125 | var answers []dns.RR 126 | for i := 0; i < len(record.MXServers); i++ { 127 | r := new(dns.MX) 128 | r.Hdr = dns.RR_Header{Name: name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: record.TTL} 129 | r.Mx = record.MXServers[i] 130 | answers = append(answers, r) 131 | } 132 | return answers 133 | 134 | } 135 | 136 | // Host returns cname, a, and aaaa in that order 137 | func (s *RedisDNSServer) Host(name string, record *Record) []dns.RR { 138 | var answers []dns.RR 139 | 140 | answers = append(answers, CNAME(name, record)...) 141 | answers = append(answers, A(name, record)...) 142 | answers = append(answers, AAAA(name, record)...) 143 | 144 | return answers 145 | } 146 | -------------------------------------------------------------------------------- /scripts/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ARE WE LINKED TO A DOCKER CONTAINER? 4 | if [ ! -z $DB_PORT_6379_TCP_ADDR ]; then 5 | REDIS_HOST=$DB_PORT_6379_TCP_ADDR 6 | REDIS_PORT=$DB_PORT_6379_TCP_PORT 7 | fi 8 | 9 | [ -z $REDIS_HOST ] && echo "REDIS_HOST Not Set" && exit 1 10 | [ -z $REDIS_PORT ] && REDIS_PORT="6379" 11 | [ -z $REDIS_DB ] && REDIS_DB="0" 12 | [ -z $HOSTNAME ] && echo "HOSTNAME Not Set" && exit 1 13 | 14 | if [ ! -z $REDIS_USERNAME ]; then 15 | USER_PASS="${REDIS_USERNAME}:${REDIS_PASSWORD}@" 16 | else 17 | USER_PASS="" 18 | fi 19 | 20 | URI="redis://${USER_PASS}${REDIS_HOST}:${REDIS_PORT}/${REDIS_DB}" 21 | /redis-dns-server \ 22 | --hostname=${HOSTNAME} \ 23 | --redis-server-url=${URI} 24 | -------------------------------------------------------------------------------- /scripts/vagrant-up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wget -qO- https://get.docker.io/gpg | sudo apt-key add - 4 | sh -c "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list" 5 | apt-get update && apt-get upgrade -y 6 | 7 | # install latest docker, and redis-server (for testing external redis conn) 8 | apt-get install -y redis-server lxc-docker 9 | pip install -U docker-compose 10 | 11 | exit 0 12 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "strings" 6 | 7 | "github.com/hoisie/redis" 8 | "github.com/miekg/dns" 9 | ) 10 | 11 | // TTL Time to Live in seconds, default value 12 | const TTL uint32 = 300 13 | 14 | // The key used to read the serial number 15 | const serialNumberKey = "redis-dns-server-serial-no" 16 | 17 | // RedisDNSServer contains the configuration details for the server 18 | type RedisDNSServer struct { 19 | hostname string 20 | redisClient redis.Client 21 | mbox string 22 | } 23 | 24 | // NewRedisDNSServer is a convienence for creating a new server 25 | func NewRedisDNSServer(hostname string, redisClient redis.Client, mbox string) *RedisDNSServer { 26 | if !strings.HasSuffix(hostname, ".") { 27 | hostname += "." 28 | } 29 | 30 | server := &RedisDNSServer{ 31 | hostname: hostname, 32 | redisClient: redisClient, 33 | } 34 | 35 | dns.HandleFunc(".", server.handleRequest) 36 | return server 37 | } 38 | 39 | func (s *RedisDNSServer) listenAndServe(port, net string) { 40 | server := &dns.Server{Addr: port, Net: net} 41 | if err := server.ListenAndServe(); err != nil { 42 | log.Fatalf("%s", err) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /upstart/redis-dns-server.conf: -------------------------------------------------------------------------------- 1 | description "DNS Server" 2 | start on filesystem or runlevel [2345] 3 | stop on runlevel [!2345] 4 | respawn 5 | respawn limit 10 5 6 | setuid nobody 7 | setgid nogroup 8 | 9 | exec /usr/local/bin/redis-dns-server --domain example.org --redis-server-url redis://localhost:6379 10 | --------------------------------------------------------------------------------