Compare commits

...

27 Commits

Author SHA1 Message Date
Star Brilliant
12df47f45f Release 1.3.2 2018-04-17 03:28:36 +08:00
Star Brilliant
450c10a594 Fix version number in User-Agent 2018-04-17 03:27:42 +08:00
Star Brilliant
e7c4450787 Fix build system 2018-04-17 03:27:21 +08:00
Star Brilliant
bd5ef5d61e Fix build 2018-04-16 21:36:26 +08:00
Star Brilliant
ff0e9529cb Update User-Agent 2018-04-16 21:35:33 +08:00
Star Brilliant
627e2d639d Release 1.3.1 2018-04-16 13:59:03 +08:00
Star Brilliant
7d5cf98d2b Fix the "address already in use" issue 2018-04-16 13:58:38 +08:00
Star Brilliant
34adf40b36 Release 1.3.0 2018-04-16 13:35:38 +08:00
Star Brilliant
b9c1bcaad2 Put [::1] into default listen addresses 2018-04-16 13:18:51 +08:00
Star Brilliant
47df06b6e2 Merge pull request #9 from gdm85/master
Add client/server support for multiple listen addresses
2018-04-16 03:36:30 +08:00
gdm85
1abba72898 Add server support for multiple listen addresses 2018-04-15 19:57:17 +02:00
gdm85
ce656ac3f7 Add client support for multiple listen addresses 2018-04-15 19:57:01 +02:00
Star Brilliant
83f20767ea Update to IETF draft-07 2018-04-13 02:46:03 +08:00
Star Brilliant
07db7ba200 Merge pull request #7 from joubin/master
Linux (Ubuntu) easy install
2018-04-13 02:32:03 +08:00
Joubin Jabbari
cdb8599c9f Made changes per pull request feedback #7. 2018-04-12 08:24:33 -10:00
Star Brilliant
236f7931e6 Update to IETF draft-06 2018-04-10 14:02:51 +08:00
Joubin Jabbari
196207631b Added instructions and script for easy installation under Ubuntu
Tested with:

  * Ubuntu 16.04 LTS
  * Raspbian (Jesse, Pi 3 B+)
2018-04-09 16:17:45 -10:00
Star Brilliant
9562c2fe5c Add 1.1.1.1 and 1.0.0.1 DOH 2018-04-04 00:14:28 +08:00
Star Brilliant
0a107be362 Use absolute path for ../json-dns 2018-04-02 21:07:49 +08:00
Star Brilliant
efa272bc52 Add documentation about /etc/hosts preloading 2018-04-02 17:19:39 +08:00
Star Brilliant
36da908686 Add no_cookies option, update documentation for more instructions on privacy 2018-04-01 23:28:31 +08:00
Star Brilliant
8b45c99dfc Adapt for CloudFlare DNS service 2018-04-01 22:57:18 +08:00
Star Brilliant
68c3f30d14 Merge branch 'launchd' 2018-04-01 22:44:22 +08:00
Star Brilliant
7c4b818967 Merge branch 'clientswap' 2018-04-01 22:44:16 +08:00
Star Brilliant
57c956594f Adapt for macOS 2018-03-31 02:08:19 +08:00
Star Brilliant
542585b1ec Register a new HTTP client whenever an HTTP connection error happens 2018-03-31 01:16:07 +08:00
Star Brilliant
1819deb6c0 Update Readme 2018-03-26 00:48:10 +08:00
21 changed files with 482 additions and 120 deletions

70
Changelog.md Normal file
View File

