Skip to content

Instantly share code, notes, and snippets.

@Regentag
Created May 28, 2018 17:18
Show Gist options
  • Save Regentag/046ab75a5d889a20673a91c1e060eff5 to your computer and use it in GitHub Desktop.
Save Regentag/046ab75a5d889a20673a91c1e060eff5 to your computer and use it in GitHub Desktop.
DNS Over HTTPS server in Golang.
// DNS Over HTTPS server in Golang.
// 2018.5.29.
package main
import (
"bytes"
"crypto/tls"
"flag"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"github.com/miekg/dns"
)
/// Declare error clase
type htError struct {
msg string
}
func (e *htError) Error() string {
return e.msg
}
func NewErr(msg string) error {
return &htError{msg}
}
///
const CLOUDFLARE_DNS = "1.1.1.1:53"
const CLOUDFLARE_DOH_HOST = "cloudflare-dns.com."
const CLOUDFLARE_DOH_URL = "https://cloudflare-dns.com/dns-query"
// Create HTTPS request and POST.
func makeHttpsRequest(wire []byte) (respWire []byte, err error) {
// disable security check for client
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
buff := bytes.NewBuffer(wire)
resp, err := client.Post(CLOUDFLARE_DOH_URL, "application/dns-udpwireformat", buff)
if err == nil {
defer resp.Body.Close()
log.Printf("Response Status: %s\n", resp.Status)
if resp.StatusCode != 200 {
return nil, NewErr(resp.Status)
}
respBody, err := ioutil.ReadAll(resp.Body)
if err == nil {
return respBody, nil
} else {
// io: read error
return nil, err
}
} else {
// http error
return nil, err
}
}
type SecHandler struct {
ServiceType string
Host *dns.Msg
}
func (s SecHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
// Check for "cloudflare-dns.com" host.
if len(r.Question) > 0 {
if r.Question[0].Name == CLOUDFLARE_DOH_HOST &&
r.Question[0].Qtype == dns.TypeA {
s.Host.SetReply(r)
w.WriteMsg(s.Host)
// End func.
return
}
}
log.Printf("Request(%s)\n%s\n", s.ServiceType, r.String())
wire, err := r.Pack()
if err == nil {
resp, err := makeHttpsRequest(wire)
if err == nil {
// Good response then
m := new(dns.Msg)
err := m.Unpack(resp)
if err == nil {
m.SetReply(r)
w.WriteMsg(m)
log.Printf("Response(%s)\n%s\n", s.ServiceType, m.String())
} else {
log.Printf("Can't unpack message from wireformat: %s\n", err.Error())
dns.HandleFailed(w, r)
}
} else {
log.Printf("HTTPS Request failed: %s\n", err.Error())
dns.HandleFailed(w, r)
}
} else {
log.Printf("Can't pack message to wire format: %s\n", err.Error())
dns.HandleFailed(w, r)
}
}
func getDohHostAddr() (*dns.Msg, error) {
m := new(dns.Msg)
m.SetQuestion(CLOUDFLARE_DOH_HOST, dns.TypeA)
client := new(dns.Client)
r, _, err := client.Exchange(m, CLOUDFLARE_DNS)
return r, err
}
func main() {
port := flag.Int("port", 53, "port to run on")
flag.Parse()
// get host
h, e := getDohHostAddr()
if e != nil {
log.Fatalf("Initialize error: %s", e)
}
go func() {
handler := SecHandler{"UDP", h}
srv := &dns.Server{Addr: ":" + strconv.Itoa(*port), Net: "udp"}
srv.Handler = handler
if err := srv.ListenAndServe(); err != nil {
log.Fatalf("Failed to set udp listener %s\n", err.Error())
}
}()
go func() {
handler := SecHandler{"TCP", h}
srv := &dns.Server{Addr: ":" + strconv.Itoa(*port), Net: "tcp"}
srv.Handler = handler
if err := srv.ListenAndServe(); err != nil {
log.Fatalf("Failed to set tcp listener %s\n", err.Error())
}
}()
log.Printf("DNS server on port %v\n", *port)
sig := make(chan os.Signal)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
s := <-sig
log.Printf("Signal (%v) received, stopping\n", s)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment