forked from pforemski/dingo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cfdns.go
121 lines (104 loc) · 3.23 KB
/
cfdns.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/**
* dingo: a DNS caching proxy written in Go
* This file implements a Cloudflare DNS-over-HTTPS client
*
* Copyright (C) 2016 Pawel Foremski <[email protected]>
* Licensed under GNU GPL v3
*/
/* API Docs https://developers.cloudflare.com/1.1.1.1/dns-over-https/request-structure/ */
package main
import (
"encoding/json"
"flag"
"fmt"
"math/rand"
"net/url"
"time"
)
type Cfdns struct {
workers *int
server *string
auto *bool
sni *string
host *string
edns *string
nopad *bool
}
/* command-line arguments */
func (r *Cfdns) Init() {
r.workers = flag.Int("cfdns:workers", 10,
"Cloudflare DNS: number of independent workers")
r.server = flag.String("cfdns:server", "1.1.1.1", /* or 1.0.0.1 */
"Cloudflare DNS: server address")
r.auto = flag.Bool("cfdns:auto", false,
"Cloudflare DNS: try to lookup the closest IPv4 server")
r.sni = flag.String("cfdns:sni", "dns.cloudflare.com",
"Cloudflare DNS: SNI string to send (should match server certificate)")
r.host = flag.String("cfdns:host", "dns.cloudflare.com",
"Cloudflare DNS: HTTP 'Host' header (real FQDN, encrypted in TLS)")
r.edns = flag.String("cfdns:edns", "",
"Cloudflare DNS: EDNS client subnet (set 0.0.0.0/0 to disable)")
r.nopad = flag.Bool("cfdns:nopad", false,
"Cloudflare DNS: disable random padding")
}
/**********************************************************************/
func (R *Cfdns) Start() {
if *R.workers <= 0 {
return
}
if *R.auto {
dbg(1, "resolving dns.cloudflare.com...")
r4 := R.resolve(NewHttps(*R.sni, false), *R.server, "dns.cloudflare.com", 1)
if r4.Status == 0 && len(r4.Answer) > 0 {
R.server = &r4.Answer[0].Data
}
}
dbg(1, "starting %d Cloudflare Public DNS client(s) querying server %s",
*R.workers, *R.server)
for i := 0; i < *R.workers; i++ {
go R.worker(*R.server)
}
}
// To prevent misinterpretation of the URL, restrict the padding characters to the unreserved URL characters:
// upper- and lower-case letters, digits, hyphen, period, underscore and tilde. http://stackoverflow.com/a/695469/18829
const padChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"
func getPaddedStr(n int) string {
s := make([]byte, n)
for i := range s {
s[i] = padChars[rand.Intn(len(padChars))]
}
return string(s)
}
func (R *Cfdns) worker(server string) {
var https = NewHttps(*R.sni, false)
for q := range qchan {
*q.rchan <- *R.resolve(https, server, q.Name, q.Type)
}
}
func (R *Cfdns) resolve(https *Https, server string, qname string, qtype int) *Reply {
r := Reply{Status: -1}
v := url.Values{}
/* prepare */
v.Set("ct", "application/dns-json") /* cfdns special: must set content type here */
v.Set("name", qname)
v.Set("type", fmt.Sprintf("%d", qtype))
if len(*R.edns) > 0 {
v.Set("edns_client_subnet", *R.edns)
}
if !*R.nopad {
// maximum dnslength+type.length (longest possible Type 5 digits)
// minus current to make always equal query length url
v.Set("random_padding", getPaddedStr(259-len(qname)-len(fmt.Sprintf("%d", qtype))))
}
/* query */
buf, err := https.Get(server, *R.host, "/dns-query?"+v.Encode())
if err != nil {
return &r
}
/* parse */
r.Now = time.Now()
json.Unmarshal(buf, &r)
return &r
}
/* register module */
var _ = register("cfdns", new(Cfdns))