| @ -0,0 +1,2 @@ | |||||
| /realm | |||||
| *.zone | |||||
| @ -0,0 +1,10 @@ | |||||
| realm: ./cmd/realm/main.go ./server.go ./zone.go | |||||
| go build ./cmd/realm | |||||
| clean: | |||||
| rm -f ./realm | |||||
| run: | |||||
| go run ./cmd/realm/main.go | |||||
| .PHONY: clean run | |||||
| @ -0,0 +1,50 @@ | |||||
| package main | |||||
| import ( | |||||
| "log" | |||||
| "github.com/brettlangdon/realm" | |||||
| "github.com/codegangsta/cli" | |||||
| ) | |||||
| func main() { | |||||
| var app *cli.App = cli.NewApp() | |||||
| app.Name = "realm" | |||||
| app.Version = "0.1.0" | |||||
| app.Author = "Brett Langdon" | |||||
| app.Email = "me@brett.is" | |||||
| app.Flags = []cli.Flag{ | |||||
| cli.StringFlag{ | |||||
| Name: "zone, z", | |||||
| EnvVar: "REALM_ZONE", | |||||
| Usage: "location to DNS zone file [required]", | |||||
| }, | |||||
| cli.StringFlag{ | |||||
| Name: "bind, b", | |||||
| EnvVar: "REALM_BIND", | |||||
| Value: ":53", | |||||
| Usage: "'[<host>]:<port>' to bind too", | |||||
| }, | |||||
| } | |||||
| app.Action = func(c *cli.Context) { | |||||
| var filename string = c.String("zone") | |||||
| if filename == "" { | |||||
| log.Fatal("must supply zone file via \"--zone\" flag or \"REALM_ZONE\" environment variable") | |||||
| } | |||||
| var zone *realm.Zone | |||||
| var err error | |||||
| log.Printf("parsing zone file \"%s\"\n", filename) | |||||
| zone, err = realm.ParseZone(filename) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| var bind string = c.String("bind") | |||||
| log.Printf("starting the server on \"%s\"\n", bind) | |||||
| var server *realm.Server = realm.NewServer(bind, zone) | |||||
| log.Fatal(server.ListenAndServe()) | |||||
| } | |||||
| app.RunAndExitOnError() | |||||
| } | |||||
| @ -0,0 +1,35 @@ | |||||
| package realm | |||||
| import "github.com/miekg/dns" | |||||
| type Server struct { | |||||
| server *dns.Server | |||||
| zone *Zone | |||||
| } | |||||
| func NewServer(listen string, zone *Zone) *Server { | |||||
| var server *Server = &Server{zone: zone} | |||||
| server.server = &dns.Server{ | |||||
| Addr: listen, | |||||
| Net: "udp", | |||||
| Handler: server, | |||||
| } | |||||
| return server | |||||
| } | |||||
| func (server *Server) ListenAndServe() error { | |||||
| return server.server.ListenAndServe() | |||||
| } | |||||
| func (server *Server) ServeDNS(w dns.ResponseWriter, request *dns.Msg) { | |||||
| var response *dns.Msg = &dns.Msg{} | |||||
| response.SetReply(request) | |||||
| response.Compress = true | |||||
| for _, question := range request.Question { | |||||
| var records []dns.RR = server.zone.Lookup(question.Name, question.Qtype, question.Qclass) | |||||
| response.Answer = append(response.Answer, records...) | |||||
| } | |||||
| w.WriteMsg(response) | |||||
| } | |||||
| @ -0,0 +1,59 @@ | |||||
| package realm | |||||
| import ( | |||||
| "fmt" | |||||
| "os" | |||||
| "github.com/miekg/dns" | |||||
| ) | |||||
| type Zone struct { | |||||
| records []dns.RR | |||||
| } | |||||
| func ParseZone(filename string) (*Zone, error) { | |||||
| var zone *Zone | |||||
| var err error | |||||
| zone = &Zone{ | |||||
| records: make([]dns.RR, 0), | |||||
| } | |||||
| var file *os.File | |||||
| file, err = os.Open(filename) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("could not parse zone file \"%s\": \"%s\"", filename, err) | |||||
| } | |||||
| defer file.Close() | |||||
| var tokens chan *dns.Token | |||||
| tokens = dns.ParseZone(file, "", "") | |||||
| for token := range tokens { | |||||
| if token.Error != nil { | |||||
| return nil, fmt.Errorf("could not parse zone file \"%s\": \"%s\"", filename, token.Error) | |||||
| } | |||||
| zone.records = append(zone.records, token.RR) | |||||
| } | |||||
| return zone, nil | |||||
| } | |||||
| func (zone *Zone) Lookup(name string, reqType uint16, reqClass uint16) []dns.RR { | |||||
| name = dns.Fqdn(name) | |||||
| var records []dns.RR = make([]dns.RR, 0) | |||||
| for _, record := range zone.records { | |||||
| var header *dns.RR_Header = record.Header() | |||||
| if header.Name != name || (header.Class != reqClass && reqClass != dns.ClassANY) { | |||||
| continue | |||||
| } | |||||
| if reqType == dns.TypeANY || reqType == header.Rrtype { | |||||
| records = append(records, record) | |||||
| } else if header.Rrtype == dns.TypeCNAME { | |||||
| records = append(records, record) | |||||
| var cname *dns.CNAME = record.(*dns.CNAME) | |||||
| var cnameRecords []dns.RR = zone.Lookup(dns.Fqdn(cname.Target), reqType, reqClass) | |||||
| records = append(records, cnameRecords...) | |||||
| } | |||||
| } | |||||
| return records | |||||
| } | |||||