Compare commits

...

33 Commits

Author SHA1 Message Date
Star Brilliant
e480251e67 Release 1.3.10 2018-08-21 01:44:35 +08:00
Star Brilliant
027480afeb Enable application/dns-message (draft-13) by default, since Google has finally supported it 2018-08-21 01:43:46 +08:00
Star Brilliant
4839498ad5 Move linux-install.* to contrib/ 2018-08-14 09:11:09 +08:00
Star Brilliant
a303c21036 Bump version to 1.3.10 2018-08-14 09:08:46 +08:00
Star Brilliant
3586688aa6 Release 1.3.9 2018-08-14 09:08:27 +08:00
Star Brilliant
ffe5573552 Change the ECS prefix length from /48 to /56 for IPv6, per RFC 7871 2018-08-14 09:06:13 +08:00
Star Brilliant
f40116b1f8 Update Readme to instruct Debian users to set $GOROOT 2018-08-14 01:43:41 +08:00
Star Brilliant
58e6cdfb71 If $GOROOT is defined, Makefile should respect the value, fix #8 2018-08-14 01:37:19 +08:00
Star Brilliant
1491138f69 Add 5380 as an additional default doh-client port 2018-08-10 03:50:38 +08:00
Star Brilliant
83df8964d8 Fix #16: doh-client panics when connecting no_cookies = true 2018-07-04 22:43:08 +08:00
Star Brilliant
07f39088d4 Update example configuration 2018-07-02 20:42:11 +08:00
Star Brilliant
db007fbded Update example configuration 2018-07-02 20:40:56 +08:00
Star Brilliant
89d809d469 Bump version to 1.3.9 2018-07-02 20:12:04 +08:00
Star Brilliant
5ca6813801 Release 1.3.8 2018-07-02 20:11:41 +08:00
Star Brilliant
033865e508 Workaround a bug causing Firefox 61-62 to reject responses with Content-Type = application/dns-message 2018-07-02 19:42:57 +08:00
Star Brilliant
7e7cd1ee90 Workaround a bug causing Firefox 61-62 to reject responses with Content-Type = application/dns-message 2018-07-02 17:55:58 +08:00
Star Brilliant
e19250dc99 Workaround a bug causing Firefox 61-62 to reject responses with Content-Type = application/dns-message 2018-07-02 17:49:34 +08:00
Star Brilliant
a64df3f048 Preserve TransactionID 2018-07-02 13:49:59 +08:00
Star Brilliant
f54b49c090 Disable preventing capitalization scrambling 2018-07-02 13:48:05 +08:00
Star Brilliant
87436b7cbf Turn on no_cookies by default according to the IETF draft 2018-07-02 13:45:08 +08:00
Star Brilliant
11056bcad5 Construct a real DNS packet for DNSCrypt-Proxy 2018-06-26 15:20:43 +08:00
Star Brilliant
ca33027e04 Remove offensive words :-) 2018-06-26 14:39:05 +08:00
Star Brilliant
6ff0a6a9ab Update Readme 2018-06-26 13:28:07 +08:00
Star Brilliant
359c81a019 Update Readme 2018-06-26 13:26:42 +08:00
Star Brilliant
07143d5890 Update documentation about TLS
This is to avoid confusions like issue #12
2018-06-26 13:22:32 +08:00
Star Brilliant
1d367cb7e7 Bump version to 1.3.8 2018-06-26 04:10:58 +08:00
Star Brilliant
b132de608f Release 1.3.7 2018-06-26 04:10:31 +08:00
Star Brilliant
523b6120b9 Workaround a bug causing DNSCrypt-Proxy to expect a response with TransactionID = 0xcafe 2018-06-26 03:55:59 +08:00
Star Brilliant
82fe91395d Workaround a bug causing Unbound to refuse returning anything about the root 2018-06-26 03:04:34 +08:00
Star Brilliant
06ce104d2a Fix typo 2018-06-16 19:41:41 +08:00
Star Brilliant
71b8c093c0 Install default configuration files to *.conf.example 2018-06-16 19:40:52 +08:00
Star Brilliant
abdd033310 Print upstream information if error happens 2018-06-12 11:12:13 +08:00
Star Brilliant
461d546082 Add CloudFlare DNS resolver for Tor to the preset 2018-06-06 01:26:36 +08:00
16 changed files with 173 additions and 50 deletions

