add dns listener

This commit is contained in:
ginuerzh 2021-12-21 21:54:33 +08:00
parent e21c35a47f
commit 8600ee7c5d
6 changed files with 377 additions and 3 deletions

View File

@ -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"

6
go.mod
View File

@ -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

7
go.sum
View File

@ -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=

View File

@ -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()
}

View File

@ -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
}

108
pkg/listener/dns/server.go Normal file
View File

@ -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")}
}