Compare commits

..

60 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
Star Brilliant
56973c827d Update Readme 2018-03-26 00:45:48 +08:00
Star Brilliant
c1be2ddd18 Update Readme 2018-03-26 00:44:28 +08:00
Star Brilliant
cb78b9b696 A DNS API server SHOULD respond with HTTP status code 415 (Unsupported Media Type) upon receiving a media type it is unable to process. 2018-03-24 17:33:50 +08:00
Star Brilliant
972d404ebc Add Last-Modified header 2018-03-23 15:32:51 +08:00
Star Brilliant
1be17bff4d Fix a problem when a single HTTP error crashes the program 2018-03-23 15:28:42 +08:00
Star Brilliant
ab2bf57995 Comment out the Googl experimental server 2018-03-21 17:17:14 +08:00
Star Brilliant
06b700cb7e Fix server Content-Type problem 2018-03-21 17:07:40 +08:00
Star Brilliant
0e36d3b31b Content-Type auto detection for client 2018-03-21 16:58:42 +08:00
Star Brilliant
5723558934 Fix Accept header detection 2018-03-21 15:28:47 +08:00
Star Brilliant
2176e14e65 Extract date from Date header 2018-03-21 15:17:36 +08:00
Star Brilliant
1be3052cda Fix cache privateness 2018-03-21 15:08:12 +08:00
Star Brilliant
2b3a261247 Fix case issue 2018-03-21 04:14:45 +08:00
Star Brilliant
5f96e35f29 Add uninstallation instructions, fix #5 2018-03-21 03:48:57 +08:00
Star Brilliant
8034d5417d Update Readme 2018-03-21 03:21:06 +08:00
Star Brilliant
b3f495e50f Use round for TTL values 2018-03-21 02:58:06 +08:00
Star Brilliant
cefa3a6ba8 Fix HTTP header time zone 2018-03-21 02:52:22 +08:00
Star Brilliant
a81a7eff58 It's time to flip the version number 2018-03-21 02:38:13 +08:00
Star Brilliant
26d4cd413d Revert "Skip go get if compiled"
This reverts commit 78e40722e8.
2018-03-21 02:27:55 +08:00
Star Brilliant
fba928e4e4 Fix Makefile 2018-03-21 02:25:09 +08:00
Star Brilliant
78e40722e8 Skip go get if compiled 2018-03-21 02:18:05 +08:00
Star Brilliant
ebaaa7ff71 Enable verbose for IETF protocol 2018-03-21 02:14:11 +08:00
Star Brilliant
08af085d9a Merge branch 'ietf' 2018-03-21 02:07:57 +08:00
Star Brilliant
47d706f1b5 Update Readme 2018-03-21 02:07:52 +08:00
Star Brilliant
2ab2120a39 Implement IETF protocol 2018-03-21 02:01:56 +08:00
Star Brilliant
64664a59b8 Implement IETF protocol 2018-03-21 01:38:21 +08:00
Star Brilliant
521b4b6abc Implement IETF protocol 2018-03-21 00:14:59 +08:00
Star Brilliant
f4e27c93a6 Correct time.RFC1123 to http.TimeFormat 2018-01-22 17:45:53 +08:00
Star Brilliant
dcd6b0bb57 Add a timeout configuration for server 2018-01-17 20:31:49 +08:00
Star Brilliant
d3b322842d Merge pull request #4 from mghadam/readme_golang19
Updated required golang version to 1.9
2018-01-06 18:20:21 +08:00
Mos GH
fd2b1f47ae Golang 1.9 is required since the dial field in client.go:87 has been added in golang 1.9 2018-01-06 13:40:43 +03:30
Star Brilliant
c20614e19c Fix systemd-service-file-missing-documentation-key 2017-12-19 21:36:22 +08:00
Star Brilliant
02f848e46f Merge pull request #1 from farseerfc/master
go get once to avoid errors when parallel build
2017-12-19 09:54:44 +08:00
Jiachen Yang
34a072aea9 go get once to avoid errors when parallel build 2017-12-19 10:48:56 +09:00
30 changed files with 1749 additions and 637 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

@@ -1,6 +1,6 @@
MIT License
Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>
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

View File

@@ -1,29 +1,52 @@
.PHONY: all clean install uninstall
.PHONY: all clean install uninstall deps
GOBUILD=go build
GOGET=go get -d -v .
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
doh-client/doh-client: 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
cd doh-client && $(GOGET) && $(GOBUILD)
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-server/doh-server: 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
cd doh-server && $(GOGET) && $(GOBUILD)
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)
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)

View File

@@ -1,11 +1,12 @@
DNS-over-HTTPS
==============
Client and server software to query DNS over HTTPS, using [Google DNS-over-HTTPS protocol](https://developers.google.com/speed/public-dns/docs/dns-over-https).
Client and server software to query DNS over HTTPS, using [Google DNS-over-HTTPS protocol](https://developers.google.com/speed/public-dns/docs/dns-over-https)
and [draft-ietf-doh-dns-over-https](https://github.com/dohwg/draft-ietf-doh-dns-over-https).
## Easy start
Install [Go](https://golang.org), at least version 1.8.
Install [Go](https://golang.org), at least version 1.9.
First create an empty directory, used for `$GOPATH`:
@@ -41,6 +42,14 @@ If it is OK, you will wee:
;; 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
The following is a typical DNS-over-HTTPS architecture:
@@ -63,24 +72,49 @@ you can host DNS-over-HTTPS along with other HTTPS services.
## DNSSEC
DNSSEC validation is not built-in. It is highly recommended that you install
`unbound` or `bind` and pass results for them to validate DNS records.
DNS-over-HTTPS is compatible with DNSSEC, and requests DNSSEC signatures by
default. However signature validation is not built-in. It is highly recommended
that you install `unbound` or `bind` and pass results for them to validate DNS
records.
If you are running a server without anycast, you probably want to enable EDNS0
Client Subnet during your configuring `unbound` or `bind`.
## EDNS0-Client-Subnet (GeoDNS)
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.
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 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.
If your server is backed by `unbound` or `bind`, you probably want to enable
the EDNS0-Client-Subnet feature in their configuration files as well.
## Protocol compatibility
DNS-over-HTTPS use a protocol compatible to [Google DNS-over-HTTPS](https://developers.google.com/speed/public-dns/docs/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),
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
API.
### IETF DNS-over-HTTPS Protocol (Draft)
DNS-over-HTTPS uses a protocol compatible to [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
it is finished.
### Supported features
Currently supported features are:
- [X] IPv4 / IPv6
- [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, /48 for IPv6 by default)
## License

View File

@@ -1,133 +1,178 @@
/*
DNS-over-HTTPS
Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>
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:
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 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.
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 main
import (
"context"
"encoding/json"
"fmt"
"math/rand"
"io/ioutil"
"log"
"math/rand"
"net"
"net/http"
"net/http/cookiejar"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
"../json-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 {
response *http.Response
reply *dns.Msg
udpSize uint16
ednsClientAddress net.IP
ednsClientNetmask uint8
err error
}
func NewClient(conf *config) (c *Client, err error) {
c = &Client {
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 {
bootstrapAddr, err := net.ResolveUDPAddr("udp", bootstrap)
if err != nil {
bootstrapAddr, err = net.ResolveUDPAddr("udp", "[" + bootstrap + "]:53")
bootstrapAddr, err = net.ResolveUDPAddr("udp", "["+bootstrap+"]:53")
}
if err != nil {
return nil, err
}
if err != nil { return nil, err }
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
num_servers := len(c.bootstrap)
bootstrap := c.bootstrap[rand.Intn(num_servers)]
numServers := len(c.bootstrap)
bootstrap := c.bootstrap[rand.Intn(numServers)]
conn, err := d.DialContext(ctx, network, bootstrap)
return conn, err
},
}
}
c.httpTransport = new(http.Transport)
*c.httpTransport = *http.DefaultTransport.(*http.Transport)
c.httpTransport.DialContext = (&net.Dialer {
Timeout: time.Duration(conf.Timeout) * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
Resolver: bootResolver,
}).DialContext
c.httpTransport.ResponseHeaderTimeout = time.Duration(conf.Timeout) * time.Second
// 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,
// 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) 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
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(c.conf.Timeout) * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
Resolver: c.bootstrapResolver,
}).DialContext,
ExpectContinueTimeout: 1 * time.Second,
IdleConnTimeout: 90 * time.Second,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
Proxy: http.ProxyFromEnvironment,
ResponseHeaderTimeout: time.Duration(c.conf.Timeout) * time.Second,
TLSHandshakeTimeout: time.Duration(c.conf.Timeout) * time.Second,
}
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) {
@@ -136,110 +181,57 @@ func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
return
}
reply := jsonDNS.PrepareReply(r)
if len(r.Question) != 1 {
log.Println("Number of questions is not 1")
reply.Rcode = dns.RcodeFormatError
w.WriteMsg(reply)
return
}
question := r.Question[0]
questionName := strings.ToLower(question.Name)
questionType := ""
if qtype, ok := dns.TypeToString[question.Qtype]; ok {
questionType = qtype
requestType := ""
if len(c.conf.UpstreamIETF) == 0 {
requestType = "application/dns-json"
} else if len(c.conf.UpstreamGoogle) == 0 {
requestType = "application/dns-message"
} else {
questionType = strconv.Itoa(int(question.Qtype))
}
if c.conf.Verbose {
fmt.Printf("%s - - [%s] \"%s IN %s\"\n", w.RemoteAddr(), time.Now().Format("02/Jan/2006:15:04:05 -0700"), questionName, questionType)
}
num_servers := len(c.conf.Upstream)
upstream := c.conf.Upstream[rand.Intn(num_servers)]
requestURL := fmt.Sprintf("%s?name=%s&type=%s", upstream, url.QueryEscape(questionName), url.QueryEscape(questionType))
if r.CheckingDisabled {
requestURL += "&cd=1"
}
udpSize := uint16(512)
if opt := r.IsEdns0(); opt != nil {
udpSize = opt.UDPSize()
}
ednsClientAddress, ednsClientNetmask := c.findClientIP(w, r)
if ednsClientAddress != nil {
requestURL += fmt.Sprintf("&edns_client_subnet=%s/%d", ednsClientAddress.String(), ednsClientNetmask)
}
req, err := http.NewRequest("GET", requestURL, nil)
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return
}
req.Header.Set("User-Agent", "DNS-over-HTTPS/1.0 (+https://github.com/m13253/dns-over-https)")
resp, err := c.httpClient.Do(req)
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
c.httpTransport.CloseIdleConnections()
return
}
if resp.StatusCode != 200 {
log.Printf("HTTP error: %s\n", resp.Status)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
contentType := resp.Header.Get("Content-Type")
if contentType != "application/json" && !strings.HasPrefix(contentType, "application/json;") {
return
numServers := len(c.conf.UpstreamGoogle) + len(c.conf.UpstreamIETF)
random := rand.Intn(numServers)
if random < len(c.conf.UpstreamGoogle) {
requestType = "application/dns-json"
} else {
requestType = "application/dns-message"
}
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
var req *DNSRequest
if requestType == "application/dns-json" {
req = c.generateRequestGoogle(w, r, isTCP)
} else if requestType == "application/dns-message" {
req = c.generateRequestIETF(w, r, isTCP)
} else {
panic("Unknown request Content-Type")
}
if req.err != nil {
return
}
var respJson jsonDNS.Response
err = json.Unmarshal(body, &respJson)
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return
}
if respJson.Status != dns.RcodeSuccess && respJson.Comment != "" {
log.Printf("DNS error: %s\n", respJson.Comment)
}
fullReply := jsonDNS.Unmarshal(reply, &respJson, udpSize, ednsClientNetmask)
buf, err := fullReply.Pack()
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return
}
if !isTCP && len(buf) > int(udpSize) {
fullReply.Truncated = true
buf, err = fullReply.Pack()
if err != nil {
log.Println(err)
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-message" {
contentType = "application/dns-message"
} else if candidateType == "application/dns-udpwireformat" {
contentType = "application/dns-message"
} else {
if requestType == "application/dns-json" {
contentType = "application/json"
} else if requestType == "application/dns-message" {
contentType = "application/dns-message"
}
buf = buf[:udpSize]
}
w.Write(buf)
if contentType == "application/json" {
c.parseResponseGoogle(w, r, isTCP, req)
} else if contentType == "application/dns-message" {
c.parseResponseIETF(w, r, isTCP, req)
} else {
panic("Unknown response Content-Type")
}
}
func (c *Client) udpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) {
@@ -251,8 +243,8 @@ func (c *Client) tcpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) {
}
var (
ipv4Mask24 net.IPMask = net.IPMask { 255, 255, 255, 0 }
ipv6Mask48 net.IPMask = net.CIDRMask(48, 128)
ipv4Mask24 = net.IPMask{255, 255, 255, 0}
ipv6Mask48 = net.CIDRMask(48, 128)
)
func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddress net.IP, ednsClientNetmask uint8) {

View File

@@ -1,57 +1,60 @@
/*
DNS-over-HTTPS
Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>
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:
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 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.
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 main
import (
"fmt"
"github.com/BurntSushi/toml"
)
type config struct {
Listen string `toml:"listen"`
Upstream []string `toml:"upstream"`
Bootstrap []string `toml:"bootstrap"`
Timeout uint `toml:"timeout"`
NoECS bool `toml:"no_ecs"`
Verbose bool `toml:"verbose"`
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"`
}
func loadConfig(path string) (*config, error) {
conf := &config {}
conf := &config{}
metaData, err := toml.DecodeFile(path, conf)
if err != nil {
return nil, err
}
for _, key := range metaData.Undecoded() {
return nil, &configError { fmt.Sprintf("unknown option %q", key.String()) }
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.Upstream) == 0 {
conf.Upstream = []string { "https://dns.google.com/resolve" }
if len(conf.UpstreamGoogle) == 0 && len(conf.UpstreamIETF) == 0 {
conf.UpstreamGoogle = []string{"https://dns.google.com/resolve"}
}
if conf.Timeout == 0 {
conf.Timeout = 10
@@ -61,7 +64,7 @@ func loadConfig(path string) (*config, error) {
}
type configError struct {
err string
err string
}
func (e *configError) Error() string {

View File

@@ -1,24 +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 = [
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

171
doh-client/google.go Normal file
View File

@@ -0,0 +1,171 @@
/*
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 main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
)
func (c *Client) generateRequestGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool) *DNSRequest {
reply := jsonDNS.PrepareReply(r)
if len(r.Question) != 1 {
log.Println("Number of questions is not 1")
reply.Rcode = dns.RcodeFormatError
w.WriteMsg(reply)
return &DNSRequest{
err: &dns.Error{},
}
}
question := &r.Question[0]
// knot-resolver scrambles capitalization, I think it is unfriendly to cache
questionName := strings.ToLower(question.Name)
questionType := ""
if qtype, ok := dns.TypeToString[question.Qtype]; ok {
questionType = qtype
} else {
questionType = strconv.Itoa(int(question.Qtype))
}
if c.conf.Verbose {
fmt.Printf("%s - - [%s] \"%s IN %s\"\n", w.RemoteAddr(), time.Now().Format("02/Jan/2006:15:04:05 -0700"), questionName, questionType)
}
numServers := len(c.conf.UpstreamGoogle)
upstream := c.conf.UpstreamGoogle[rand.Intn(numServers)]
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"
}
udpSize := uint16(512)
if opt := r.IsEdns0(); opt != nil {
udpSize = opt.UDPSize()
}
ednsClientAddress, ednsClientNetmask := c.findClientIP(w, r)
if ednsClientAddress != nil {
requestURL += fmt.Sprintf("&edns_client_subnet=%s/%d", ednsClientAddress.String(), ednsClientNetmask)
}
req, err := http.NewRequest("GET", requestURL, nil)
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return &DNSRequest{
err: err,
}
}
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)
err1 := c.newHTTPClient()
if err1 != nil {
log.Fatalln(err1)
}
return &DNSRequest{
err: err,
}
}
return &DNSRequest{
response: resp,
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;") {
w.WriteMsg(req.reply)
return
}
}
body, err := ioutil.ReadAll(req.response.Body)
if err != nil {
log.Println(err)
req.reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(req.reply)
return
}
var respJSON jsonDNS.Response
err = json.Unmarshal(body, &respJSON)
if err != nil {
log.Println(err)
req.reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(req.reply)
return
}
if respJSON.Status != dns.RcodeSuccess && respJSON.Comment != "" {
log.Printf("DNS error: %s\n", respJSON.Comment)
}
fullReply := jsonDNS.Unmarshal(req.reply, &respJSON, req.udpSize, req.ednsClientNetmask)
buf, err := fullReply.Pack()
if err != nil {
log.Println(err)
req.reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(req.reply)
return
}
if !isTCP && len(buf) > int(req.udpSize) {
fullReply.Truncated = true
buf, err = fullReply.Pack()
if err != nil {
log.Println(err)
return
}
buf = buf[:req.udpSize]
}
w.Write(buf)
}

276
doh-client/ietf.go Normal file
View File

@@ -0,0 +1,276 @@
/*
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 main
import (
"bytes"
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
)
func (c *Client) generateRequestIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) *DNSRequest {
reply := jsonDNS.PrepareReply(r)
if len(r.Question) != 1 {
log.Println("Number of questions is not 1")
reply.Rcode = dns.RcodeFormatError
w.WriteMsg(reply)
return &DNSRequest{
err: &dns.Error{},
}
}
question := &r.Question[0]
// knot-resolver scrambles capitalization, I think it is unfriendly to cache
questionName := strings.ToLower(question.Name)
questionType := ""
if qtype, ok := dns.TypeToString[question.Qtype]; ok {
questionType = qtype
} else {
questionType = strconv.Itoa(int(question.Qtype))
}
if c.conf.Verbose {
fmt.Printf("%s - - [%s] \"%s IN %s\"\n", w.RemoteAddr(), time.Now().Format("02/Jan/2006:15:04:05 -0700"), questionName, questionType)
}
question.Name = questionName
opt := r.IsEdns0()
udpSize := uint16(512)
if opt == nil {
opt = new(dns.OPT)
opt.Hdr.Name = "."
opt.Hdr.Rrtype = dns.TypeOPT
opt.SetUDPSize(4096)
opt.SetDo(false)
r.Extra = append(r.Extra, opt)
} else {
udpSize = opt.UDPSize()
}
var edns0Subnet *dns.EDNS0_SUBNET
for _, option := range opt.Option {
if option.Option() == dns.EDNS0SUBNET {
edns0Subnet = option.(*dns.EDNS0_SUBNET)
break
}
}
ednsClientAddress, ednsClientNetmask := net.IP(nil), uint8(255)
if edns0Subnet == nil {
ednsClientFamily := uint16(0)
ednsClientAddress, ednsClientNetmask = c.findClientIP(w, r)
if ednsClientAddress != nil {
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
ednsClientNetmask = 24
} else {
ednsClientFamily = 2
ednsClientNetmask = 48
}
edns0Subnet = new(dns.EDNS0_SUBNET)
edns0Subnet.Code = dns.EDNS0SUBNET
edns0Subnet.Family = ednsClientFamily
edns0Subnet.SourceNetmask = ednsClientNetmask
edns0Subnet.SourceScope = 0
edns0Subnet.Address = ednsClientAddress
opt.Option = append(opt.Option, edns0Subnet)
}
} else {
ednsClientAddress, ednsClientNetmask = edns0Subnet.Address, edns0Subnet.SourceNetmask
}
requestID := r.Id
r.Id = 0
requestBinary, err := r.Pack()
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeFormatError
w.WriteMsg(reply)
return &DNSRequest{
err: err,
}
}
r.Id = requestID
requestBase64 := base64.RawURLEncoding.EncodeToString(requestBinary)
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 {
req, err = http.NewRequest("GET", requestURL, nil)
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return &DNSRequest{
err: err,
}
}
} else {
req, err = http.NewRequest("POST", upstream, bytes.NewReader(requestBinary))
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return &DNSRequest{
err: err,
}
}
req.Header.Set("Content-Type", "application/dns-message")
}
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)
err1 := c.newHTTPClient()
if err1 != nil {
log.Fatalln(err1)
}
return &DNSRequest{
err: err,
}
}
return &DNSRequest{
response: resp,
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-message" && !strings.HasPrefix(contentType, "application/dns-message;") {
w.WriteMsg(req.reply)
return
}
}
body, err := ioutil.ReadAll(req.response.Body)
if err != nil {
log.Println(err)
req.reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(req.reply)
return
}
headerNow := req.response.Header.Get("Date")
now := time.Now().UTC()
if headerNow != "" {
if nowDate, err := time.Parse(http.TimeFormat, headerNow); err == nil {
now = nowDate
} else {
log.Println(err)
}
}
headerLastModified := req.response.Header.Get("Last-Modified")
lastModified := now
if headerLastModified != "" {
if lastModifiedDate, err := time.Parse(http.TimeFormat, headerLastModified); err == nil {
lastModified = lastModifiedDate
} else {
log.Println(err)
}
}
timeDelta := now.Sub(lastModified)
if timeDelta < 0 {
timeDelta = 0
}
fullReply := new(dns.Msg)
err = fullReply.Unpack(body)
if err != nil {
log.Println(err)
req.reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(req.reply)
return
}
fullReply.Id = r.Id
for _, rr := range fullReply.Answer {
_ = fixRecordTTL(rr, timeDelta)
}
for _, rr := range fullReply.Ns {
_ = fixRecordTTL(rr, timeDelta)
}
for _, rr := range fullReply.Extra {
if rr.Header().Rrtype == dns.TypeOPT {
continue
}
_ = fixRecordTTL(rr, timeDelta)
}
buf, err := fullReply.Pack()
if err != nil {
log.Println(err)
req.reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(req.reply)
return
}
if !isTCP && len(buf) > int(req.udpSize) {
fullReply.Truncated = true
buf, err = fullReply.Pack()
if err != nil {
log.Println(err)
return
}
buf = buf[:req.udpSize]
}
w.Write(buf)
}
func fixRecordTTL(rr dns.RR, delta time.Duration) dns.RR {
rrHeader := rr.Header()
oldTTL := time.Duration(rrHeader.Ttl) * time.Second
newTTL := oldTTL - delta
if newTTL > 0 {
rrHeader.Ttl = uint32(newTTL / time.Second)
} else {
rrHeader.Ttl = 0
}
return rr
}

View File

@@ -1,24 +1,24 @@
/*
DNS-over-HTTPS
Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>
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:
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 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.
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 main

View File

@@ -1,76 +1,82 @@
/*
DNS-over-HTTPS
Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>
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:
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 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.
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 main
import (
"fmt"
"github.com/BurntSushi/toml"
)
type config struct {
Listen string `toml:"listen"`
Cert string `toml:"cert"`
Key string `toml:"key"`
Path string `toml:"path"`
Upstream []string `toml:"upstream"`
Tries uint `toml:"tries"`
TCPOnly bool `toml:"tcp_only"`
Verbose bool `toml:"verbose"`
Listen []string `toml:"listen"`
Cert string `toml:"cert"`
Key string `toml:"key"`
Path string `toml:"path"`
Upstream []string `toml:"upstream"`
Timeout uint `toml:"timeout"`
Tries uint `toml:"tries"`
TCPOnly bool `toml:"tcp_only"`
Verbose bool `toml:"verbose"`
}
func loadConfig(path string) (*config, error) {
conf := &config {}
conf := &config{}
metaData, err := toml.DecodeFile(path, conf)
if err != nil {
return nil, err
}
for _, key := range metaData.Undecoded() {
return nil, &configError { fmt.Sprintf("unknown option %q", key.String()) }
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 = "/resolve"
conf.Path = "/dns-query"
}
if len(conf.Upstream) == 0 {
conf.Upstream = []string { "8.8.8.8:53", "8.8.4.4:53" }
conf.Upstream = []string{"8.8.8.8:53", "8.8.4.4:53"}
}
if conf.Timeout == 0 {
conf.Timeout = 10
}
if conf.Tries == 0 {
conf.Tries = 3
conf.Tries = 1
}
if (conf.Cert != "") != (conf.Key != "") {
return nil, &configError { "You must specify both -cert and -key to enable TLS" }
return nil, &configError{"You must specify both -cert and -key to enable TLS"}
}
return conf, nil
}
type configError struct {
err string
err string
}
func (e *configError) Error() string {

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 = ""
@@ -8,7 +11,7 @@ cert = ""
key = ""
# HTTP path for resolve application
path = "/resolve"
path = "/dns-query"
# Upstream DNS resolver
# If multiple servers are specified, a random one will be chosen each time.
@@ -17,6 +20,9 @@ upstream = [
"8.8.4.4:53",
]
# Upstream timeout
timeout = 10
# Number of tries if upstream DNS fails
tries = 3

197
doh-server/google.go Normal file
View File

@@ -0,0 +1,197 @@
/*
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 main
import (
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
"golang.org/x/net/idna"
)
func (s *Server) parseRequestGoogle(w http.ResponseWriter, r *http.Request) *DNSRequest {
name := r.FormValue("name")
if name == "" {
return &DNSRequest{
errcode: 400,
errtext: "Invalid argument value: \"name\"",
}
}
name = strings.ToLower(name)
if punycode, err := idna.ToASCII(name); err == nil {
name = punycode
} else {
return &DNSRequest{
errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"name\" = %q (%s)", name, err.Error()),
}
}
rrTypeStr := r.FormValue("type")
rrType := uint16(1)
if rrTypeStr == "" {
} else if v, err := strconv.ParseUint(rrTypeStr, 10, 16); err == nil {
rrType = uint16(v)
} else if v, ok := dns.StringToType[strings.ToUpper(rrTypeStr)]; ok {
rrType = v
} else {
return &DNSRequest{
errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"type\" = %q", rrTypeStr),
}
}
cdStr := r.FormValue("cd")
cd := false
if cdStr == "1" || cdStr == "true" {
cd = true
} else if cdStr == "0" || cdStr == "false" || cdStr == "" {
} else {
return &DNSRequest{
errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"cd\" = %q", cdStr),
}
}
ednsClientSubnet := r.FormValue("edns_client_subnet")
ednsClientFamily := uint16(0)
ednsClientAddress := net.IP(nil)
ednsClientNetmask := uint8(255)
if ednsClientSubnet != "" {
if ednsClientSubnet == "0/0" {
ednsClientSubnet = "0.0.0.0/0"
}
slash := strings.IndexByte(ednsClientSubnet, '/')
if slash < 0 {
ednsClientAddress = net.ParseIP(ednsClientSubnet)
if ednsClientAddress == nil {
return &DNSRequest{
errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"edns_client_subnet\" = %q", ednsClientSubnet),
}
}
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
ednsClientNetmask = 24
} else {
ednsClientFamily = 2
ednsClientNetmask = 48
}
} else {
ednsClientAddress = net.ParseIP(ednsClientSubnet[:slash])
if ednsClientAddress == nil {
return &DNSRequest{
errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"edns_client_subnet\" = %q", ednsClientSubnet),
}
}
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
} else {
ednsClientFamily = 2
}
netmask, err := strconv.ParseUint(ednsClientSubnet[slash+1:], 10, 8)
if err != nil {
return &DNSRequest{
errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"edns_client_subnet\" = %q", ednsClientSubnet),
}
}
ednsClientNetmask = uint8(netmask)
}
} else {
ednsClientAddress = s.findClientIP(r)
if ednsClientAddress == nil {
ednsClientNetmask = 0
} else if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
ednsClientNetmask = 24
} else {
ednsClientFamily = 2
ednsClientNetmask = 48
}
}
msg := new(dns.Msg)
msg.SetQuestion(dns.Fqdn(name), rrType)
msg.CheckingDisabled = cd
opt := new(dns.OPT)
opt.Hdr.Name = "."
opt.Hdr.Rrtype = dns.TypeOPT
opt.SetUDPSize(4096)
opt.SetDo(true)
if ednsClientAddress != nil {
edns0Subnet := new(dns.EDNS0_SUBNET)
edns0Subnet.Code = dns.EDNS0SUBNET
edns0Subnet.Family = ednsClientFamily
edns0Subnet.SourceNetmask = ednsClientNetmask
edns0Subnet.SourceScope = 0
edns0Subnet.Address = ednsClientAddress
opt.Option = append(opt.Option, edns0Subnet)
}
msg.Extra = append(msg.Extra, opt)
return &DNSRequest{
request: msg,
isTailored: ednsClientSubnet == "",
}
}
func (s *Server) generateResponseGoogle(w http.ResponseWriter, r *http.Request, req *DNSRequest) {
respJSON := jsonDNS.Marshal(req.response)
respStr, err := json.Marshal(respJSON)
if err != nil {
log.Println(err)
jsonDNS.FormatError(w, fmt.Sprintf("DNS packet parse failure (%s)", err.Error()), 500)
return
}
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 req.isTailored {
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))
}
if respJSON.Status == dns.RcodeServerFailure {
w.WriteHeader(503)
}
w.Write(respStr)
}

163
doh-server/ietf.go Normal file
View File

@@ -0,0 +1,163 @@
/*
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 main
import (
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"net/http"
"strconv"
"time"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
)
func (s *Server) parseRequestIETF(w http.ResponseWriter, r *http.Request) *DNSRequest {
requestBase64 := r.FormValue("dns")
requestBinary, err := base64.RawURLEncoding.DecodeString(requestBase64)
if err != nil {
return &DNSRequest{
errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"dns\" = %q", requestBase64),
}
}
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{
errcode: 400,
errtext: fmt.Sprintf("Failed to read request body (%s)", err.Error()),
}
}
}
if len(requestBinary) == 0 {
return &DNSRequest{
errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"dns\""),
}
}
msg := new(dns.Msg)
err = msg.Unpack(requestBinary)
if err != nil {
return &DNSRequest{
errcode: 400,
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()
opt := msg.IsEdns0()
if opt == nil {
opt = new(dns.OPT)
opt.Hdr.Name = "."
opt.Hdr.Rrtype = dns.TypeOPT
opt.SetUDPSize(4096)
opt.SetDo(false)
msg.Extra = append(msg.Extra, opt)
}
var edns0Subnet *dns.EDNS0_SUBNET
for _, option := range opt.Option {
if option.Option() == dns.EDNS0SUBNET {
edns0Subnet = option.(*dns.EDNS0_SUBNET)
break
}
}
isTailored := edns0Subnet == nil
if edns0Subnet == nil {
ednsClientFamily := uint16(0)
ednsClientAddress := s.findClientIP(r)
ednsClientNetmask := uint8(255)
if ednsClientAddress != nil {
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
ednsClientNetmask = 24
} else {
ednsClientFamily = 2
ednsClientNetmask = 48
}
edns0Subnet = new(dns.EDNS0_SUBNET)
edns0Subnet.Code = dns.EDNS0SUBNET
edns0Subnet.Family = ednsClientFamily
edns0Subnet.SourceNetmask = ednsClientNetmask
edns0Subnet.SourceScope = 0
edns0Subnet.Address = ednsClientAddress
opt.Option = append(opt.Option, edns0Subnet)
}
}
return &DNSRequest{
request: msg,
isTailored: isTailored,
}
}
func (s *Server) generateResponseIETF(w http.ResponseWriter, r *http.Request, req *DNSRequest) {
respJSON := jsonDNS.Marshal(req.response)
req.response.Id = 0
respBytes, err := req.response.Pack()
if err != nil {
log.Println(err)
jsonDNS.FormatError(w, fmt.Sprintf("DNS packet construct failure (%s)", err.Error()), 500)
return
}
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)
if respJSON.HaveTTL {
if req.isTailored {
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))
}
if respJSON.Status == dns.RcodeServerFailure {
w.WriteHeader(503)
}
w.Write(respBytes)
}

View File

@@ -1,24 +1,24 @@
/*
DNS-over-HTTPS
Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>
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:
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 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.
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 main
@@ -43,8 +43,5 @@ func main() {
}
server := NewServer(conf)
err = server.Start()
if err != nil {
log.Fatalln(err)
}
_ = server.Start()
}

View File

@@ -1,60 +1,68 @@
/*
DNS-over-HTTPS
Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>
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:
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 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.
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 main
import (
"encoding/json"
"fmt"
"math/rand"
"log"
"math/rand"
"net"
"net/http"
"os"
"strconv"
"strings"
"time"
"golang.org/x/net/idna"
"github.com/gorilla/handlers"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
"../json-dns"
)
type Server struct {
conf *config
udpClient *dns.Client
tcpClient *dns.Client
servemux *http.ServeMux
conf *config
udpClient *dns.Client
tcpClient *dns.Client
servemux *http.ServeMux
}
type DNSRequest struct {
request *dns.Msg
response *dns.Msg
isTailored bool
errcode int
errtext string
}
func NewServer(conf *config) (s *Server) {
s = &Server {
s = &Server{
conf: conf,
udpClient: &dns.Client {
Net: "udp",
udpClient: &dns.Client{
Net: "udp",
Timeout: time.Duration(conf.Timeout) * time.Second,
},
tcpClient: &dns.Client {
Net: "tcp",
tcpClient: &dns.Client{
Net: "tcp",
Timeout: time.Duration(conf.Timeout) * time.Second,
},
servemux: http.NewServeMux(),
}
@@ -67,153 +75,107 @@ 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)
} else {
return http.ListenAndServe(s.conf.Listen, 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)
}
// 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("Content-Type", "application/json; charset=UTF-8")
w.Header().Set("Server", "DNS-over-HTTPS/1.0 (+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("Server", jsonDNS.USER_AGENT)
w.Header().Set("X-Powered-By", jsonDNS.USER_AGENT)
name := r.FormValue("name")
if name == "" {
jsonDNS.FormatError(w, "Invalid argument value: \"name\"", 400)
return
if r.Form == nil {
const maxMemory = 32 << 20 // 32 MB
r.ParseMultipartForm(maxMemory)
}
name = strings.ToLower(name)
if punycode, err := idna.ToASCII(name); err == nil {
name = punycode
} else {
jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"name\" = %q (%s)", name, err.Error()), 400)
return
contentType := r.Header.Get("Content-Type")
if ct := r.FormValue("ct"); ct != "" {
contentType = ct
}
rrTypeStr := r.FormValue("type")
rrType := uint16(1)
if rrTypeStr == "" {
} else if v, err := strconv.ParseUint(rrTypeStr, 10, 16); err == nil {
rrType = uint16(v)
} else if v, ok := dns.StringToType[strings.ToUpper(rrTypeStr)]; ok {
rrType = v
} else {
jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"type\" = %q", rrTypeStr), 400)
return
}
cdStr := r.FormValue("cd")
cd := false
if cdStr == "1" || cdStr == "true" {
cd = true
} else if cdStr == "0" || cdStr == "false" || cdStr == "" {
} else {
jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"cd\" = %q", cdStr), 400)
return
}
ednsClientSubnet := r.FormValue("edns_client_subnet")
ednsClientFamily := uint16(0)
ednsClientAddress := net.IP(nil)
ednsClientNetmask := uint8(255)
if ednsClientSubnet != "" {
if ednsClientSubnet == "0/0" {
ednsClientSubnet = "0.0.0.0/0"
if contentType == "" {
// Guess request Content-Type based on other parameters
if r.FormValue("name") != "" {
contentType = "application/dns-json"
} else if r.FormValue("dns") != "" {
contentType = "application/dns-message"
}
slash := strings.IndexByte(ednsClientSubnet, '/')
if slash < 0 {
ednsClientAddress = net.ParseIP(ednsClientSubnet)
if ednsClientAddress == nil {
jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"edns_client_subnet\" = %q", ednsClientSubnet), 400)
return
}
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
ednsClientNetmask = 24
} else {
ednsClientFamily = 2
ednsClientNetmask = 48
}
} else {
ednsClientAddress = net.ParseIP(ednsClientSubnet[:slash])
if ednsClientAddress == nil {
jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"edns_client_subnet\" = %q", ednsClientSubnet), 400)
return
}
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
} else {
ednsClientFamily = 2
}
netmask, err := strconv.ParseUint(ednsClientSubnet[slash + 1:], 10, 8)
if err != nil {
jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"edns_client_subnet\" = %q (%s)", ednsClientSubnet, err.Error()), 400)
return
}
ednsClientNetmask = uint8(netmask)
}
var responseType string
for _, responseCandidate := range strings.Split(r.Header.Get("Accept"), ",") {
responseCandidate = strings.SplitN(responseCandidate, ";", 2)[0]
if responseCandidate == "application/json" {
responseType = "application/json"
break
} else if responseCandidate == "application/dns-udpwireformat" {
responseType = "application/dns-message"
break
} else if responseCandidate == "application/dns-message" {
responseType = "application/dns-message"
break
}
} else {
ednsClientAddress = s.findClientIP(r)
if ednsClientAddress == nil {
ednsClientNetmask = 0
} else if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
ednsClientNetmask = 24
} else {
ednsClientFamily = 2
ednsClientNetmask = 48
}
if responseType == "" {
// Guess response Content-Type based on request Content-Type
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-message"
}
}
msg := new(dns.Msg)
msg.SetQuestion(dns.Fqdn(name), rrType)
msg.CheckingDisabled = cd
opt := new(dns.OPT)
opt.Hdr.Name = "."
opt.Hdr.Rrtype = dns.TypeOPT
opt.SetUDPSize(4096)
opt.SetDo(true)
if ednsClientAddress != nil {
edns0Subnet := new(dns.EDNS0_SUBNET)
edns0Subnet.Code = dns.EDNS0SUBNET
edns0Subnet.Family = ednsClientFamily
edns0Subnet.SourceNetmask = ednsClientNetmask
edns0Subnet.SourceScope = 0
edns0Subnet.Address = ednsClientAddress
opt.Option = append(opt.Option, edns0Subnet)
var req *DNSRequest
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 {
jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"ct\" = %q", contentType), 415)
return
}
if req.errcode != 0 {
jsonDNS.FormatError(w, req.errtext, req.errcode)
return
}
msg.Extra = append(msg.Extra, opt)
resp, err := s.doDNSQuery(msg)
var err error
req.response, err = s.doDNSQuery(req.request)
if err != nil {
jsonDNS.FormatError(w, fmt.Sprintf("DNS query failure (%s)", err.Error()), 503)
return
}
respJson := jsonDNS.Marshal(resp)
respStr, err := json.Marshal(respJson)
if err != nil {
log.Println(err)
jsonDNS.FormatError(w, fmt.Sprintf("DNS packet parse failure (%s)", err.Error()), 500)
return
}
if respJson.HaveTTL {
if ednsClientSubnet != "" {
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("Expires", respJson.EarliestExpires.Format(time.RFC1123))
if responseType == "application/json" {
s.generateResponseGoogle(w, r, req)
} else if responseType == "application/dns-message" {
s.generateResponseIETF(w, r, req)
} else {
panic("Unknown response Content-Type")
}
if respJson.Status == dns.RcodeServerFailure {
w.WriteHeader(503)
}
w.Write(respStr)
}
func (s *Server) findClientIP(r *http.Request) net.IP {
@@ -246,9 +208,9 @@ func (s *Server) findClientIP(r *http.Request) net.IP {
}
func (s *Server) doDNSQuery(msg *dns.Msg) (resp *dns.Msg, err error) {
num_servers := len(s.conf.Upstream)
numServers := len(s.conf.Upstream)
for i := uint(0); i < s.conf.Tries; i++ {
server := s.conf.Upstream[rand.Intn(num_servers)]
server := s.conf.Upstream[rand.Intn(numServers)]
if !s.conf.TCPOnly {
resp, _, err = s.udpClient.Exchange(msg, server)
if err == dns.ErrTruncated {

View File

@@ -1,24 +1,24 @@
/*
DNS-over-HTTPS
Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>
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:
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 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.
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
@@ -27,17 +27,19 @@ import (
"encoding/json"
"log"
"net/http"
"github.com/miekg/dns"
)
type dnsError struct {
Status uint32 `json:"Status"`
Comment string `json:"Comment,omitempty"`
Status uint32 `json:"Status"`
Comment string `json:"Comment,omitempty"`
}
func FormatError(w http.ResponseWriter, comment string, errcode int) {
errJson := dnsError {
Status: dns.RcodeServerFailure,
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
errJson := dnsError{
Status: dns.RcodeServerFailure,
Comment: comment,
}
errStr, err := json.Marshal(errJson)

View File

@@ -1,24 +1,24 @@
/*
DNS-over-HTTPS
Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>
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:
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 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.
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
@@ -28,80 +28,80 @@ import (
)
// RFC6890
var localIPv4Nets = []net.IPNet {
var localIPv4Nets = []net.IPNet{
// This host on this network
net.IPNet {
net.IP { 0, 0, 0, 0 },
net.IPMask { 255, 0, 0, 0 },
net.IPNet{
net.IP{0, 0, 0, 0},
net.IPMask{255, 0, 0, 0},
},
// Private-Use Networks
net.IPNet {
net.IP { 10, 0, 0, 0 },
net.IPMask { 255, 0, 0, 0 },
net.IPNet{
net.IP{10, 0, 0, 0},
net.IPMask{255, 0, 0, 0},
},
// Shared Address Space
net.IPNet {
net.IP { 100, 64, 0, 0 },
net.IPMask { 255, 192, 0, 0 },
net.IPNet{
net.IP{100, 64, 0, 0},
net.IPMask{255, 192, 0, 0},
},
// Loopback
net.IPNet {
net.IP { 127, 0, 0, 0 },
net.IPMask { 255, 0, 0, 0 },
net.IPNet{
net.IP{127, 0, 0, 0},
net.IPMask{255, 0, 0, 0},
},
// Link Local
net.IPNet {
net.IP { 169, 254, 0, 0 },
net.IPMask { 255, 255, 0, 0 },
net.IPNet{
net.IP{169, 254, 0, 0},
net.IPMask{255, 255, 0, 0},
},
// Private-Use Networks
net.IPNet {
net.IP { 172, 16, 0, 0 },
net.IPMask { 255, 240, 0, 0 },
net.IPNet{
net.IP{172, 16, 0, 0},
net.IPMask{255, 240, 0, 0},
},
// DS-Lite
net.IPNet {
net.IP { 192, 0, 0, 0 },
net.IPMask { 255, 255, 255, 248 },
net.IPNet{
net.IP{192, 0, 0, 0},
net.IPMask{255, 255, 255, 248},
},
// 6to4 Relay Anycast
net.IPNet {
net.IP { 192, 88, 99, 0 },
net.IPMask { 255, 255, 255, 0 },
net.IPNet{
net.IP{192, 88, 99, 0},
net.IPMask{255, 255, 255, 0},
},
// Private-Use Networks
net.IPNet {
net.IP { 192, 168, 0, 0 },
net.IPMask { 255, 255, 0, 0 },
net.IPNet{
net.IP{192, 168, 0, 0},
net.IPMask{255, 255, 0, 0},
},
// Reserved for Future Use & Limited Broadcast
net.IPNet {
net.IP { 240, 0, 0, 0 },
net.IPMask { 240, 0, 0, 0 },
net.IPNet{
net.IP{240, 0, 0, 0},
net.IPMask{240, 0, 0, 0},
},
}
// RFC6890
var localIPv6Nets = []net.IPNet {
var localIPv6Nets = []net.IPNet{
// Unspecified & Loopback Address
net.IPNet {
net.IP { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
net.IPMask { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe },
net.IPNet{
net.IP{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe},
},
// Discard-Only Prefix
net.IPNet {
net.IP { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
net.IPMask { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
net.IPNet{
net.IP{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
},
// Unique-Local
net.IPNet {
net.IP { 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
net.IPMask { 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
net.IPNet{
net.IP{0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
net.IPMask{0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
},
// Linked-Scoped Unicast
net.IPNet {
net.IP { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
net.IPMask { 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
net.IPNet{
net.IP{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
net.IPMask{0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
},
}

View File

@@ -1,24 +1,24 @@
/*
DNS-over-HTTPS
Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>
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:
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 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.
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
@@ -28,6 +28,7 @@ import (
"strconv"
"strings"
"time"
"github.com/miekg/dns"
)
@@ -44,7 +45,7 @@ func Marshal(msg *dns.Msg) *Response {
resp.Question = make([]Question, 0, len(msg.Question))
for _, question := range msg.Question {
jsonQuestion := Question {
jsonQuestion := Question{
Name: question.Name,
Type: question.Qtype,
}
@@ -85,7 +86,7 @@ func Marshal(msg *dns.Msg) *Response {
edns0 := option.(*dns.EDNS0_SUBNET)
clientAddress := edns0.Address
if clientAddress == nil {
clientAddress = net.IP { 0, 0, 0, 0 }
clientAddress = net.IP{0, 0, 0, 0}
} else if ipv4 := clientAddress.To4(); ipv4 != nil {
clientAddress = ipv4
}
@@ -106,7 +107,7 @@ func Marshal(msg *dns.Msg) *Response {
}
func marshalRR(rr dns.RR, now time.Time) RR {
jsonRR := RR {}
jsonRR := RR{}
rrHeader := rr.Header()
jsonRR.Name = rrHeader.Name
jsonRR.Type = rrHeader.Rrtype

View File

@@ -1,24 +1,24 @@
/*
DNS-over-HTTPS
Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>
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:
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 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.
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
@@ -29,44 +29,44 @@ import (
type Response struct {
// Standard DNS response code (32 bit integer)
Status uint32 `json:"Status"`
Status uint32 `json:"Status"`
// Whether the response is truncated
TC bool `json:"TC"`
TC bool `json:"TC"`
// Recursion desired
RD bool `json:"RD"`
RD bool `json:"RD"`
// Recursion available
RA bool `json:"RA"`
RA bool `json:"RA"`
// Whether all response data was validated with DNSSEC
// FIXME: We don't have DNSSEC yet! This bit is not reliable!
AD bool `json:"AD"`
AD bool `json:"AD"`
// Whether the client asked to disable DNSSEC
CD bool `json:"CD"`
Question []Question `json:"Question"`
Answer []RR `json:"Answer,omitempty"`
Authority []RR `json:"Authority,omitempty"`
Additional []RR `json:"Additional,omitempty"`
Comment string `json:"Comment,omitempty"`
EdnsClientSubnet string `json:"edns_client_subnet,omitempty"`
CD bool `json:"CD"`
Question []Question `json:"Question"`
Answer []RR `json:"Answer,omitempty"`
Authority []RR `json:"Authority,omitempty"`
Additional []RR `json:"Additional,omitempty"`
Comment string `json:"Comment,omitempty"`
EdnsClientSubnet string `json:"edns_client_subnet,omitempty"`
// Least time-to-live
HaveTTL bool `json:"-"`
LeastTTL uint32 `json:"-"`
EarliestExpires time.Time `json:"-"`
HaveTTL bool `json:"-"`
LeastTTL uint32 `json:"-"`
EarliestExpires time.Time `json:"-"`
}
type Question struct {
// FQDN with trailing dot
Name string `json:"name"`
Name string `json:"name"`
// Standard DNS RR type
Type uint16 `json:"type"`
Type uint16 `json:"type"`
}
type RR struct {
Question
// Record's time-to-live in seconds
TTL uint32 `json:"TTL"`
TTL uint32 `json:"TTL"`
// TTL in absolute time
Expires time.Time `json:"-"`
ExpiresStr string `json:"Expires"`
Expires time.Time `json:"-"`
ExpiresStr string `json:"Expires"`
// Data
Data string `json:"data"`
Data string `json:"data"`
}

View File

@@ -1,24 +1,24 @@
/*
DNS-over-HTTPS
Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>
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:
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 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.
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
@@ -30,6 +30,7 @@ import (
"strconv"
"strings"
"time"
"github.com/miekg/dns"
)
@@ -77,7 +78,7 @@ func Unmarshal(msg *dns.Msg, resp *Response, udpSize uint16, ednsClientNetmask u
}
}
reply.Extra = make([]dns.RR, 0, len(resp.Additional) + 1)
reply.Extra = make([]dns.RR, 0, len(resp.Additional)+1)
opt := new(dns.OPT)
opt.Hdr.Name = "."
opt.Hdr.Rrtype = dns.TypeOPT
@@ -94,20 +95,20 @@ func Unmarshal(msg *dns.Msg, resp *Response, udpSize uint16, ednsClientNetmask u
if ednsClientSubnet != "" {
slash := strings.IndexByte(ednsClientSubnet, '/')
if slash < 0 {
log.Println(UnmarshalError { "Invalid client subnet" })
log.Println(UnmarshalError{"Invalid client subnet"})
} else {
ednsClientAddress = net.ParseIP(ednsClientSubnet[:slash])
if ednsClientAddress == nil {
log.Println(UnmarshalError { "Invalid client subnet address" })
log.Println(UnmarshalError{"Invalid client subnet address"})
} else if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
} else {
ednsClientFamily = 2
}
scope, err := strconv.ParseUint(ednsClientSubnet[slash + 1:], 10, 8)
scope, err := strconv.ParseUint(ednsClientSubnet[slash+1:], 10, 8)
if err != nil {
log.Println(UnmarshalError { "Invalid client subnet address" })
log.Println(UnmarshalError{"Invalid client subnet address"})
} else {
ednsClientScope = uint8(scope)
}
@@ -147,12 +148,12 @@ func Unmarshal(msg *dns.Msg, resp *Response, udpSize uint16, ednsClientNetmask u
func unmarshalRR(rr RR, now time.Time) (dnsRR dns.RR, err error) {
if strings.ContainsAny(rr.Name, "\t\r\n \"();\\") {
return nil, UnmarshalError { fmt.Sprintf("Record name contains space: %q", rr.Name) }
return nil, UnmarshalError{fmt.Sprintf("Record name contains space: %q", rr.Name)}
}
if rr.ExpiresStr != "" {
rr.Expires, err = time.Parse(time.RFC1123, rr.ExpiresStr)
if err != nil {
return nil, UnmarshalError { fmt.Sprintf("Invalid expire time: %q", rr.ExpiresStr) }
return nil, UnmarshalError{fmt.Sprintf("Invalid expire time: %q", rr.ExpiresStr)}
}
ttl := rr.Expires.Sub(now) / time.Second
if ttl >= 0 && ttl <= 0xffffffff {
@@ -161,10 +162,10 @@ func unmarshalRR(rr RR, now time.Time) (dnsRR dns.RR, err error) {
}
rrType, ok := dns.TypeToString[rr.Type]
if !ok {
return nil, UnmarshalError { fmt.Sprintf("Unknown record type: %d", rr.Type) }
return nil, UnmarshalError{fmt.Sprintf("Unknown record type: %d", rr.Type)}
}
if strings.ContainsAny(rr.Data, "\r\n") {
return nil, UnmarshalError { fmt.Sprintf("Record data contains newline: %q", rr.Data) }
return nil, UnmarshalError{fmt.Sprintf("Record data contains newline: %q", rr.Data)}
}
zone := fmt.Sprintf("%s %d IN %s %s", rr.Name, rr.TTL, rrType, rr.Data)
dnsRR, err = dns.NewRR(zone)
@@ -172,7 +173,7 @@ func unmarshalRR(rr RR, now time.Time) (dnsRR dns.RR, err error) {
}
type UnmarshalError struct {
err string
err string
}
func (e UnmarshalError) Error() string {

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

View File

@@ -1,5 +1,6 @@
[Unit]
Description=DNS-over-HTTPS Client
Documentation=https://github.com/m13253/dns-over-https
After=network.target
Before=nss-lookup.target
Wants=nss-lookup.target

View File

@@ -1,5 +1,6 @@
[Unit]
Description=DNS-over-HTTPS Server
Documentation=https://github.com/m13253/dns-over-https
After=network.target
[Service]