From 8600ee7c5d3596254ab4eb7dbc820c950f68bfd4 Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Tue, 21 Dec 2021 21:54:33 +0800 Subject: [PATCH] add dns listener --- cmd/gost/register.go | 1 + go.mod | 6 +- go.sum | 7 ++ pkg/listener/dns/listener.go | 205 +++++++++++++++++++++++++++++++++++ pkg/listener/dns/metadata.go | 53 +++++++++ pkg/listener/dns/server.go | 108 ++++++++++++++++++ 6 files changed, 377 insertions(+), 3 deletions(-) create mode 100644 pkg/listener/dns/listener.go create mode 100644 pkg/listener/dns/metadata.go create mode 100644 pkg/listener/dns/server.go diff --git a/cmd/gost/register.go b/cmd/gost/register.go index d0e54c1..764e55b 100644 --- a/cmd/gost/register.go +++ b/cmd/gost/register.go @@ -48,6 +48,7 @@ import ( _ "github.com/go-gost/gost/pkg/handler/tun" // Register listeners + _ "github.com/go-gost/gost/pkg/listener/dns" _ "github.com/go-gost/gost/pkg/listener/ftcp" _ "github.com/go-gost/gost/pkg/listener/http2" _ "github.com/go-gost/gost/pkg/listener/http2/h2" diff --git a/go.mod b/go.mod index 615fee0..634b54e 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/cheekybits/genny v1.0.0 // indirect github.com/coreos/go-iptables v0.5.0 // indirect + github.com/docker/libcontainer v2.2.1+incompatible github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/go-gost/gosocks4 v0.0.1 github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09 @@ -25,6 +26,7 @@ require ( github.com/magiconair/properties v1.8.5 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect + github.com/miekg/dns v1.1.44 github.com/milosgajdos/tenus v0.0.3 github.com/mitchellh/mapstructure v1.4.2 // indirect github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 // indirect @@ -54,11 +56,9 @@ require ( golang.org/x/net v0.0.0-20211209124913-491a49abca63 golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect golang.org/x/text v0.3.6 // indirect - golang.org/x/tools v0.1.5 // indirect + golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/ini.v1 v1.63.2 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 ) - -require github.com/docker/libcontainer v2.2.1+incompatible // indirect diff --git a/go.sum b/go.sum index a4d660d..e09a15e 100644 --- a/go.sum +++ b/go.sum @@ -281,7 +281,10 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.44 h1:4rpqcegYPVkvIeVhITrKP1sRR3KjfRc1nrOPMUZmLyc= +github.com/miekg/dns v1.1.44/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/milosgajdos/tenus v0.0.3 h1:jmaJzwaY1DUyYVD0lM4U+uvP2kkEg1VahDqRFxIkVBE= github.com/milosgajdos/tenus v0.0.3/go.mod h1:eIjx29vNeDOYWJuCnaHY2r4fq5egetV26ry3on7p8qY= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= @@ -529,6 +532,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -560,6 +564,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -702,6 +707,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/listener/dns/listener.go b/pkg/listener/dns/listener.go new file mode 100644 index 0000000..912bcf8 --- /dev/null +++ b/pkg/listener/dns/listener.go @@ -0,0 +1,205 @@ +package dns + +import ( + "bytes" + "encoding/base64" + "errors" + "io/ioutil" + "net" + "net/http" + "strings" + + "github.com/go-gost/gost/pkg/listener" + "github.com/go-gost/gost/pkg/logger" + md "github.com/go-gost/gost/pkg/metadata" + "github.com/go-gost/gost/pkg/registry" + "github.com/miekg/dns" +) + +func init() { + registry.RegisterListener("dns", NewListener) +} + +type dnsListener struct { + saddr string + addr net.Addr + server Server + cqueue chan net.Conn + errChan chan error + logger logger.Logger + md metadata +} + +func NewListener(opts ...listener.Option) listener.Listener { + options := &listener.Options{} + for _, opt := range opts { + opt(options) + } + return &dnsListener{ + saddr: options.Addr, + logger: options.Logger, + } +} + +func (l *dnsListener) Init(md md.Metadata) (err error) { + if err = l.parseMetadata(md); err != nil { + return + } + + l.addr, err = net.ResolveTCPAddr("tcp", l.saddr) + if err != nil { + return err + } + + switch strings.ToLower(l.md.mode) { + case "tcp": + l.server = &dns.Server{ + Net: "tcp", + Addr: l.saddr, + Handler: l, + ReadTimeout: l.md.readTimeout, + WriteTimeout: l.md.writeTimeout, + } + case "tls": + l.server = &dns.Server{ + Net: "tcp-tls", + Addr: l.saddr, + Handler: l, + TLSConfig: l.md.tlsConfig, + ReadTimeout: l.md.readTimeout, + WriteTimeout: l.md.writeTimeout, + } + case "https": + l.server = &dohServer{ + addr: l.saddr, + tlsConfig: l.md.tlsConfig, + server: &http.Server{ + Handler: l, + ReadTimeout: l.md.readTimeout, + WriteTimeout: l.md.writeTimeout, + }, + } + default: + l.addr, err = net.ResolveUDPAddr("udp", l.saddr) + l.server = &dns.Server{ + Net: "udp", + Addr: l.saddr, + Handler: l, + UDPSize: l.md.readBufferSize, + ReadTimeout: l.md.readTimeout, + WriteTimeout: l.md.writeTimeout, + } + } + + if err != nil { + return + } + + l.cqueue = make(chan net.Conn, l.md.backlog) + l.errChan = make(chan error, 1) + + go func() { + err := l.server.ListenAndServe() + if err != nil { + l.errChan <- err + } + close(l.errChan) + }() + return +} + +func (l *dnsListener) Accept() (conn net.Conn, err error) { + var ok bool + select { + case conn = <-l.cqueue: + case err, ok = <-l.errChan: + if !ok { + err = listener.ErrClosed + } + } + return +} + +func (l *dnsListener) Close() error { + return l.server.Shutdown() +} + +func (l *dnsListener) Addr() net.Addr { + return l.addr +} + +func (l *dnsListener) ServeDNS(w dns.ResponseWriter, m *dns.Msg) { + b, err := m.Pack() + if err != nil { + l.logger.Error(err) + return + } + if err := l.serve(w, b); err != nil { + l.logger.Error(err) + } +} + +// Based on https://github.com/semihalev/sdns +func (l *dnsListener) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var buf []byte + var err error + switch r.Method { + case http.MethodGet: + buf, err = base64.RawURLEncoding.DecodeString(r.URL.Query().Get("dns")) + if len(buf) == 0 || err != nil { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + case http.MethodPost: + if ct := r.Header.Get("Content-Type"); ct != "application/dns-message" { + l.logger.Errorf("unsupported media type: %s", ct) + http.Error(w, http.StatusText(http.StatusUnsupportedMediaType), http.StatusUnsupportedMediaType) + return + } + + buf, err = ioutil.ReadAll(r.Body) + if err != nil { + l.logger.Error(err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + default: + l.logger.Errorf("method not allowd: %s", r.Method) + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + + mq := &dns.Msg{} + if err := mq.Unpack(buf); err != nil { + l.logger.Error(err) + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + + w.Header().Set("Server", "SDNS") + w.Header().Set("Content-Type", "application/dns-message") + + raddr, _ := net.ResolveTCPAddr("tcp", r.RemoteAddr) + if err := l.serve(&dohResponseWriter{raddr: raddr, ResponseWriter: w}, buf); err != nil { + l.logger.Error(err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } +} + +func (l *dnsListener) serve(w ResponseWriter, msg []byte) (err error) { + conn := &serverConn{ + r: bytes.NewReader(msg), + w: w, + laddr: l.addr, + closed: make(chan struct{}), + } + + select { + case l.cqueue <- conn: + default: + l.logger.Warnf("connection queue is full, client %s discarded", w.RemoteAddr()) + return errors.New("connection queue is full") + } + + return conn.Wait() +} diff --git a/pkg/listener/dns/metadata.go b/pkg/listener/dns/metadata.go new file mode 100644 index 0000000..4269924 --- /dev/null +++ b/pkg/listener/dns/metadata.go @@ -0,0 +1,53 @@ +package dns + +import ( + "crypto/tls" + "time" + + tls_util "github.com/go-gost/gost/pkg/common/util/tls" + mdata "github.com/go-gost/gost/pkg/metadata" +) + +const ( + defaultBacklog = 128 +) + +type metadata struct { + mode string + readBufferSize int + readTimeout time.Duration + writeTimeout time.Duration + tlsConfig *tls.Config + backlog int +} + +func (l *dnsListener) parseMetadata(md mdata.Metadata) (err error) { + const ( + mode = "mode" + readBufferSize = "readBufferSize" + + certFile = "certFile" + keyFile = "keyFile" + caFile = "caFile" + + backlog = "backlog" + ) + + l.md.mode = mdata.GetString(md, mode) + l.md.readBufferSize = mdata.GetInt(md, readBufferSize) + + l.md.tlsConfig, err = tls_util.LoadServerConfig( + mdata.GetString(md, certFile), + mdata.GetString(md, keyFile), + mdata.GetString(md, caFile), + ) + if err != nil { + return + } + l.md.backlog = mdata.GetInt(md, backlog) + if l.md.backlog <= 0 { + l.md.backlog = defaultBacklog + } + + return +} diff --git a/pkg/listener/dns/server.go b/pkg/listener/dns/server.go new file mode 100644 index 0000000..7b62891 --- /dev/null +++ b/pkg/listener/dns/server.go @@ -0,0 +1,108 @@ +package dns + +import ( + "context" + "crypto/tls" + "errors" + "io" + "net" + "net/http" + "time" +) + +type Server interface { + ListenAndServe() error + Shutdown() error +} + +type dohServer struct { + addr string + tlsConfig *tls.Config + server *http.Server +} + +func (s *dohServer) ListenAndServe() error { + ln, err := net.Listen("tcp", s.addr) + if err != nil { + return err + } + ln = tls.NewListener(ln, s.tlsConfig) + return s.server.Serve(ln) +} + +func (s *dohServer) Shutdown() error { + return s.server.Shutdown(context.Background()) +} + +type ResponseWriter interface { + io.Writer + RemoteAddr() net.Addr +} + +type dohResponseWriter struct { + raddr net.Addr + http.ResponseWriter +} + +func (w *dohResponseWriter) RemoteAddr() net.Addr { + return w.raddr +} + +type serverConn struct { + r io.Reader + w ResponseWriter + laddr net.Addr + closed chan struct{} +} + +func (c *serverConn) Read(b []byte) (n int, err error) { + select { + case <-c.closed: + err = io.ErrClosedPipe + return + } + return c.r.Read(b) +} + +func (c *serverConn) Write(b []byte) (n int, err error) { + select { + case <-c.closed: + err = io.ErrClosedPipe + return + } + return c.w.Write(b) +} + +func (c *serverConn) Close() error { + select { + case <-c.closed: + default: + close(c.closed) + } + return nil +} + +func (c *serverConn) Wait() error { + <-c.closed + return nil +} + +func (c *serverConn) LocalAddr() net.Addr { + return c.laddr +} + +func (c *serverConn) RemoteAddr() net.Addr { + return c.w.RemoteAddr() +} + +func (c *serverConn) SetDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "dns", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} + +func (c *serverConn) SetReadDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "dns", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} + +func (c *serverConn) SetWriteDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "dns", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +}