9 Commits

Author SHA1 Message Date
c995920b68 fixed typo in drone file
All checks were successful
continuous-integration/drone/tag Build is passing
2020-12-29 14:47:00 +01:00
88a9ead27d added drone file 2020-12-29 14:45:41 +01:00
d77db9234e moved handle of blacklist domains 2020-12-28 00:36:54 +01:00
9bc041ca7e implemented blacklist 2020-12-27 22:13:13 +01:00
1771ca2a53 zoneparsing improvment and log newline 2020-12-26 21:56:51 +01:00
0da61804f7 moved blocking wait 2020-12-26 21:34:11 +01:00
5ce381f370 added config path as parameter 2020-12-26 21:31:50 +01:00
85db27cdde added README 2020-12-26 16:45:54 +01:00
8fdb5ac3dd added address to bind to 2020-12-26 14:41:08 +01:00
5 changed files with 236 additions and 34 deletions

30
.drone.yml Normal file
View File

@@ -0,0 +1,30 @@
kind: pipeline
name: default
steps:
- name: build
image: golang
commands:
- go build
- name: gitea_release
image: plugins/gitea-release
settings:
api_key:
from_secret: GITEA_API_KEY
base_url: https://git.kapelle.org
files:
- cool-dns
checksum:
- md5
- sha1
- sha256
title: v0.1
prerelease: true
when:
event:
- tag
trigger:
event:
- tag

32
README.md Normal file
View File

@@ -0,0 +1,32 @@
# Cool dns
A simple dns server written in Go. It supports zonefile parsing, different zones based on IP and forwarding.
# Configuration
Example config file
```yaml
zones:
- zone: example.com. # Fully qualified domain name of the zone
file: zonefile.txt # Path to the zonefile to parse
- zone: example.com.
file: zonefile2.txt
acl: # What IPs can query the zone
- lan
acl: # List of ip filter rules
- name: vpn # Name of the rule
cidr: 10.0.0.0/24 # CIDR of the rule
- name: lan
cidr: 192.168.0.0/16
- name: local
cidr: 127.0.0.1/32
forward:
acl: # What IPs are allowed
- vpn
server: "8.8.8.8:53" # DNS server to forward to
address: 0.0.0.0:8053 # What address and port to listen on
```

115
blacklist.go Normal file
View File

@@ -0,0 +1,115 @@
package main
import (
"io/ioutil"
"log"
"net"
"net/http"
"regexp"
"github.com/miekg/dns"
)
const blockTTL uint32 = 300
var nullIPv4 = net.IPv4(0, 0, 0, 0)
var nullIPv6 = net.ParseIP("::/0")
func loadBlacklist(config []configBlacklist) map[string]bool {
list := make([]string, 0)
for _, element := range config {
raw, err := requestBacklist(element)
if err != nil {
log.Printf("Failed to load blacklist %s reason: %s", element.URL, err.Error())
continue
}
domains := parseRawBlacklist(element, *raw)
list = append(list, domains...)
}
// list = removeDuplicates(list)
// sort.Strings(list)
domainMap := make(map[string]bool)
for _, e := range list {
domainMap[e] = true
}
return domainMap
}
func removeDuplicates(elements []string) []string {
encountered := map[string]bool{}
result := []string{}
for v := range elements {
if !encountered[elements[v]] {
encountered[elements[v]] = true
result = append(result, elements[v])
}
}
return result
}
func requestBacklist(blacklist configBlacklist) (*string, error) {
// Request list
resp, err := http.Get(blacklist.URL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
bodyString := string(body)
log.Printf("Downloaded blacklist %s", blacklist.URL)
return &bodyString, err
}
func parseRawBlacklist(blacklist configBlacklist, raw string) []string {
finalList := make([]string, 0)
reg := regexp.MustCompile(`(?mi)^\s*(#*)\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+([a-zA-Z0-9\.\- ]+)$`)
matches := reg.FindAllStringSubmatch(raw, -1)
for _, match := range matches {
if match[1] != "#" {
finalList = append(finalList, dns.Fqdn(match[3]))
}
}
return finalList
}
func handleBlockedDomain(w dns.ResponseWriter, r *dns.Msg) {
q := r.Question[0]
m := new(dns.Msg)
m.SetReply(r)
if q.Qtype == dns.TypeA {
m.Answer = append(m.Answer, &dns.A{
Hdr: dns.RR_Header{
Name: q.Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: blockTTL,
},
A: nullIPv4,
})
} else if q.Qtype == dns.TypeAAAA {
m.Answer = append(m.Answer, &dns.AAAA{
Hdr: dns.RR_Header{
Name: q.Name,
Rrtype: dns.TypeAAAA,
Class: dns.ClassINET,
Ttl: blockTTL,
},
AAAA: nullIPv6,
})
}
w.WriteMsg(m)
}

View File

@@ -17,5 +17,11 @@ acl:
forward:
acl:
- vpn
- local
server: "8.8.8.8:53"
address: 0.0.0.0:8053
blacklist:
- url: https://raw.githubusercontent.com/anudeepND/blacklist/master/adservers.txt
format: host

View File

@@ -1,12 +1,12 @@
package main
import (
"flag"
"io/ioutil"
"log"
"net"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
@@ -24,9 +24,11 @@ type zoneMap map[string][]zoneView
type rrMap map[uint16]map[string][]dns.RR
type config struct {
Zones []configZone `yaml:"zones"`
ACL []configACL `yaml:"acl"`
Forward configForward `yaml:"forward"`
Zones []configZone `yaml:"zones"`
ACL []configACL `yaml:"acl"`
Forward configForward `yaml:"forward"`
Address string `yaml:"address"`
Blacklist []configBlacklist `yaml:"blacklist"`
}
type configForward struct {
@@ -45,6 +47,11 @@ type configZone struct {
ACL []string `yaml:"acl"`
}
type configBlacklist struct {
URL string `yaml:"url"`
Format string `yaml:"format"`
}
var anyRecordTypes = []uint16{
dns.TypeSOA,
dns.TypeA,
@@ -57,8 +64,8 @@ var anyRecordTypes = []uint16{
dns.TypeCAA,
}
func loadConfig() (*config, error) {
file, err := ioutil.ReadFile("config.yml")
func loadConfig(configPath string) (*config, error) {
file, err := ioutil.ReadFile(configPath)
if err != nil {
return nil, err
}
@@ -76,7 +83,7 @@ func loadConfig() (*config, error) {
func loadZones(configZones []configZone) (zoneMap, error) {
zones := make(zoneMap)
for _, z := range configZones {
rrs, err := loadZonefile(z.File)
rrs, err := loadZonefile(z.File, z.Zone)
if err != nil {
return nil, err
}
@@ -110,14 +117,14 @@ func createRRMap(rrs []dns.RR) rrMap {
return rrMap
}
func loadZonefile(filepath string) ([]dns.RR, error) {
func loadZonefile(filepath, origin string) ([]dns.RR, error) {
file, err := os.Open(filepath)
if err != nil {
return nil, err
}
parser := dns.NewZoneParser(file, "", "")
parser := dns.NewZoneParser(file, origin, filepath)
var rrs = make([]dns.RR, 0)
@@ -148,7 +155,7 @@ func createACLList(config []configACL) (map[string]*net.IPNet, error) {
return acls, nil
}
func createServer(zones zoneMap, config config, aclList map[string]*net.IPNet) *dns.ServeMux {
func createServer(zones zoneMap, config config, aclList map[string]*net.IPNet, blacklist map[string]bool) *dns.ServeMux {
srv := dns.NewServeMux()
c := new(dns.Client)
@@ -159,7 +166,7 @@ func createServer(zones zoneMap, config config, aclList map[string]*net.IPNet) *
remoteIP, _, err := net.SplitHostPort(w.RemoteAddr().String())
ip := net.ParseIP(remoteIP)
if err != nil && ip != nil {
log.Printf("Faild to parse remote IP WTF? :%s", err.Error())
log.Printf("Faild to parse remote IP WTF? :%s\n", err.Error())
return
}
@@ -187,7 +194,7 @@ func createServer(zones zoneMap, config config, aclList map[string]*net.IPNet) *
ip := net.ParseIP(remoteIP)
if err != nil && ip != nil {
log.Printf("Faild to parse remote IP WTF? :%s", err.Error())
log.Printf("Faild to parse remote IP WTF? :%s\n", err.Error())
return
}
@@ -197,38 +204,36 @@ func createServer(zones zoneMap, config config, aclList map[string]*net.IPNet) *
return
}
// Forward request
in, _, err := c.Exchange(r, config.Forward.Server)
if _, ok := blacklist[r.Question[0].Name]; ok {
handleBlockedDomain(w, r)
} else {
// Forward request
in, _, err := c.Exchange(r, config.Forward.Server)
if err != nil {
rcodeRequest(w, r, dns.RcodeServerFailure)
return
if err != nil {
rcodeRequest(w, r, dns.RcodeServerFailure)
return
}
w.WriteMsg(in)
}
w.WriteMsg(in)
})
return srv
}
func listenAndServer(server *dns.ServeMux) {
func listenAndServer(server *dns.ServeMux, address string) {
go func() {
if err := dns.ListenAndServe(":"+strconv.Itoa(8053), "udp", server); err != nil {
if err := dns.ListenAndServe(address, "udp", server); err != nil {
log.Fatalf("Failed to set udp listener %s\n", err.Error())
}
}()
go func() {
if err := dns.ListenAndServe(":"+strconv.Itoa(8053), "tcp", server); err != nil {
if err := dns.ListenAndServe(address, "tcp", server); err != nil {
log.Fatalf("Failed to set tcp listener %s\n", err.Error())
}
}()
sig := make(chan os.Signal)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
s := <-sig
log.Printf("Signal (%v) received, stopping\n", s)
os.Exit(0)
}
func checkACL(alcRules []string, aclList map[string]*net.IPNet, ip net.IP) bool {
@@ -324,22 +329,36 @@ func handleRequest(w dns.ResponseWriter, r *dns.Msg, zone zoneView) {
}
func main() {
config, err := loadConfig()
configPath := flag.String("c", "/etc/cool-dns/config.yaml", "path to the config file")
flag.Parse()
config, err := loadConfig(*configPath)
if err != nil {
log.Fatalf("Failed to load config: %s", err.Error())
log.Fatalf("Failed to load config: %s\n", err.Error())
}
zones, err := loadZones(config.Zones)
if err != nil {
log.Fatalf("Failed to load zones: %s", err.Error())
log.Fatalf("Failed to load zones: %s\n", err.Error())
}
aclList, err := createACLList(config.ACL)
if err != nil {
log.Fatalf("Failed to parse ACL rules: %s", err.Error())
log.Fatalf("Failed to parse ACL rules: %s\n", err.Error())
}
server := createServer(zones, *config, aclList)
blacklist := loadBlacklist(config.Blacklist)
listenAndServer(server)
server := createServer(zones, *config, aclList, blacklist)
listenAndServer(server, config.Address)
log.Printf("Start listening on udp %s and tcp %s\n", config.Address, config.Address)
sig := make(chan os.Signal)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
s := <-sig
log.Printf("Signal (%v) received, stopping\n", s)
os.Exit(0)
}