Compare commits

...

32 Commits

Author SHA1 Message Date
Star Brilliant
236f7931e6 Update to IETF draft-06 2018-04-10 14:02:51 +08: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
14 changed files with 456 additions and 143 deletions

View File

@@ -3,30 +3,47 @@
GOBUILD=go build GOBUILD=go build
GOGET=go get -d -v GOGET=go get -d -v
PREFIX=/usr/local 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 all: doh-client/doh-client doh-server/doh-server
clean: clean:
rm -f doh-client/doh-client doh-server/doh-server rm -f doh-client/doh-client doh-server/doh-server
install: doh-client/doh-client doh-server/doh-server install:
install -Dm0755 doh-client/doh-client "$(DESTDIR)$(PREFIX)/bin/doh-client" [ -e doh-client/doh-client ] || $(MAKE) doh-client/doh-client
install -Dm0755 doh-server/doh-server "$(DESTDIR)$(PREFIX)/bin/doh-server" [ -e doh-server/doh-server ] || $(MAKE) doh-server/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" mkdir -p "$(DESTDIR)$(PREFIX)/bin/"
[ -e "$(DESTDIR)/etc/dns-over-https/doh-server.conf" ] || install -Dm0644 doh-server/doh-server.conf "$(DESTDIR)/etc/dns-over-https/doh-server.conf" install -m0755 doh-client/doh-client "$(DESTDIR)$(PREFIX)/bin/doh-client"
$(MAKE) -C systemd install "DESTDIR=$(DESTDIR)" "PREFIX=$(PREFIX)" install -m0755 doh-server/doh-server "$(DESTDIR)$(PREFIX)/bin/doh-server"
$(MAKE) -C NetworkManager install "DESTDIR=$(DESTDIR)" "PREFIX=$(PREFIX)" 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: uninstall:
rm -f "$(DESTDIR)$(PREFIX)/bin/doh-client" "$(DESTDIR)$(PREFIX)/bin/doh-server" rm -f "$(DESTDIR)$(PREFIX)/bin/doh-client" "$(DESTDIR)$(PREFIX)/bin/doh-server"
$(MAKE) -C systemd uninstall "DESTDIR=$(DESTDIR)" "PREFIX=$(PREFIX)" if [ "`uname`" = "Linux" ]; then \
$(MAKE) -C NetworkManager uninstall "DESTDIR=$(DESTDIR)" "PREFIX=$(PREFIX)" $(MAKE) -C systemd uninstall "DESTDIR=$(DESTDIR)"; \
$(MAKE) -C NetworkManager uninstall "DESTDIR=$(DESTDIR)"; \
elif [ "`uname`" = "Darwin" ]; then \
$(MAKE) -C launchd uninstall "DESTDIR=$(DESTDIR)"; \
fi
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