@@ -0,0 +1,70 @@
# Changelog
This Changelog records major changes between versions.
Not all changes are recorded. Please check git log for details.
## Version 1.3.2
- Fix version string in HTTP User-Agent
## Version 1.3.1
- Fix the "address already in use" issue
## Version 1.3.0
- Breaking change: Add client / server support for multiple listen address
The `listen` option in the configuration file is a list now
## Version 1.2.1
- Update protocol to IETF draft-07
- Update installation documentations for Ubuntu / Debian
## Version 1.2.0
- Add installation documentations for Ubuntu / Debian
- Include CloudFlare DOH server (1.1.1.1, 1.0.0.1) in default configuration
- Fix a problem causing `go get` to fail due to relative paths
- Add documentation about `/etc/hosts` preloading
## Version 1.1.4
- Add `no_cookies` option
- Add documentation on privacy issues
- Adapt for CloudFlare DNS service
- Fix a problem causing a single network failure blocking future requests
- Add experimental macOS support
## Version 1.1.3
- Unsupported Content-Type now generates HTTP error code 415
## Version 1.1.2
- Adapt to IETF protocol
- Optimize for HTTP caches
## Version 1.1.1
- Adapt to IETF protocol
- Optimize for HTTP caches
- Add documentation for uninstallation instructions
- Fix build issues
## Version 1.1.0
- Adpat to IETF protocol
- Fix issues regarding to HTTP caching
- Require Go 1.9 to build now
- Fix systemd issue
## Version 1.0.1
- Fix build issues
## Version 1.0.0
- First release
- Relicense as MIT license

View File

@@ -2,27 +2,47 @@
GOBUILD=go build
GOGET=go get -d -v
GOGET_UPDATE=go get -d -u -v
PREFIX=/usr/local
ifeq ($(shell uname),Darwin)
CONFDIR=/usr/local/etc/dns-over-https
else
CONFDIR=/etc/dns-over-https
endif
all: doh-client/doh-client doh-server/doh-server
clean:
rm -f doh-client/doh-client doh-server/doh-server
install: doh-client/doh-client doh-server/doh-server
install -Dm0755 doh-client/doh-client "$(DESTDIR)$(PREFIX)/bin/doh-client"
install -Dm0755 doh-server/doh-server "$(DESTDIR)$(PREFIX)/bin/doh-server"
[ -e "$(DESTDIR)/etc/dns-over-https/doh-client.conf" ] || install -Dm0644 doh-client/doh-client.conf "$(DESTDIR)/etc/dns-over-https/doh-client.conf"
[ -e "$(DESTDIR)/etc/dns-over-https/doh-server.conf" ] || install -Dm0644 doh-server/doh-server.conf "$(DESTDIR)/etc/dns-over-https/doh-server.conf"
$(MAKE) -C systemd install "DESTDIR=$(DESTDIR)" "PREFIX=$(PREFIX)"
$(MAKE) -C NetworkManager install "DESTDIR=$(DESTDIR)" "PREFIX=$(PREFIX)"
install:
[ -e doh-client/doh-client ] || $(MAKE) doh-client/doh-client
[ -e doh-server/doh-server ] || $(MAKE) doh-server/doh-server
mkdir -p "$(DESTDIR)$(PREFIX)/bin/"
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)/"
[ -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 launchd install "DESTDIR=$(DESTDIR)"; \
fi
uninstall:
rm -f "$(DESTDIR)$(PREFIX)/bin/doh-client" "$(DESTDIR)$(PREFIX)/bin/doh-server"
$(MAKE) -C systemd uninstall "DESTDIR=$(DESTDIR)" "PREFIX=$(PREFIX)"
$(MAKE) -C NetworkManager uninstall "DESTDIR=$(DESTDIR)" "PREFIX=$(PREFIX)"
if [ "`uname`" = "Linux" ]; then \
$(MAKE) -C systemd uninstall "DESTDIR=$(DESTDIR)"; \
$(MAKE) -C NetworkManager uninstall "DESTDIR=$(DESTDIR)"; \
elif [ "`uname`" = "Darwin" ]; then \
$(MAKE) -C launchd uninstall "DESTDIR=$(DESTDIR)"; \
fi
deps:
@# I am not sure if it is the correct way to keep the common library updated
$(GOGET_UPDATE) github.com/m13253/dns-over-https/json-dns
$(GOGET) ./doh-client ./doh-server
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

View File

@@ -85,7 +85,7 @@ server. This is useful for GeoDNS and CDNs to work, and is exactly the same
configuration as most public DNS servers.
Keep in mind that /24 is not enough to track a single user, although it is
precise enough to know the city where the user is from. If you think
precise enough to know the city where the user is located. If you think
EDNS0-Client-Subnet is affecting your privacy, you can set `no_ecs = true` in
`/etc/dns-over-https/doh-client.conf`, with the cost of slower video streaming
or software downloading speed.

View File

@@ -31,20 +31,24 @@ import (
"net/http"
"net/http/cookiejar"
"strings"
"sync"
"time"
"../json-dns"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
"golang.org/x/net/http2"
)
type Client struct {
conf *config
bootstrap []string
udpServer *dns.Server
tcpServer *dns.Server
httpTransport *http.Transport
httpClient *http.Client
conf *config
bootstrap []string
udpServers []*dns.Server
tcpServers []*dns.Server
bootstrapResolver *net.Resolver
cookieJar *cookiejar.Jar
httpClientMux *sync.RWMutex
httpTransport *http.Transport
httpClient *http.Client
}
type DNSRequest struct {
@@ -60,18 +64,23 @@ func NewClient(conf *config) (c *Client, err error) {
c = &Client{
conf: conf,
}
c.udpServer = &dns.Server{
Addr: conf.Listen,
Net: "udp",
Handler: dns.HandlerFunc(c.udpHandlerFunc),
UDPSize: 4096,
udpH := dns.HandlerFunc(c.udpHandlerFunc)
tcpH := dns.HandlerFunc(c.tcpHandlerFunc)
for _, addr := range conf.Listen {
c.udpServers = append(c.udpServers, &dns.Server{
Addr: addr,
Net: "udp",
Handler: udpH,
UDPSize: 4096,
})
c.tcpServers = append(c.tcpServers, &dns.Server{
Addr: addr,
Net: "tcp",
Handler: tcpH,
})
}
c.tcpServer = &dns.Server{
Addr: conf.Listen,
Net: "tcp",
Handler: dns.HandlerFunc(c.tcpHandlerFunc),
}
bootResolver := net.DefaultResolver
c.bootstrapResolver = net.DefaultResolver
if len(conf.Bootstrap) != 0 {
c.bootstrap = make([]string, len(conf.Bootstrap))
for i, bootstrap := range conf.Bootstrap {
@@ -84,7 +93,7 @@ func NewClient(conf *config) (c *Client, err error) {
}
c.bootstrap[i] = bootstrapAddr.String()
}
bootResolver = &net.Resolver{
c.bootstrapResolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
var d net.Dialer
@@ -95,57 +104,75 @@ func NewClient(conf *config) (c *Client, err error) {
},
}
}
c.httpTransport = new(http.Transport)
// Most CDNs require Cookie support to prevent DDoS attack.
// Disabling Cookie does not effectively prevent tracking,
// so I will leave it on to make anti-DDoS services happy.
if !c.conf.NoCookies {
c.cookieJar, err = cookiejar.New(nil)
if err != nil {
return nil, err
}
}
c.httpClientMux = new(sync.RWMutex)
err = c.newHTTPClient()
if err != nil {
return nil, err
}
return c, nil
}
func (c *Client) newHTTPClient() error {
c.httpClientMux.Lock()
defer c.httpClientMux.Unlock()
if c.httpTransport != nil {
c.httpTransport.CloseIdleConnections()
}
c.httpTransport = &http.Transport{
DialContext: (&net.Dialer{
Timeout: time.Duration(conf.Timeout) * time.Second,
Timeout: time.Duration(c.conf.Timeout) * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
Resolver: bootResolver,
Resolver: c.bootstrapResolver,
}).DialContext,
ExpectContinueTimeout: 1 * time.Second,
IdleConnTimeout: 90 * time.Second,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
Proxy: http.ProxyFromEnvironment,
ResponseHeaderTimeout: time.Duration(conf.Timeout) * time.Second,
TLSHandshakeTimeout: time.Duration(conf.Timeout) * time.Second,
ResponseHeaderTimeout: time.Duration(c.conf.Timeout) * time.Second,
TLSHandshakeTimeout: time.Duration(c.conf.Timeout) * time.Second,
}
http2.ConfigureTransport(c.httpTransport)
// Most CDNs require Cookie support to prevent DDoS attack
cookieJar, err := cookiejar.New(nil)
if err != nil {
return nil, err
}
c.httpClient = &http.Client{
Transport: c.httpTransport,
Jar: cookieJar,
}
return c, nil
}
func (c *Client) Start() error {
result := make(chan error)
go func() {
err := c.udpServer.ListenAndServe()
if err != nil {
log.Println(err)
}
result <- err
}()
go func() {
err := c.tcpServer.ListenAndServe()
if err != nil {
log.Println(err)
}
result <- err
}()
err := <-result
err := http2.ConfigureTransport(c.httpTransport)
if err != nil {
return err
}
err = <-result
return err
c.httpClient = &http.Client{
Transport: c.httpTransport,
Jar: c.cookieJar,
}
return nil
}
func (c *Client) Start() error {
results := make(chan error, len(c.udpServers)+len(c.tcpServers))
for _, srv := range append(c.udpServers, c.tcpServers...) {
go func(srv *dns.Server) {
err := srv.ListenAndServe()
if err != nil {
log.Println(err)
}
results <- err
}(srv)
}
for i := 0; i < cap(results); i++ {
err := <-results
if err != nil {
return err
}
}
close(results)
return nil
}
func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
@@ -156,23 +183,23 @@ func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
requestType := ""
if len(c.conf.UpstreamIETF) == 0 {
requestType = "application/x-www-form-urlencoded"
requestType = "application/dns-json"
} else if len(c.conf.UpstreamGoogle) == 0 {
requestType = "application/dns-udpwireformat"
requestType = "application/dns-message"
} else {
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"
requestType = "application/dns-json"
} else {
requestType = "application/dns-udpwireformat"
requestType = "application/dns-message"
}
}
var req *DNSRequest
if requestType == "application/x-www-form-urlencoded" {
if requestType == "application/dns-json" {
req = c.generateRequestGoogle(w, r, isTCP)
} else if requestType == "application/dns-udpwireformat" {
} else if requestType == "application/dns-message" {
req = c.generateRequestIETF(w, r, isTCP)
} else {
panic("Unknown request Content-Type")
@@ -186,19 +213,21 @@ func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
candidateType := strings.SplitN(req.response.Header.Get("Content-Type"), ";", 2)[0]
if candidateType == "application/json" {
contentType = "application/json"
} else if candidateType == "application/dns-message" {
contentType = "application/dns-message"
} else if candidateType == "application/dns-udpwireformat" {
contentType = "application/dns-udpwireformat"
contentType = "application/dns-message"
} else {
if requestType == "application/x-www-form-urlencoded" {
if requestType == "application/dns-json" {
contentType = "application/json"
} else if requestType == "application/dns-udpwireformat" {
contentType = "application/dns-udpwireformat"
} else if requestType == "application/dns-message" {
contentType = "application/dns-message"
}
}
if contentType == "application/json" {
c.parseResponseGoogle(w, r, isTCP, req)
} else if contentType == "application/dns-udpwireformat" {
} else if contentType == "application/dns-message" {
c.parseResponseIETF(w, r, isTCP, req)
} else {
panic("Unknown response Content-Type")

View File

@@ -30,11 +30,12 @@ import (
)
type config struct {
Listen string `toml:"listen"`
Listen []string `toml:"listen"`
UpstreamGoogle []string `toml:"upstream_google"`
UpstreamIETF []string `toml:"upstream_ietf"`
Bootstrap []string `toml:"bootstrap"`
Timeout uint `toml:"timeout"`
NoCookies bool `toml:"no_cookies"`
NoECS bool `toml:"no_ecs"`
Verbose bool `toml:"verbose"`
}
@@ -49,8 +50,8 @@ func loadConfig(path string) (*config, error) {
return nil, &configError{fmt.Sprintf("unknown option %q", key.String())}
}
if conf.Listen == "" {
conf.Listen = "127.0.0.1:53"
if len(conf.Listen) == 0 {
conf.Listen = []string{"127.0.0.1:53", "[::1]:53"}
}
if len(conf.UpstreamGoogle) == 0 && len(conf.UpstreamIETF) == 0 {
conf.UpstreamGoogle = []string{"https://dns.google.com/resolve"}

View File

@@ -1,27 +1,68 @@
# DNS listen port
listen = "127.0.0.1:53"
listen = [
"127.0.0.1:53",
"[::1]:53",
]
# HTTP path for upstream resolver
# If multiple servers are specified, a random one will be chosen each time.
upstream_google = [
# Google's productive resolver, good ECS, bad DNSSEC
"https://dns.google.com/resolve",
# CloudFlare's resolver, bad ECS, good DNSSEC
#"https://cloudflare-dns.com/dns-query",
#"https://1.1.1.1/dns-query",
#"https://1.0.0.1/dns-query",
]
upstream_ietf = [
# Google's experimental resolver, good ECS, good DNSSEC
#"https://dns.google.com/experimental",
# CloudFlare's resolver, bad ECS, good DNSSEC
#"https://cloudflare-dns.com/dns-query",
#"https://1.1.1.1/dns-query",
#"https://1.0.0.1/dns-query",
]
# Bootstrap DNS server to resolve the address of the upstream resolver
# If multiple servers are specified, a random one will be chosen each time.
# If empty, use the system DNS settings.
# If you want to preload IP addresses in /etc/hosts instead of using a
# bootstrap server, please make this list empty.
bootstrap = [
# Google's resolver, bad ECS, good DNSSEC
"8.8.8.8:53",
"8.8.4.4:53",
# CloudFlare's resolver, bad ECS, good DNSSEC
#"1.1.1.1:53",
#"1.0.0.1:53",
]
# Timeout for upstream request
timeout = 10
# Disable EDNS0-Client-Subnet, do not send client's IP address
# Disable HTTP Cookies
#
# Cookies may be useful if your upstream resolver is protected by some
# anti-DDoS services to identify clients.
# Note that DNS Cookies (an DNS protocol extension to DNS) also has the ability
# to track uesrs and is not controlled by doh-client.
no_cookies = false
# Disable EDNS0-Client-Subnet (ECS)
#
# 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 server. This is useful for GeoDNS and CDNs to work, and is exactly
# the same configuration as most public DNS servers.
no_ecs = false
# Enable logging

View File

@@ -35,7 +35,7 @@ import (
"strings"
"time"
"../json-dns"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
)
@@ -66,7 +66,7 @@ func (c *Client) generateRequestGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP b
numServers := len(c.conf.UpstreamGoogle)
upstream := c.conf.UpstreamGoogle[rand.Intn(numServers)]
requestURL := fmt.Sprintf("%s?name=%s&type=%s", upstream, url.QueryEscape(questionName), url.QueryEscape(questionType))
requestURL := fmt.Sprintf("%s?ct=application/dns-json&name=%s&type=%s", upstream, url.QueryEscape(questionName), url.QueryEscape(questionType))
if r.CheckingDisabled {
requestURL += "&cd=1"
@@ -91,14 +91,19 @@ func (c *Client) generateRequestGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP b
err: err,
}
}
req.Header.Set("Accept", "application/json, application/dns-udpwireformat")
req.Header.Set("User-Agent", "DNS-over-HTTPS/1.1 (+https://github.com/m13253/dns-over-https)")
req.Header.Set("Accept", "application/json, application/dns-message, application/dns-udpwireformat")
req.Header.Set("User-Agent", jsonDNS.USER_AGENT)
c.httpClientMux.RLock()
resp, err := c.httpClient.Do(req)
c.httpClientMux.RUnlock()
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
c.httpTransport.CloseIdleConnections()
err1 := c.newHTTPClient()
if err1 != nil {
log.Fatalln(err1)
}
return &DNSRequest{
err: err,
}

View File

@@ -36,7 +36,7 @@ import (
"strings"
"time"
"../json-dns"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
)
@@ -128,6 +128,7 @@ func (c *Client) generateRequestIETF(w dns.ResponseWriter, r *dns.Msg, isTCP boo
numServers := len(c.conf.UpstreamIETF)
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)
var req *http.Request
if len(requestURL) < 2048 {
@@ -150,16 +151,21 @@ func (c *Client) generateRequestIETF(w dns.ResponseWriter, r *dns.Msg, isTCP boo
err: err,
}
}
req.Header.Set("Content-Type", "application/dns-udpwireformat")
req.Header.Set("Content-Type", "application/dns-message")
}
req.Header.Set("Accept", "application/dns-udpwireformat, application/json")
req.Header.Set("User-Agent", "DNS-over-HTTPS/1.1 (+https://github.com/m13253/dns-over-https)")
req.Header.Set("Accept", "application/dns-message, application/dns-udpwireformat, application/json")
req.Header.Set("User-Agent", jsonDNS.USER_AGENT)
c.httpClientMux.RLock()
resp, err := c.httpClient.Do(req)
c.httpClientMux.RUnlock()
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
c.httpTransport.CloseIdleConnections()
err1 := c.newHTTPClient()
if err1 != nil {
log.Fatalln(err1)
}
return &DNSRequest{
err: err,
}
@@ -179,7 +185,7 @@ func (c *Client) parseResponseIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool,
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-message" && !strings.HasPrefix(contentType, "application/dns-message;") {
w.WriteMsg(req.reply)
return
}

View File

@@ -30,7 +30,7 @@ import (
)
type config struct {
Listen string `toml:"listen"`
Listen []string `toml:"listen"`
Cert string `toml:"cert"`
Key string `toml:"key"`
Path string `toml:"path"`
@@ -51,9 +51,10 @@ func loadConfig(path string) (*config, error) {
return nil, &configError{fmt.Sprintf("unknown option %q", key.String())}
}
if conf.Listen == "" {
conf.Listen = "127.0.0.1:8053"
if len(conf.Listen) == 0 {
conf.Listen = []string{"127.0.0.1:8053", "[::1]:8053"}
}
if conf.Path == "" {
conf.Path = "/dns-query"
}

View File

@@ -1,5 +1,8 @@
# HTTP listen port
listen = "127.0.0.1:8053"
listen = [
"127.0.0.1:8053",
"[::1]:8053",
]
# TLS certification file
cert = ""

View File

@@ -33,7 +33,7 @@ import (
"strings"
"time"
"../json-dns"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
"golang.org/x/net/idna"
)

View File

@@ -32,7 +32,7 @@ import (
"strconv"
"time"
"../json-dns"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
)
@@ -45,7 +45,7 @@ func (s *Server) parseRequestIETF(w http.ResponseWriter, r *http.Request) *DNSRe
errtext: fmt.Sprintf("Invalid argument value: \"dns\" = %q", requestBase64),
}
}
if len(requestBinary) == 0 && r.Header.Get("Content-Type") == "application/dns-udpwireformat" {
if len(requestBinary) == 0 && (r.Header.Get("Content-Type") == "application/dns-message" || r.Header.Get("Content-Type") == "application/dns-udpwireformat") {
requestBinary, err = ioutil.ReadAll(r.Body)
if err != nil {
return &DNSRequest{
@@ -144,7 +144,7 @@ func (s *Server) generateResponseIETF(w http.ResponseWriter, r *http.Request, re
return
}
w.Header().Set("Content-Type", "application/dns-udpwireformat")
w.Header().Set("Content-Type", "application/dns-message")
now := time.Now().UTC().Format(http.TimeFormat)
w.Header().Set("Date", now)
w.Header().Set("Last-Modified", now)

View File

@@ -43,8 +43,5 @@ func main() {
}
server := NewServer(conf)
err = server.Start()
if err != nil {
log.Fatalln(err)
}
_ = server.Start()
}

View File

@@ -33,8 +33,8 @@ import (
"strings"
"time"
"../json-dns"
"github.com/gorilla/handlers"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
)
@@ -75,15 +75,35 @@ func (s *Server) Start() error {
if s.conf.Verbose {
servemux = handlers.CombinedLoggingHandler(os.Stdout, servemux)
}
if s.conf.Cert != "" || s.conf.Key != "" {
return http.ListenAndServeTLS(s.conf.Listen, s.conf.Cert, s.conf.Key, servemux)
results := make(chan error, len(s.conf.Listen))
for _, addr := range s.conf.Listen {
go func(addr string) {
var err error
if s.conf.Cert != "" || s.conf.Key != "" {
err = http.ListenAndServeTLS(addr, s.conf.Cert, s.conf.Key, servemux)
} else {
err = http.ListenAndServe(addr, servemux)
}
if err != nil {
log.Println(err)
}
results <- err
}(addr)
}
return http.ListenAndServe(s.conf.Listen, servemux)
// wait for all handlers
for i := 0; i < cap(results); i++ {
err := <-results
if err != nil {
return err
}
}
close(results)
return nil
}
func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
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.1 (+https://github.com/m13253/dns-over-https)")
w.Header().Set("Server", jsonDNS.USER_AGENT)
w.Header().Set("X-Powered-By", jsonDNS.USER_AGENT)
if r.Form == nil {
const maxMemory = 32 << 20 // 32 MB
@@ -96,34 +116,41 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
if contentType == "" {
// Guess request Content-Type based on other parameters
if r.FormValue("name") != "" {
contentType = "application/x-www-form-urlencoded"
contentType = "application/dns-json"
} else if r.FormValue("dns") != "" {
contentType = "application/dns-udpwireformat"
contentType = "application/dns-message"
}
}
var responseType string
for _, responseCandidate := range strings.Split(r.Header.Get("Accept"), ",") {
responseCandidate = strings.ToLower(strings.SplitN(responseCandidate, ";", 2)[0])
responseCandidate = strings.SplitN(responseCandidate, ";", 2)[0]
if responseCandidate == "application/json" {
responseType = "application/json"
break
} else if responseCandidate == "application/dns-udpwireformat" {
responseType = "application/dns-udpwireformat"
responseType = "application/dns-message"
break
} else if responseCandidate == "application/dns-message" {
responseType = "application/dns-message"
break
}
}
if responseType == "" {
// Guess response Content-Type based on request Content-Type
if contentType == "application/x-www-form-urlencoded" {
if contentType == "application/dns-json" {
responseType = "application/json"
} else if contentType == "application/dns-message" {
responseType = "application/dns-message"
} else if contentType == "application/dns-udpwireformat" {
responseType = "application/dns-udpwireformat"
responseType = "application/dns-message"
}
}
var req *DNSRequest
if contentType == "application/x-www-form-urlencoded" {
if contentType == "application/dns-json" {
req = s.parseRequestGoogle(w, r)
} else if contentType == "application/dns-message" {
req = s.parseRequestIETF(w, r)
} else if contentType == "application/dns-udpwireformat" {
req = s.parseRequestIETF(w, r)
} else {
@@ -144,7 +171,7 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
if responseType == "application/json" {
s.generateResponseGoogle(w, r, req)
} else if responseType == "application/dns-udpwireformat" {
} else if responseType == "application/dns-message" {
s.generateResponseIETF(w, r, req)
} else {
panic("Unknown response Content-Type")

27
json-dns/version.go Normal file
View File

@@ -0,0 +1,27 @@
/*
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.
*/
package jsonDNS
const VERSION = "1.3.2"
const USER_AGENT = "DNS-over-HTTPS/" + VERSION + " (+https://github.com/m13253/dns-over-https)"

16
launchd/Makefile Normal file
View File

@@ -0,0 +1,16 @@
.PHONY: install uninstall
PREFIX = /usr/local
LAUNCHD_DIR = /Library/LaunchDaemons
install:
mkdir -p "$(DESTDIR)$(LAUNCHD_DIR)"
install -m0644 doh-client.plist "$(DESTDIR)$(LAUNCHD_DIR)/doh-client.plist"
install -m0644 doh-server.plist "$(DESTDIR)$(LAUNCHD_DIR)/doh-server.plist"
@echo
@echo 'Note:'
@echo ' Use "sudo launchctl load $(DESTDIR)$(LAUNCHD_DIR)/doh-client.plist" to start doh-client,'
@echo ' use "sudo launchctl load -w $(DESTDIR)$(LAUNCHD_DIR)/doh-server.plist" to enable doh-server.'
uninstall:
rm -f "$(DESTDIR)$(LAUNCHD_DIR)/doh-client.plist" "$(DESTDIR)$(LAUNCHD_DIR)/doh-server.plist"

27
launchd/doh-client.plist Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.eu.starlab.doh.client</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/doh-client</string>
<string>-conf</string>
<string>/usr/local/etc/dns-over-https/doh-client.conf</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>UserName</key>
<string>root</string>
<key>GroupName</key>
<string>wheel</string>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>ThrottleInterval</key>
<integer>5</integer>
</dict>
</plist>

29
launchd/doh-server.plist Normal file
View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.eu.starlab.doh.server</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/doh-server</string>
<string>-conf</string>
<string>/usr/local/etc/dns-over-https/doh-server.conf</string>
</array>
<key>Disabled</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>UserName</key>
<string>root</string>
<key>GroupName</key>
<string>wheel</string>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>ThrottleInterval</key>
<integer>5</integer>
</dict>
</plist>

51
linux-install.md Normal file
View File

@@ -0,0 +1,51 @@
# Ubuntu Install
> Tested on a clean install of `Ubuntu 16.04 LTS`
## Intalling go
Install `Go >= 1.9`
```bash
sudo apt update
sudo apt install golang-1.10 -y
```
Add the newly install `go` to the path
```bash
export PATH=$PATH:/usr/lib/go-1.10/bin
```
Test to make sure that you can execute `go`
```bash
go version
```
which should output something like
```bash
go version go1.10.1 linux/amd64
```
## Installing dns-over-https
Clone this repo
```bash
git clone https://github.com/m13253/dns-over-https.git
```
Change directory to the cloned repo
```bash
cd dns-over-https
```
make and install
```bash
make
sudo make install
```

13
linux-install.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
# See the linux-install.md (README) first.
set -e
sudo apt update
sudo apt install golang-1.10 git -y
export PATH=$PATH:/usr/lib/go-1.10/bin
cd /tmp
git clone https://github.com/m13253/dns-over-https.git
cd dns-over-https
make
sudo make install

View File

@@ -1,6 +1,5 @@
.PHONY: install uninstall
PREFIX = /usr/local
SYSTEMD_DIR = /usr/lib/systemd
SYSTEMD_UNIT_DIR = $(SYSTEMD_DIR)/system