commit 36ce33e8f1b295fa9e23eea749823936045c1a94 Author: Mael GRAMAIN Date: Sat Dec 12 23:01:04 2020 -0400 Init diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9dbdbde --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +hello: + echo "Lighthouse" + +build: + go build -o bin/lighthouse + +run: + go run main.go + +compile: + echo "Compiling for every OS and Platform" + GOOS=linux GOARCH=arm go build -o bin/lighthouse-linux-arm main.go + GOOS=linux GOARCH=arm64 go build -o bin/lighthouse-linux-arm64 main.go + GOOS=linux GOARCH=amd64 go build -o bin/lighthouse-linux-arm64 main.go + +all: hello build \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a42ea5 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# LightHouse + +LightHouse is a DNS authoritative nameserver made in Go with ``github.com/miekg/dns`` library. + +Records are stored in a MySQL Database and cached using REDIS. + +This software is currently in development and NOT ready for production. + +## What is working +- Read records (stricts & wildcard) from MySQL +- Read and write records (stricts & wildcard) in REDIS +- Recursive wildcard for reverse DNS (IPv6 only) +- Generate dynamic reverse DNS (IPv6 only) +- Respond to all requested MySQL queries + +## ToDo +- Recursive wildcard for reverse DNS (IPv4 part) +- Generate dynamic reverse DNS (IPv4 part) +- XFR +- DNSSEC +- Unit tests +- CI with auto packaging +- Optimization \ No newline at end of file diff --git a/core/handleDnsRequest.go b/core/handleDnsRequest.go new file mode 100644 index 0000000..312c516 --- /dev/null +++ b/core/handleDnsRequest.go @@ -0,0 +1,20 @@ +package core + +import "github.com/miekg/dns" + +//Handle the DNS request +func HandleDnsRequest(w dns.ResponseWriter, r *dns.Msg) { + + //dns.Msg object + //Will be passed to the parseQuery() function + m := new(dns.Msg) + m.SetReply(r) + m.Compress = true //Less CPU usage (?) + + switch r.Opcode { //Only respond to dns queries + case dns.OpcodeQuery: + parseQuery(m) + } + + w.WriteMsg(m) //Write the DNS response +} diff --git a/core/parseQuery.go b/core/parseQuery.go new file mode 100644 index 0000000..7a7558a --- /dev/null +++ b/core/parseQuery.go @@ -0,0 +1,40 @@ +package core + +import ( + "fmt" + + "github.com/miekg/dns" + "github.com/outout14/lighthouse/utils" + "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" +) + +/* + Qtype memo + A = 1 + NS = 2 + PTR = 12 + TXT = 16 + AAAA = 28 +*/ + +//Function called by handleDnsRequest to parse the query from records +func parseQuery(m *dns.Msg) { + for _, q := range m.Question { + + log.Infof("DNS : Query for %s (type : %v)\n", q.Name, q.Qtype) //Log + + record := utils.GetRecord(utils.Record{Fqdn: q.Name, Qtype: q.Qtype}) + + if record.Content != "" { //If the record is found, return it + log.Infof("DNS : Record found for '%s' => '%s'\n", q.Name, record.Content) + rr, err := dns.NewRR(fmt.Sprintf("%s %v %s %s", q.Name, record.TTL, dns.TypeToString[q.Qtype], record.Content)) //Create the response + if err == nil { //If no err + m.Answer = append(m.Answer, rr) + } + } else { + logrus.Debugf("DNS : No record for '%s' (type '%v')\n", record.Fqdn, record.Qtype) + } + + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c6b028f --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module github.com/outout14/lighthouse + +go 1.15 + +require ( + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/go-redis/redis v6.15.9+incompatible + github.com/go-redis/redis/v8 v8.4.2 + github.com/go-sql-driver/mysql v1.5.0 + github.com/mattn/go-colorable v0.1.8 + github.com/miekg/dns v1.1.35 + github.com/sirupsen/logrus v1.7.0 + github.com/smartystreets/goconvey v1.6.4 // indirect + github.com/snowzach/rotatefilehook v0.0.0-20180327172521-2f64f265f58c + gopkg.in/ini.v1 v1.62.0 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c429c04 --- /dev/null +++ b/go.sum @@ -0,0 +1,107 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis/v8 v8.4.2 h1:gKRo1KZ+O3kXRfxeRblV5Tr470d2YJZJVIAv2/S8960= +github.com/go-redis/redis/v8 v8.4.2/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= +github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/snowzach/rotatefilehook v0.0.0-20180327172521-2f64f265f58c h1:iUEy7/LRto3JqR/GLXDTEFP+s+qIjWw4pM8yzMfXC9A= +github.com/snowzach/rotatefilehook v0.0.0-20180327172521-2f64f265f58c/go.mod h1:ZLVe3VfhAuMYLYWliGEydMBoRnfib8EFSqkBYu1ck9E= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/otel v0.14.0 h1:YFBEfjCk9MTjaytCNSUkp9Q8lF7QJezA06T71FbQxLQ= +go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..f502c00 --- /dev/null +++ b/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "database/sql" + "strconv" + + "github.com/go-redis/redis" + "github.com/miekg/dns" + "github.com/outout14/lighthouse/core" + "github.com/outout14/lighthouse/utils" + "github.com/sirupsen/logrus" + "gopkg.in/ini.v1" +) + +//Global vars +var conf *utils.Conf +var DB *sql.DB +var redisDb *redis.Client + +//Main loop +func main() { + //Load Configuration + conf = new(utils.Conf) + err := ini.MapTo(conf, "./config.ini") + utils.CheckErr(err) + + utils.InitLogger(conf) + + // attach request handler func + dns.HandleFunc(".", core.HandleDnsRequest) + + //Init redis database + utils.RedisDatabase(conf) + + //Init sql database + utils.SqlDatabase(conf) + + // start server + server := &dns.Server{Addr: conf.App.Ip + strconv.Itoa(conf.App.Port), Net: "udp"} //define the server + logrus.WithFields(logrus.Fields{"ip": conf.App.Ip, "port": conf.App.Port}).Infof("SERVER : Started") + err = server.ListenAndServe() //start it + utils.CheckErr(err) + + defer server.Shutdown() //shut down on application closing +} diff --git a/utils/queries.go b/utils/queries.go new file mode 100644 index 0000000..7df412a --- /dev/null +++ b/utils/queries.go @@ -0,0 +1,77 @@ +package utils + +import ( + "fmt" + "strings" + + "github.com/go-redis/redis/v8" + "github.com/sirupsen/logrus" +) + +func getDomain(fqdn string) *Domain { + domain := new(Domain) + err := DB.QueryRow( + "SELECT id, friendly_name, fqdn, owner_id, last_edit FROM lighthouse.domains WHERE `fqdn` = ?;", fqdn).Scan( + &domain.ID, + &domain.FriendlyName, + &domain.Fqdn, + &domain.OwnerId, + &domain.LastEdit, + ) + print(err) + return domain +} + +func GetRecord(entry Record) Record { + //Check for strict record in Redis cache + redisKey := entry.Fqdn + "--" + fmt.Sprint(entry.Qtype) + + result, redisErr := redisCheckForRecord(redisKey, entry) + + var sqlErr int + + //If reverse DNS + reverseCheck := IsReverse(entry.Fqdn) + if reverseCheck > 0 { + + if redisErr == redis.Nil { + logrus.Debug("QUERIES : Check for strict reverse in MySQL") + result, sqlErr = sqlCheckForRecord(redisKey, entry.Fqdn, entry) + if sqlErr == 1 { + logrus.Debug("QUERIES : Check for wildcard reverse in MySQL") + result, redisErr = sqlCheckForReverse6Wildcard(redisKey, entry.Fqdn, entry) + } + } + + //For dynamic reverse dns + if strings.Contains(result.Content, "%s") { + record := ExtractAddressFromReverse(entry.Fqdn) + var recordFormated string + if reverseCheck == 1 { + recordFormated = strings.Replace(record, ".", "-", -1) + } else { + recordFormated = strings.Replace(record, ":", "-", -1) + } + result.Content = fmt.Sprintf(result.Content, recordFormated) + } + } else if redisErr == redis.Nil { //If strict record NOT in Redis cache & not Reverse + //Check for wildcard in Redis cache + logrus.Debug("QUERIES : Check for wildcard in redis cache") + mainDomainKey := fmt.Sprintf("*%s", entry.Fqdn[strings.Index(entry.Fqdn, "."):]) //Remove the last subdomain + redismdKey := fmt.Sprintf("%s--%v", mainDomainKey, entry.Qtype) + result, redisErr = redisCheckForRecord(redismdKey, entry) + //If none of both check in mysql + if redisErr == redis.Nil { + //Check for strict record in mysql + logrus.Debug("QUERIES : Check for strict record in MSQL") + result, sqlErr = sqlCheckForRecord(redisKey, entry.Fqdn, entry) + if sqlErr == 1 { + //Check for wildcard record in mysql + logrus.Debug("QUERIES : Check for wildcard in MSQL") + result, sqlErr = sqlCheckForRecord(redismdKey, fmt.Sprint(mainDomainKey), entry) + } + } + } + + return result +} diff --git a/utils/redis.go b/utils/redis.go new file mode 100644 index 0000000..7a60b29 --- /dev/null +++ b/utils/redis.go @@ -0,0 +1,68 @@ +package utils + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/go-redis/redis/v8" + "github.com/sirupsen/logrus" +) + +var ctx = context.Background() + +var redisDb *redis.Client + +func RedisDatabase(conf *Conf) *redis.Client { + logrus.WithFields(logrus.Fields{"ip": conf.Redis.Ip, "port": conf.Redis.Port}).Infof("REDIS : Connection to DB") + rdb := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%v", conf.Redis.Ip, conf.Redis.Port), + Password: conf.Redis.Password, + DB: conf.Redis.Db, + }) + + //Test Redis connection + err := rdb.Set(ctx, "alive", 1, 0).Err() + alive, err := rdb.Get(ctx, "alive").Result() + CheckErr(err) + if alive != "1" { + logrus.WithFields(logrus.Fields{"alive": alive}).Panic("REDIS : Test not passed. alive != 1") + } + CheckErr(err) + logrus.WithFields(logrus.Fields{"db": conf.Redis.Db}).Info("REDIS : Successfull connection") + + redisDb = rdb + + return rdb +} + +func redisCheckForRecord(redisKey string, entry Record) (Record, error) { + val, err := redisDb.Get(ctx, redisKey).Result() + + //If Record in Redis cache + if err == nil { + err := json.Unmarshal([]byte(val), &entry) + logrus.Debugf("REDIS : %s => %s", redisKey, entry.Content) + return entry, err + } else { + //Else return nil + return entry, redis.Nil + } +} + +func redisSet(c *redis.Client, key string, ttl time.Duration, value interface{}) error { + p, err := json.Marshal(value) + if err != nil { + return err + } + return c.Set(ctx, key, p, ttl).Err() +} + +func redisGet(c *redis.Client, key string, dest interface{}) error { + p, err := c.Get(ctx, key).Result() + if err != nil { + return err + } + return json.Unmarshal([]byte(p), dest) +} diff --git a/utils/reverse.go b/utils/reverse.go new file mode 100644 index 0000000..6a6bbb6 --- /dev/null +++ b/utils/reverse.go @@ -0,0 +1,89 @@ +package utils + +import ( + "net" + "strings" + + "github.com/sirupsen/logrus" +) + +// Based on github.com/coredns/coredns/plugin/pkg/dnsutil/reverse.go + +// IsReverse returns 0 is name is not in a reverse zone. Anything > 0 indicates +// name is in a reverse zone. The returned integer will be 1 for in-addr.arpa. (IPv4) +// and 2 for ip6.arpa. (IPv6). +func IsReverse(name string) int { + if strings.HasSuffix(name, IP4arpa) { + return 1 + } + if strings.HasSuffix(name, IP6arpa) { + return 2 + } + logrus.Debug("REVERSE : Not Reverse") + return 0 +} + +// ExtractAddressFromReverse turns a standard PTR reverse record name +// into an IP address. This works for ipv4 or ipv6. +// +// 54.119.58.176.in-addr.arpa. becomes 176.58.119.54. If the conversion +// fails the empty string is returned. +func ExtractAddressFromReverse(reverseName string) string { + search := "" + + f := reverse + + switch { + case strings.HasSuffix(reverseName, IP4arpa): + search = strings.TrimSuffix(reverseName, IP4arpa) + case strings.HasSuffix(reverseName, IP6arpa): + search = strings.TrimSuffix(reverseName, IP6arpa) + f = reverse6 + default: + logrus.Debug("REVERSE : ExtractAddressFromReverse : No result") + return "" + } + + // Reverse the segments and then combine them. + return f(strings.Split(search, ".")) +} + +func reverse(slice []string) string { + for i := 0; i < len(slice)/2; i++ { + j := len(slice) - i - 1 + slice[i], slice[j] = slice[j], slice[i] + } + ip := net.ParseIP(strings.Join(slice, ".")).To4() + if ip == nil { + logrus.Debug("REVERSE : reverse : No result") + logrus.Debug(ip) + return "" + } + return ip.String() +} + +// reverse6 reverse the segments and combine them according to RFC3596: +// b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2 +// is reversed to 2001:db8::567:89ab +func reverse6(slice []string) string { + for i := 0; i < len(slice)/2; i++ { + j := len(slice) - i - 1 + slice[i], slice[j] = slice[j], slice[i] + } + slice6 := []string{} + for i := 0; i < len(slice)/4; i++ { + slice6 = append(slice6, strings.Join(slice[i*4:i*4+4], "")) + } + ip := net.ParseIP(strings.Join(slice6, ":")).To16() + if ip == nil { + return "" + } + return ip.String() +} + +const ( + // IP4arpa is the reverse tree suffix for v4 IP addresses. + IP4arpa = ".in-addr.arpa." + // IP6arpa is the reverse tree suffix for v6 IP addresses. + IP6arpa = ".ip6.arpa." +) diff --git a/utils/sql.go b/utils/sql.go new file mode 100644 index 0000000..0dd5079 --- /dev/null +++ b/utils/sql.go @@ -0,0 +1,87 @@ +package utils + +import ( + "database/sql" + "fmt" + "time" + + "github.com/go-redis/redis" + _ "github.com/go-sql-driver/mysql" + "github.com/sirupsen/logrus" +) + +var DB *sql.DB + +func SqlDatabase(conf *Conf) { + logrus.WithFields(logrus.Fields{"database": conf.Database.Db}).Infof("SQL : Connection to DB") + //db conn + db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", conf.Database.Username, conf.Database.Password, conf.Database.Ip, conf.Database.Port, conf.Database.Db)) + CheckErr(err) + DB = db + + // if there is an error opening the connection, handle it + CheckErr(err) +} + +func SqlTest() { + results, err := DB.Query("SELECT name, content FROM records") + CheckErr(err) + + for results.Next() { + var record Record + err = results.Scan(&record.Fqdn, &record.Content) + CheckErr(err) + logrus.Debugf(record.Content) + } +} + +func sqlCheckForRecord(redisKey string, dKey string, entry Record) (Record, int) { + dbg := DB.QueryRow( + "SELECT id, content, ttl FROM lighthouse.records WHERE `name` = ? AND `type` = ?;", dKey, entry.Qtype).Scan( + &entry.Id, + &entry.Content, + &entry.TTL, + ) + + //logrus.WithFields(logrus.Fields{"name": dKey, "type": entry.Qtype}).Debugf("SQL : ") + + if dbg != nil { + logrus.Debugf("SQL : %v", dbg) + } + + logrus.Debugf("SQL : %s => %s", entry.Fqdn, entry.Content) + + if entry.Content != "" { + //Cache the request in Redis if any result + logrus.Debugf("REDIS : Set entry for %s", redisKey) + logrus.Warningf("REDIS : %s", redisKey) + _ = redisSet(redisDb, redisKey, 30*time.Second, entry) + return entry, 0 + } else { + //Else return nil + return entry, 1 + } +} + +func sqlCheckForReverse6Wildcard(redisKey string, dKey string, entry Record) (Record, error) { + returnedEntry := entry + + results, err := DB.Query("SELECT id, content, name FROM lighthouse.records WHERE name LIKE '*%.ip6.arpa.';") + + for results.Next() { + err = results.Scan(&returnedEntry.Id, &returnedEntry.Content, &returnedEntry.Fqdn) + CheckErr(err) + + if checkReverse6(entry, returnedEntry) { + logrus.Debug("REVERSE : Correct wildcard reverse.") + //Cache the request in Redis if any result + _ = redisSet(redisDb, redisKey, 10*time.Second, returnedEntry) + return returnedEntry, err + } else { + logrus.Debug("REVERSE : WRONG wildcard reverse .") + } + } + + return entry, redis.Nil + +} diff --git a/utils/structs.go b/utils/structs.go new file mode 100644 index 0000000..2a98e8a --- /dev/null +++ b/utils/structs.go @@ -0,0 +1,50 @@ +package utils + +//Structs for configuration +type App struct { + Port int + Ip string + Logdir string + Logfile bool +} + +type Database struct { + Ip string + Port string + Username string + Password string + Db string +} + +type Redis struct { + Ip string + Port int + Password string + Db int + Ttl int +} + +type Conf struct { + App_mode string + App + Database + Redis +} + +type Domain struct { + ID int `json:"id"` + FriendlyName string + Fqdn string + OwnerId int + LastEdit string +} + +type Record struct { + Id int + DomainId int + Fqdn string + Content string + Type int + Qtype uint16 + TTL int +} diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..df8c130 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,69 @@ +package utils + +import ( + "strings" + "time" + + "github.com/mattn/go-colorable" + "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" + "github.com/snowzach/rotatefilehook" +) + +func CheckErr(err error) { + if err != nil { + log.Fatalf("%s\n ", err.Error()) + panic(err) + } +} + +func InitLogger(conf *Conf) { + var logLevel = logrus.InfoLevel + + if conf.App_mode != "production" { + logLevel = logrus.DebugLevel + } + + rotateFileHook, err := rotatefilehook.NewRotateFileHook(rotatefilehook.RotateFileConfig{ + Filename: conf.App.Logdir + "/console.log", + MaxSize: 50, // megabytes + MaxBackups: 3, + MaxAge: 28, //days + Level: logLevel, + Formatter: &logrus.TextFormatter{ + DisableColors: true, + TimestampFormat: "2006-01-02 15:04:05", + FullTimestamp: true, + }, + }) + + if err != nil { + logrus.Fatalf("Failed to initialize file rotate hook: %v", err) + } + + logrus.SetLevel(logLevel) + logrus.SetOutput(colorable.NewColorableStdout()) + logrus.SetFormatter(&logrus.TextFormatter{ + ForceColors: false, + FullTimestamp: true, + TimestampFormat: time.RFC822, + }) + + if conf.App.Logfile { + logrus.AddHook(rotateFileHook) + } + + log.WithFields(log.Fields{"app_mode": conf.App_mode}).Info("Application mode") + log.WithFields(log.Fields{"logLevel": logLevel}).Debug("Log level") +} + +func checkReverse6(entry Record, result Record) bool { + check := strings.Replace(entry.Fqdn, result.Fqdn[1:], "", 1) + logrus.WithFields(logrus.Fields{"entry": entry.Fqdn, "result": result.Fqdn[1:]}).Debug("REVERSE checkReverse6 :") + logrus.Debugf("REVERSE checkReverse6 : %s", check) + if strings.Contains(check, IP6arpa) { + return false + } else { + return true + } +}