Compare commits

..

12 Commits

Author SHA1 Message Date
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
Star Brilliant
51b065eed7 Release 1.3.6 2018-05-27 21:03:51 +08:00
Star Brilliant
a58607dc3d Add an option to disable IPv6, this option is available to client only 2018-05-27 21:00:13 +08:00
Star Brilliant
cf94354b08 Revert "Conflict with systemd-resolved.service"
This reverts commit 88b3c95710.
2018-05-15 19:46:04 +08:00
Star Brilliant
f2e3a642e3 Write a logger for macOS systems 2018-05-08 21:11:03 +08:00
Star Brilliant
88b3c95710 Conflict with systemd-resolved.service 2018-04-29 22:30:43 +08:00
15 changed files with 232 additions and 25 deletions

View File

@@ -4,6 +4,19 @@ This Changelog records major changes between versions.
Not all changes are recorded. Please check git log for details.
## 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
- We have a logger for macOS platform now, so logs can be sent to Console.app
- Add an option to disable IPv6, this option is available to client only
## Version 1.3.5
- Limit the frequency of creating HTTP client on bad network condition

View File

@@ -11,9 +11,15 @@ else
endif
all: doh-client/doh-client doh-server/doh-server
if [ "`uname`" = "Darwin" ]; then \
$(MAKE) -C darwin-wrapper; \
fi
clean:
rm -f doh-client/doh-client doh-server/doh-server
if [ "`uname`" = "Darwin" ]; then \
$(MAKE) -C darwin-wrapper clean; \
fi
install:
[ -e doh-client/doh-client ] || $(MAKE) doh-client/doh-client
@@ -22,17 +28,20 @@ install:
install -m0755 doh-client/doh-client "$(DESTDIR)$(PREFIX)/bin/doh-client"
install -m0755 doh-server/doh-server "$(DESTDIR)$(PREFIX)/bin/doh-server"
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-server.conf" ] || install -m0644 doh-server/doh-server.conf "$(DESTDIR)$(CONFDIR)/doh-server.conf"
if [ "`uname`" = "Linux" ]; then \
$(MAKE) -C systemd install "DESTDIR=$(DESTDIR)"; \
$(MAKE) -C NetworkManager install "DESTDIR=$(DESTDIR)"; \
elif [ "`uname`" = "Darwin" ]; then \
$(MAKE) -C darwin-wrapper install "DESTDIR=$(DESTDIR)" "PREFIX=$(PREFIX)"; \
$(MAKE) -C launchd install "DESTDIR=$(DESTDIR)"; \
fi
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 \
$(MAKE) -C systemd uninstall "DESTDIR=$(DESTDIR)"; \
$(MAKE) -C NetworkManager uninstall "DESTDIR=$(DESTDIR)"; \

19
darwin-wrapper/Makefile Normal file
View File

@@ -0,0 +1,19 @@
.PHONY: all clean install uninstall
SWIFTC = swiftc
PREFIX = /usr/local
all: doh-logger
doh-logger: doh-logger.swift
$(SWIFTC) -o $@ -O $<
clean:
rm -f doh-logger
install: doh-logger
mkdir -p $(DESTDIR)$(PREFIX)/bin
install -m0755 doh-logger $(DESTDIR)$(PREFIX)/bin
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/doh-logger

View File

@@ -0,0 +1,94 @@
#!/usr/bin/swift
/*
DNS-over-HTTPS
Copyright (C) 2017-2018 Star Brilliant <m13253@hotmail.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
import Foundation
import os.log
if CommandLine.arguments.count < 3 {
let programName = CommandLine.arguments[0]
print("Usage: \(programName) LOG_NAME PROGRAM [ARGUMENTS]\n")
exit(1)
}
let logSubsystem = CommandLine.arguments[1]
let logger = OSLog(subsystem: logSubsystem, category: "default")
let pipe = Pipe()
var buffer = Data()
NotificationCenter.default.addObserver(forName: FileHandle.readCompletionNotification, object: pipe.fileHandleForReading, queue: nil) { notification in
let data = notification.userInfo?["NSFileHandleNotificationDataItem"] as? Data ?? Data()
buffer.append(data)
var lastIndex = 0
for (i, byte) in buffer.enumerated() {
if byte == 0x0a {
let line = String(data: buffer.subdata(in: lastIndex..<i), encoding: .utf8) ?? ""
print(line)
os_log("%{public}@", log: logger, line)
lastIndex = i + 1
}
}
buffer = buffer.subdata(in: lastIndex..<buffer.count)
if data.count == 0 && buffer.count != 0 {
let line = String(data: buffer, encoding: .utf8) ?? ""
print(line, terminator: "")
os_log("%{public}@", log: logger, line)
}
pipe.fileHandleForReading.readInBackgroundAndNotify()
}
pipe.fileHandleForReading.readInBackgroundAndNotify()
let process = Process()
process.arguments = Array(CommandLine.arguments[3...])
process.executableURL = URL(fileURLWithPath: CommandLine.arguments[2])
process.standardError = pipe.fileHandleForWriting
process.standardInput = FileHandle.standardInput
process.standardOutput = pipe.fileHandleForWriting
NotificationCenter.default.addObserver(forName: Process.didTerminateNotification, object: process, queue: nil) { notification in
if buffer.count != 0 {
let line = String(data: buffer, encoding: .utf8) ?? ""
print(line, terminator: "")
os_log("%{public}@", log: logger, line)
}
exit(process.terminationStatus)
}
let SIGINTSource = DispatchSource.makeSignalSource(signal: SIGINT)
let SIGTERMSource = DispatchSource.makeSignalSource(signal: SIGTERM)
SIGINTSource.setEventHandler(handler: process.interrupt)
SIGTERMSource.setEventHandler(handler: process.terminate)
signal(SIGINT, SIG_IGN)
signal(SIGTERM, SIG_IGN)
SIGINTSource.resume()
SIGTERMSource.resume()
do {
try process.run()
} catch {
let errorMessage = error.localizedDescription
print(errorMessage)
os_log("%{public}@", log: logger, type: .fault, errorMessage)
exit(1)
}
RunLoop.current.run()

View File

@@ -58,6 +58,7 @@ type DNSRequest struct {
udpSize uint16
ednsClientAddress net.IP
ednsClientNetmask uint8
currentUpstream string
err error
}
@@ -131,13 +132,14 @@ func (c *Client) newHTTPClient() error {
if c.httpTransport != nil {
c.httpTransport.CloseIdleConnections()
}
dialer := &net.Dialer{
Timeout: time.Duration(c.conf.Timeout) * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
Resolver: c.bootstrapResolver,
}
c.httpTransport = &http.Transport{
DialContext: (&net.Dialer{
Timeout: time.Duration(c.conf.Timeout) * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
Resolver: c.bootstrapResolver,
}).DialContext,
DialContext: dialer.DialContext,
ExpectContinueTimeout: 1 * time.Second,
IdleConnTimeout: 90 * time.Second,
MaxIdleConns: 100,
@@ -146,6 +148,14 @@ func (c *Client) newHTTPClient() error {
ResponseHeaderTimeout: time.Duration(c.conf.Timeout) * time.Second,
TLSHandshakeTimeout: time.Duration(c.conf.Timeout) * time.Second,
}
if c.conf.NoIPv6 {
c.httpTransport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
if strings.HasPrefix(network, "tcp") {
network = "tcp4"
}
return dialer.DialContext(ctx, network, address)
}
}
err := http2.ConfigureTransport(c.httpTransport)
if err != nil {
return err

View File

@@ -37,6 +37,7 @@ type config struct {
Timeout uint `toml:"timeout"`
NoCookies bool `toml:"no_cookies"`
NoECS bool `toml:"no_ecs"`
NoIPv6 bool `toml:"no_ipv6"`
Verbose bool `toml:"verbose"`
}

View File

@@ -16,6 +16,11 @@ upstream_google = [
#"https://1.1.1.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 = [
@@ -27,6 +32,11 @@ upstream_ietf = [
#"https://1.1.1.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
@@ -65,5 +75,13 @@ no_cookies = false
# the same configuration as most public DNS servers.
no_ecs = false
# Disable IPv6 when querying upstream
#
# Only enable this if you really have trouble connecting.
# Doh-client uses both IPv4 and IPv6 by default and should not have problems
# with an IPv4-only environment.
# Note that DNS listening and bootstrapping is not controlled by this option.
no_ipv6 = false
# Enable logging
verbose = false

View File

@@ -115,12 +115,13 @@ func (c *Client) generateRequestGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP b
udpSize: udpSize,
ednsClientAddress: ednsClientAddress,
ednsClientNetmask: ednsClientNetmask,
currentUpstream: upstream,
}
}
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)
log.Printf("HTTP error from upstream %s: %s\n", req.currentUpstream, req.response.Status)
req.reply.Rcode = dns.RcodeServerFailure
contentType := req.response.Header.Get("Content-Type")
if contentType != "application/json" && !strings.HasPrefix(contentType, "application/json;") {

View File

@@ -175,12 +175,13 @@ func (c *Client) generateRequestIETF(w dns.ResponseWriter, r *dns.Msg, isTCP boo
udpSize: udpSize,
ednsClientAddress: ednsClientAddress,
ednsClientNetmask: ednsClientNetmask,
currentUpstream: upstream,
}
}
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)
log.Printf("HTTP error from upstream %s: %s\n", req.currentUpstream, req.response.Status)
req.reply.Rcode = dns.RcodeServerFailure
contentType := req.response.Header.Get("Content-Type")
if contentType != "application/dns-message" && !strings.HasPrefix(contentType, "application/dns-message;") {

View File

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

View File

@@ -24,6 +24,7 @@
package main
import (
"bytes"
"encoding/base64"
"fmt"
"io/ioutil"
@@ -60,6 +61,13 @@ func (s *Server) parseRequestIETF(w http.ResponseWriter, r *http.Request) *DNSRe
errtext: fmt.Sprintf("Invalid argument value: \"dns\""),
}
}
if s.patchDNSCryptProxyReqID(requestBinary, w) {
return &DNSRequest{
errcode: 444,
}
}
msg := new(dns.Msg)
err = msg.Unpack(requestBinary)
if err != nil {
@@ -161,3 +169,16 @@ func (s *Server) generateResponseIETF(w http.ResponseWriter, r *http.Request, re
}
w.Write(respBytes)
}
// Workaround a bug causing DNSCrypt-Proxy to expect a response with TransactionID = 0xcafe
func (s *Server) patchDNSCryptProxyReqID(requestBinary []byte, w http.ResponseWriter) bool {
if 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/octet-stream")
now := time.Now().UTC().Format(http.TimeFormat)
w.Header().Set("Date", now)
w.Write([]byte("\xca\xfe\x81\x01\x00\x01\r\nWorkaround a bug causing DNSCrypt-Proxy to expect a response with TransactionID = 0xcafe\r\nDo you know it is a violation of the protocol you fxxking DNSCrypt-Proxy?!\r\n"))
return true
}
return false
}

View File

@@ -46,11 +46,12 @@ type Server struct {
}
type DNSRequest struct {
request *dns.Msg
response *dns.Msg
isTailored bool
errcode int
errtext string
request *dns.Msg
response *dns.Msg
currentUpstream string
isTailored bool
errcode int
errtext string
}
func NewServer(conf *config) (s *Server) {
@@ -158,13 +159,18 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"ct\" = %q", contentType), 415)
return
}
if req.errcode == 444 {
return
}
if req.errcode != 0 {
jsonDNS.FormatError(w, req.errtext, req.errcode)
return
}
req = s.patchRootRD(req)
var err error
req.response, err = s.doDNSQuery(req.request)
req, err = s.doDNSQuery(req)
if err != nil {
jsonDNS.FormatError(w, fmt.Sprintf("DNS query failure (%s)", err.Error()), 503)
return
@@ -208,23 +214,33 @@ func (s *Server) findClientIP(r *http.Request) net.IP {
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)
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 {
resp, _, err = s.udpClient.Exchange(msg, server)
req.response, _, err = s.udpClient.Exchange(req.request, req.currentUpstream)
if err == dns.ErrTruncated {
log.Println(err)
resp, _, err = s.tcpClient.Exchange(msg, server)
req.response, _, err = s.tcpClient.Exchange(req.request, req.currentUpstream)
}
} else {
resp, _, err = s.tcpClient.Exchange(msg, server)
req.response, _, err = s.tcpClient.Exchange(req.request, req.currentUpstream)
}
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
const (
VERSION = "1.3.5"
VERSION = "1.3.7"
USER_AGENT = "DNS-over-HTTPS/" + VERSION + " (+https://github.com/m13253/dns-over-https)"
)

View File

@@ -6,6 +6,8 @@
<string>org.eu.starlab.doh.client</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/doh-logger</string>
<string>doh-client</string>
<string>/usr/local/bin/doh-client</string>
<string>-conf</string>
<string>/usr/local/etc/dns-over-https/doh-client.conf</string>

View File

@@ -6,6 +6,8 @@
<string>org.eu.starlab.doh.server</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/doh-logger</string>
<string>doh-server</string>
<string>/usr/local/bin/doh-server</string>
<string>-conf</string>
<string>/usr/local/etc/dns-over-https/doh-server.conf</string>