mirror of
https://github.com/apernet/hysteria.git
synced 2025-04-04 21:17:47 +03:00
add DoQ Feature
This commit is contained in:
parent
ddb5b511fc
commit
9784dad9cd
2 changed files with 207 additions and 0 deletions
|
@ -141,12 +141,19 @@ type serverConfigResolverHTTPS struct {
|
||||||
Insecure bool `mapstructure:"insecure"`
|
Insecure bool `mapstructure:"insecure"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type serverConfigResolverQUIC struct {
|
||||||
|
Addr string `mapstructure:"addr"`
|
||||||
|
Sni string `mapstructure:"sni"`
|
||||||
|
Insecure bool `mapstructure:"insecure"`
|
||||||
|
}
|
||||||
|
|
||||||
type serverConfigResolver struct {
|
type serverConfigResolver struct {
|
||||||
Type string `mapstructure:"type"`
|
Type string `mapstructure:"type"`
|
||||||
TCP serverConfigResolverTCP `mapstructure:"tcp"`
|
TCP serverConfigResolverTCP `mapstructure:"tcp"`
|
||||||
UDP serverConfigResolverUDP `mapstructure:"udp"`
|
UDP serverConfigResolverUDP `mapstructure:"udp"`
|
||||||
TLS serverConfigResolverTLS `mapstructure:"tls"`
|
TLS serverConfigResolverTLS `mapstructure:"tls"`
|
||||||
HTTPS serverConfigResolverHTTPS `mapstructure:"https"`
|
HTTPS serverConfigResolverHTTPS `mapstructure:"https"`
|
||||||
|
QUIC serverConfigResolverQUIC `mapstructure:"quic"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type serverConfigACL struct {
|
type serverConfigACL struct {
|
||||||
|
@ -527,6 +534,15 @@ func (c *serverConfig) fillOutboundConfig(hyConfig *server.Config) error {
|
||||||
return configError{Field: "resolver.https.addr", Err: errors.New("empty resolver address")}
|
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)
|
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:
|
default:
|
||||||
return configError{Field: "resolver.type", Err: errors.New("unsupported resolver type")}
|
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