View File

@@ -4,6 +4,33 @@ This Changelog records major changes between versions.
Not all changes are recorded. Please check git log for details. Not all changes are recorded. Please check git log for details.
## Version 1.3.10
- Enable application/dns-message (draft-13) by default, since Google has finally supported it
## Version 1.3.9
- Fix client crash with `no_cookies = true`
- Add 5380 as an additional default doh-client port
- If `$GOROOT` is defined, Makefile now respects the value for the convenience of Debian/Ubuntu users
- Change the ECS prefix length from /48 to /56 for IPv6, per RFC 7871
## Version 1.3.8
- Workaround a bug causing Firefox 61-62 to reject responses with Content-Type = application/dns-message
- Workaround a bug causing DNSCrypt-Proxy to expect a response with TransactionID = 0xcafe
- TransactionID is now preserved to maintain compatibility with some clients
- Turn on `no_cookies` by default according to the IETF draft
- Update Documentation
## Version 1.3.7
- Add CloudFlare DNS resolver for Tor to the preset
- It is now able to print upstream information if error happens
- Updated default configuration files are now installed to `*.conf.example`
- Workaround a bug causing Unbound to refuse returning anything about the root
- Workaround a bug causing DNSCrypt-Proxy to expect a response with TransactionID = 0xcafe
## Version 1.3.6 ## Version 1.3.6
- We have a logger for macOS platform now, so logs can be sent to Console.app - We have a logger for macOS platform now, so logs can be sent to Console.app

View File

@@ -1,13 +1,21 @@
.PHONY: all clean install uninstall deps .PHONY: all clean install uninstall deps
GOBUILD=go build PREFIX = /usr/local
GOGET=go get -d -v
GOGET_UPDATE=go get -d -u -v ifeq ($(GOROOT),)
PREFIX=/usr/local GOBUILD = go build
ifeq ($(shell uname),Darwin) GOGET = go get -d -v
CONFDIR=/usr/local/etc/dns-over-https GOGET_UPDATE = go get -d -u -v
else else
CONFDIR=/etc/dns-over-https GOBUILD = $(GOROOT)/bin/go build
GOGET = $(GOROOT)/bin/go get -d -v
GOGET_UPDATE = $(GOROOT)/bin/go get -d -u -v
endif
ifeq ($(shell uname),Darwin)
CONFDIR = /usr/local/etc/dns-over-https
else
CONFDIR = /etc/dns-over-https
endif endif
all: doh-client/doh-client doh-server/doh-server all: doh-client/doh-client doh-server/doh-server
@@ -28,6 +36,8 @@ install:
install -m0755 doh-client/doh-client "$(DESTDIR)$(PREFIX)/bin/doh-client" install -m0755 doh-client/doh-client "$(DESTDIR)$(PREFIX)/bin/doh-client"
install -m0755 doh-server/doh-server "$(DESTDIR)$(PREFIX)/bin/doh-server" install -m0755 doh-server/doh-server "$(DESTDIR)$(PREFIX)/bin/doh-server"
mkdir -p "$(DESTDIR)$(CONFDIR)/" mkdir -p "$(DESTDIR)$(CONFDIR)/"
install -m0644 doh-client/doh-client.conf "$(DESTDIR)$(CONFDIR)/doh-client.conf.example"
install -m0644 doh-server/doh-server.conf "$(DESTDIR)$(CONFDIR)/doh-server.conf.example"
[ -e "$(DESTDIR)$(CONFDIR)/doh-client.conf" ] || install -m0644 doh-client/doh-client.conf "$(DESTDIR)$(CONFDIR)/doh-client.conf" [ -e "$(DESTDIR)$(CONFDIR)/doh-client.conf" ] || install -m0644 doh-client/doh-client.conf "$(DESTDIR)$(CONFDIR)/doh-client.conf"
[ -e "$(DESTDIR)$(CONFDIR)/doh-server.conf" ] || install -m0644 doh-server/doh-server.conf "$(DESTDIR)$(CONFDIR)/doh-server.conf" [ -e "$(DESTDIR)$(CONFDIR)/doh-server.conf" ] || install -m0644 doh-server/doh-server.conf "$(DESTDIR)$(CONFDIR)/doh-server.conf"
if [ "`uname`" = "Linux" ]; then \ if [ "`uname`" = "Linux" ]; then \
@@ -39,7 +49,7 @@ install:
fi fi
uninstall: uninstall:
rm -f "$(DESTDIR)$(PREFIX)/bin/doh-client" "$(DESTDIR)$(PREFIX)/bin/doh-server" rm -f "$(DESTDIR)$(PREFIX)/bin/doh-client" "$(DESTDIR)$(PREFIX)/bin/doh-server" "$(DESTDIR)$(CONFDIR)/doh-client.conf.example" "$(DESTDIR)$(CONFDIR)/doh-server.conf.example"
if [ "`uname`" = "Linux" ]; then \ if [ "`uname`" = "Linux" ]; then \
$(MAKE) -C systemd uninstall "DESTDIR=$(DESTDIR)"; \ $(MAKE) -C systemd uninstall "DESTDIR=$(DESTDIR)"; \
$(MAKE) -C NetworkManager uninstall "DESTDIR=$(DESTDIR)"; \ $(MAKE) -C NetworkManager uninstall "DESTDIR=$(DESTDIR)"; \

View File

@@ -8,6 +8,8 @@ and [draft-ietf-doh-dns-over-https](https://github.com/dohwg/draft-ietf-doh-dns-
Install [Go](https://golang.org), at least version 1.9. Install [Go](https://golang.org), at least version 1.9.
(Note for Debian/Ubuntu users: You need to set `$GOROOT` if you could not get your new version of Go selected by the Makefile.)
First create an empty directory, used for `$GOPATH`: First create an empty directory, used for `$GOPATH`:
mkdir ~/gopath mkdir ~/gopath
@@ -83,7 +85,7 @@ records.
## EDNS0-Client-Subnet (GeoDNS) ## EDNS0-Client-Subnet (GeoDNS)
DNS-over-HTTPS supports EDNS0-Client-Subnet protocol, which submits part of the DNS-over-HTTPS supports EDNS0-Client-Subnet protocol, which submits part of the
client's IP address (/24 for IPv4, /48 for IPv6 by default) to the upstream client's IP address (/24 for IPv4, /56 for IPv6 by default) to the upstream
server. This is useful for GeoDNS and CDNs to work, and is exactly the same server. This is useful for GeoDNS and CDNs to work, and is exactly the same
configuration as most public DNS servers. configuration as most public DNS servers.
@@ -119,7 +121,13 @@ Currently supported features are:
- [X] IPv4 / IPv6 - [X] IPv4 / IPv6
- [X] EDNS0 large UDP packet (4 KiB by default) - [X] EDNS0 large UDP packet (4 KiB by default)
- [X] EDNS0-Client-Subnet (/24 for IPv4, /48 for IPv6 by default) - [X] EDNS0-Client-Subnet (/24 for IPv4, /56 for IPv6 by default)
## The name of the project
This project is named "DNS-over-HTTPS" because it was written before the IETF DoH project. Although this project is compatible with IETF DoH, the project is not affiliated with IETF.
To avoid confusion, you may also call this project "m13253/DNS-over-HTTPS" or anything you like.
## License ## License

View File

@@ -45,7 +45,7 @@ type Client struct {
udpServers []*dns.Server udpServers []*dns.Server
tcpServers []*dns.Server tcpServers []*dns.Server
bootstrapResolver *net.Resolver bootstrapResolver *net.Resolver
cookieJar *cookiejar.Jar cookieJar http.CookieJar
httpClientMux *sync.RWMutex httpClientMux *sync.RWMutex
httpTransport *http.Transport httpTransport *http.Transport
httpClient *http.Client httpClient *http.Client
@@ -58,6 +58,7 @@ type DNSRequest struct {
udpSize uint16 udpSize uint16
ednsClientAddress net.IP ednsClientAddress net.IP
ednsClientNetmask uint8 ednsClientNetmask uint8
currentUpstream string
err error err error
} }
@@ -258,7 +259,7 @@ func (c *Client) tcpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) {
var ( var (
ipv4Mask24 = net.IPMask{255, 255, 255, 0} ipv4Mask24 = net.IPMask{255, 255, 255, 0}
ipv6Mask48 = net.CIDRMask(48, 128) ipv6Mask56 = net.CIDRMask(56, 128)
) )
func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddress net.IP, ednsClientNetmask uint8) { func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddress net.IP, ednsClientNetmask uint8) {
@@ -285,8 +286,8 @@ func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddre
ednsClientAddress = ipv4.Mask(ipv4Mask24) ednsClientAddress = ipv4.Mask(ipv4Mask24)
ednsClientNetmask = 24 ednsClientNetmask = 24
} else { } else {
ednsClientAddress = ip.Mask(ipv6Mask48) ednsClientAddress = ip.Mask(ipv6Mask56)
ednsClientNetmask = 48 ednsClientNetmask = 56
} }
} }
return return

View File

@@ -1,7 +1,9 @@
# DNS listen port # DNS listen port
listen = [ listen = [
"127.0.0.1:53", "127.0.0.1:53",
"127.0.0.1:5380",
"[::1]:53", "[::1]:53",
"[::1]:5380",
] ]
# HTTP path for upstream resolver # HTTP path for upstream resolver
@@ -16,6 +18,11 @@ upstream_google = [
#"https://1.1.1.1/dns-query", #"https://1.1.1.1/dns-query",
#"https://1.0.0.1/dns-query", #"https://1.0.0.1/dns-query",
# CloudFlare's resolver for Tor, available only with Tor
# Remember to disable ECS below when using Tor!
# Blog: https://blog.cloudflare.com/welcome-hidden-resolver/
#"https://dns4torpnlfs2ifuz2s2yf3fc7rdmsbhm6rw75euj35pac6ap25zgqad.onion/dns-query",
] ]
upstream_ietf = [ upstream_ietf = [
@@ -27,6 +34,11 @@ upstream_ietf = [
#"https://1.1.1.1/dns-query", #"https://1.1.1.1/dns-query",
#"https://1.0.0.1/dns-query", #"https://1.0.0.1/dns-query",
# CloudFlare's resolver for Tor, available only with Tor
# Remember to disable ECS below when using Tor!
# Blog: https://blog.cloudflare.com/welcome-hidden-resolver/
#"https://dns4torpnlfs2ifuz2s2yf3fc7rdmsbhm6rw75euj35pac6ap25zgqad.onion/dns-query",
] ]
# Bootstrap DNS server to resolve the address of the upstream resolver # Bootstrap DNS server to resolve the address of the upstream resolver
@@ -55,12 +67,12 @@ timeout = 30
# anti-DDoS services to identify clients. # anti-DDoS services to identify clients.
# Note that DNS Cookies (an DNS protocol extension to DNS) also has the ability # Note that DNS Cookies (an DNS protocol extension to DNS) also has the ability
# to track uesrs and is not controlled by doh-client. # to track uesrs and is not controlled by doh-client.
no_cookies = false no_cookies = true
# Disable EDNS0-Client-Subnet (ECS) # Disable EDNS0-Client-Subnet (ECS)
# #
# DNS-over-HTTPS supports EDNS0-Client-Subnet protocol, which submits part of # DNS-over-HTTPS supports EDNS0-Client-Subnet protocol, which submits part of
# the client's IP address (/24 for IPv4, /48 for IPv6 by default) to the # the client's IP address (/24 for IPv4, /56 for IPv6 by default) to the
# upstream server. This is useful for GeoDNS and CDNs to work, and is exactly # upstream server. This is useful for GeoDNS and CDNs to work, and is exactly
# the same configuration as most public DNS servers. # the same configuration as most public DNS servers.
no_ecs = false no_ecs = false

View File

@@ -51,8 +51,7 @@ func (c *Client) generateRequestGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP b
} }
} }
question := &r.Question[0] question := &r.Question[0]
// knot-resolver scrambles capitalization, I think it is unfriendly to cache questionName := question.Name
questionName := strings.ToLower(question.Name)
questionType := "" questionType := ""
if qtype, ok := dns.TypeToString[question.Qtype]; ok { if qtype, ok := dns.TypeToString[question.Qtype]; ok {
questionType = qtype questionType = qtype
@@ -115,12 +114,13 @@ func (c *Client) generateRequestGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP b
udpSize: udpSize, udpSize: udpSize,
ednsClientAddress: ednsClientAddress, ednsClientAddress: ednsClientAddress,
ednsClientNetmask: ednsClientNetmask, ednsClientNetmask: ednsClientNetmask,
currentUpstream: upstream,
} }
} }
func (c *Client) parseResponseGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool, req *DNSRequest) { func (c *Client) parseResponseGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool, req *DNSRequest) {
if req.response.StatusCode != 200 { if req.response.StatusCode != 200 {
log.Printf("HTTP error: %s\n", req.response.Status) log.Printf("HTTP error from upstream %s: %s\n", req.currentUpstream, req.response.Status)
req.reply.Rcode = dns.RcodeServerFailure req.reply.Rcode = dns.RcodeServerFailure
contentType := req.response.Header.Get("Content-Type") 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;") {

View File

@@ -53,8 +53,7 @@ func (c *Client) generateRequestIETF(w dns.ResponseWriter, r *dns.Msg, isTCP boo
} }
question := &r.Question[0] question := &r.Question[0]
// knot-resolver scrambles capitalization, I think it is unfriendly to cache questionName := question.Name
questionName := strings.ToLower(question.Name)
questionType := "" questionType := ""
if qtype, ok := dns.TypeToString[question.Qtype]; ok { if qtype, ok := dns.TypeToString[question.Qtype]; ok {
questionType = qtype questionType = qtype
@@ -97,7 +96,7 @@ func (c *Client) generateRequestIETF(w dns.ResponseWriter, r *dns.Msg, isTCP boo
ednsClientNetmask = 24 ednsClientNetmask = 24
} else { } else {
ednsClientFamily = 2 ednsClientFamily = 2
ednsClientNetmask = 48 ednsClientNetmask = 56
} }
edns0Subnet = new(dns.EDNS0_SUBNET) edns0Subnet = new(dns.EDNS0_SUBNET)
edns0Subnet.Code = dns.EDNS0SUBNET edns0Subnet.Code = dns.EDNS0SUBNET
@@ -127,8 +126,7 @@ func (c *Client) generateRequestIETF(w dns.ResponseWriter, r *dns.Msg, isTCP boo
numServers := len(c.conf.UpstreamIETF) numServers := len(c.conf.UpstreamIETF)
upstream := c.conf.UpstreamIETF[rand.Intn(numServers)] upstream := c.conf.UpstreamIETF[rand.Intn(numServers)]
requestURL := fmt.Sprintf("%s?ct=application/dns-udpwireformat&dns=%s", upstream, requestBase64) requestURL := fmt.Sprintf("%s?ct=application/dns-message&dns=%s", upstream, requestBase64)
//requestURL := fmt.Sprintf("%s?ct=application/dns-message&dns=%s", upstream, requestBase64)
var req *http.Request var req *http.Request
if len(requestURL) < 2048 { if len(requestURL) < 2048 {
@@ -175,12 +173,13 @@ func (c *Client) generateRequestIETF(w dns.ResponseWriter, r *dns.Msg, isTCP boo
udpSize: udpSize, udpSize: udpSize,
ednsClientAddress: ednsClientAddress, ednsClientAddress: ednsClientAddress,
ednsClientNetmask: ednsClientNetmask, ednsClientNetmask: ednsClientNetmask,
currentUpstream: upstream,
} }
} }
func (c *Client) parseResponseIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool, req *DNSRequest) { func (c *Client) parseResponseIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool, req *DNSRequest) {
if req.response.StatusCode != 200 { if req.response.StatusCode != 200 {
log.Printf("HTTP error: %s\n", req.response.Status) log.Printf("HTTP error from upstream %s: %s\n", req.currentUpstream, req.response.Status)
req.reply.Rcode = dns.RcodeServerFailure req.reply.Rcode = dns.RcodeServerFailure
contentType := req.response.Header.Get("Content-Type") contentType := req.response.Header.Get("Content-Type")
if contentType != "application/dns-message" && !strings.HasPrefix(contentType, "application/dns-message;") { if contentType != "application/dns-message" && !strings.HasPrefix(contentType, "application/dns-message;") {

View File

@@ -24,6 +24,6 @@
package main package main
const ( const (
VERSION = "1.3.6" VERSION = "1.3.10"
USER_AGENT = "DNS-over-HTTPS/" + VERSION + " (+https://github.com/m13253/dns-over-https)" USER_AGENT = "DNS-over-HTTPS/" + VERSION + " (+https://github.com/m13253/dns-over-https)"
) )

View File

@@ -5,9 +5,14 @@ listen = [
] ]
# TLS certification file # TLS certification file
# If left empty, plain-text HTTP will be used.
# You are recommended to leave empty and to use a server load balancer (e.g.
# Caddy, Nginx) and set up TLS there, because this program does not do OCSP
# Stapling, which is necessary for client bootstrapping in a network
# environment with completely no traditional DNS service.
cert = "" cert = ""
# TLS key file # TLS private key file
key = "" key = ""
# HTTP path for resolve application # HTTP path for resolve application
@@ -16,6 +21,8 @@ path = "/dns-query"
# Upstream DNS resolver # Upstream DNS resolver
# If multiple servers are specified, a random one will be chosen each time. # If multiple servers are specified, a random one will be chosen each time.
upstream = [ upstream = [
"1.1.1.1:53",
"1.0.0.1:53",
"8.8.8.8:53", "8.8.8.8:53",
"8.8.4.4:53", "8.8.4.4:53",
] ]

View File

@@ -46,7 +46,6 @@ func (s *Server) parseRequestGoogle(w http.ResponseWriter, r *http.Request) *DNS
errtext: "Invalid argument value: \"name\"", errtext: "Invalid argument value: \"name\"",
} }
} }
name = strings.ToLower(name)
if punycode, err := idna.ToASCII(name); err == nil { if punycode, err := idna.ToASCII(name); err == nil {
name = punycode name = punycode
} else { } else {
@@ -105,7 +104,7 @@ func (s *Server) parseRequestGoogle(w http.ResponseWriter, r *http.Request) *DNS
ednsClientNetmask = 24 ednsClientNetmask = 24
} else { } else {
ednsClientFamily = 2 ednsClientFamily = 2
ednsClientNetmask = 48 ednsClientNetmask = 56
} }
} else { } else {
ednsClientAddress = net.ParseIP(ednsClientSubnet[:slash]) ednsClientAddress = net.ParseIP(ednsClientSubnet[:slash])
@@ -140,7 +139,7 @@ func (s *Server) parseRequestGoogle(w http.ResponseWriter, r *http.Request) *DNS
ednsClientNetmask = 24 ednsClientNetmask = 24
} else { } else {
ednsClientFamily = 2 ednsClientFamily = 2
ednsClientNetmask = 48 ednsClientNetmask = 56
} }
} }
@@ -182,6 +181,7 @@ func (s *Server) generateResponseGoogle(w http.ResponseWriter, r *http.Request,
now := time.Now().UTC().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)
w.Header().Set("Vary", "Accept")
if respJSON.HaveTTL { if respJSON.HaveTTL {
if req.isTailored { if req.isTailored {
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)))

View File

@@ -24,12 +24,14 @@
package main package main
import ( import (
"bytes"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/m13253/dns-over-https/json-dns" "github.com/m13253/dns-over-https/json-dns"
@@ -60,6 +62,13 @@ func (s *Server) parseRequestIETF(w http.ResponseWriter, r *http.Request) *DNSRe
errtext: fmt.Sprintf("Invalid argument value: \"dns\""), errtext: fmt.Sprintf("Invalid argument value: \"dns\""),
} }
} }
if s.patchDNSCryptProxyReqID(w, r, requestBinary) {
return &DNSRequest{
errcode: 444,
}
}
msg := new(dns.Msg) msg := new(dns.Msg)
err = msg.Unpack(requestBinary) err = msg.Unpack(requestBinary)
if err != nil { if err != nil {
@@ -87,6 +96,7 @@ func (s *Server) parseRequestIETF(w http.ResponseWriter, r *http.Request) *DNSRe
fmt.Printf("%s - - [%s] \"%s %s %s\"\n", r.RemoteAddr, time.Now().Format("02/Jan/2006:15:04:05 -0700"), questionName, questionClass, questionType) fmt.Printf("%s - - [%s] \"%s %s %s\"\n", r.RemoteAddr, time.Now().Format("02/Jan/2006:15:04:05 -0700"), questionName, questionClass, questionType)
} }
transactionID := msg.Id
msg.Id = dns.Id() msg.Id = dns.Id()
opt := msg.IsEdns0() opt := msg.IsEdns0()
if opt == nil { if opt == nil {
@@ -116,7 +126,7 @@ func (s *Server) parseRequestIETF(w http.ResponseWriter, r *http.Request) *DNSRe
ednsClientNetmask = 24 ednsClientNetmask = 24
} else { } else {
ednsClientFamily = 2 ednsClientFamily = 2
ednsClientNetmask = 48 ednsClientNetmask = 56
} }
edns0Subnet = new(dns.EDNS0_SUBNET) edns0Subnet = new(dns.EDNS0_SUBNET)
edns0Subnet.Code = dns.EDNS0SUBNET edns0Subnet.Code = dns.EDNS0SUBNET
@@ -129,14 +139,15 @@ func (s *Server) parseRequestIETF(w http.ResponseWriter, r *http.Request) *DNSRe
} }
return &DNSRequest{ return &DNSRequest{
request: msg, request: msg,
isTailored: isTailored, transactionID: transactionID,
isTailored: isTailored,
} }
} }
func (s *Server) generateResponseIETF(w http.ResponseWriter, r *http.Request, req *DNSRequest) { func (s *Server) generateResponseIETF(w http.ResponseWriter, r *http.Request, req *DNSRequest) {
respJSON := jsonDNS.Marshal(req.response) respJSON := jsonDNS.Marshal(req.response)
req.response.Id = 0 req.response.Id = req.transactionID
respBytes, err := req.response.Pack() respBytes, err := req.response.Pack()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@@ -148,6 +159,10 @@ func (s *Server) generateResponseIETF(w http.ResponseWriter, r *http.Request, re
now := time.Now().UTC().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)
w.Header().Set("Vary", "Accept")
_ = s.patchFirefoxContentType(w, r, req)
if respJSON.HaveTTL { if respJSON.HaveTTL {
if req.isTailored { if req.isTailored {
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)))
@@ -156,8 +171,35 @@ func (s *Server) generateResponseIETF(w http.ResponseWriter, r *http.Request, re
} }
w.Header().Set("Expires", respJSON.EarliestExpires.Format(http.TimeFormat)) w.Header().Set("Expires", respJSON.EarliestExpires.Format(http.TimeFormat))
} }
if respJSON.Status == dns.RcodeServerFailure { if respJSON.Status == dns.RcodeServerFailure {
w.WriteHeader(503) w.WriteHeader(503)
} }
w.Write(respBytes) w.Write(respBytes)
} }
// Workaround a bug causing DNSCrypt-Proxy to expect a response with TransactionID = 0xcafe
func (s *Server) patchDNSCryptProxyReqID(w http.ResponseWriter, r *http.Request, requestBinary []byte) bool {
if strings.Contains(r.UserAgent(), "dnscrypt-proxy") && bytes.Equal(requestBinary, []byte("\xca\xfe\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x00\x02\x00\x01\x00\x00\x29\x10\x00\x00\x00\x80\x00\x00\x00")) {
log.Println("DNSCrypt-Proxy detected. Patching response.")
w.Header().Set("Content-Type", "application/dns-message")
w.Header().Set("Vary", "Accept, User-Agent")
now := time.Now().UTC().Format(http.TimeFormat)
w.Header().Set("Date", now)
w.Write([]byte("\xca\xfe\x81\x05\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x02\x00\x01\x00\x00\x10\x00\x01\x00\x00\x00\x00\x00\xa8\xa7\r\nWorkaround a bug causing DNSCrypt-Proxy to expect a response with TransactionID = 0xcafe\r\nRefer to https://github.com/jedisct1/dnscrypt-proxy/issues/526 for details."))
return true
}
return false
}
// Workaround a bug causing Firefox 61-62 to reject responses with Content-Type = application/dns-message
func (s *Server) patchFirefoxContentType(w http.ResponseWriter, r *http.Request, req *DNSRequest) bool {
if strings.Contains(r.UserAgent(), "Firefox") && strings.Contains(r.Header.Get("Accept"), "application/dns-udpwireformat") && !strings.Contains(r.Header.Get("Accept"), "application/dns-message") {
log.Println("Firefox 61-62 detected. Patching response.")
w.Header().Set("Content-Type", "application/dns-udpwireformat")
w.Header().Set("Vary", "Accept, User-Agent")
req.isTailored = true
return true
}
return false
}

