Compare commits

...

23 Commits

Author SHA1 Message Date
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
22 changed files with 1224 additions and 585 deletions

View File

@@ -1,6 +1,6 @@
MIT License 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 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 this software and associated documentation files (the "Software"), to deal in

View File

@@ -25,8 +25,8 @@ uninstall:
deps: deps:
$(GOGET) ./doh-client ./doh-server $(GOGET) ./doh-client ./doh-server
doh-client/doh-client: deps doh-client/client.go doh-client/config.go doh-client/main.go json-dns/error.go json-dns/globalip.go json-dns/marshal.go json-dns/response.go json-dns/unmarshal.go doh-client/doh-client: deps doh-client/client.go doh-client/config.go doh-client/google.go doh-client/ietf.go doh-client/main.go json-dns/error.go json-dns/globalip.go json-dns/marshal.go json-dns/response.go json-dns/unmarshal.go
cd doh-client && $(GOBUILD) cd doh-client && $(GOBUILD)
doh-server/doh-server: deps doh-server/config.go doh-server/main.go doh-server/server.go json-dns/error.go json-dns/globalip.go json-dns/marshal.go json-dns/response.go json-dns/unmarshal.go doh-server/doh-server: deps doh-server/config.go doh-server/google.go doh-server/ietf.go doh-server/main.go doh-server/server.go json-dns/error.go json-dns/globalip.go json-dns/marshal.go json-dns/response.go json-dns/unmarshal.go
cd doh-server && $(GOBUILD) cd doh-server && $(GOBUILD)

View File

@@ -1,11 +1,12 @@
DNS-over-HTTPS 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 ## 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`: 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) ;; SERVER: 127.0.0.1#53(127.0.0.1)
### Uninstalling
To uninstall, type:
sudo make uninstall
The configuration files are kept at `/etc/dns-over-https`. Remove them manually if you want.
## Server Configuration ## Server Configuration
The following is a typical DNS-over-HTTPS architecture: The following is a typical DNS-over-HTTPS architecture:
@@ -71,11 +80,21 @@ Client Subnet during your configuring `unbound` or `bind`.
## Protocol compatibility ## 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 except for absolute expire time is preferred to relative TTL value. Refer to
[json-dns/response.go](json-dns/response.go) for a complete description of the [json-dns/response.go](json-dns/response.go) for a complete description of the
API. API.
### IETF DNS-over-HTTPS 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: Currently supported features are:
- [X] IPv4 / IPv6 - [X] IPv4 / IPv6

View File

