Skip to content

Commit 20de808

Browse files
committed
Add dns over http server feature
1 parent 5912121 commit 20de808

File tree

11 files changed

+783
-31
lines changed

11 files changed

+783
-31
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Configuration file is "config.json" by default:
8282
{
8383
"BindAddress": ":53",
8484
"DebugHTTPAddress": "127.0.0.1:5555",
85+
"DohEnabled": false,
8586
"PrimaryDNS": [
8687
{
8788
"Name": "DNSPod",
@@ -188,7 +189,7 @@ IPv6). Overture will handle both TCP and UDP requests. Literal IPv6 addresses ar
188189
}
189190
}
190191
```
191-
192+
+ DohEnabled(Experimental): Enable DNS over HTTP server using `DebugHTTPAddress` above with url path `/dns-query`. DNS over HTTPS server can be easily achieved helping by other web server software like caddy or nginx.
192193
+ DNS: You can specify multiple DNS upstream servers here.
193194
+ Name: This field is only used for logging.
194195
+ Address: Same as BindAddress.

config.sample.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"BindAddress": ":53",
33
"DebugHTTPAddress": "127.0.0.1:5555",
4+
"DohEnabled": false,
45
"PrimaryDNS": [
56
{
67
"Name": "DNSPod",

config.test.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"BindAddress": ":53",
33
"DebugHTTPAddress": "127.0.0.1:5555",
4+
"DohEnabled": true,
45
"PrimaryDNS": [
56
{
67
"Name": "DNSPod",

core/common/edns.go

+22-11
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,30 @@ func SetEDNSClientSubnet(m *dns.Msg, ip string, isNoCookie bool) {
2727
}
2828

2929
es := IsEDNSClientSubnet(o)
30-
if es == nil {
31-
es = new(dns.EDNS0_SUBNET)
32-
es.Code = dns.EDNS0SUBNET
33-
es.Address = net.ParseIP(ip)
34-
if es.Address.To4() != nil {
35-
es.Family = 1 // 1 for IPv4 source address, 2 for IPv6
36-
es.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6
30+
if es == nil || es.Address.IsUnspecified() {
31+
nes := new(dns.EDNS0_SUBNET)
32+
nes.Code = dns.EDNS0SUBNET
33+
nes.Address = net.ParseIP(ip)
34+
if nes.Address.To4() != nil {
35+
nes.Family = 1 // 1 for IPv4 source address, 2 for IPv6
36+
nes.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6
3737
} else {
38-
es.Family = 2 // 1 for IPv4 source address, 2 for IPv6
39-
es.SourceNetmask = 128 // 32 for IPV4, 128 for IPv6
38+
nes.Family = 2 // 1 for IPv4 source address, 2 for IPv6
39+
nes.SourceNetmask = 128 // 32 for IPV4, 128 for IPv6
4040
}
41-
es.SourceScope = 0
42-
o.Option = append(o.Option, es)
41+
nes.SourceScope = 0
42+
if es != nil && es.Address.IsUnspecified() {
43+
var edns0 []dns.EDNS0
44+
for _, s := range o.Option {
45+
switch e := s.(type) {
46+
case *dns.EDNS0_SUBNET:
47+
default:
48+
edns0 = append(edns0, e)
49+
}
50+
}
51+
o.Option = edns0
52+
}
53+
o.Option = append(o.Option, nes)
4354
if isNoCookie {
4455
deleteCookie(o)
4556
}

core/config/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type Config struct {
3434
FilePath string
3535
BindAddress string
3636
DebugHTTPAddress string
37+
DohEnabled bool
3738
PrimaryDNS []*common.DNSUpstream
3839
AlternativeDNS []*common.DNSUpstream
3940
OnlyPrimaryDNS bool

core/control.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func Start() {
4949
}
5050
dispatcher.Init()
5151

52-
srv = inbound.NewServer(conf.BindAddress, conf.DebugHTTPAddress, dispatcher, conf.RejectQType)
52+
srv = inbound.NewServer(conf.BindAddress, conf.DebugHTTPAddress, dispatcher, conf.RejectQType, conf.DohEnabled)
5353
srv.HTTPMux.HandleFunc("/reload", ReloadHandler)
5454

5555
go srv.Run()

core/inbound/server.go

+61-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package inbound
44
import (
55
"context"
66
"encoding/json"
7+
"fmt"
78
"io"
89
"net"
910
"net/http"
@@ -12,8 +13,13 @@ import (
1213
"strconv"
1314
"strings"
1415
"sync"
16+
"time"
1517

18+
"github.com/coredns/coredns/plugin/pkg/dnsutil"
19+
"github.com/coredns/coredns/plugin/pkg/doh"
20+
"github.com/coredns/coredns/plugin/pkg/response"
1621
"github.com/miekg/dns"
22+
"github.com/shawn1m/overture/core/common"
1723
log "github.com/sirupsen/logrus"
1824

1925
"github.com/shawn1m/overture/core/outbound"
@@ -27,20 +33,70 @@ type Server struct {
2733
HTTPMux *http.ServeMux
2834
ctx context.Context
2935
cancel context.CancelFunc
36+
dohEnabled bool
3037
}
3138

32-
func NewServer(bindAddress string, debugHTTPAddress string, dispatcher outbound.Dispatcher, rejectQType []uint16) *Server {
39+
func NewServer(bindAddress string, debugHTTPAddress string, dispatcher outbound.Dispatcher, rejectQType []uint16, dohEnabled bool) *Server {
3340
s := &Server{
3441
bindAddress: bindAddress,
3542
debugHttpAddress: debugHTTPAddress,
3643
dispatcher: dispatcher,
3744
rejectQType: rejectQType,
45+
dohEnabled: dohEnabled,
3846
}
3947
s.ctx, s.cancel = context.WithCancel(context.Background())
4048
s.HTTPMux = http.NewServeMux()
4149
return s
4250
}
4351

52+
func (s *Server) ServeDNSHttp(w http.ResponseWriter, r *http.Request) {
53+
if r.URL.Path != doh.Path {
54+
http.Error(w, "", http.StatusNotFound)
55+
return
56+
}
57+
58+
q, err := doh.RequestToMsg(r)
59+
if err != nil {
60+
http.Error(w, err.Error(), http.StatusBadRequest)
61+
return
62+
}
63+
64+
// Create a DoHWriter with the correct addresses in it.
65+
inboundIP, _, _ := net.SplitHostPort(r.RemoteAddr)
66+
forwardIP := r.Header.Get("X-Forwarded-For")
67+
if net.ParseIP(forwardIP) != nil && common.ReservedIPNetworkList.Contains(net.ParseIP(inboundIP), false, "") {
68+
inboundIP = forwardIP
69+
}
70+
log.Debugf("Question from %s: %s", inboundIP, q.Question[0].String())
71+
72+
for _, qt := range s.rejectQType {
73+
if isQuestionType(q, qt) {
74+
log.Debugf("Reject %s: %s", inboundIP, q.Question[0].String())
75+
http.Error(w, "Rejected", http.StatusForbidden)
76+
return
77+
}
78+
}
79+
80+
responseMessage := s.dispatcher.Exchange(q, inboundIP)
81+
82+
if responseMessage == nil {
83+
http.Error(w, "No response", http.StatusInternalServerError)
84+
return
85+
}
86+
87+
buf, _ := responseMessage.Pack()
88+
89+
mt, _ := response.Typify(responseMessage, time.Now().UTC())
90+
age := dnsutil.MinimalTTL(responseMessage, mt)
91+
92+
w.Header().Set("Content-Type", doh.MimeType)
93+
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%f", age.Seconds()))
94+
w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
95+
w.WriteHeader(http.StatusOK)
96+
97+
w.Write(buf)
98+
}
99+
44100
func (s *Server) DumpCache(w http.ResponseWriter, req *http.Request) {
45101
if s.dispatcher.Cache == nil {
46102
io.WriteString(w, "error: cache not enabled")
@@ -136,6 +192,10 @@ func (s *Server) Run() {
136192
s.HTTPMux.HandleFunc("/debug/pprof/profile", pprof.Profile)
137193
s.HTTPMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
138194
s.HTTPMux.HandleFunc("/debug/pprof/trace", pprof.Trace)
195+
if s.dohEnabled {
196+
log.Info("Dns over http server started!")
197+
s.HTTPMux.HandleFunc(doh.Path, s.ServeDNSHttp)
198+
}
139199

140200
wg.Add(1)
141201
go func() {

core/matcher/mix/list.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ func (s *List) Has(str string) bool {
4848
case "domain":
4949
idx := len(str) - len(data.Content)
5050
if idx >= 0 && data.Content == str[idx:] {
51-
if idx >=1 && (str[idx-1] != '.') {
52-
return false
53-
}
54-
return true
55-
}
51+
if idx >= 1 && (str[idx-1] != '.') {
52+
return false
53+
}
54+
return true
55+
}
5656
case "regex":
5757
reg := regexp.MustCompile(data.Content)
5858
if reg.MatchString(str) {

core/outbound/clients/remote.go

+2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ func (c *RemoteClient) ExchangeFromCache() *dns.Msg {
6666
func (c *RemoteClient) Exchange(isLog bool) *dns.Msg {
6767
common.SetEDNSClientSubnet(c.questionMessage, c.ednsClientSubnetIP,
6868
c.dnsUpstream.EDNSClientSubnet.NoCookie)
69+
log.Debugf("Use " + c.ednsClientSubnetIP + " as original ednsClientSubnetIP")
6970
c.ednsClientSubnetIP = common.GetEDNSClientSubnetIP(c.questionMessage)
71+
log.Debugf("Use " + c.ednsClientSubnetIP + " as ednsClientSubnetIP")
7072

7173
if c.responseMessage != nil {
7274
return c.responseMessage

go.mod

+5-4
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ module github.com/shawn1m/overture
33
go 1.12
44

55
require (
6-
github.com/miekg/dns v1.1.8
6+
github.com/coredns/coredns v1.8.0
7+
github.com/miekg/dns v1.1.34
78
github.com/silenceper/pool v0.0.0-20191105065223-1f4530b6ba17
8-
github.com/sirupsen/logrus v1.4.1
9-
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 // indirect
10-
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
9+
github.com/sirupsen/logrus v1.6.0
10+
golang.org/x/net v0.0.0-20200707034311-ab3426394381
11+
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
1112
)

0 commit comments

Comments
 (0)