mirror of
https://github.com/apernet/hysteria.git
synced 2025-04-03 04:27:39 +03:00
Merge 9784dad9cd
into 245c6e9bd1
This commit is contained in:
commit
d362a9a6ff
2 changed files with 207 additions and 0 deletions
|
@ -175,12 +175,19 @@ type serverConfigResolverHTTPS struct {
|
|||
Insecure bool `mapstructure:"insecure"`
|
||||
}
|
||||
|
||||
type serverConfigResolverQUIC struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Sni string `mapstructure:"sni"`
|
||||
Insecure bool `mapstructure:"insecure"`
|
||||
}
|
||||
|
||||
type serverConfigResolver struct {
|
||||
Type string `mapstructure:"type"`
|
||||
TCP serverConfigResolverTCP `mapstructure:"tcp"`
|
||||
UDP serverConfigResolverUDP `mapstructure:"udp"`
|
||||
TLS serverConfigResolverTLS `mapstructure:"tls"`
|
||||
HTTPS serverConfigResolverHTTPS `mapstructure:"https"`
|
||||
QUIC serverConfigResolverQUIC `mapstructure:"quic"`
|
||||
}
|
||||
|
||||
type serverConfigSniff struct {
|
||||
|
@ -695,6 +702,15 @@ func (c *serverConfig) fillOutboundConfig(hyConfig *server.Config) error {
|
|||
return configError{Field: "resolver.https.addr", Err: errors.New("empty resolver address")}
|
||||
}
|
||||
uOb = outbounds.NewDoHResolver(c.Resolver.HTTPS.Addr, c.Resolver.HTTPS.Timeout, c.Resolver.HTTPS.SNI, c.Resolver.HTTPS.Insecure, uOb)
|
||||
case "quic":
|
||||
if c.Resolver.QUIC.Addr == "" {
|
||||
return configError{Field: "resolver.quic.addr", Err: errors.New("empty resolver address")}
|
||||
}
|
||||
var err error
|
||||
uOb, err = outbounds.NewDOQResolver(c.Resolver.QUIC.Addr, c.Resolver.QUIC.Sni, c.Resolver.QUIC.Insecure, uOb)
|
||||
if err != nil {
|
||||
return configError{Field: "resolver.quic", Err: err}
|
||||
}
|
||||
default:
|
||||
return configError{Field: "resolver.type", Err: errors.New("unsupported resolver type")}
|
||||
}
|
||||
|
|
191
extras/outbounds/dns_quic.go
Normal file
191
extras/outbounds/dns_quic.go
Normal file
|
@ -0,0 +1,191 @@
|
|||
package outbounds
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/quic-go"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// DoqResolver Client stores a DoQ client
|
||||
type DoqResolver struct {
|
||||
Resolver doqClient
|
||||
Next PluggableOutbound
|
||||
}
|
||||
|
||||
type doqClient struct {
|
||||
ctx context.Context
|
||||
conn quic.Connection
|
||||
}
|
||||
|
||||
func NewDOQResolver(server, sni string, tlsInsecureSkipVerify bool, next PluggableOutbound) (PluggableOutbound, error) {
|
||||
d, err := newDoqClient(server, sni, tlsInsecureSkipVerify)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DoqResolver{
|
||||
Resolver: *d,
|
||||
Next: next,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newDoqClient(server, sni string, tlsInsecureSkipVerify bool) (*doqClient, error) {
|
||||
var d doqClient
|
||||
var err error
|
||||
d.ctx = context.Background()
|
||||
serverAddr := strings.SplitN(server, ":", 2)
|
||||
if len(serverAddr) != 2 {
|
||||
// DOQ: If the port is not specified, use the default port 853
|
||||
server = server + ":853"
|
||||
}
|
||||
if sni == "" {
|
||||
// DOQ: If the SNI is not specified, use the server address host
|
||||
sni = serverAddr[0]
|
||||
}
|
||||
d.conn, err = quic.DialAddrEarly(d.ctx, server, &tls.Config{
|
||||
ServerName: sni,
|
||||
InsecureSkipVerify: tlsInsecureSkipVerify,
|
||||
NextProtos: []string{"doq"},
|
||||
SessionTicketsDisabled: false,
|
||||
}, &quic.Config{
|
||||
KeepAlivePeriod: 20 * time.Second,
|
||||
TokenStore: quic.NewLRUTokenStore(1, 10),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
func (r *DoqResolver) resolve(reqAddr *AddrEx) {
|
||||
if tryParseIP(reqAddr) {
|
||||
// The host is already an IP address, we don't need to resolve it.
|
||||
return
|
||||
}
|
||||
type lookupResult struct {
|
||||
ip net.IP
|
||||
err error
|
||||
}
|
||||
ch4, ch6 := make(chan lookupResult, 1), make(chan lookupResult, 1)
|
||||
go func() {
|
||||
recs, err := r.Resolver.LookupA(reqAddr.Host)
|
||||
var ip net.IP
|
||||
if err == nil && len(recs) > 0 {
|
||||
ip = recs[0].A
|
||||
}
|
||||
ch4 <- lookupResult{ip, err}
|
||||
}()
|
||||
go func() {
|
||||
recs, err := r.Resolver.LookupAAAA(reqAddr.Host)
|
||||
var ip net.IP
|
||||
if err == nil && len(recs) > 0 {
|
||||
ip = recs[0].AAAA
|
||||
}
|
||||
ch6 <- lookupResult{ip, err}
|
||||
}()
|
||||
result4, result6 := <-ch4, <-ch6
|
||||
reqAddr.ResolveInfo = &ResolveInfo{
|
||||
IPv4: result4.ip,
|
||||
IPv6: result6.ip,
|
||||
}
|
||||
if result4.err != nil {
|
||||
reqAddr.ResolveInfo.Err = result4.err
|
||||
} else if result6.err != nil {
|
||||
reqAddr.ResolveInfo.Err = result6.err
|
||||
}
|
||||
}
|
||||
|
||||
func (r *DoqResolver) TCP(reqAddr *AddrEx) (net.Conn, error) {
|
||||
r.resolve(reqAddr)
|
||||
return r.Next.TCP(reqAddr)
|
||||
}
|
||||
|
||||
func (r *DoqResolver) UDP(reqAddr *AddrEx) (UDPConn, error) {
|
||||
r.resolve(reqAddr)
|
||||
return r.Next.UDP(reqAddr)
|
||||
}
|
||||
|
||||
// Close closes a Client QUIC connection
|
||||
func (c *doqClient) Close() error {
|
||||
return c.conn.CloseWithError(0, "")
|
||||
}
|
||||
|
||||
func (c *doqClient) LookupA(name string) ([]dns.A, error) {
|
||||
var msg dns.Msg
|
||||
msg.SetQuestion(fmt.Sprintf("%s.", name), dns.TypeA)
|
||||
resp, err := c.Lookup(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ret []dns.A
|
||||
for _, a := range resp.Answer {
|
||||
if ans, ok := a.(*dns.A); ok {
|
||||
ret = append(ret, *ans)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (c *doqClient) LookupAAAA(name string) ([]dns.AAAA, error) {
|
||||
var msg dns.Msg
|
||||
msg.SetQuestion(fmt.Sprintf("%s.", name), dns.TypeAAAA)
|
||||
resp, err := c.Lookup(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ret []dns.AAAA
|
||||
for _, a := range resp.Answer {
|
||||
if ans, ok := a.(*dns.AAAA); ok {
|
||||
ret = append(ret, *ans)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Lookup sends a DNS query to the server and returns the response
|
||||
// Code From: https://github.com/natesales/doqd/blob/main/pkg/client/main.go
|
||||
func (c *doqClient) Lookup(message dns.Msg) (dns.Msg, error) {
|
||||
// Open a new QUIC stream
|
||||
stream, err := c.conn.OpenStreamSync(c.ctx)
|
||||
if err != nil {
|
||||
return dns.Msg{}, errors.New("quic stream open: " + err.Error())
|
||||
}
|
||||
|
||||
// Pack the DNS message for transmission
|
||||
packed, err := message.Pack()
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return dns.Msg{}, errors.New("dns message pack: " + err.Error())
|
||||
}
|
||||
|
||||
_, err = stream.Write(packed)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return dns.Msg{}, errors.New("quic stream write: " + err.Error())
|
||||
}
|
||||
// Close the stream after writing the message
|
||||
_ = stream.Close()
|
||||
|
||||
// Read the response from the QUIC stream
|
||||
buffer, err := io.ReadAll(stream)
|
||||
if err != nil {
|
||||
return dns.Msg{}, errors.New("quic stream read: " + err.Error())
|
||||
}
|
||||
|
||||
// Unpack the DNS message
|
||||
var msg dns.Msg
|
||||
err = msg.Unpack(buffer)
|
||||
if err != nil {
|
||||
return dns.Msg{}, errors.New("dns message unpack: " + err.Error())
|
||||
}
|
||||
|
||||
return msg, nil // nil error
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue