Skip to content

Commit

Permalink
Add dns over http server feature
Browse files Browse the repository at this point in the history
  • Loading branch information
shawn1m committed Dec 2, 2020
1 parent 5912121 commit 20de808
Show file tree
Hide file tree
Showing 11 changed files with 783 additions and 31 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ Configuration file is "config.json" by default:
{
"BindAddress": ":53",
"DebugHTTPAddress": "127.0.0.1:5555",
"DohEnabled": false,
"PrimaryDNS": [
{
"Name": "DNSPod",
Expand Down Expand Up @@ -188,7 +189,7 @@ IPv6). Overture will handle both TCP and UDP requests. Literal IPv6 addresses ar
}
}
```

+ 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.
+ DNS: You can specify multiple DNS upstream servers here.
+ Name: This field is only used for logging.
+ Address: Same as BindAddress.
Expand Down
1 change: 1 addition & 0 deletions config.sample.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"BindAddress": ":53",
"DebugHTTPAddress": "127.0.0.1:5555",
"DohEnabled": false,
"PrimaryDNS": [
{
"Name": "DNSPod",
Expand Down
1 change: 1 addition & 0 deletions config.test.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"BindAddress": ":53",
"DebugHTTPAddress": "127.0.0.1:5555",
"DohEnabled": true,
"PrimaryDNS": [
{
"Name": "DNSPod",
Expand Down
33 changes: 22 additions & 11 deletions core/common/edns.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,30 @@ func SetEDNSClientSubnet(m *dns.Msg, ip string, isNoCookie bool) {
}

es := IsEDNSClientSubnet(o)
if es == nil {
es = new(dns.EDNS0_SUBNET)
es.Code = dns.EDNS0SUBNET
es.Address = net.ParseIP(ip)
if es.Address.To4() != nil {
es.Family = 1 // 1 for IPv4 source address, 2 for IPv6
es.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6
if es == nil || es.Address.IsUnspecified() {
nes := new(dns.EDNS0_SUBNET)
nes.Code = dns.EDNS0SUBNET
nes.Address = net.ParseIP(ip)
if nes.Address.To4() != nil {
nes.Family = 1 // 1 for IPv4 source address, 2 for IPv6
nes.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6
} else {
es.Family = 2 // 1 for IPv4 source address, 2 for IPv6
es.SourceNetmask = 128 // 32 for IPV4, 128 for IPv6
nes.Family = 2 // 1 for IPv4 source address, 2 for IPv6
nes.SourceNetmask = 128 // 32 for IPV4, 128 for IPv6
}
es.SourceScope = 0
o.Option = append(o.Option, es)
nes.SourceScope = 0
if es != nil && es.Address.IsUnspecified() {
var edns0 []dns.EDNS0
for _, s := range o.Option {
switch e := s.(type) {
case *dns.EDNS0_SUBNET:
default:
edns0 = append(edns0, e)
}
}
o.Option = edns0
}
o.Option = append(o.Option, nes)
if isNoCookie {
deleteCookie(o)
}
Expand Down
1 change: 1 addition & 0 deletions core/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Config struct {
FilePath string
BindAddress string
DebugHTTPAddress string
DohEnabled bool
PrimaryDNS []*common.DNSUpstream
AlternativeDNS []*common.DNSUpstream
OnlyPrimaryDNS bool
Expand Down
2 changes: 1 addition & 1 deletion core/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func Start() {
}
dispatcher.Init()

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

go srv.Run()
Expand Down
62 changes: 61 additions & 1 deletion core/inbound/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package inbound
import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
Expand All @@ -12,8 +13,13 @@ import (
"strconv"
"strings"
"sync"
"time"

"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/plugin/pkg/doh"
"github.com/coredns/coredns/plugin/pkg/response"
"github.com/miekg/dns"
"github.com/shawn1m/overture/core/common"
log "github.com/sirupsen/logrus"

"github.com/shawn1m/overture/core/outbound"
Expand All @@ -27,20 +33,70 @@ type Server struct {
HTTPMux *http.ServeMux
ctx context.Context
cancel context.CancelFunc
dohEnabled bool
}

func NewServer(bindAddress string, debugHTTPAddress string, dispatcher outbound.Dispatcher, rejectQType []uint16) *Server {
func NewServer(bindAddress string, debugHTTPAddress string, dispatcher outbound.Dispatcher, rejectQType []uint16, dohEnabled bool) *Server {
s := &Server{
bindAddress: bindAddress,
debugHttpAddress: debugHTTPAddress,
dispatcher: dispatcher,
rejectQType: rejectQType,
dohEnabled: dohEnabled,
}
s.ctx, s.cancel = context.WithCancel(context.Background())
s.HTTPMux = http.NewServeMux()
return s
}

func (s *Server) ServeDNSHttp(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != doh.Path {
http.Error(w, "", http.StatusNotFound)
return
}

q, err := doh.RequestToMsg(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// Create a DoHWriter with the correct addresses in it.
inboundIP, _, _ := net.SplitHostPort(r.RemoteAddr)
forwardIP := r.Header.Get("X-Forwarded-For")
if net.ParseIP(forwardIP) != nil && common.ReservedIPNetworkList.Contains(net.ParseIP(inboundIP), false, "") {
inboundIP = forwardIP
}
log.Debugf("Question from %s: %s", inboundIP, q.Question[0].String())

for _, qt := range s.rejectQType {
if isQuestionType(q, qt) {
log.Debugf("Reject %s: %s", inboundIP, q.Question[0].String())
http.Error(w, "Rejected", http.StatusForbidden)
return
}
}

responseMessage := s.dispatcher.Exchange(q, inboundIP)

if responseMessage == nil {
http.Error(w, "No response", http.StatusInternalServerError)
return
}

buf, _ := responseMessage.Pack()

mt, _ := response.Typify(responseMessage, time.Now().UTC())
age := dnsutil.MinimalTTL(responseMessage, mt)

w.Header().Set("Content-Type", doh.MimeType)
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%f", age.Seconds()))
w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
w.WriteHeader(http.StatusOK)

w.Write(buf)
}

func (s *Server) DumpCache(w http.ResponseWriter, req *http.Request) {
if s.dispatcher.Cache == nil {
io.WriteString(w, "error: cache not enabled")
Expand Down Expand Up @@ -136,6 +192,10 @@ func (s *Server) Run() {
s.HTTPMux.HandleFunc("/debug/pprof/profile", pprof.Profile)
s.HTTPMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
s.HTTPMux.HandleFunc("/debug/pprof/trace", pprof.Trace)
if s.dohEnabled {
log.Info("Dns over http server started!")
s.HTTPMux.HandleFunc(doh.Path, s.ServeDNSHttp)
}

wg.Add(1)
go func() {
Expand Down
10 changes: 5 additions & 5 deletions core/matcher/mix/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ func (s *List) Has(str string) bool {
case "domain":
idx := len(str) - len(data.Content)
if idx >= 0 && data.Content == str[idx:] {
if idx >=1 && (str[idx-1] != '.') {
return false
}
return true
}
if idx >= 1 && (str[idx-1] != '.') {
return false
}
return true
}
case "regex":
reg := regexp.MustCompile(data.Content)
if reg.MatchString(str) {
Expand Down
2 changes: 2 additions & 0 deletions core/outbound/clients/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ func (c *RemoteClient) ExchangeFromCache() *dns.Msg {
func (c *RemoteClient) Exchange(isLog bool) *dns.Msg {
common.SetEDNSClientSubnet(c.questionMessage, c.ednsClientSubnetIP,
c.dnsUpstream.EDNSClientSubnet.NoCookie)
log.Debugf("Use " + c.ednsClientSubnetIP + " as original ednsClientSubnetIP")
c.ednsClientSubnetIP = common.GetEDNSClientSubnetIP(c.questionMessage)
log.Debugf("Use " + c.ednsClientSubnetIP + " as ednsClientSubnetIP")

if c.responseMessage != nil {
return c.responseMessage
Expand Down
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ module github.com/shawn1m/overture
go 1.12

require (
github.com/miekg/dns v1.1.8
github.com/coredns/coredns v1.8.0
github.com/miekg/dns v1.1.34
github.com/silenceper/pool v0.0.0-20191105065223-1f4530b6ba17
github.com/sirupsen/logrus v1.4.1
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 // indirect
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
github.com/sirupsen/logrus v1.6.0
golang.org/x/net v0.0.0-20200707034311-ab3426394381
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
)
Loading

0 comments on commit 20de808

Please sign in to comment.