mirror of
https://github.com/m13253/dns-over-https.git
synced 2026-04-03 13:35:38 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
972d404ebc | ||
|
|
1be17bff4d | ||
|
|
ab2bf57995 | ||
|
|
06b700cb7e | ||
|
|
0e36d3b31b | ||
|
|
5723558934 | ||
|
|
2176e14e65 | ||
|
|
1be3052cda | ||
|
|
2b3a261247 | ||
|
|
5f96e35f29 | ||
|
|
8034d5417d | ||
|
|
b3f495e50f | ||
|
|
cefa3a6ba8 | ||
|
|
a81a7eff58 | ||
|
|
26d4cd413d | ||
|
|
fba928e4e4 | ||
|
|
78e40722e8 | ||
|
|
ebaaa7ff71 |
4
Makefile
4
Makefile
@@ -25,8 +25,8 @@ uninstall:
|
|||||||
deps:
|
deps:
|
||||||
$(GOGET) ./doh-client ./doh-server
|
$(GOGET) ./doh-client ./doh-server
|
||||||
|
|
||||||
doh-client/doh-client: deps doh-client/client.go doh-client/config.go doh-client/main.go json-dns/error.go json-dns/globalip.go json-dns/marshal.go json-dns/response.go json-dns/unmarshal.go
|
doh-client/doh-client: deps doh-client/client.go doh-client/config.go doh-client/google.go doh-client/ietf.go doh-client/main.go json-dns/error.go json-dns/globalip.go json-dns/marshal.go json-dns/response.go json-dns/unmarshal.go
|
||||||
cd doh-client && $(GOBUILD)
|
cd doh-client && $(GOBUILD)
|
||||||
|
|
||||||
doh-server/doh-server: deps doh-server/config.go doh-server/main.go doh-server/server.go json-dns/error.go json-dns/globalip.go json-dns/marshal.go json-dns/response.go json-dns/unmarshal.go
|
doh-server/doh-server: deps doh-server/config.go doh-server/google.go doh-server/ietf.go doh-server/main.go doh-server/server.go json-dns/error.go json-dns/globalip.go json-dns/marshal.go json-dns/response.go json-dns/unmarshal.go
|
||||||
cd doh-server && $(GOBUILD)
|
cd doh-server && $(GOBUILD)
|
||||||
|
|||||||
15
Readme.md
15
Readme.md
@@ -42,6 +42,14 @@ If it is OK, you will wee:
|
|||||||
|
|
||||||
;; SERVER: 127.0.0.1#53(127.0.0.1)
|
;; SERVER: 127.0.0.1#53(127.0.0.1)
|
||||||
|
|
||||||
|
### Uninstalling
|
||||||
|
|
||||||
|
To uninstall, type:
|
||||||
|
|
||||||
|
sudo make uninstall
|
||||||
|
|
||||||
|
The configuration files are kept at `/etc/dns-over-https`. Remove them manually if you want.
|
||||||
|
|
||||||
## Server Configuration
|
## Server Configuration
|
||||||
|
|
||||||
The following is a typical DNS-over-HTTPS architecture:
|
The following is a typical DNS-over-HTTPS architecture:
|
||||||
@@ -72,17 +80,16 @@ Client Subnet during your configuring `unbound` or `bind`.
|
|||||||
|
|
||||||
## Protocol compatibility
|
## Protocol compatibility
|
||||||
|
|
||||||
### Google DNS-over-HTTPS
|
### Google DNS-over-HTTPS Protocol
|
||||||
|
|
||||||
DNS-over-HTTPS uses a protocol compatible to [Google DNS-over-HTTPS](https://developers.google.com/speed/public-dns/docs/dns-over-https),
|
DNS-over-HTTPS uses a protocol compatible to [Google DNS-over-HTTPS](https://developers.google.com/speed/public-dns/docs/dns-over-https),
|
||||||
except for absolute expire time is preferred to relative TTL value. Refer to
|
except for absolute expire time is preferred to relative TTL value. Refer to
|
||||||
[json-dns/response.go](json-dns/response.go) for a complete description of the
|
[json-dns/response.go](json-dns/response.go) for a complete description of the
|
||||||
API.
|
API.
|
||||||
|
|
||||||
### IETF DNS-over-HTTPS (Draft)
|
### IETF DNS-over-HTTPS Protocol (Draft)
|
||||||
|
|
||||||
DNS-over-HTTPS uses a protocol compatible to [draft-ietf-doh-dns-over-https
|
DNS-over-HTTPS uses a protocol compatible to [draft-ietf-doh-dns-over-https](https://github.com/dohwg/draft-ietf-doh-dns-over-https).
|
||||||
](https://github.com/dohwg/draft-ietf-doh-dns-over-https).
|
|
||||||
This protocol is in draft stage. Any incompatibility may be introduced before
|
This protocol is in draft stage. Any incompatibility may be introduced before
|
||||||
it is finished.
|
it is finished.
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"../json-dns"
|
"../json-dns"
|
||||||
@@ -46,6 +47,15 @@ type Client struct {
|
|||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DNSRequest struct {
|
||||||
|
response *http.Response
|
||||||
|
reply *dns.Msg
|
||||||
|
udpSize uint16
|
||||||
|
ednsClientAddress net.IP
|
||||||
|
ednsClientNetmask uint8
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
func NewClient(conf *config) (c *Client, err error) {
|
func NewClient(conf *config) (c *Client, err error) {
|
||||||
c = &Client{
|
c = &Client{
|
||||||
conf: conf,
|
conf: conf,
|
||||||
@@ -144,20 +154,54 @@ func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestType := ""
|
||||||
if len(c.conf.UpstreamIETF) == 0 {
|
if len(c.conf.UpstreamIETF) == 0 {
|
||||||
c.handlerFuncGoogle(w, r, isTCP)
|
requestType = "application/x-www-form-urlencoded"
|
||||||
return
|
} else if len(c.conf.UpstreamGoogle) == 0 {
|
||||||
}
|
requestType = "application/dns-udpwireformat"
|
||||||
if len(c.conf.UpstreamGoogle) == 0 {
|
|
||||||
c.handlerFuncIETF(w, r, isTCP)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
numServers := len(c.conf.UpstreamGoogle) + len(c.conf.UpstreamIETF)
|
|
||||||
random := rand.Intn(numServers)
|
|
||||||
if random < len(c.conf.UpstreamGoogle) {
|
|
||||||
c.handlerFuncGoogle(w, r, isTCP)
|
|
||||||
} else {
|
} else {
|
||||||
c.handlerFuncIETF(w, r, isTCP)
|
numServers := len(c.conf.UpstreamGoogle) + len(c.conf.UpstreamIETF)
|
||||||
|
random := rand.Intn(numServers)
|
||||||
|
if random < len(c.conf.UpstreamGoogle) {
|
||||||
|
requestType = "application/x-www-form-urlencoded"
|
||||||
|
} else {
|
||||||
|
requestType = "application/dns-udpwireformat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var req *DNSRequest
|
||||||
|
if requestType == "application/x-www-form-urlencoded" {
|
||||||
|
req = c.generateRequestGoogle(w, r, isTCP)
|
||||||
|
} else if requestType == "application/dns-udpwireformat" {
|
||||||
|
req = c.generateRequestIETF(w, r, isTCP)
|
||||||
|
} else {
|
||||||
|
panic("Unknown request Content-Type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType := ""
|
||||||
|
candidateType := strings.SplitN(req.response.Header.Get("Content-Type"), ";", 2)[0]
|
||||||
|
if candidateType == "application/json" {
|
||||||
|
contentType = "application/json"
|
||||||
|
} else if candidateType == "application/dns-udpwireformat" {
|
||||||
|
contentType = "application/dns-udpwireformat"
|
||||||
|
} else {
|
||||||
|
if requestType == "application/x-www-form-urlencoded" {
|
||||||
|
contentType = "application/json"
|
||||||
|
} else if requestType == "application/dns-udpwireformat" {
|
||||||
|
contentType = "application/dns-udpwireformat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if contentType == "application/json" {
|
||||||
|
c.parseResponseGoogle(w, r, isTCP, req)
|
||||||
|
} else if contentType == "application/dns-udpwireformat" {
|
||||||
|
c.parseResponseIETF(w, r, isTCP, req)
|
||||||
|
} else {
|
||||||
|
panic("Unknown response Content-Type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ upstream_google = [
|
|||||||
"https://dns.google.com/resolve",
|
"https://dns.google.com/resolve",
|
||||||
]
|
]
|
||||||
upstream_ietf = [
|
upstream_ietf = [
|
||||||
"https://dns.google.com/experimental",
|
#"https://dns.google.com/experimental",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Bootstrap DNS server to resolve the address of the upstream resolver
|
# Bootstrap DNS server to resolve the address of the upstream resolver
|
||||||
|
|||||||
@@ -39,16 +39,18 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) handlerFuncGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
|
func (c *Client) generateRequestGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool) *DNSRequest {
|
||||||
reply := jsonDNS.PrepareReply(r)
|
reply := jsonDNS.PrepareReply(r)
|
||||||
|
|
||||||
if len(r.Question) != 1 {
|
if len(r.Question) != 1 {
|
||||||
log.Println("Number of questions is not 1")
|
log.Println("Number of questions is not 1")
|
||||||
reply.Rcode = dns.RcodeFormatError
|
reply.Rcode = dns.RcodeFormatError
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(reply)
|
||||||
return
|
return &DNSRequest{
|
||||||
|
err: &dns.Error{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
question := r.Question[0]
|
question := &r.Question[0]
|
||||||
// knot-resolver scrambles capitalization, I think it is unfriendly to cache
|
// knot-resolver scrambles capitalization, I think it is unfriendly to cache
|
||||||
questionName := strings.ToLower(question.Name)
|
questionName := strings.ToLower(question.Name)
|
||||||
questionType := ""
|
questionType := ""
|
||||||
@@ -85,33 +87,48 @@ func (c *Client) handlerFuncGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool)
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
reply.Rcode = dns.RcodeServerFailure
|
reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(reply)
|
||||||
return
|
return &DNSRequest{
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json, application/dns-udpwireformat")
|
||||||
req.Header.Set("User-Agent", "DNS-over-HTTPS/1.0 (+https://github.com/m13253/dns-over-https)")
|
req.Header.Set("User-Agent", "DNS-over-HTTPS/1.1 (+https://github.com/m13253/dns-over-https)")
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
reply.Rcode = dns.RcodeServerFailure
|
reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(reply)
|
||||||
c.httpTransport.CloseIdleConnections()
|
c.httpTransport.CloseIdleConnections()
|
||||||
return
|
return &DNSRequest{
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
log.Printf("HTTP error: %s\n", resp.Status)
|
return &DNSRequest{
|
||||||
reply.Rcode = dns.RcodeServerFailure
|
response: resp,
|
||||||
contentType := resp.Header.Get("Content-Type")
|
reply: reply,
|
||||||
|
udpSize: udpSize,
|
||||||
|
ednsClientAddress: ednsClientAddress,
|
||||||
|
ednsClientNetmask: ednsClientNetmask,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) parseResponseGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool, req *DNSRequest) {
|
||||||
|
if req.response.StatusCode != 200 {
|
||||||
|
log.Printf("HTTP error: %s\n", req.response.Status)
|
||||||
|
req.reply.Rcode = dns.RcodeServerFailure
|
||||||
|
contentType := req.response.Header.Get("Content-Type")
|
||||||
if contentType != "application/json" && !strings.HasPrefix(contentType, "application/json;") {
|
if contentType != "application/json" && !strings.HasPrefix(contentType, "application/json;") {
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(req.reply)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(req.response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
reply.Rcode = dns.RcodeServerFailure
|
req.reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(req.reply)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,8 +136,8 @@ func (c *Client) handlerFuncGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool)
|
|||||||
err = json.Unmarshal(body, &respJSON)
|
err = json.Unmarshal(body, &respJSON)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
reply.Rcode = dns.RcodeServerFailure
|
req.reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(req.reply)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,22 +145,22 @@ func (c *Client) handlerFuncGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool)
|
|||||||
log.Printf("DNS error: %s\n", respJSON.Comment)
|
log.Printf("DNS error: %s\n", respJSON.Comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
fullReply := jsonDNS.Unmarshal(reply, &respJSON, udpSize, ednsClientNetmask)
|
fullReply := jsonDNS.Unmarshal(req.reply, &respJSON, req.udpSize, req.ednsClientNetmask)
|
||||||
buf, err := fullReply.Pack()
|
buf, err := fullReply.Pack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
reply.Rcode = dns.RcodeServerFailure
|
req.reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(req.reply)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !isTCP && len(buf) > int(udpSize) {
|
if !isTCP && len(buf) > int(req.udpSize) {
|
||||||
fullReply.Truncated = true
|
fullReply.Truncated = true
|
||||||
buf, err = fullReply.Pack()
|
buf, err = fullReply.Pack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
buf = buf[:udpSize]
|
buf = buf[:req.udpSize]
|
||||||
}
|
}
|
||||||
w.Write(buf)
|
w.Write(buf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -39,17 +40,19 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
|
func (c *Client) generateRequestIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) *DNSRequest {
|
||||||
reply := jsonDNS.PrepareReply(r)
|
reply := jsonDNS.PrepareReply(r)
|
||||||
|
|
||||||
if len(r.Question) != 1 {
|
if len(r.Question) != 1 {
|
||||||
log.Println("Number of questions is not 1")
|
log.Println("Number of questions is not 1")
|
||||||
reply.Rcode = dns.RcodeFormatError
|
reply.Rcode = dns.RcodeFormatError
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(reply)
|
||||||
return
|
return &DNSRequest{
|
||||||
|
err: &dns.Error{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
question := r.Question[0]
|
question := &r.Question[0]
|
||||||
// knot-resolver scrambles capitalization, I think it is unfriendly to cache
|
// knot-resolver scrambles capitalization, I think it is unfriendly to cache
|
||||||
questionName := strings.ToLower(question.Name)
|
questionName := strings.ToLower(question.Name)
|
||||||
questionType := ""
|
questionType := ""
|
||||||
@@ -63,8 +66,6 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
|
|||||||
fmt.Printf("%s - - [%s] \"%s IN %s\"\n", w.RemoteAddr(), time.Now().Format("02/Jan/2006:15:04:05 -0700"), questionName, questionType)
|
fmt.Printf("%s - - [%s] \"%s IN %s\"\n", w.RemoteAddr(), time.Now().Format("02/Jan/2006:15:04:05 -0700"), questionName, questionType)
|
||||||
}
|
}
|
||||||
|
|
||||||
requestID := r.Id
|
|
||||||
r.Id = 0
|
|
||||||
question.Name = questionName
|
question.Name = questionName
|
||||||
opt := r.IsEdns0()
|
opt := r.IsEdns0()
|
||||||
udpSize := uint16(512)
|
udpSize := uint16(512)
|
||||||
@@ -85,9 +86,10 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ednsClientAddress, ednsClientNetmask := net.IP(nil), uint8(255)
|
||||||
if edns0Subnet == nil {
|
if edns0Subnet == nil {
|
||||||
ednsClientFamily := uint16(0)
|
ednsClientFamily := uint16(0)
|
||||||
ednsClientAddress, ednsClientNetmask := c.findClientIP(w, r)
|
ednsClientAddress, ednsClientNetmask = c.findClientIP(w, r)
|
||||||
if ednsClientAddress != nil {
|
if ednsClientAddress != nil {
|
||||||
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
|
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
|
||||||
ednsClientFamily = 1
|
ednsClientFamily = 1
|
||||||
@@ -105,15 +107,22 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
|
|||||||
edns0Subnet.Address = ednsClientAddress
|
edns0Subnet.Address = ednsClientAddress
|
||||||
opt.Option = append(opt.Option, edns0Subnet)
|
opt.Option = append(opt.Option, edns0Subnet)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ednsClientAddress, ednsClientNetmask = edns0Subnet.Address, edns0Subnet.SourceNetmask
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestID := r.Id
|
||||||
|
r.Id = 0
|
||||||
requestBinary, err := r.Pack()
|
requestBinary, err := r.Pack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
reply.Rcode = dns.RcodeFormatError
|
reply.Rcode = dns.RcodeFormatError
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(reply)
|
||||||
return
|
return &DNSRequest{
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
r.Id = requestID
|
||||||
requestBase64 := base64.RawURLEncoding.EncodeToString(requestBinary)
|
requestBase64 := base64.RawURLEncoding.EncodeToString(requestBinary)
|
||||||
|
|
||||||
numServers := len(c.conf.UpstreamIETF)
|
numServers := len(c.conf.UpstreamIETF)
|
||||||
@@ -127,7 +136,9 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
reply.Rcode = dns.RcodeServerFailure
|
reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(reply)
|
||||||
return
|
return &DNSRequest{
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
req, err = http.NewRequest("POST", upstream, bytes.NewReader(requestBinary))
|
req, err = http.NewRequest("POST", upstream, bytes.NewReader(requestBinary))
|
||||||
@@ -135,48 +146,71 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
reply.Rcode = dns.RcodeServerFailure
|
reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(reply)
|
||||||
return
|
return &DNSRequest{
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/dns-udpwireformat")
|
req.Header.Set("Content-Type", "application/dns-udpwireformat")
|
||||||
}
|
}
|
||||||
req.Header.Set("Accept", "application/dns-udpwireformat")
|
req.Header.Set("Accept", "application/dns-udpwireformat, application/json")
|
||||||
req.Header.Set("User-Agent", "DNS-over-HTTPS/1.0 (+https://github.com/m13253/dns-over-https)")
|
req.Header.Set("User-Agent", "DNS-over-HTTPS/1.1 (+https://github.com/m13253/dns-over-https)")
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
reply.Rcode = dns.RcodeServerFailure
|
reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(reply)
|
||||||
c.httpTransport.CloseIdleConnections()
|
c.httpTransport.CloseIdleConnections()
|
||||||
return
|
return &DNSRequest{
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
log.Printf("HTTP error: %s\n", resp.Status)
|
return &DNSRequest{
|
||||||
reply.Rcode = dns.RcodeServerFailure
|
response: resp,
|
||||||
contentType := resp.Header.Get("Content-Type")
|
reply: reply,
|
||||||
|
udpSize: udpSize,
|
||||||
|
ednsClientAddress: ednsClientAddress,
|
||||||
|
ednsClientNetmask: ednsClientNetmask,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) parseResponseIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool, req *DNSRequest) {
|
||||||
|
if req.response.StatusCode != 200 {
|
||||||
|
log.Printf("HTTP error: %s\n", req.response.Status)
|
||||||
|
req.reply.Rcode = dns.RcodeServerFailure
|
||||||
|
contentType := req.response.Header.Get("Content-Type")
|
||||||
if contentType != "application/dns-udpwireformat" && !strings.HasPrefix(contentType, "application/dns-udpwireformat;") {
|
if contentType != "application/dns-udpwireformat" && !strings.HasPrefix(contentType, "application/dns-udpwireformat;") {
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(req.reply)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(req.response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
reply.Rcode = dns.RcodeServerFailure
|
req.reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(req.reply)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lastModified := resp.Header.Get("Last-Modified")
|
headerNow := req.response.Header.Get("Date")
|
||||||
if lastModified == "" {
|
now := time.Now().UTC()
|
||||||
lastModified = resp.Header.Get("Date")
|
if headerNow != "" {
|
||||||
|
if nowDate, err := time.Parse(http.TimeFormat, headerNow); err == nil {
|
||||||
|
now = nowDate
|
||||||
|
} else {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
now := time.Now()
|
headerLastModified := req.response.Header.Get("Last-Modified")
|
||||||
lastModifiedDate, err := time.Parse(http.TimeFormat, lastModified)
|
lastModified := now
|
||||||
if err != nil {
|
if headerLastModified != "" {
|
||||||
log.Println(err)
|
if lastModifiedDate, err := time.Parse(http.TimeFormat, headerLastModified); err == nil {
|
||||||
lastModifiedDate = now
|
lastModified = lastModifiedDate
|
||||||
|
} else {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
timeDelta := now.Sub(lastModifiedDate)
|
timeDelta := now.Sub(lastModified)
|
||||||
if timeDelta < 0 {
|
if timeDelta < 0 {
|
||||||
timeDelta = 0
|
timeDelta = 0
|
||||||
}
|
}
|
||||||
@@ -185,12 +219,12 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
|
|||||||
err = fullReply.Unpack(body)
|
err = fullReply.Unpack(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
reply.Rcode = dns.RcodeServerFailure
|
req.reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(req.reply)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fullReply.Id = requestID
|
fullReply.Id = r.Id
|
||||||
for _, rr := range fullReply.Answer {
|
for _, rr := range fullReply.Answer {
|
||||||
_ = fixRecordTTL(rr, timeDelta)
|
_ = fixRecordTTL(rr, timeDelta)
|
||||||
}
|
}
|
||||||
@@ -207,18 +241,18 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
|
|||||||
buf, err := fullReply.Pack()
|
buf, err := fullReply.Pack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
reply.Rcode = dns.RcodeServerFailure
|
req.reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(req.reply)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !isTCP && len(buf) > int(udpSize) {
|
if !isTCP && len(buf) > int(req.udpSize) {
|
||||||
fullReply.Truncated = true
|
fullReply.Truncated = true
|
||||||
buf, err = fullReply.Pack()
|
buf, err = fullReply.Pack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
buf = buf[:udpSize]
|
buf = buf[:req.udpSize]
|
||||||
}
|
}
|
||||||
w.Write(buf)
|
w.Write(buf)
|
||||||
}
|
}
|
||||||
@@ -227,7 +261,7 @@ func fixRecordTTL(rr dns.RR, delta time.Duration) dns.RR {
|
|||||||
rrHeader := rr.Header()
|
rrHeader := rr.Header()
|
||||||
oldTTL := time.Duration(rrHeader.Ttl) * time.Second
|
oldTTL := time.Duration(rrHeader.Ttl) * time.Second
|
||||||
newTTL := oldTTL - delta
|
newTTL := oldTTL - delta
|
||||||
if newTTL >= 0 {
|
if newTTL > 0 {
|
||||||
rrHeader.Ttl = uint32(newTTL / time.Second)
|
rrHeader.Ttl = uint32(newTTL / time.Second)
|
||||||
} else {
|
} else {
|
||||||
rrHeader.Ttl = 0
|
rrHeader.Ttl = 0
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"../json-dns"
|
"../json-dns"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@@ -178,11 +179,14 @@ func (s *Server) generateResponseGoogle(w http.ResponseWriter, r *http.Request,
|
|||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
now := time.Now().UTC().Format(http.TimeFormat)
|
||||||
|
w.Header().Set("Date", now)
|
||||||
|
w.Header().Set("Last-Modified", now)
|
||||||
if respJSON.HaveTTL {
|
if respJSON.HaveTTL {
|
||||||
if req.isTailored {
|
if req.isTailored {
|
||||||
w.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(int(respJSON.LeastTTL)))
|
|
||||||
} else {
|
|
||||||
w.Header().Set("Cache-Control", "private, max-age="+strconv.Itoa(int(respJSON.LeastTTL)))
|
w.Header().Set("Cache-Control", "private, max-age="+strconv.Itoa(int(respJSON.LeastTTL)))
|
||||||
|
} else {
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(int(respJSON.LeastTTL)))
|
||||||
}
|
}
|
||||||
w.Header().Set("Expires", respJSON.EarliestExpires.Format(http.TimeFormat))
|
w.Header().Set("Expires", respJSON.EarliestExpires.Format(http.TimeFormat))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,25 @@ func (s *Server) parseRequestIETF(w http.ResponseWriter, r *http.Request) *DNSRe
|
|||||||
errtext: fmt.Sprintf("DNS packet parse failure (%s)", err.Error()),
|
errtext: fmt.Sprintf("DNS packet parse failure (%s)", err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.conf.Verbose && len(msg.Question) > 0 {
|
||||||
|
question := &msg.Question[0]
|
||||||
|
questionName := question.Name
|
||||||
|
questionClass := ""
|
||||||
|
if qclass, ok := dns.ClassToString[question.Qclass]; ok {
|
||||||
|
questionClass = qclass
|
||||||
|
} else {
|
||||||
|
questionClass = strconv.Itoa(int(question.Qclass))
|
||||||
|
}
|
||||||
|
questionType := ""
|
||||||
|
if qtype, ok := dns.TypeToString[question.Qtype]; ok {
|
||||||
|
questionType = qtype
|
||||||
|
} else {
|
||||||
|
questionType = strconv.Itoa(int(question.Qtype))
|
||||||
|
}
|
||||||
|
fmt.Printf("%s - - [%s] \"%s %s %s\"\n", r.RemoteAddr, time.Now().Format("02/Jan/2006:15:04:05 -0700"), questionName, questionClass, questionType)
|
||||||
|
}
|
||||||
|
|
||||||
msg.Id = dns.Id()
|
msg.Id = dns.Id()
|
||||||
opt := msg.IsEdns0()
|
opt := msg.IsEdns0()
|
||||||
if opt == nil {
|
if opt == nil {
|
||||||
@@ -126,14 +145,14 @@ func (s *Server) generateResponseIETF(w http.ResponseWriter, r *http.Request, re
|
|||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/dns-udpwireformat")
|
w.Header().Set("Content-Type", "application/dns-udpwireformat")
|
||||||
now := time.Now().Format(http.TimeFormat)
|
now := time.Now().UTC().Format(http.TimeFormat)
|
||||||
w.Header().Set("Date", now)
|
w.Header().Set("Date", now)
|
||||||
w.Header().Set("Last-Modified", now)
|
w.Header().Set("Last-Modified", now)
|
||||||
if respJSON.HaveTTL {
|
if respJSON.HaveTTL {
|
||||||
if req.isTailored {
|
if req.isTailored {
|
||||||
w.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(int(respJSON.LeastTTL)))
|
|
||||||
} else {
|
|
||||||
w.Header().Set("Cache-Control", "private, max-age="+strconv.Itoa(int(respJSON.LeastTTL)))
|
w.Header().Set("Cache-Control", "private, max-age="+strconv.Itoa(int(respJSON.LeastTTL)))
|
||||||
|
} else {
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(int(respJSON.LeastTTL)))
|
||||||
}
|
}
|
||||||
w.Header().Set("Expires", respJSON.EarliestExpires.Format(http.TimeFormat))
|
w.Header().Set("Expires", respJSON.EarliestExpires.Format(http.TimeFormat))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ func (s *Server) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Server", "DNS-over-HTTPS/1.0 (+https://github.com/m13253/dns-over-https)")
|
w.Header().Set("Server", "DNS-over-HTTPS/1.1 (+https://github.com/m13253/dns-over-https)")
|
||||||
w.Header().Set("X-Powered-By", "DNS-over-HTTPS/1.0 (+https://github.com/m13253/dns-over-https)")
|
w.Header().Set("X-Powered-By", "DNS-over-HTTPS/1.1 (+https://github.com/m13253/dns-over-https)")
|
||||||
|
|
||||||
if r.Form == nil {
|
if r.Form == nil {
|
||||||
const maxMemory = 32 << 20 // 32 MB
|
const maxMemory = 32 << 20 // 32 MB
|
||||||
@@ -142,10 +142,12 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if contentType == "application/x-www-form-urlencoded" {
|
if responseType == "application/json" {
|
||||||
s.generateResponseGoogle(w, r, req)
|
s.generateResponseGoogle(w, r, req)
|
||||||
} else if contentType == "application/dns-udpwireformat" {
|
} else if responseType == "application/dns-udpwireformat" {
|
||||||
s.generateResponseIETF(w, r, req)
|
s.generateResponseIETF(w, r, req)
|
||||||
|
} else {
|
||||||
|
panic("Unknown response Content-Type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user