@@ -42,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:
@@ -64,25 +72,39 @@ you can host DNS-over-HTTPS along with other HTTPS services.
## DNSSEC ## DNSSEC
DNSSEC validation is not built-in. It is highly recommended that you install DNS-over-HTTPS is compatible with DNSSEC, and requests DNSSEC signatures by
`unbound` or `bind` and pass results for them to validate DNS records. 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 ## EDNS0-Client-Subnet (GeoDNS)
Client Subnet during your configuring `unbound` or `bind`.
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 ## Protocol compatibility
### Google 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), 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 (Draft) ### IETF DNS-over-HTTPS Protocol (Draft)
DNS-over-HTTPS uses a protocol compatible to [draft-ietf-doh-dns-over-https DNS-over-HTTPS uses a protocol compatible to [draft-ietf-doh-dns-over-https](https://github.com/dohwg/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 This protocol is in draft stage. Any incompatibility may be introduced before
it is finished. it is finished.
@@ -92,7 +114,7 @@ Currently supported features are:
- [X] IPv4 / IPv6 - [X] IPv4 / IPv6
- [X] EDNS0 large UDP packet (4 KiB by default) - [X] EDNS0 large UDP packet (4 KiB by default)
- [X] EDNS0 Client Subnet (/24 for IPv4, /48 for IPv6 by default) - [X] EDNS0-Client-Subnet (/24 for IPv4, /48 for IPv6 by default)
## License ## License

View File

@@ -30,20 +30,34 @@ import (
"net" "net"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"strings"
"sync"
"time" "time"
"../json-dns" "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/net/http2" "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 bootstrapResolver *net.Resolver
httpClient *http.Client 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) { func NewClient(conf *config) (c *Client, err error) {
@@ -61,7 +75,7 @@ func NewClient(conf *config) (c *Client, err error) {
Net: "tcp", Net: "tcp",
Handler: dns.HandlerFunc(c.tcpHandlerFunc), Handler: dns.HandlerFunc(c.tcpHandlerFunc),
} }
bootResolver := net.DefaultResolver c.bootstrapResolver = net.DefaultResolver
if len(conf.Bootstrap) != 0 { if len(conf.Bootstrap) != 0 {
c.bootstrap = make([]string, len(conf.Bootstrap)) c.bootstrap = make([]string, len(conf.Bootstrap))
for i, bootstrap := range conf.Bootstrap { for i, bootstrap := range conf.Bootstrap {
@@ -74,7 +88,7 @@ func NewClient(conf *config) (c *Client, err error) {
} }
c.bootstrap[i] = bootstrapAddr.String() c.bootstrap[i] = bootstrapAddr.String()
} }
bootResolver = &net.Resolver{ c.bootstrapResolver = &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
@@ -85,33 +99,53 @@ func NewClient(conf *config) (c *Client, err error) {
}, },
} }
} }
c.httpTransport = new(http.Transport) // Most CDNs require Cookie support to prevent DDoS attack.
// Disabling Cookie does not effectively prevent tracking,
// so I will leave it on to make anti-DDoS services happy.
if !c.conf.NoCookies {
c.cookieJar, err = cookiejar.New(nil)
if err != nil {
return nil, err
}
}
c.httpClientMux = new(sync.RWMutex)
err = c.newHTTPClient()
if err != nil {
return nil, err
}
return c, nil
}
func (c *Client) newHTTPClient() error {
c.httpClientMux.Lock()
defer c.httpClientMux.Unlock()
if c.httpTransport != nil {
c.httpTransport.CloseIdleConnections()
}
c.httpTransport = &http.Transport{ c.httpTransport = &http.Transport{
DialContext: (&net.Dialer{ DialContext: (&net.Dialer{
Timeout: time.Duration(conf.Timeout) * time.Second, Timeout: time.Duration(c.conf.Timeout) * time.Second,
KeepAlive: 30 * time.Second, KeepAlive: 30 * time.Second,
DualStack: true, DualStack: true,
Resolver: bootResolver, Resolver: c.bootstrapResolver,
}).DialContext, }).DialContext,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
IdleConnTimeout: 90 * time.Second, IdleConnTimeout: 90 * time.Second,
MaxIdleConns: 100, MaxIdleConns: 100,
MaxIdleConnsPerHost: 10, MaxIdleConnsPerHost: 10,
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
ResponseHeaderTimeout: time.Duration(conf.Timeout) * time.Second, ResponseHeaderTimeout: time.Duration(c.conf.Timeout) * time.Second,
TLSHandshakeTimeout: time.Duration(conf.Timeout) * time.Second, TLSHandshakeTimeout: time.Duration(c.conf.Timeout) * time.Second,
} }
http2.ConfigureTransport(c.httpTransport) err := http2.ConfigureTransport(c.httpTransport)
// Most CDNs require Cookie support to prevent DDoS attack
cookieJar, err := cookiejar.New(nil)
if err != nil { if err != nil {
return nil, err return err
} }
c.httpClient = &http.Client{ c.httpClient = &http.Client{
Transport: c.httpTransport, Transport: c.httpTransport,
Jar: cookieJar, Jar: c.cookieJar,
} }
return c, nil return nil
} }
func (c *Client) Start() error { func (c *Client) Start() error {
@@ -144,20 +178,56 @@ func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
return return
} }
requestType := ""
if len(c.conf.UpstreamIETF) == 0 { if len(c.conf.UpstreamIETF) == 0 {
c.handlerFuncGoogle(w, r, isTCP) requestType = "application/dns-json"
return } else if len(c.conf.UpstreamGoogle) == 0 {
} requestType = "message/dns"
if len(c.conf.UpstreamGoogle) == 0 {
c.handlerFuncIETF(w, r, isTCP)
return
}
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 {
c.handlerFuncIETF(w, r, isTCP) 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 = "message/dns"
}
}
var req *DNSRequest
if requestType == "application/dns-json" {
req = c.generateRequestGoogle(w, r, isTCP)
} else if requestType == "message/dns" {
req = c.generateRequestIETF(w, r, isTCP)
} else {
panic("Unknown request Content-Type")
}
if req.err != nil {
return
}
contentType := ""
candidateType := strings.SplitN(req.response.Header.Get("Content-Type"), ";", 2)[0]
if candidateType == "application/json" {
contentType = "application/json"
} else if candidateType == "message/dns" {
contentType = "message/dns"
} else if candidateType == "application/dns-udpwireformat" {
contentType = "message/dns"
} else {
if requestType == "application/dns-json" {
contentType = "application/json"
} else if requestType == "message/dns" {
contentType = "message/dns"
}
}
if contentType == "application/json" {
c.parseResponseGoogle(w, r, isTCP, req)
} else if contentType == "message/dns" {
c.parseResponseIETF(w, r, isTCP, req)
} else {
panic("Unknown response Content-Type")
} }
} }