@@ -1,68 +1,64 @@
/* /*
DNS-over-HTTPS DNS-over-HTTPS
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 Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the 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: Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
package main package main
import ( import (
"context" "context"
"encoding/json"
"fmt"
"math/rand"
"io/ioutil"
"log" "log"
"math/rand"
"net" "net"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"net/url"
"strconv"
"strings"
"time" "time"
"github.com/miekg/dns"
"../json-dns" "../json-dns"
"github.com/miekg/dns"
"golang.org/x/net/http2"
) )
type Client struct { type Client struct {
conf *config conf *config
bootstrap []string bootstrap []string
udpServer *dns.Server udpServer *dns.Server
tcpServer *dns.Server tcpServer *dns.Server
httpTransport *http.Transport httpTransport *http.Transport
httpClient *http.Client httpClient *http.Client
} }
func NewClient(conf *config) (c *Client, err error) { func NewClient(conf *config) (c *Client, err error) {
c = &Client { c = &Client{
conf: conf, conf: conf,
} }
c.udpServer = &dns.Server { c.udpServer = &dns.Server{
Addr: conf.Listen, Addr: conf.Listen,
Net: "udp", Net: "udp",
Handler: dns.HandlerFunc(c.udpHandlerFunc), Handler: dns.HandlerFunc(c.udpHandlerFunc),
UDPSize: 4096, UDPSize: 4096,
} }
c.tcpServer = &dns.Server { c.tcpServer = &dns.Server{
Addr: conf.Listen, Addr: conf.Listen,
Net: "tcp", Net: "tcp",
Handler: dns.HandlerFunc(c.tcpHandlerFunc), Handler: dns.HandlerFunc(c.tcpHandlerFunc),
} }
bootResolver := net.DefaultResolver bootResolver := net.DefaultResolver
@@ -71,37 +67,49 @@ func NewClient(conf *config) (c *Client, err error) {
for i, bootstrap := range conf.Bootstrap { for i, bootstrap := range conf.Bootstrap {
bootstrapAddr, err := net.ResolveUDPAddr("udp", bootstrap) bootstrapAddr, err := net.ResolveUDPAddr("udp", bootstrap)
if err != nil { 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() c.bootstrap[i] = bootstrapAddr.String()
} }
bootResolver = &net.Resolver { bootResolver = &net.Resolver{
PreferGo: true, PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) { Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
var d net.Dialer var d net.Dialer
num_servers := len(c.bootstrap) numServers := len(c.bootstrap)
bootstrap := c.bootstrap[rand.Intn(num_servers)] bootstrap := c.bootstrap[rand.Intn(numServers)]
conn, err := d.DialContext(ctx, network, bootstrap) conn, err := d.DialContext(ctx, network, bootstrap)
return conn, err return conn, err
}, },
} }
} }
c.httpTransport = new(http.Transport) c.httpTransport = new(http.Transport)
*c.httpTransport = *http.DefaultTransport.(*http.Transport) c.httpTransport = &http.Transport{
c.httpTransport.DialContext = (&net.Dialer { DialContext: (&net.Dialer{
Timeout: time.Duration(conf.Timeout) * time.Second, Timeout: time.Duration(conf.Timeout) * time.Second,
KeepAlive: 30 * time.Second, KeepAlive: 30 * time.Second,
DualStack: true, DualStack: true,
Resolver: bootResolver, Resolver: bootResolver,
}).DialContext }).DialContext,
c.httpTransport.ResponseHeaderTimeout = time.Duration(conf.Timeout) * time.Second ExpectContinueTimeout: 1 * time.Second,
IdleConnTimeout: 90 * time.Second,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
Proxy: http.ProxyFromEnvironment,
ResponseHeaderTimeout: time.Duration(conf.Timeout) * time.Second,
TLSHandshakeTimeout: time.Duration(conf.Timeout) * time.Second,
}
http2.ConfigureTransport(c.httpTransport)
// Most CDNs require Cookie support to prevent DDoS attack // Most CDNs require Cookie support to prevent DDoS attack
cookieJar, err := cookiejar.New(nil) cookieJar, err := cookiejar.New(nil)
if err != nil { return nil, err } if err != nil {
c.httpClient = &http.Client { return nil, err
}
c.httpClient = &http.Client{
Transport: c.httpTransport, Transport: c.httpTransport,
Jar: cookieJar, Jar: cookieJar,
} }
return c, nil return c, nil
} }
@@ -114,14 +122,14 @@ func (c *Client) Start() error {
log.Println(err) log.Println(err)
} }
result <- err result <- err
} () }()
go func() { go func() {
err := c.tcpServer.ListenAndServe() err := c.tcpServer.ListenAndServe()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
result <- err result <- err
} () }()
err := <-result err := <-result
if err != nil { if err != nil {
return err return err
@@ -136,110 +144,21 @@ func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
return return
} }
reply := jsonDNS.PrepareReply(r) if len(c.conf.UpstreamIETF) == 0 {
c.handlerFuncGoogle(w, r, isTCP)
if len(r.Question) != 1 {
log.Println("Number of questions is not 1")
reply.Rcode = dns.RcodeFormatError
w.WriteMsg(reply)
return return
} }
question := r.Question[0] if len(c.conf.UpstreamGoogle) == 0 {
questionName := strings.ToLower(question.Name) c.handlerFuncIETF(w, r, isTCP)
questionType := "" return
if qtype, ok := dns.TypeToString[question.Qtype]; ok { }
questionType = qtype numServers := len(c.conf.UpstreamGoogle) + len(c.conf.UpstreamIETF)
random := rand.Intn(numServers)
if random < len(c.conf.UpstreamGoogle) {
c.handlerFuncGoogle(w, r, isTCP)
} else { } else {
questionType = strconv.Itoa(int(question.Qtype)) c.handlerFuncIETF(w, r, isTCP)
} }
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
}
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
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
}
buf = buf[:udpSize]
}
w.Write(buf)
} }
func (c *Client) udpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) { func (c *Client) udpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) {
@@ -251,8 +170,8 @@ func (c *Client) tcpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) {
} }
var ( var (
ipv4Mask24 net.IPMask = net.IPMask { 255, 255, 255, 0 } ipv4Mask24 = net.IPMask{255, 255, 255, 0}
ipv6Mask48 net.IPMask = net.CIDRMask(48, 128) ipv6Mask48 = net.CIDRMask(48, 128)
) )
func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddress net.IP, ednsClientNetmask uint8) { func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddress net.IP, ednsClientNetmask uint8) {

View File

@@ -1,57 +1,59 @@
/* /*
DNS-over-HTTPS DNS-over-HTTPS
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 Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the 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: Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
package main package main
import ( import (
"fmt" "fmt"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
) )
type config struct { type config struct {
Listen string `toml:"listen"` Listen string `toml:"listen"`
Upstream []string `toml:"upstream"` UpstreamGoogle []string `toml:"upstream_google"`
Bootstrap []string `toml:"bootstrap"` UpstreamIETF []string `toml:"upstream_ietf"`
Timeout uint `toml:"timeout"` Bootstrap []string `toml:"bootstrap"`
NoECS bool `toml:"no_ecs"` Timeout uint `toml:"timeout"`
Verbose bool `toml:"verbose"` NoECS bool `toml:"no_ecs"`
Verbose bool `toml:"verbose"`
} }
func loadConfig(path string) (*config, error) { func loadConfig(path string) (*config, error) {
conf := &config {} conf := &config{}
metaData, err := toml.DecodeFile(path, conf) metaData, err := toml.DecodeFile(path, conf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, key := range metaData.Undecoded() { 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 == "" { if conf.Listen == "" {
conf.Listen = "127.0.0.1:53" conf.Listen = "127.0.0.1:53"
} }
if len(conf.Upstream) == 0 { if len(conf.UpstreamGoogle) == 0 && len(conf.UpstreamIETF) == 0 {
conf.Upstream = []string { "https://dns.google.com/resolve" } conf.UpstreamGoogle = []string{"https://dns.google.com/resolve"}
} }
if conf.Timeout == 0 { if conf.Timeout == 0 {
conf.Timeout = 10 conf.Timeout = 10
@@ -61,7 +63,7 @@ func loadConfig(path string) (*config, error) {
} }
type configError struct { type configError struct {
err string err string
} }
func (e *configError) Error() string { func (e *configError) Error() string {

View File

@@ -3,9 +3,12 @@ listen = "127.0.0.1:53"
# HTTP path for upstream resolver # HTTP path for upstream resolver
# If multiple servers are specified, a random one will be chosen each time. # If multiple servers are specified, a random one will be chosen each time.
upstream = [ upstream_google = [
"https://dns.google.com/resolve", "https://dns.google.com/resolve",
] ]
upstream_ietf = [
"https://dns.google.com/experimental",
]
# Bootstrap DNS server to resolve the address of the upstream resolver # Bootstrap DNS server to resolve the address of the upstream resolver
# If multiple servers are specified, a random one will be chosen each time. # If multiple servers are specified, a random one will be chosen each time.

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

@@ -0,0 +1,149 @@
/*
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"
"../json-dns"
"github.com/miekg/dns"
)
func (c *Client) handlerFuncGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
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]
// 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?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("Accept", "application/json")
req.Header.Set("User-Agent", "DNS-over-HTTPS/1.1 (+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
contentType := resp.Header.Get("Content-Type")
if contentType != "application/json" && !strings.HasPrefix(contentType, "application/json;") {
w.WriteMsg(reply)
return
}
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
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
}
buf = buf[:udpSize]
}
w.Write(buf)
}

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

@@ -0,0 +1,244 @@
/*
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/http"
"strconv"
"strings"
"time"
"../json-dns"
"github.com/miekg/dns"
)
func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
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]
// 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)
}
requestID := r.Id
r.Id = 0
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
}
}
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)
}
}
requestBinary, err := r.Pack()
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeFormatError
w.WriteMsg(reply)
return
}
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)
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
}
} else {
req, err = http.NewRequest("POST", upstream, bytes.NewReader(requestBinary))
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return
}
req.Header.Set("Content-Type", "application/dns-udpwireformat")
}
req.Header.Set("Accept", "application/dns-udpwireformat")
req.Header.Set("User-Agent", "DNS-over-HTTPS/1.1 (+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
contentType := resp.Header.Get("Content-Type")
if contentType != "application/dns-udpwireformat" && !strings.HasPrefix(contentType, "application/dns-udpwireformat;") {
w.WriteMsg(reply)
return
}
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return
}
headerNow := resp.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 := resp.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)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return
}
fullReply.Id = requestID
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)
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
}
buf = buf[: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 DNS-over-HTTPS
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 Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the 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: Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
package main package main

View File

@@ -1,76 +1,81 @@
/* /*
DNS-over-HTTPS DNS-over-HTTPS
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 Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the 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: Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
package main package main
import ( import (
"fmt" "fmt"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
) )
type config struct { type config struct {
Listen string `toml:"listen"` Listen string `toml:"listen"`
Cert string `toml:"cert"` Cert string `toml:"cert"`
Key string `toml:"key"` Key string `toml:"key"`
Path string `toml:"path"` Path string `toml:"path"`
Upstream []string `toml:"upstream"` Upstream []string `toml:"upstream"`
Tries uint `toml:"tries"` Timeout uint `toml:"timeout"`
TCPOnly bool `toml:"tcp_only"` Tries uint `toml:"tries"`
Verbose bool `toml:"verbose"` TCPOnly bool `toml:"tcp_only"`
Verbose bool `toml:"verbose"`
} }
func loadConfig(path string) (*config, error) { func loadConfig(path string) (*config, error) {
conf := &config {} conf := &config{}
metaData, err := toml.DecodeFile(path, conf) metaData, err := toml.DecodeFile(path, conf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, key := range metaData.Undecoded() { 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 == "" { if conf.Listen == "" {
conf.Listen = "127.0.0.1:8053" conf.Listen = "127.0.0.1:8053"
} }
if conf.Path == "" { if conf.Path == "" {
conf.Path = "/resolve" conf.Path = "/dns-query"
} }
if len(conf.Upstream) == 0 { 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 { if conf.Tries == 0 {
conf.Tries = 3 conf.Tries = 1
} }
if (conf.Cert != "") != (conf.Key != "") { 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 return conf, nil
} }
type configError struct { type configError struct {
err string err string
} }
func (e *configError) Error() string { func (e *configError) Error() string {

View File

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

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

@@ -0,0 +1,193 @@
/*
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"
"../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")
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"
"../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-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-udpwireformat")
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 DNS-over-HTTPS
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 Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the 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: Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
package main package main

View File

@@ -1,60 +1,68 @@
/* /*
DNS-over-HTTPS DNS-over-HTTPS
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 Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the 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: Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"math/rand"
"log" "log"
"math/rand"
"net" "net"
"net/http" "net/http"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
"golang.org/x/net/idna"
"../json-dns"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
"github.com/miekg/dns" "github.com/miekg/dns"
"../json-dns"
) )
type Server struct { type Server struct {
conf *config conf *config
udpClient *dns.Client udpClient *dns.Client
tcpClient *dns.Client tcpClient *dns.Client
servemux *http.ServeMux servemux *http.ServeMux
}
type DNSRequest struct {
request *dns.Msg
response *dns.Msg
isTailored bool
errcode int
errtext string
} }
func NewServer(conf *config) (s *Server) { func NewServer(conf *config) (s *Server) {
s = &Server { s = &Server{
conf: conf, conf: conf,
udpClient: &dns.Client { udpClient: &dns.Client{
Net: "udp", Net: "udp",
Timeout: time.Duration(conf.Timeout) * time.Second,
}, },
tcpClient: &dns.Client { tcpClient: &dns.Client{
Net: "tcp", Net: "tcp",
Timeout: time.Duration(conf.Timeout) * time.Second,
}, },
servemux: http.NewServeMux(), servemux: http.NewServeMux(),
} }
@@ -69,151 +77,76 @@ func (s *Server) Start() error {
} }
if s.conf.Cert != "" || s.conf.Key != "" { if s.conf.Cert != "" || s.conf.Key != "" {
return http.ListenAndServeTLS(s.conf.Listen, s.conf.Cert, s.conf.Key, servemux) return http.ListenAndServeTLS(s.conf.Listen, s.conf.Cert, s.conf.Key, servemux)
} else {
return http.ListenAndServe(s.conf.Listen, servemux)
} }
return http.ListenAndServe(s.conf.Listen, servemux)
} }
func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) { 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.1 (+https://github.com/m13253/dns-over-https)")
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.1 (+https://github.com/m13253/dns-over-https)")
w.Header().Set("X-Powered-By", "DNS-over-HTTPS/1.0 (+https://github.com/m13253/dns-over-https)")
name := r.FormValue("name") if r.Form == nil {
if name == "" { const maxMemory = 32 << 20 // 32 MB
jsonDNS.FormatError(w, "Invalid argument value: \"name\"", 400) r.ParseMultipartForm(maxMemory)
return
} }
name = strings.ToLower(name) contentType := r.Header.Get("Content-Type")
if punycode, err := idna.ToASCII(name); err == nil { if ct := r.FormValue("ct"); ct != "" {
name = punycode contentType = ct
} else {
jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"name\" = %q (%s)", name, err.Error()), 400)
return
} }
if contentType == "" {
rrTypeStr := r.FormValue("type") // Guess request Content-Type based on other parameters
rrType := uint16(1) if r.FormValue("name") != "" {
if rrTypeStr == "" { contentType = "application/x-www-form-urlencoded"
} else if v, err := strconv.ParseUint(rrTypeStr, 10, 16); err == nil { } else if r.FormValue("dns") != "" {
rrType = uint16(v) contentType = "application/dns-udpwireformat"
} 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"
} }
slash := strings.IndexByte(ednsClientSubnet, '/') }
if slash < 0 { var responseType string
ednsClientAddress = net.ParseIP(ednsClientSubnet) for _, responseCandidate := range strings.Split(r.Header.Get("Accept"), ",") {
if ednsClientAddress == nil { responseCandidate = strings.ToLower(strings.SplitN(responseCandidate, ";", 2)[0])
jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"edns_client_subnet\" = %q", ednsClientSubnet), 400) if responseCandidate == "application/json" {
return responseType = "application/json"
} break
if ipv4 := ednsClientAddress.To4(); ipv4 != nil { } else if responseCandidate == "application/dns-udpwireformat" {
ednsClientFamily = 1 responseType = "application/dns-udpwireformat"
ednsClientAddress = ipv4 break
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)
} }
} else { }
ednsClientAddress = s.findClientIP(r) if responseType == "" {
if ednsClientAddress == nil { // Guess response Content-Type based on request Content-Type
ednsClientNetmask = 0 if contentType == "application/x-www-form-urlencoded" {
} else if ipv4 := ednsClientAddress.To4(); ipv4 != nil { responseType = "application/json"
ednsClientFamily = 1 } else if contentType == "application/dns-udpwireformat" {
ednsClientAddress = ipv4 responseType = "application/dns-udpwireformat"
ednsClientNetmask = 24
} else {
ednsClientFamily = 2
ednsClientNetmask = 48
} }
} }
msg := new(dns.Msg) var req *DNSRequest
msg.SetQuestion(dns.Fqdn(name), rrType) if contentType == "application/x-www-form-urlencoded" {
msg.CheckingDisabled = cd req = s.parseRequestGoogle(w, r)
opt := new(dns.OPT) } else if contentType == "application/dns-udpwireformat" {
opt.Hdr.Name = "." req = s.parseRequestIETF(w, r)
opt.Hdr.Rrtype = dns.TypeOPT } else {
opt.SetUDPSize(4096) jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"ct\" = %q", contentType), 400)
opt.SetDo(true) return
if ednsClientAddress != nil { }
edns0Subnet := new(dns.EDNS0_SUBNET) if req.errcode != 0 {
edns0Subnet.Code = dns.EDNS0SUBNET jsonDNS.FormatError(w, req.errtext, req.errcode)
edns0Subnet.Family = ednsClientFamily return
edns0Subnet.SourceNetmask = ednsClientNetmask
edns0Subnet.SourceScope = 0
edns0Subnet.Address = ednsClientAddress
opt.Option = append(opt.Option, edns0Subnet)
} }
msg.Extra = append(msg.Extra, opt)
resp, err := s.doDNSQuery(msg) var err error
req.response, err = s.doDNSQuery(req.request)
if err != nil { if err != nil {
jsonDNS.FormatError(w, fmt.Sprintf("DNS query failure (%s)", err.Error()), 503) jsonDNS.FormatError(w, fmt.Sprintf("DNS query failure (%s)", err.Error()), 503)
return return
} }
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 responseType == "application/json" {
if ednsClientSubnet != "" { s.generateResponseGoogle(w, r, req)
w.Header().Set("Cache-Control", "public, max-age=" + strconv.Itoa(int(respJson.LeastTTL))) } else if contentType == "application/dns-udpwireformat" {
} else { s.generateResponseIETF(w, r, req)
w.Header().Set("Cache-Control", "private, max-age=" + strconv.Itoa(int(respJson.LeastTTL)))
}
w.Header().Set("Expires", respJson.EarliestExpires.Format(time.RFC1123))
} }
if respJson.Status == dns.RcodeServerFailure {
w.WriteHeader(503)
}
w.Write(respStr)
} }
func (s *Server) findClientIP(r *http.Request) net.IP { func (s *Server) findClientIP(r *http.Request) net.IP {
@@ -246,9 +179,9 @@ func (s *Server) findClientIP(r *http.Request) net.IP {
} }
func (s *Server) doDNSQuery(msg *dns.Msg) (resp *dns.Msg, err error) { 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++ { 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 { if !s.conf.TCPOnly {
resp, _, err = s.udpClient.Exchange(msg, server) resp, _, err = s.udpClient.Exchange(msg, server)
if err == dns.ErrTruncated { if err == dns.ErrTruncated {

View File

@@ -1,24 +1,24 @@
/* /*
DNS-over-HTTPS DNS-over-HTTPS
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 Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the 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: Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
package jsonDNS package jsonDNS
@@ -27,17 +27,19 @@ import (
"encoding/json" "encoding/json"
"log" "log"
"net/http" "net/http"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
type dnsError struct { type dnsError struct {
Status uint32 `json:"Status"` Status uint32 `json:"Status"`
Comment string `json:"Comment,omitempty"` Comment string `json:"Comment,omitempty"`
} }
func FormatError(w http.ResponseWriter, comment string, errcode int) { func FormatError(w http.ResponseWriter, comment string, errcode int) {
errJson := dnsError { w.Header().Set("Content-Type", "application/json; charset=UTF-8")
Status: dns.RcodeServerFailure, errJson := dnsError{
Status: dns.RcodeServerFailure,
Comment: comment, Comment: comment,
} }
errStr, err := json.Marshal(errJson) errStr, err := json.Marshal(errJson)

View File

@@ -1,24 +1,24 @@
/* /*
DNS-over-HTTPS DNS-over-HTTPS
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 Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the 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: Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
package jsonDNS package jsonDNS
@@ -28,80 +28,80 @@ import (
) )
// RFC6890 // RFC6890
var localIPv4Nets = []net.IPNet { var localIPv4Nets = []net.IPNet{
// This host on this network // This host on this network
net.IPNet { net.IPNet{
net.IP { 0, 0, 0, 0 }, net.IP{0, 0, 0, 0},
net.IPMask { 255, 0, 0, 0 }, net.IPMask{255, 0, 0, 0},
}, },
// Private-Use Networks // Private-Use Networks
net.IPNet { net.IPNet{
net.IP { 10, 0, 0, 0 }, net.IP{10, 0, 0, 0},
net.IPMask { 255, 0, 0, 0 }, net.IPMask{255, 0, 0, 0},
}, },
// Shared Address Space // Shared Address Space
net.IPNet { net.IPNet{
net.IP { 100, 64, 0, 0 }, net.IP{100, 64, 0, 0},
net.IPMask { 255, 192, 0, 0 }, net.IPMask{255, 192, 0, 0},
}, },
// Loopback // Loopback
net.IPNet { net.IPNet{
net.IP { 127, 0, 0, 0 }, net.IP{127, 0, 0, 0},
net.IPMask { 255, 0, 0, 0 }, net.IPMask{255, 0, 0, 0},
}, },
// Link Local // Link Local
net.IPNet { net.IPNet{
net.IP { 169, 254, 0, 0 }, net.IP{169, 254, 0, 0},
net.IPMask { 255, 255, 0, 0 }, net.IPMask{255, 255, 0, 0},
}, },
// Private-Use Networks // Private-Use Networks
net.IPNet { net.IPNet{
net.IP { 172, 16, 0, 0 }, net.IP{172, 16, 0, 0},
net.IPMask { 255, 240, 0, 0 }, net.IPMask{255, 240, 0, 0},
}, },
// DS-Lite // DS-Lite
net.IPNet { net.IPNet{
net.IP { 192, 0, 0, 0 }, net.IP{192, 0, 0, 0},
net.IPMask { 255, 255, 255, 248 }, net.IPMask{255, 255, 255, 248},
}, },
// 6to4 Relay Anycast // 6to4 Relay Anycast
net.IPNet { net.IPNet{
net.IP { 192, 88, 99, 0 }, net.IP{192, 88, 99, 0},
net.IPMask { 255, 255, 255, 0 }, net.IPMask{255, 255, 255, 0},
}, },
// Private-Use Networks // Private-Use Networks
net.IPNet { net.IPNet{
net.IP { 192, 168, 0, 0 }, net.IP{192, 168, 0, 0},
net.IPMask { 255, 255, 0, 0 }, net.IPMask{255, 255, 0, 0},
}, },
// Reserved for Future Use & Limited Broadcast // Reserved for Future Use & Limited Broadcast
net.IPNet { net.IPNet{
net.IP { 240, 0, 0, 0 }, net.IP{240, 0, 0, 0},
net.IPMask { 240, 0, 0, 0 }, net.IPMask{240, 0, 0, 0},
}, },
} }
// RFC6890 // RFC6890
var localIPv6Nets = []net.IPNet { var localIPv6Nets = []net.IPNet{
// Unspecified & Loopback Address // Unspecified & Loopback Address
net.IPNet { net.IPNet{
net.IP { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 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.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe},
}, },
// Discard-Only Prefix // Discard-Only Prefix
net.IPNet { net.IPNet{
net.IP { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 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.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
}, },
// Unique-Local // Unique-Local
net.IPNet { net.IPNet{
net.IP { 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 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.IPMask{0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
}, },
// Linked-Scoped Unicast // Linked-Scoped Unicast
net.IPNet { net.IPNet{
net.IP { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 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.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 DNS-over-HTTPS
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 Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the 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: Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
package jsonDNS package jsonDNS
@@ -28,6 +28,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@@ -44,7 +45,7 @@ func Marshal(msg *dns.Msg) *Response {
resp.Question = make([]Question, 0, len(msg.Question)) resp.Question = make([]Question, 0, len(msg.Question))
for _, question := range msg.Question { for _, question := range msg.Question {
jsonQuestion := Question { jsonQuestion := Question{
Name: question.Name, Name: question.Name,
Type: question.Qtype, Type: question.Qtype,
} }
@@ -85,7 +86,7 @@ func Marshal(msg *dns.Msg) *Response {
edns0 := option.(*dns.EDNS0_SUBNET) edns0 := option.(*dns.EDNS0_SUBNET)
clientAddress := edns0.Address clientAddress := edns0.Address
if clientAddress == nil { if clientAddress == nil {
clientAddress = net.IP { 0, 0, 0, 0 } clientAddress = net.IP{0, 0, 0, 0}
} else if ipv4 := clientAddress.To4(); ipv4 != nil { } else if ipv4 := clientAddress.To4(); ipv4 != nil {
clientAddress = ipv4 clientAddress = ipv4
} }
@@ -106,7 +107,7 @@ func Marshal(msg *dns.Msg) *Response {
} }
func marshalRR(rr dns.RR, now time.Time) RR { func marshalRR(rr dns.RR, now time.Time) RR {
jsonRR := RR {} jsonRR := RR{}
rrHeader := rr.Header() rrHeader := rr.Header()
jsonRR.Name = rrHeader.Name jsonRR.Name = rrHeader.Name
jsonRR.Type = rrHeader.Rrtype jsonRR.Type = rrHeader.Rrtype

View File

@@ -1,24 +1,24 @@
/* /*
DNS-over-HTTPS DNS-over-HTTPS
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 Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the 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: Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
package jsonDNS package jsonDNS
@@ -29,44 +29,44 @@ import (
type Response struct { type Response struct {
// Standard DNS response code (32 bit integer) // Standard DNS response code (32 bit integer)
Status uint32 `json:"Status"` Status uint32 `json:"Status"`
// Whether the response is truncated // Whether the response is truncated
TC bool `json:"TC"` TC bool `json:"TC"`
// Recursion desired // Recursion desired
RD bool `json:"RD"` RD bool `json:"RD"`
// Recursion available // Recursion available
RA bool `json:"RA"` RA bool `json:"RA"`
// Whether all response data was validated with DNSSEC // Whether all response data was validated with DNSSEC
// FIXME: We don't have DNSSEC yet! This bit is not reliable! // 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 // Whether the client asked to disable DNSSEC
CD bool `json:"CD"` CD bool `json:"CD"`
Question []Question `json:"Question"` Question []Question `json:"Question"`
Answer []RR `json:"Answer,omitempty"` Answer []RR `json:"Answer,omitempty"`
Authority []RR `json:"Authority,omitempty"` Authority []RR `json:"Authority,omitempty"`
Additional []RR `json:"Additional,omitempty"` Additional []RR `json:"Additional,omitempty"`
Comment string `json:"Comment,omitempty"` Comment string `json:"Comment,omitempty"`
EdnsClientSubnet string `json:"edns_client_subnet,omitempty"` EdnsClientSubnet string `json:"edns_client_subnet,omitempty"`
// Least time-to-live // Least time-to-live
HaveTTL bool `json:"-"` HaveTTL bool `json:"-"`
LeastTTL uint32 `json:"-"` LeastTTL uint32 `json:"-"`
EarliestExpires time.Time `json:"-"` EarliestExpires time.Time `json:"-"`
} }
type Question struct { type Question struct {
// FQDN with trailing dot // FQDN with trailing dot
Name string `json:"name"` Name string `json:"name"`
// Standard DNS RR type // Standard DNS RR type
Type uint16 `json:"type"` Type uint16 `json:"type"`
} }
type RR struct { type RR struct {
Question Question
// Record's time-to-live in seconds // Record's time-to-live in seconds
TTL uint32 `json:"TTL"` TTL uint32 `json:"TTL"`
// TTL in absolute time // TTL in absolute time
Expires time.Time `json:"-"` Expires time.Time `json:"-"`
ExpiresStr string `json:"Expires"` ExpiresStr string `json:"Expires"`
// Data // Data
Data string `json:"data"` Data string `json:"data"`
} }

View File

@@ -1,24 +1,24 @@
/* /*
DNS-over-HTTPS DNS-over-HTTPS
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 Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the 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: Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
package jsonDNS package jsonDNS
@@ -30,6 +30,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/miekg/dns" "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 := new(dns.OPT)
opt.Hdr.Name = "." opt.Hdr.Name = "."
opt.Hdr.Rrtype = dns.TypeOPT opt.Hdr.Rrtype = dns.TypeOPT
@@ -94,20 +95,20 @@ func Unmarshal(msg *dns.Msg, resp *Response, udpSize uint16, ednsClientNetmask u
if ednsClientSubnet != "" { if ednsClientSubnet != "" {
slash := strings.IndexByte(ednsClientSubnet, '/') slash := strings.IndexByte(ednsClientSubnet, '/')
if slash < 0 { if slash < 0 {
log.Println(UnmarshalError { "Invalid client subnet" }) log.Println(UnmarshalError{"Invalid client subnet"})
} else { } else {
ednsClientAddress = net.ParseIP(ednsClientSubnet[:slash]) ednsClientAddress = net.ParseIP(ednsClientSubnet[:slash])
if ednsClientAddress == nil { if ednsClientAddress == nil {
log.Println(UnmarshalError { "Invalid client subnet address" }) log.Println(UnmarshalError{"Invalid client subnet address"})
} else if ipv4 := ednsClientAddress.To4(); ipv4 != nil { } else if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1 ednsClientFamily = 1
ednsClientAddress = ipv4 ednsClientAddress = ipv4
} else { } else {
ednsClientFamily = 2 ednsClientFamily = 2
} }
scope, err := strconv.ParseUint(ednsClientSubnet[slash + 1:], 10, 8) scope, err := strconv.ParseUint(ednsClientSubnet[slash+1:], 10, 8)
if err != nil { if err != nil {
log.Println(UnmarshalError { "Invalid client subnet address" }) log.Println(UnmarshalError{"Invalid client subnet address"})
} else { } else {
ednsClientScope = uint8(scope) 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) { func unmarshalRR(rr RR, now time.Time) (dnsRR dns.RR, err error) {
if strings.ContainsAny(rr.Name, "\t\r\n \"();\\") { 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 != "" { if rr.ExpiresStr != "" {
rr.Expires, err = time.Parse(time.RFC1123, rr.ExpiresStr) rr.Expires, err = time.Parse(time.RFC1123, rr.ExpiresStr)
if err != nil { 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 ttl := rr.Expires.Sub(now) / time.Second
if ttl >= 0 && ttl <= 0xffffffff { 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] rrType, ok := dns.TypeToString[rr.Type]
if !ok { 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") { 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) zone := fmt.Sprintf("%s %d IN %s %s", rr.Name, rr.TTL, rrType, rr.Data)
dnsRR, err = dns.NewRR(zone) dnsRR, err = dns.NewRR(zone)
@@ -172,7 +173,7 @@ func unmarshalRR(rr RR, now time.Time) (dnsRR dns.RR, err error) {
} }
type UnmarshalError struct { type UnmarshalError struct {
err string err string
} }
func (e UnmarshalError) Error() string { func (e UnmarshalError) Error() string {

View File

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

View File

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