From e0fb9c4e7faf9d1ac97200fdd2470413cb166d62 Mon Sep 17 00:00:00 2001 From: p4gefau1t Date: Sun, 3 May 2020 10:01:07 -0400 Subject: [PATCH] add dns support --- common/common.go | 2 +- conf/conf.go | 9 +++ docs/config.toml | 2 +- go.mod | 1 + go.sum | 2 + protocol/direct/outbound.go | 119 +++++++++++++++++++++++++++++-- protocol/direct/outbound_test.go | 100 +++++++++++++++++++++++--- proxy/client/client.go | 4 +- proxy/server/server.go | 4 +- test/target.go | 28 ++++++++ 10 files changed, 251 insertions(+), 20 deletions(-) diff --git a/common/common.go b/common/common.go index 31ca8cf..9bc1451 100644 --- a/common/common.go +++ b/common/common.go @@ -11,7 +11,7 @@ import ( ) const ( - Version = "v0.4.3" + Version = "v0.4.4" ) type Runnable interface { diff --git a/conf/conf.go b/conf/conf.go index 4338aad..f26d690 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -18,6 +18,15 @@ const ( Relay RunType = "relay" ) +type DNSType string + +const ( + UDP DNSType = "udp" + DOH DNSType = "https" + DOT DNSType = "dot" + TCP DNSType = "tcp" +) + type TLSConfig struct { Verify bool `json:"verify"` VerifyHostname bool `json:"verify_hostname"` diff --git a/docs/config.toml b/docs/config.toml index 9905672..5c8a945 100755 --- a/docs/config.toml +++ b/docs/config.toml @@ -16,7 +16,7 @@ enableMissingTranslationPlaceholders = false # Source Code repository section description = "An unidentifiable mechanism that helps you bypass GFW. " github_repository = "https://github.com/p4gefau1t/trojan-go" - version = "0.4.3" + version = "0.4.4" # Documentation repository section # documentation repository (set edit link to documentation repository) diff --git a/go.mod b/go.mod index 0783f76..bf81f03 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.14 require ( github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed + github.com/babolivier/go-doh-client v0.0.0-20190212214242-43780baf3561 github.com/go-acme/lego/v3 v3.5.0 github.com/go-sql-driver/mysql v1.5.0 github.com/golang/protobuf v1.4.0 diff --git a/go.sum b/go.sum index 60575e4..72b1973 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190808125512-07798873deee/go.mod github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/babolivier/go-doh-client v0.0.0-20190212214242-43780baf3561 h1:Zq0ikckJ47+8cG0oAYH/XC3syJ0vGgSlTGtIsqlyPFE= +github.com/babolivier/go-doh-client v0.0.0-20190212214242-43780baf3561/go.mod h1:WorCk0sF6w5RjJorPxPL80q35XcMAPew90L8WSZxobY= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 9c21997..66bca87 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -2,11 +2,15 @@ package direct import ( "context" + "crypto/tls" "io" "net" + "net/url" "time" + "github.com/babolivier/go-doh-client" "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/conf" "github.com/p4gefau1t/trojan-go/log" "github.com/p4gefau1t/trojan-go/protocol" ) @@ -29,10 +33,117 @@ func (o *DirectOutboundConnSession) Close() error { return o.conn.Close() } -func NewOutboundConnSession(req *protocol.Request) (protocol.ConnSession, error) { - newConn, err := net.Dial(req.Network(), req.String()) - if err != nil { - return nil, err +func NewOutboundConnSession(ctx context.Context, req *protocol.Request, config *conf.GlobalConfig) (protocol.ConnSession, error) { + var newConn net.Conn + //custom dns server + if req.AddressType == common.DomainName && len(config.DNS) != 0 { + //find a avaliable dns server + for _, s := range config.DNS { + var dnsType conf.DNSType + var dnsAddr string + dnsURL, err := url.Parse(s) + if err != nil { + dnsType = conf.UDP + dnsAddr = s + } else { + dnsType = conf.DNSType(dnsURL.Scheme) + dnsAddr = dnsURL.Host + } + + if dnsType == conf.DOH { + resolver := doh.Resolver{ + Host: dnsURL.Host, + Class: doh.IN, + } + result := []string{} + a, _, err := resolver.LookupA(req.DomainName) + if err != nil { + log.Error(err) + continue + } + if !config.TCP.PreferIPV4 { + aaaa, _, err := resolver.LookupAAAA(req.DomainName) + if err != nil { + log.Error(err) + continue + } + for _, record := range aaaa { + result = append(result, record.IP6) + } + } + for _, record := range a { + result = append(result, record.IP4) + } + if len(result) == 0 { + log.Error("a record not found for" + req.DomainName) + continue + } + for _, ip := range result { + newConn, err = net.DialTCP("tcp", nil, &net.TCPAddr{ + IP: net.ParseIP(ip), + Port: req.Port, + }) + if err != nil { + return nil, err + } + break + } + } else { + resolver := &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + switch dnsType { + case conf.UDP, conf.TCP: + d := net.Dialer{ + Timeout: time.Second * time.Duration(protocol.UDPTimeout), + } + conn, err := d.DialContext(ctx, string(dnsType), dnsAddr) + if err != nil { + return nil, err + } + return conn, nil + case conf.DOT: + tlsConn, err := tls.Dial("tcp", dnsAddr, nil) + if err != nil { + return nil, err + } + return tlsConn, nil + } + return nil, common.NewError("invalid dns type" + string(dnsType)) + }, + } + ips, err := resolver.LookupIPAddr(ctx, req.DomainName) + if err != nil { + log.Debug("dns server " + s + " sucks") + continue + } + log.Debug("dns connected:" + s) + if len(ips) == 0 { + return nil, common.NewError("record of " + req.DomainName + " not found in dns server " + s) + } + for _, ip := range ips { + newConn, err = net.DialTCP("tcp", nil, &net.TCPAddr{ + IP: ip.IP, + Port: req.Port, + }) + if err != nil { + return nil, err + } + break + } + } + break + } + if newConn == nil { + return nil, common.NewError("all dns servers are down") + } + } else { + //default resolver + var err error + newConn, err = net.Dial(req.Network(), req.String()) + if err != nil { + return nil, err + } } o := &DirectOutboundConnSession{ request: req, diff --git a/protocol/direct/outbound_test.go b/protocol/direct/outbound_test.go index cf7b7ff..428ae70 100644 --- a/protocol/direct/outbound_test.go +++ b/protocol/direct/outbound_test.go @@ -5,23 +5,31 @@ import ( "fmt" "math/rand" "net" + "net/http" "testing" + "time" "github.com/p4gefau1t/trojan-go/common" + "github.com/p4gefau1t/trojan-go/conf" "github.com/p4gefau1t/trojan-go/protocol" "github.com/p4gefau1t/trojan-go/test" ) -func TestDirectOutbound(t *testing.T) { - for i := 0; i < 10; i++ { - go test.RunEchoUDPServer(context.Background()) - } +func TestUDPDirectOutbound(t *testing.T) { + go test.RunMultipleUDPEchoServer(context.Background()) outbound, _ := NewOutboundPacketSession(context.Background()) - for i := 0; i < 30; i++ { + go func() { + for i := 0; i < 5; i++ { + req, buf, err := outbound.ReadPacket() + fmt.Println(req, string(buf), err) + } + }() + for i := 0; i < 5; i++ { req := &protocol.Request{ Address: &common.Address{ - IP: net.ParseIP("127.0.0.1"), - Port: 6543, + IP: net.ParseIP("127.0.0.1"), + Port: 6000 + rand.Intn(10), + AddressType: common.IPv4, }, } req.Port += rand.Intn(10) @@ -29,8 +37,80 @@ func TestDirectOutbound(t *testing.T) { _, err := outbound.WritePacket(req, packet) common.Must(err) } - for i := 0; i < 30; i++ { - req, buf, err := outbound.ReadPacket() - fmt.Println(req, string(buf), err) + time.Sleep(time.Second * 5) +} + +func TestDNS(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + go test.RunEchoTCPServer(ctx) + config := &conf.GlobalConfig{ + DNS: []string{"114.114.114.114:53"}, } + req := &protocol.Request{ + Address: &common.Address{ + DomainName: "www.baidu.com", + Port: 80, + AddressType: common.DomainName, + NetworkType: "tcp", + }, + } + conn, err := NewOutboundConnSession(ctx, req, config) + common.Must(err) + httpReq, err := http.NewRequest("GET", "http://www.baidu.com", nil) + common.Must(err) + httpReq.Write(conn) + buf := [128]byte{} + conn.Read(buf[:]) + fmt.Println(string(buf[:])) + cancel() +} + +func TestDOT(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + go test.RunEchoTCPServer(ctx) + config := &conf.GlobalConfig{ + DNS: []string{"dot://223.5.5.5:853"}, + } + req := &protocol.Request{ + Address: &common.Address{ + DomainName: "www.baidu.com", + Port: 80, + AddressType: common.DomainName, + NetworkType: "tcp", + }, + } + conn, err := NewOutboundConnSession(ctx, req, config) + common.Must(err) + httpReq, err := http.NewRequest("GET", "http://www.baidu.com", nil) + common.Must(err) + httpReq.Write(conn) + buf := [128]byte{} + conn.Read(buf[:]) + fmt.Println(string(buf[:])) + cancel() +} + +func TestDOH(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + go test.RunEchoTCPServer(ctx) + config := &conf.GlobalConfig{ + DNS: []string{"https://223.5.5.5:443"}, + } + req := &protocol.Request{ + Address: &common.Address{ + DomainName: "www.baidu.com", + Port: 80, + AddressType: common.DomainName, + NetworkType: "tcp", + }, + } + conn, err := NewOutboundConnSession(ctx, req, config) + common.Must(err) + httpReq, err := http.NewRequest("GET", "http://www.baidu.com", nil) + common.Must(err) + httpReq.Write(conn) + buf := [128]byte{} + conn.Read(buf[:]) + fmt.Println(string(buf[:])) + cancel() } diff --git a/proxy/client/client.go b/proxy/client/client.go index 2b63130..58918f5 100644 --- a/proxy/client/client.go +++ b/proxy/client/client.go @@ -95,7 +95,7 @@ func (c *Client) handleSocksConn(conn io.ReadWriteCloser) { return } if policy == router.Bypass { - outboundConn, err := direct.NewOutboundConnSession(req) + outboundConn, err := direct.NewOutboundConnSession(c.ctx, req, c.config) if err != nil { log.Error(err) return @@ -139,7 +139,7 @@ func (c *Client) handleHTTPConn(conn io.ReadWriteCloser) { return } if policy == router.Bypass { - outboundConn, err := direct.NewOutboundConnSession(req) + outboundConn, err := direct.NewOutboundConnSession(c.ctx, req, c.config) if err != nil { log.Error(err) return diff --git a/proxy/server/server.go b/proxy/server/server.go index 5964fe7..f625496 100644 --- a/proxy/server/server.go +++ b/proxy/server/server.go @@ -41,7 +41,7 @@ func (s *Server) handleMuxConn(stream *smux.Stream) { } switch req.Command { case protocol.Connect: - outboundConn, err := direct.NewOutboundConnSession(req) + outboundConn, err := direct.NewOutboundConnSession(s.ctx, req, s.config) if err != nil { log.Error(err) return @@ -102,7 +102,7 @@ func (s *Server) handleConn(conn *tls.Conn) { } defer inboundConn.Close() - outboundConn, err := direct.NewOutboundConnSession(req) + outboundConn, err := direct.NewOutboundConnSession(s.ctx, req, s.config) if err != nil { log.Error(err) return diff --git a/test/target.go b/test/target.go index 1e8a3c0..7595c9d 100644 --- a/test/target.go +++ b/test/target.go @@ -3,6 +3,7 @@ package test import ( "context" "crypto/rand" + "fmt" "io" "io/ioutil" "net" @@ -35,6 +36,33 @@ func RunEchoUDPServer(ctx context.Context) { <-ctx.Done() } +func RunMultipleUDPEchoServer(ctx context.Context) { + for i := 0; i < 10; i++ { + go func(port int) { + conn, err := net.ListenUDP("udp", &net.UDPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: port, + }) + common.Must(err) + fmt.Println("udp echo:", conn.LocalAddr()) + defer conn.Close() + go func() { + for { + buf := make([]byte, 2048) + n, addr, err := conn.ReadFromUDP(buf[:]) + if err != nil { + return + } + log.Info("echo from", addr) + conn.WriteToUDP(buf[0:n], addr) + } + }() + <-ctx.Done() + }(6000 + i) + } + <-ctx.Done() +} + func RunEchoTCPServer(ctx context.Context) { listener, err := net.Listen("tcp", "127.0.0.1:5000") common.Must(err)