[Feat]
1.Add DNS over QUIC support 2.Replace Country.mmdb with GeoIP.dat 3.build with Alpha tag
This commit is contained in:
146
dns/quic.go
Normal file
146
dns/quic.go
Normal file
@ -0,0 +1,146 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const NextProtoDQ = "doq-i00"
|
||||
|
||||
var bytesPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
|
||||
|
||||
type quicClient struct {
|
||||
addr string
|
||||
session quic.Session
|
||||
sync.RWMutex // protects session and bytesPool
|
||||
}
|
||||
|
||||
func (dc *quicClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
return dc.ExchangeContext(context.Background(), m)
|
||||
}
|
||||
|
||||
func (dc *quicClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||
stream, err := dc.openStream(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open new stream to %s", dc.addr)
|
||||
}
|
||||
|
||||
buf, err := m.Pack()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = stream.Write(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The client MUST send the DNS query over the selected stream, and MUST
|
||||
// indicate through the STREAM FIN mechanism that no further data will
|
||||
// be sent on that stream.
|
||||
// stream.Close() -- closes the write-direction of the stream.
|
||||
_ = stream.Close()
|
||||
|
||||
respBuf := bytesPool.Get().(*bytes.Buffer)
|
||||
defer bytesPool.Put(respBuf)
|
||||
defer respBuf.Reset()
|
||||
|
||||
n, err := respBuf.ReadFrom(stream)
|
||||
if err != nil && n == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply := new(D.Msg)
|
||||
err = reply.Unpack(respBuf.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
func isActive(s quic.Session) bool {
|
||||
select {
|
||||
case <-s.Context().Done():
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// getSession - opens or returns an existing quic.Session
|
||||
// useCached - if true and cached session exists, return it right away
|
||||
// otherwise - forcibly creates a new session
|
||||
func (dc *quicClient) getSession() (quic.Session, error) {
|
||||
var session quic.Session
|
||||
dc.RLock()
|
||||
session = dc.session
|
||||
if session != nil && isActive(session) {
|
||||
dc.RUnlock()
|
||||
return session, nil
|
||||
}
|
||||
if session != nil {
|
||||
// we're recreating the session, let's create a new one
|
||||
_ = session.CloseWithError(0, "")
|
||||
}
|
||||
dc.RUnlock()
|
||||
|
||||
dc.Lock()
|
||||
defer dc.Unlock()
|
||||
|
||||
var err error
|
||||
session, err = dc.openSession()
|
||||
if err != nil {
|
||||
// This does not look too nice, but QUIC (or maybe quic-go)
|
||||
// doesn't seem stable enough.
|
||||
// Maybe retransmissions aren't fully implemented in quic-go?
|
||||
// Anyways, the simple solution is to make a second try when
|
||||
// it fails to open the QUIC session.
|
||||
session, err = dc.openSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
dc.session = session
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (dc *quicClient) openSession() (quic.Session, error) {
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{
|
||||
"http/1.1", "h2", NextProtoDQ,
|
||||
},
|
||||
SessionTicketsDisabled: false,
|
||||
}
|
||||
quicConfig := &quic.Config{
|
||||
ConnectionIDLength: 12,
|
||||
HandshakeIdleTimeout: time.Second * 8,
|
||||
}
|
||||
|
||||
log.Debugln("opening session to %s", dc.addr)
|
||||
session, err := quic.DialAddrContext(context.Background(), dc.addr, tlsConfig, quicConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open QUIC session: %w", err)
|
||||
}
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (dc *quicClient) openStream(ctx context.Context) (quic.Stream, error) {
|
||||
session, err := dc.getSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// open a new stream
|
||||
return session.OpenStreamSync(ctx)
|
||||
}
|
@ -61,6 +61,9 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
case "dhcp":
|
||||
ret = append(ret, newDHCPClient(s.Addr))
|
||||
continue
|
||||
case "quic":
|
||||
ret = append(ret, &quicClient{addr: s.Addr})
|
||||
continue
|
||||
}
|
||||
|
||||
host, port, _ := net.SplitHostPort(s.Addr)
|
||||
|
Reference in New Issue
Block a user