| @ -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 | |||
| } | |||