Compare commits

..

35 Commits

Author SHA1 Message Date
Dingjun Fang b3bd7c3421
Merge pull request #1 from fangdingjun/dependabot/go_modules/github.com/pires/go-proxyproto-0.6.1
Bump github.com/pires/go-proxyproto from 0.6.0 to 0.6.1
2 years ago
dependabot[bot] c8aac178b8
Bump github.com/pires/go-proxyproto from 0.6.0 to 0.6.1
Bumps [github.com/pires/go-proxyproto](https://github.com/pires/go-proxyproto) from 0.6.0 to 0.6.1.
- [Release notes](https://github.com/pires/go-proxyproto/releases)
- [Commits](https://github.com/pires/go-proxyproto/compare/v0.6.0...v0.6.1)

---
updated-dependencies:
- dependency-name: github.com/pires/go-proxyproto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2 years ago
dingjun f8a6b1bb0a add err check 2 years ago
dingjun 9d3d523309 line ending: crlf->lf 2 years ago
dingjun aad8f7d3e2 fix static check warnings 2 years ago
dingjun 2d4867ee39 getResponseFromUpstream: use timeout in conf 2 years ago
dingjun b5fd23222e update dependence 3 years ago
dingjun 5d56089426 update log library 3 years ago
dingjun 7096afd7d6 update miekg/dns to 1.1.43 3 years ago
dingjun 67ff5ead55 fix crash issue 3 years ago
dingjun f36fdafdd4 update README 3 years ago
dingjun 73a94dbd4e Merge branch 'master' of github.com:fangdingjun/gdns 3 years ago
dingjun 4bfd632bbd support http proxy on DOH 3 years ago
Dingjun Fang 552bda0e98
Update README.md 4 years ago
dingjun 7e84fc502a filter some upstream errors 4 years ago
dingjun 072b83c49d remove unused field 4 years ago
dingjun 4eaa46ff45 not check content-type 4 years ago
dingjun 934f6fed2a use http.Error send error message 4 years ago
dingjun a4258c73de support plain http 5 years ago
dingjun 1e4c6e5c05 update go.mod 5 years ago
dingjun 18f6ec0139 update dependence 6 years ago
dingjun 8bbe643f4d seperate protolistener 6 years ago
dingjun e5010163d1 add proxy proto v2 support 6 years ago
dingjun 6238ed3e39 add go modules file 6 years ago
dingjun 638a66a14c add go module files 6 years ago
dingjun f04444c210 add signal handle 6 years ago
dingjun 560a38afcf new DoH client
miekg/dns dropped DoH support in v1.0.15,
so use my own DoH client
6 years ago
fangdingjun c50ba09949 log library updated 6 years ago
fangdingjun 96df0202b5 define dns message type as constant 6 years ago
fangdingjun 640d3e2018 correct file name man.go->main.go 6 years ago
fangdingjun db0d021927 DoH: add http/1.1 support 6 years ago
fangdingjun 0c196e8267 set DOH content-type 6 years ago
fangdingjun 20c5e4794d query upstream on concurrency 6 years ago
fangdingjun e52286ebc7 update example config file 6 years ago
fangdingjun f54e957d42 use go http2 library 6 years ago

7
.gitignore vendored

@ -22,3 +22,10 @@ _testmain.go
*.exe *.exe
*.test *.test
*.prof *.prof
*~
*.swp
gdns*
*.key
*.crt
client.yaml
server.yaml

@ -23,3 +23,5 @@ use
[dns](https://github.com/miekg/dns) [dns](https://github.com/miekg/dns)
library to parse the dns message library to parse the dns message

@ -0,0 +1,59 @@
package main
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"time"
"github.com/miekg/dns"
)
type httpclient struct {
Net string
Timeout time.Duration
HTTPClient *http.Client
}
func (c *httpclient) Exchange(msg *dns.Msg, upstream string) (*dns.Msg, int, error) {
data, err := msg.Pack()
if err != nil {
return nil, 0, err
}
req, err := http.NewRequest("POST", upstream, bytes.NewReader(data))
if err != nil {
return nil, 0, err
}
req.Header.Set("Content-Type", dnsMsgType)
ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
defer cancel()
req = req.WithContext(ctx)
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, 0, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, 0, fmt.Errorf("http error %d", resp.StatusCode)
}
data, err = io.ReadAll(resp.Body)
if err != nil {
return nil, 0, err
}
m := new(dns.Msg)
if err = m.Unpack(data); err != nil {
return nil, 0, err
}
return m, 0, nil
}

@ -1,7 +1,7 @@
package main package main
import ( import (
"io/ioutil" "os"
"github.com/go-yaml/yaml" "github.com/go-yaml/yaml"
) )
@ -21,7 +21,7 @@ type listen struct {
} }
func loadConfig(f string) (*conf, error) { func loadConfig(f string) (*conf, error) {
data, err := ioutil.ReadFile(f) data, err := os.ReadFile(f)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -9,7 +9,7 @@ upstream_servers:
# bootstrap dns server, # bootstrap dns server,
# this used to resolve the domain name used in Doh and TLS, # this used to resolve the domain name used in Doh and TLS,
# not needed if you don't use DoH and tls # not needed if you don't use DoH and tls
bootstrap_severs: bootstrap_servers:
- tcp://1.0.0.1:53 - tcp://1.0.0.1:53
- udp://1.0.0.1:53 - udp://1.0.0.1:53
@ -37,4 +37,4 @@ upstream_timeout: 3
# check upstream certificate or not # check upstream certificate or not
# if you use self signed certificate, set this to true # if you use self signed certificate, set this to true
upstream_insecure: true upstream_insecure: false

@ -0,0 +1,22 @@
module github.com/fangdingjun/gdns
go 1.17
require (
github.com/fangdingjun/go-log/v5 v5.0.0
github.com/fangdingjun/protolistener v0.0.0-20210804081554-626e6590d6e7
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/miekg/dns v1.1.43
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985
)
require (
github.com/kr/pretty v0.3.0 // indirect
github.com/pires/go-proxyproto v0.6.1 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
golang.org/x/text v0.3.6 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

@ -0,0 +1,51 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/fangdingjun/go-log/v5 v5.0.0 h1:vdh9Bk9C4ZFL6KoO6rII73zQIyaLf7hFdBvucO/ckiE=
github.com/fangdingjun/go-log/v5 v5.0.0/go.mod h1:V012Oxo0/pSbccX4OFSp9MJglXwNsZo2ByBBorr7zzM=
github.com/fangdingjun/protolistener v0.0.0-20210804081554-626e6590d6e7 h1:z5NlLvUNbGZxQhtUZELvIorAqpDmcIhGQXE5GdO7+5I=
github.com/fangdingjun/protolistener v0.0.0-20210804081554-626e6590d6e7/go.mod h1:ljbjhI4fVrT5GwMu1iBhWTZwLJSqsXKwKlGKas5GudM=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

@ -4,12 +4,14 @@ import (
"flag" "flag"
"fmt" "fmt"
"os" "os"
"os/signal"
"syscall"
"github.com/fangdingjun/go-log" log "github.com/fangdingjun/go-log/v5"
"github.com/fangdingjun/go-log/formatters"
"github.com/fangdingjun/go-log/writers"
) )
var cfg *conf
func main() { func main() {
var configfile string var configfile string
var logFileCount int var logFileCount int
@ -22,11 +24,11 @@ func main() {
flag.Int64Var(&logFileSize, "log_size", 10, "max log file size MB") flag.Int64Var(&logFileSize, "log_size", 10, "max log file size MB")
flag.StringVar(&loglevel, "log_level", "INFO", flag.StringVar(&loglevel, "log_level", "INFO",
"log level, values:\nOFF, FATAL, PANIC, ERROR, WARN, INFO, DEBUG") "log level, values:\nOFF, FATAL, PANIC, ERROR, WARN, INFO, DEBUG")
flag.StringVar(&configfile, "c", "", "config file") flag.StringVar(&configfile, "c", "gdns.yaml", "config file")
flag.Parse() flag.Parse()
if logfile != "" { if logfile != "" {
log.Default.Out = &writers.FixedSizeFileWriter{ log.Default.Out = &log.FixedSizeFileWriter{
MaxCount: logFileCount, MaxCount: logFileCount,
Name: logfile, Name: logfile,
MaxSize: logFileSize * 1024 * 1024, MaxSize: logFileSize * 1024 * 1024,
@ -42,18 +44,24 @@ func main() {
log.Default.Level = lv log.Default.Level = lv
} }
log.Default.Formatter = &formatters.TextFormatter{ _cfg, err := loadConfig(configfile)
TimeFormat: "2006-01-02 15:04:05.000"}
cfg, err := loadConfig(configfile)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
cfg = _cfg
if cfg.UpstreamTimeout == 0 { if cfg.UpstreamTimeout == 0 {
cfg.UpstreamTimeout = 5 cfg.UpstreamTimeout = 5
} }
initDNSClient(cfg) initDNSClient(cfg)
log.Debugf("%+v", cfg) log.Debugf("%+v", cfg)
makeServers(cfg) makeServers(cfg)
select {} ch := make(chan os.Signal, 2)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
s := <-ch
log.Errorf("received signal %s, exit...", s)
log.Println("exit.")
} }

@ -0,0 +1,56 @@
package main
import (
"net/http"
log "github.com/fangdingjun/go-log/v5"
)
type logHandler struct {
status int
w http.ResponseWriter
size int
}
func (lh *logHandler) WriteHeader(status int) {
lh.status = status
lh.w.WriteHeader(status)
}
func (lh *logHandler) Write(buf []byte) (int, error) {
lh.size += len(buf)
return lh.w.Write(buf)
}
func (lh *logHandler) Header() http.Header {
return lh.w.Header()
}
func (lh *logHandler) Status() int {
if lh.status != 0 {
return lh.status
}
return 200
}
var _ http.ResponseWriter = &logHandler{}
// LogHandler is a log middleware
// record request log
func LogHandler(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Error(err)
w.WriteHeader(http.StatusInternalServerError)
log.Infof("\"%s - %s %s %s\" - %d %d \"%s\"",
r.RemoteAddr, r.Method, r.RequestURI, r.Proto, 500, 0, r.UserAgent())
}
}()
lh := &logHandler{w: w}
handler.ServeHTTP(lh, r)
log.Infof("\"%s - %s %s %s\" - %d %d \"%s\"",
r.RemoteAddr, r.Method, r.RequestURI, r.Proto, lh.Status(), lh.size, r.UserAgent())
})
}

@ -2,11 +2,14 @@ package main
import ( import (
"crypto/tls" "crypto/tls"
"fmt"
"net" "net"
"net/http"
"net/url" "net/url"
"strconv" "strconv"
"github.com/fangdingjun/go-log" log "github.com/fangdingjun/go-log/v5"
"github.com/fangdingjun/protolistener"
) )
type server struct { type server struct {
@ -14,7 +17,6 @@ type server struct {
cert string cert string
key string key string
upstreams []*url.URL upstreams []*url.URL
bootstrap []*url.URL
} }
func (srv *server) serve() { func (srv *server) serve() {
@ -25,8 +27,10 @@ func (srv *server) serve() {
srv.serveTCP() srv.serveTCP()
case "tls": case "tls":
srv.serveTLS() srv.serveTLS()
case "http":
srv.serveHTTP()
case "https": case "https":
srv.serveHTTPS() srv.serveHTTP()
default: default:
log.Fatalf("unsupported type %s", srv.addr.Scheme) log.Fatalf("unsupported type %s", srv.addr.Scheme)
} }
@ -79,18 +83,21 @@ func (srv *server) serveTLS() {
if err != nil { if err != nil {
log.Fatalln("load certificate failed", err) log.Fatalln("load certificate failed", err)
} }
l, err := tls.Listen("tcp", srv.addr.Host,
&tls.Config{ l, err := net.Listen("tcp", srv.addr.Host)
Certificates: []tls.Certificate{cert},
//NextProtos: []string{"h2"},
})
if err != nil { if err != nil {
log.Fatalln("listen tls", err) log.Fatalln("listen tls", err)
} }
defer l.Close() defer l.Close()
log.Debugf("listen tls://%s", l.Addr().String()) log.Debugf("listen tls://%s", l.Addr().String())
tl := tls.NewListener(protolistener.New(l), &tls.Config{
Certificates: []tls.Certificate{cert},
//NextProtos: []string{"h2"},
})
for { for {
conn, err := l.Accept() conn, err := tl.Accept()
if err != nil { if err != nil {
log.Debugln("tls accept", err) log.Debugln("tls accept", err)
break break
@ -99,34 +106,34 @@ func (srv *server) serveTLS() {
} }
} }
func (srv *server) serveHTTPS() { func (srv *server) serveHTTP() {
cert, err := tls.LoadX509KeyPair(srv.cert, srv.key) log.Debugf("listen %s://%s", srv.addr.Scheme, srv.addr.Host)
if err != nil {
log.Fatalln("load certificate failed", err) l, err := net.Listen("tcp", srv.addr.Host)
}
l, err := tls.Listen("tcp", srv.addr.Host,
&tls.Config{
Certificates: []tls.Certificate{cert},
NextProtos: []string{"h2"},
})
if err != nil { if err != nil {
log.Fatalln("listen https", err) log.Fatalln("listen https", err)
} }
defer l.Close() defer l.Close()
log.Debugf("listen https://%s", l.Addr().String())
for { httpsrv := &http.Server{
conn, err := l.Accept() Handler: LogHandler(srv),
if err != nil { }
log.Debugln("https accept", err) switch srv.addr.Scheme {
break case "http":
err = httpsrv.Serve(protolistener.New(l))
case "https":
err = httpsrv.ServeTLS(protolistener.New(l), srv.cert, srv.key)
default:
err = fmt.Errorf("not supported %s", srv.addr.Scheme)
} }
go srv.handleHTTPSConn(conn) if err != nil {
log.Fatal(err)
} }
} }
func makeServers(c *conf) { func makeServers(c *conf) {
upstreams := []*url.URL{} upstreams := []*url.URL{}
bootstraps := []*url.URL{}
for _, a := range c.UpstreamServers { for _, a := range c.UpstreamServers {
u, err := url.Parse(a) u, err := url.Parse(a)
if err != nil { if err != nil {
@ -135,14 +142,6 @@ func makeServers(c *conf) {
upstreams = append(upstreams, u) upstreams = append(upstreams, u)
} }
for _, a := range c.BootstrapServers {
u, err := url.Parse(a)
if err != nil {
log.Fatal(err)
}
bootstraps = append(bootstraps, u)
}
for _, l := range c.Listen { for _, l := range c.Listen {
u, err := url.Parse(l.Addr) u, err := url.Parse(l.Addr)
if err != nil { if err != nil {
@ -153,7 +152,6 @@ func makeServers(c *conf) {
cert: l.Cert, cert: l.Cert,
key: l.Key, key: l.Key,
upstreams: upstreams, upstreams: upstreams,
bootstrap: bootstraps,
} }
go srv.serve() go srv.serve()
} }

@ -1,83 +1,66 @@
package main package main
import ( import (
"crypto/tls" "io"
"io/ioutil"
"net"
"net/http" "net/http"
"github.com/fangdingjun/go-log" log "github.com/fangdingjun/go-log/v5"
"github.com/fangdingjun/nghttp2-go"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func (srv *server) handleHTTPSConn(c net.Conn) { const dnsMsgType = "application/dns-message"
defer c.Close()
tlsconn := c.(*tls.Conn)
if err := tlsconn.Handshake(); err != nil {
log.Errorln("handshake", err)
return
}
state := tlsconn.ConnectionState()
if state.NegotiatedProtocol != "h2" {
log.Errorln("http2 is needed")
return
}
h2conn, err := nghttp2.Server(tlsconn, srv)
if err != nil {
log.Errorf("create http2 error: %s", err)
return
}
h2conn.Run()
}
func (srv *server) handleHTTP2Req(w http.ResponseWriter, r *http.Request) { func (srv *server) handleHTTPReq(w http.ResponseWriter, r *http.Request) {
/*
ctype := r.Header.Get("content-type") ctype := r.Header.Get("content-type")
if ctype != "application/dns-message" { if !strings.HasPrefix(ctype, dnsMsgType) {
log.Errorf("request type %s, require %s", ctype, dnsMsgType)
http.Error(w, "dns message is required", http.StatusBadRequest) http.Error(w, "dns message is required", http.StatusBadRequest)
return return
} }
*/
if r.ContentLength < 10 {
log.Errorf("message is too small, %v", r.ContentLength)
http.Error(w, "message is too small", http.StatusBadRequest)
return
}
data, err := ioutil.ReadAll(r.Body) data, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
log.Errorln("read request body", err) log.Errorln("read request body", err)
w.WriteHeader(http.StatusBadRequest) http.Error(w, "read request failed", http.StatusBadRequest)
return return
} }
msg := new(dns.Msg) msg := new(dns.Msg)
if err := msg.Unpack(data); err != nil { if err := msg.Unpack(data); err != nil {
log.Errorln("parse dns message", err) log.Errorln("parse dns message", err)
http.Error(w, "parse dns message error", http.StatusBadRequest)
return return
} }
reply := false
for _, up := range srv.upstreams { m, err := getResponseFromUpstream(msg, srv.upstreams)
log.Debugf("from %s query upstream %s", r.RemoteAddr, up.String()) if err != nil {
log.Debugln("query", msg.Question[0].String()) log.Debugln("query", msg.Question[0].String(), "timeout")
m, err := queryUpstream(msg, up) http.Error(w, "query upstream server failed", http.StatusServiceUnavailable)
if err == nil { return
w.Header().Set("content-type", "application/dns-message") }
w.WriteHeader(http.StatusOK)
for _, a := range m.Answer { for _, a := range m.Answer {
log.Debugln("result", a.String()) log.Debugln("result", a.String())
} }
w.Header().Set("content-type", dnsMsgType)
d, _ := m.Pack() d, _ := m.Pack()
w.Write(d) if _, err := w.Write(d); err != nil {
reply = true log.Errorln(err)
break
} else {
log.Errorf("https query upstream %s", err)
}
}
if !reply {
w.WriteHeader(http.StatusServiceUnavailable)
} }
} }
func (srv *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (srv *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.RequestURI != srv.addr.Path { if r.RequestURI != srv.addr.Path {
w.WriteHeader(http.StatusNotFound) http.Error(w, "Path not found", http.StatusNotFound)
return return
} }
srv.handleHTTP2Req(w, r) srv.handleHTTPReq(w, r)
} }

@ -3,7 +3,7 @@ package main
import ( import (
"net" "net"
"github.com/fangdingjun/go-log" log "github.com/fangdingjun/go-log/v5"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@ -17,23 +17,18 @@ func (srv *server) handleTCP(c net.Conn) {
log.Debugln("tcp read message", err) log.Debugln("tcp read message", err)
break break
} }
reply := false
for _, up := range srv.upstreams { m, err := getResponseFromUpstream(msg, srv.upstreams)
log.Debugf("from %s query upstream %s", conn.RemoteAddr(), up.String()) if err != nil {
log.Debugln("query", msg.Question[0].String()) log.Debugln("query", msg.Question[0].String(), "timeout")
m, err := queryUpstream(msg, up)
if err == nil {
for _, a := range m.Answer {
log.Debugln("result", a.String())
}
log.Debugln("got reply", m.String())
conn.WriteMsg(m)
reply = true
break break
} }
log.Debugln("query upstream", up.String(), err)
for _, a := range m.Answer {
log.Debugln("result", a.String())
} }
if !reply { if err := conn.WriteMsg(m); err != nil {
log.Errorln(err)
break break
} }
} }

@ -3,7 +3,7 @@ package main
import ( import (
"net" "net"
"github.com/fangdingjun/go-log" log "github.com/fangdingjun/go-log/v5"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@ -13,19 +13,18 @@ func (srv *server) handleUDP(buf []byte, addr net.Addr, conn *net.UDPConn) {
log.Debugln("udp parse msg", err) log.Debugln("udp parse msg", err)
return return
} }
for _, up := range srv.upstreams {
log.Debugf("from %s query upstream %s", addr, up.String()) m, err := getResponseFromUpstream(msg, srv.upstreams)
log.Debugln("query", msg.Question[0].String()) if err != nil {
m, err := queryUpstream(msg, up) log.Debugln("query", msg.Question[0].String(), "timeout")
if err == nil { return
}
for _, a := range m.Answer { for _, a := range m.Answer {
log.Debugln("result", a.String()) log.Debugln("result", a.String())
} }
d, _ := m.Pack() d, _ := m.Pack()
conn.WriteTo(d, addr) if _, err := conn.WriteTo(d, addr); err != nil {
break log.Errorln(err)
} else {
log.Debugln("udp query upstream err", err)
}
} }
} }

@ -1,23 +1,63 @@
package main package main
import ( import (
"bufio"
"context" "context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"os"
"strings"
"time" "time"
"github.com/fangdingjun/go-log" log "github.com/fangdingjun/go-log/v5"
nghttp2 "github.com/fangdingjun/nghttp2-go"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/net/http2"
) )
var dnsClientTCP *dns.Client var dnsClientTCP *dns.Client
var dnsClientHTTPS *dns.Client
var dnsClientUDP *dns.Client var dnsClientUDP *dns.Client
var dnsClientTLS *dns.Client var dnsClientTLS *dns.Client
var dnsClientHTTPS *httpclient
func getResponseFromUpstream(msg *dns.Msg, upstreams []*url.URL) (*dns.Msg, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(cfg.UpstreamTimeout+1)*time.Second)
defer cancel()
resch := make(chan *dns.Msg, len(upstreams))
for _, up := range upstreams {
go func(u *url.URL) {
m, err := queryUpstream(msg, u)
if err == nil {
resch <- m
return
}
log.Errorln(u.String(), err)
}(up)
}
var errmsg *dns.Msg
for i := 0; i < len(upstreams); i++ {
select {
case <-ctx.Done():
return nil, errors.New("time out")
case m := <-resch:
if m.MsgHdr.Rcode == dns.RcodeSuccess {
return m, nil
}
errmsg = m
}
}
if errmsg != nil {
return errmsg, nil
}
return nil, errors.New("empty result")
}
func queryUpstream(msg *dns.Msg, upstream *url.URL) (*dns.Msg, error) { func queryUpstream(msg *dns.Msg, upstream *url.URL) (*dns.Msg, error) {
switch upstream.Scheme { switch upstream.Scheme {
@ -104,17 +144,54 @@ func initDNSClient(c *conf) {
Net: "tcp", Net: "tcp",
Timeout: time.Duration(c.UpstreamTimeout) * time.Second, Timeout: time.Duration(c.UpstreamTimeout) * time.Second,
} }
dnsClientHTTPS = &dns.Client{ dnsClientHTTPS = &httpclient{
Net: "https", Net: "https",
Timeout: time.Duration(c.UpstreamTimeout) * time.Second, Timeout: time.Duration(c.UpstreamTimeout) * time.Second,
HTTPClient: &http.Client{ HTTPClient: &http.Client{
Transport: &nghttp2.Transport{ Transport: &http2.Transport{
DialTLS: func(network, addr string, cfg *tls.Config) (*tls.Conn, error) { DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
log.Debugln("dial to", network, addr) log.Debugln("dial to", network, addr)
p := os.Getenv("https_proxy")
if p == "" {
p = os.Getenv("http_proxy")
}
if p == "" {
conn, err := tls.DialWithDialer(dialer, network, addr, cfg) conn, err := tls.DialWithDialer(dialer, network, addr, cfg)
return conn, err return conn, err
}
u, _ := url.Parse(p)
log.Debugf("dial to proxy %s", u.Host)
conn, err := net.Dial(network, u.Host)
if err != nil {
return nil, err
}
fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, addr)
r := bufio.NewReader(conn)
line, err := r.ReadString('\n')
if err != nil {
return nil, err
}
ss := strings.Fields(line)
if ss[1] != "200" {
return nil, fmt.Errorf("http code %s", ss[1])
}
for {
line, err := r.ReadString('\n')
if err != nil {
return nil, err
}
line = strings.Trim(line, "\r\n")
if line == "" {
break
}
}
tlsconn := tls.Client(conn, cfg)
if err = tlsconn.Handshake(); err != nil {
return nil, err
}
return tlsconn, nil
}, },
TLSConfig: &tls.Config{ TLSClientConfig: &tls.Config{
InsecureSkipVerify: c.UpstreamInsecure, InsecureSkipVerify: c.UpstreamInsecure,
NextProtos: []string{"h2"}, NextProtos: []string{"h2"},
}, },

Loading…
Cancel
Save