View File

@@ -46,11 +46,13 @@ type Server struct {
} }
type DNSRequest struct { type DNSRequest struct {
request *dns.Msg request *dns.Msg
response *dns.Msg response *dns.Msg
isTailored bool transactionID uint16
errcode int currentUpstream string
errtext string isTailored bool
errcode int
errtext string
} }
func NewServer(conf *config) (s *Server) { func NewServer(conf *config) (s *Server) {
@@ -158,13 +160,18 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"ct\" = %q", contentType), 415) jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"ct\" = %q", contentType), 415)
return return
} }
if req.errcode == 444 {
return
}
if req.errcode != 0 { if req.errcode != 0 {
jsonDNS.FormatError(w, req.errtext, req.errcode) jsonDNS.FormatError(w, req.errtext, req.errcode)
return return
} }
req = s.patchRootRD(req)
var err error var err error
req.response, err = s.doDNSQuery(req.request) req, err = s.doDNSQuery(req)
if err != nil { if err != nil {
jsonDNS.FormatError(w, fmt.Sprintf("DNS query failure (%s)", err.Error()), 503) jsonDNS.FormatError(w, fmt.Sprintf("DNS query failure (%s)", err.Error()), 503)
return return
@@ -208,23 +215,33 @@ func (s *Server) findClientIP(r *http.Request) net.IP {
return nil return nil
} }
func (s *Server) doDNSQuery(msg *dns.Msg) (resp *dns.Msg, err error) { // Workaround a bug causing Unbound to refuse returning anything about the root
func (s *Server) patchRootRD(req *DNSRequest) *DNSRequest {
for _, question := range req.request.Question {
if question.Name == "." {
req.request.RecursionDesired = true
}
}
return req
}
func (s *Server) doDNSQuery(req *DNSRequest) (resp *DNSRequest, err error) {
numServers := len(s.conf.Upstream) numServers := len(s.conf.Upstream)
for i := uint(0); i < s.conf.Tries; i++ { for i := uint(0); i < s.conf.Tries; i++ {
server := s.conf.Upstream[rand.Intn(numServers)] req.currentUpstream = s.conf.Upstream[rand.Intn(numServers)]
if !s.conf.TCPOnly { if !s.conf.TCPOnly {
resp, _, err = s.udpClient.Exchange(msg, server) req.response, _, err = s.udpClient.Exchange(req.request, req.currentUpstream)
if err == dns.ErrTruncated { if err == dns.ErrTruncated {
log.Println(err) log.Println(err)
resp, _, err = s.tcpClient.Exchange(msg, server) req.response, _, err = s.tcpClient.Exchange(req.request, req.currentUpstream)
} }
} else { } else {
resp, _, err = s.tcpClient.Exchange(msg, server) req.response, _, err = s.tcpClient.Exchange(req.request, req.currentUpstream)
} }
if err == nil { if err == nil {
return return req, nil
} }
log.Println(err) log.Printf("DNS error from upstream %s: %s\n", req.currentUpstream, err.Error())
} }
return return req, err
} }

View File

@@ -24,6 +24,6 @@
package main package main
const ( const (
VERSION = "1.3.6" VERSION = "1.3.10"
USER_AGENT = "DNS-over-HTTPS/" + VERSION + " (+https://github.com/m13253/dns-over-https)" USER_AGENT = "DNS-over-HTTPS/" + VERSION + " (+https://github.com/m13253/dns-over-https)"
) )

View File

@@ -119,7 +119,7 @@ func Unmarshal(msg *dns.Msg, resp *Response, udpSize uint16, ednsClientNetmask u
if ednsClientFamily == 1 { if ednsClientFamily == 1 {
ednsClientNetmask = 24 ednsClientNetmask = 24
} else { } else {
ednsClientNetmask = 48 ednsClientNetmask = 56
} }
} }
edns0Subnet := new(dns.EDNS0_SUBNET) edns0Subnet := new(dns.EDNS0_SUBNET)