View File

@@ -35,6 +35,7 @@ type config struct {
UpstreamIETF []string `toml:"upstream_ietf"` UpstreamIETF []string `toml:"upstream_ietf"`
Bootstrap []string `toml:"bootstrap"` Bootstrap []string `toml:"bootstrap"`
Timeout uint `toml:"timeout"` Timeout uint `toml:"timeout"`
NoCookies bool `toml:"no_cookies"`
NoECS bool `toml:"no_ecs"` NoECS bool `toml:"no_ecs"`
Verbose bool `toml:"verbose"` Verbose bool `toml:"verbose"`
} }

View File

@@ -4,24 +4,62 @@ 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_google = [ upstream_google = [
# Google's productive resolver, good ECS, bad DNSSEC
"https://dns.google.com/resolve", "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 = [ upstream_ietf = [
"https://dns.google.com/experimental",
# 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 # 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.
# If empty, use the system DNS settings. # 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 = [ bootstrap = [
# Google's resolver, bad ECS, good DNSSEC
"8.8.8.8:53", "8.8.8.8:53",
"8.8.4.4: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 for upstream request
timeout = 10 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 no_ecs = false
# Enable logging # Enable logging

View File

@@ -35,20 +35,22 @@ import (
"strings" "strings"
"time" "time"
"../json-dns" "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func (c *Client) handlerFuncGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool) { func (c *Client) generateRequestGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool) *DNSRequest {
reply := jsonDNS.PrepareReply(r) reply := jsonDNS.PrepareReply(r)
if len(r.Question) != 1 { if len(r.Question) != 1 {
log.Println("Number of questions is not 1") log.Println("Number of questions is not 1")
reply.Rcode = dns.RcodeFormatError reply.Rcode = dns.RcodeFormatError
w.WriteMsg(reply) w.WriteMsg(reply)
return return &DNSRequest{
err: &dns.Error{},
}
} }
question := r.Question[0] question := &r.Question[0]
// knot-resolver scrambles capitalization, I think it is unfriendly to cache // knot-resolver scrambles capitalization, I think it is unfriendly to cache
questionName := strings.ToLower(question.Name) questionName := strings.ToLower(question.Name)
questionType := "" questionType := ""
@@ -64,7 +66,7 @@ func (c *Client) handlerFuncGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool)
numServers := len(c.conf.UpstreamGoogle) numServers := len(c.conf.UpstreamGoogle)
upstream := c.conf.UpstreamGoogle[rand.Intn(numServers)] upstream := c.conf.UpstreamGoogle[rand.Intn(numServers)]
requestURL := fmt.Sprintf("%s?name=%s&type=%s", upstream, url.QueryEscape(questionName), url.QueryEscape(questionType)) requestURL := fmt.Sprintf("%s?ct=application/dns-json&name=%s&type=%s", upstream, url.QueryEscape(questionName), url.QueryEscape(questionType))
if r.CheckingDisabled { if r.CheckingDisabled {
requestURL += "&cd=1" requestURL += "&cd=1"
@@ -85,33 +87,53 @@ func (c *Client) handlerFuncGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool)
log.Println(err) log.Println(err)
reply.Rcode = dns.RcodeServerFailure reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(reply)
return return &DNSRequest{
err: err,
}
} }
req.Header.Set("Accept", "application/json") req.Header.Set("Accept", "application/json, message/dns, application/dns-udpwireformat")
req.Header.Set("User-Agent", "DNS-over-HTTPS/1.0 (+https://github.com/m13253/dns-over-https)") req.Header.Set("User-Agent", "DNS-over-HTTPS/1.1 (+https://github.com/m13253/dns-over-https)")
c.httpClientMux.RLock()
resp, err := c.httpClient.Do(req) resp, err := c.httpClient.Do(req)
c.httpClientMux.RUnlock()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply.Rcode = dns.RcodeServerFailure reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(reply)
c.httpTransport.CloseIdleConnections() err1 := c.newHTTPClient()
return if err1 != nil {
log.Fatalln(err1)
}
return &DNSRequest{
err: err,
}
} }
if resp.StatusCode != 200 {
log.Printf("HTTP error: %s\n", resp.Status) return &DNSRequest{
reply.Rcode = dns.RcodeServerFailure response: resp,
contentType := resp.Header.Get("Content-Type") 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;") { if contentType != "application/json" && !strings.HasPrefix(contentType, "application/json;") {
w.WriteMsg(reply) w.WriteMsg(req.reply)
return return
} }
} }
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(req.response.Body)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply.Rcode = dns.RcodeServerFailure req.reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(req.reply)
return return
} }
@@ -119,8 +141,8 @@ func (c *Client) handlerFuncGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool)
err = json.Unmarshal(body, &respJSON) err = json.Unmarshal(body, &respJSON)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply.Rcode = dns.RcodeServerFailure req.reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(req.reply)
return return
} }
@@ -128,22 +150,22 @@ func (c *Client) handlerFuncGoogle(w dns.ResponseWriter, r *dns.Msg, isTCP bool)
log.Printf("DNS error: %s\n", respJSON.Comment) log.Printf("DNS error: %s\n", respJSON.Comment)
} }
fullReply := jsonDNS.Unmarshal(reply, &respJSON, udpSize, ednsClientNetmask) fullReply := jsonDNS.Unmarshal(req.reply, &respJSON, req.udpSize, req.ednsClientNetmask)
buf, err := fullReply.Pack() buf, err := fullReply.Pack()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply.Rcode = dns.RcodeServerFailure req.reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(req.reply)
return return
} }
if !isTCP && len(buf) > int(udpSize) { if !isTCP && len(buf) > int(req.udpSize) {
fullReply.Truncated = true fullReply.Truncated = true
buf, err = fullReply.Pack() buf, err = fullReply.Pack()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
buf = buf[:udpSize] buf = buf[:req.udpSize]
} }
w.Write(buf) w.Write(buf)
} }

View File

@@ -30,26 +30,29 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"math/rand" "math/rand"
"net"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"../json-dns" "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) { func (c *Client) generateRequestIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) *DNSRequest {
reply := jsonDNS.PrepareReply(r) reply := jsonDNS.PrepareReply(r)
if len(r.Question) != 1 { if len(r.Question) != 1 {
log.Println("Number of questions is not 1") log.Println("Number of questions is not 1")
reply.Rcode = dns.RcodeFormatError reply.Rcode = dns.RcodeFormatError
w.WriteMsg(reply) w.WriteMsg(reply)
return return &DNSRequest{
err: &dns.Error{},
}
} }
question := r.Question[0] question := &r.Question[0]
// knot-resolver scrambles capitalization, I think it is unfriendly to cache // knot-resolver scrambles capitalization, I think it is unfriendly to cache
questionName := strings.ToLower(question.Name) questionName := strings.ToLower(question.Name)
questionType := "" questionType := ""
@@ -63,8 +66,6 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
fmt.Printf("%s - - [%s] \"%s IN %s\"\n", w.RemoteAddr(), time.Now().Format("02/Jan/2006:15:04:05 -0700"), questionName, questionType) 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 question.Name = questionName
opt := r.IsEdns0() opt := r.IsEdns0()
udpSize := uint16(512) udpSize := uint16(512)
@@ -85,9 +86,10 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
break break
} }
} }
ednsClientAddress, ednsClientNetmask := net.IP(nil), uint8(255)
if edns0Subnet == nil { if edns0Subnet == nil {
ednsClientFamily := uint16(0) ednsClientFamily := uint16(0)
ednsClientAddress, ednsClientNetmask := c.findClientIP(w, r) ednsClientAddress, ednsClientNetmask = c.findClientIP(w, r)
if ednsClientAddress != nil { if ednsClientAddress != nil {
if ipv4 := ednsClientAddress.To4(); ipv4 != nil { if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1 ednsClientFamily = 1
@@ -105,20 +107,28 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
edns0Subnet.Address = ednsClientAddress edns0Subnet.Address = ednsClientAddress
opt.Option = append(opt.Option, edns0Subnet) opt.Option = append(opt.Option, edns0Subnet)
} }
} else {
ednsClientAddress, ednsClientNetmask = edns0Subnet.Address, edns0Subnet.SourceNetmask
} }
requestID := r.Id
r.Id = 0
requestBinary, err := r.Pack() requestBinary, err := r.Pack()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply.Rcode = dns.RcodeFormatError reply.Rcode = dns.RcodeFormatError
w.WriteMsg(reply) w.WriteMsg(reply)
return return &DNSRequest{
err: err,
}
} }
r.Id = requestID
requestBase64 := base64.RawURLEncoding.EncodeToString(requestBinary) requestBase64 := base64.RawURLEncoding.EncodeToString(requestBinary)
numServers := len(c.conf.UpstreamIETF) numServers := len(c.conf.UpstreamIETF)
upstream := c.conf.UpstreamIETF[rand.Intn(numServers)] upstream := c.conf.UpstreamIETF[rand.Intn(numServers)]
requestURL := fmt.Sprintf("%s?ct=application/dns-udpwireformat&dns=%s", upstream, requestBase64) requestURL := fmt.Sprintf("%s?ct=application/dns-udpwireformat&dns=%s", upstream, requestBase64)
//requestURL := fmt.Sprintf("%s?ct=message/dns&dns=%s", upstream, requestBase64)
var req *http.Request var req *http.Request
if len(requestURL) < 2048 { if len(requestURL) < 2048 {
@@ -127,7 +137,9 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
log.Println(err) log.Println(err)
reply.Rcode = dns.RcodeServerFailure reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(reply)
return return &DNSRequest{
err: err,
}
} }
} else { } else {
req, err = http.NewRequest("POST", upstream, bytes.NewReader(requestBinary)) req, err = http.NewRequest("POST", upstream, bytes.NewReader(requestBinary))
@@ -135,48 +147,76 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
log.Println(err) log.Println(err)
reply.Rcode = dns.RcodeServerFailure reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(reply)
return return &DNSRequest{
err: err,
}
} }
req.Header.Set("Content-Type", "application/dns-udpwireformat") req.Header.Set("Content-Type", "message/dns")
} }
req.Header.Set("Accept", "application/dns-udpwireformat") req.Header.Set("Accept", "message/dns, application/dns-udpwireformat, application/json")
req.Header.Set("User-Agent", "DNS-over-HTTPS/1.0 (+https://github.com/m13253/dns-over-https)") req.Header.Set("User-Agent", "DNS-over-HTTPS/1.1 (+https://github.com/m13253/dns-over-https)")
c.httpClientMux.RLock()
resp, err := c.httpClient.Do(req) resp, err := c.httpClient.Do(req)
c.httpClientMux.RUnlock()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply.Rcode = dns.RcodeServerFailure reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(reply)
c.httpTransport.CloseIdleConnections() err1 := c.newHTTPClient()
return if err1 != nil {
log.Fatalln(err1)
}
return &DNSRequest{
err: err,
}
} }
if resp.StatusCode != 200 {
log.Printf("HTTP error: %s\n", resp.Status) return &DNSRequest{
reply.Rcode = dns.RcodeServerFailure response: resp,
contentType := resp.Header.Get("Content-Type") reply: reply,
if contentType != "application/dns-udpwireformat" && !strings.HasPrefix(contentType, "application/dns-udpwireformat;") { udpSize: udpSize,
w.WriteMsg(reply) 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 != "message/dns" && !strings.HasPrefix(contentType, "message/dns;") {
w.WriteMsg(req.reply)
return return
} }
} }
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(req.response.Body)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply.Rcode = dns.RcodeServerFailure req.reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(req.reply)
return return
} }
lastModified := resp.Header.Get("Last-Modified") headerNow := req.response.Header.Get("Date")
if lastModified == "" { now := time.Now().UTC()
lastModified = resp.Header.Get("Date") if headerNow != "" {
if nowDate, err := time.Parse(http.TimeFormat, headerNow); err == nil {
now = nowDate
} else {
log.Println(err)
}
} }
now := time.Now() headerLastModified := req.response.Header.Get("Last-Modified")
lastModifiedDate, err := time.Parse(http.TimeFormat, lastModified) lastModified := now
if err != nil { if headerLastModified != "" {
log.Println(err) if lastModifiedDate, err := time.Parse(http.TimeFormat, headerLastModified); err == nil {
lastModifiedDate = now lastModified = lastModifiedDate
} else {
log.Println(err)
}
} }
timeDelta := now.Sub(lastModifiedDate) timeDelta := now.Sub(lastModified)
if timeDelta < 0 { if timeDelta < 0 {
timeDelta = 0 timeDelta = 0
} }
@@ -185,12 +225,12 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
err = fullReply.Unpack(body) err = fullReply.Unpack(body)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply.Rcode = dns.RcodeServerFailure req.reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(req.reply)
return return
} }
fullReply.Id = requestID fullReply.Id = r.Id
for _, rr := range fullReply.Answer { for _, rr := range fullReply.Answer {
_ = fixRecordTTL(rr, timeDelta) _ = fixRecordTTL(rr, timeDelta)
} }
@@ -207,18 +247,18 @@ func (c *Client) handlerFuncIETF(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
buf, err := fullReply.Pack() buf, err := fullReply.Pack()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply.Rcode = dns.RcodeServerFailure req.reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(req.reply)
return return
} }
if !isTCP && len(buf) > int(udpSize) { if !isTCP && len(buf) > int(req.udpSize) {
fullReply.Truncated = true fullReply.Truncated = true
buf, err = fullReply.Pack() buf, err = fullReply.Pack()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
buf = buf[:udpSize] buf = buf[:req.udpSize]
} }
w.Write(buf) w.Write(buf)
} }
@@ -227,7 +267,7 @@ func fixRecordTTL(rr dns.RR, delta time.Duration) dns.RR {
rrHeader := rr.Header() rrHeader := rr.Header()
oldTTL := time.Duration(rrHeader.Ttl) * time.Second oldTTL := time.Duration(rrHeader.Ttl) * time.Second
newTTL := oldTTL - delta newTTL := oldTTL - delta
if newTTL >= 0 { if newTTL > 0 {
rrHeader.Ttl = uint32(newTTL / time.Second) rrHeader.Ttl = uint32(newTTL / time.Second)
} else { } else {
rrHeader.Ttl = 0 rrHeader.Ttl = 0

View File

@@ -31,8 +31,9 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time"
"../json-dns" "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/net/idna" "golang.org/x/net/idna"
) )
@@ -178,11 +179,14 @@ func (s *Server) generateResponseGoogle(w http.ResponseWriter, r *http.Request,
} }
w.Header().Set("Content-Type", "application/json; charset=UTF-8") 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 respJSON.HaveTTL {
if req.isTailored { if req.isTailored {
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("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)) w.Header().Set("Expires", respJSON.EarliestExpires.Format(http.TimeFormat))
} }

View File

@@ -32,7 +32,7 @@ import (
"strconv" "strconv"
"time" "time"
"../json-dns" "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@@ -45,7 +45,7 @@ func (s *Server) parseRequestIETF(w http.ResponseWriter, r *http.Request) *DNSRe
errtext: fmt.Sprintf("Invalid argument value: \"dns\" = %q", requestBase64), errtext: fmt.Sprintf("Invalid argument value: \"dns\" = %q", requestBase64),
} }
} }
if len(requestBinary) == 0 && r.Header.Get("Content-Type") == "application/dns-udpwireformat" { if len(requestBinary) == 0 && (r.Header.Get("Content-Type") == "message/dns" || r.Header.Get("Content-Type") == "application/dns-udpwireformat") {
requestBinary, err = ioutil.ReadAll(r.Body) requestBinary, err = ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
return &DNSRequest{ return &DNSRequest{
@@ -68,6 +68,25 @@ func (s *Server) parseRequestIETF(w http.ResponseWriter, r *http.Request) *DNSRe
errtext: fmt.Sprintf("DNS packet parse failure (%s)", err.Error()), 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() msg.Id = dns.Id()
opt := msg.IsEdns0() opt := msg.IsEdns0()
if opt == nil { if opt == nil {
@@ -125,15 +144,15 @@ func (s *Server) generateResponseIETF(w http.ResponseWriter, r *http.Request, re
return return
} }
w.Header().Set("Content-Type", "application/dns-udpwireformat") w.Header().Set("Content-Type", "message/dns")
now := time.Now().Format(http.TimeFormat) now := time.Now().UTC().Format(http.TimeFormat)
w.Header().Set("Date", now) w.Header().Set("Date", now)
w.Header().Set("Last-Modified", now) w.Header().Set("Last-Modified", now)
if respJSON.HaveTTL { if respJSON.HaveTTL {
if req.isTailored { if req.isTailored {
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("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)) w.Header().Set("Expires", respJSON.EarliestExpires.Format(http.TimeFormat))
} }

View File

@@ -33,8 +33,8 @@ import (
"strings" "strings"
"time" "time"
"../json-dns"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@@ -82,8 +82,8 @@ func (s *Server) Start() error {
} }
func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) { func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", "DNS-over-HTTPS/1.0 (+https://github.com/m13253/dns-over-https)") w.Header().Set("Server", "DNS-over-HTTPS/1.1 (+https://github.com/m13253/dns-over-https)")
w.Header().Set("X-Powered-By", "DNS-over-HTTPS/1.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)")
if r.Form == nil { if r.Form == nil {
const maxMemory = 32 << 20 // 32 MB const maxMemory = 32 << 20 // 32 MB
@@ -96,38 +96,45 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
if contentType == "" { if contentType == "" {
// Guess request Content-Type based on other parameters // Guess request Content-Type based on other parameters
if r.FormValue("name") != "" { if r.FormValue("name") != "" {
contentType = "application/x-www-form-urlencoded" contentType = "application/dns-json"
} else if r.FormValue("dns") != "" { } else if r.FormValue("dns") != "" {
contentType = "application/dns-udpwireformat" contentType = "message/dns"
} }
} }
var responseType string var responseType string
for _, responseCandidate := range strings.Split(r.Header.Get("Accept"), ",") { for _, responseCandidate := range strings.Split(r.Header.Get("Accept"), ",") {
responseCandidate = strings.ToLower(strings.SplitN(responseCandidate, ";", 2)[0]) responseCandidate = strings.SplitN(responseCandidate, ";", 2)[0]
if responseCandidate == "application/json" { if responseCandidate == "application/json" {
responseType = "application/json" responseType = "application/json"
break break
} else if responseCandidate == "application/dns-udpwireformat" { } else if responseCandidate == "application/dns-udpwireformat" {
responseType = "application/dns-udpwireformat" responseType = "message/dns"
break
} else if responseCandidate == "message/dns" {
responseType = "message/dns"
break break
} }
} }
if responseType == "" { if responseType == "" {
// Guess response Content-Type based on request Content-Type // Guess response Content-Type based on request Content-Type
if contentType == "application/x-www-form-urlencoded" { if contentType == "application/dns-json" {
responseType = "application/json" responseType = "application/json"
} else if contentType == "message/dns" {
responseType = "message/dns"
} else if contentType == "application/dns-udpwireformat" { } else if contentType == "application/dns-udpwireformat" {
responseType = "application/dns-udpwireformat" responseType = "message/dns"
} }
} }
var req *DNSRequest var req *DNSRequest
if contentType == "application/x-www-form-urlencoded" { if contentType == "application/dns-json" {
req = s.parseRequestGoogle(w, r) req = s.parseRequestGoogle(w, r)
} else if contentType == "message/dns" {
req = s.parseRequestIETF(w, r)
} else if contentType == "application/dns-udpwireformat" { } else if contentType == "application/dns-udpwireformat" {
req = s.parseRequestIETF(w, r) req = s.parseRequestIETF(w, r)
} else { } else {
jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"ct\" = %q", contentType), 400) jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"ct\" = %q", contentType), 415)
return return
} }
if req.errcode != 0 { if req.errcode != 0 {
@@ -142,10 +149,12 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
return return
} }
if contentType == "application/x-www-form-urlencoded" { if responseType == "application/json" {
s.generateResponseGoogle(w, r, req) s.generateResponseGoogle(w, r, req)
} else if contentType == "application/dns-udpwireformat" { } else if responseType == "message/dns" {
s.generateResponseIETF(w, r, req) s.generateResponseIETF(w, r, req)
} else {
panic("Unknown response Content-Type")
} }
} }

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>

View File

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