Skip to content

Commit a695a9c

Browse files
authored
Merge pull request #1434 from apernet/wip-replace-doh
fix: remove license-conflicted doh library
2 parents a4b1cfa + c6488c2 commit a695a9c

File tree

7 files changed

+166
-18
lines changed

7 files changed

+166
-18
lines changed

app/go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ require (
3131
require (
3232
github.com/andybalholm/brotli v1.1.0 // indirect
3333
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431 // indirect
34-
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 // indirect
3534
github.com/cloudflare/circl v1.3.9 // indirect
3635
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a // indirect
3736
github.com/database64128/tfo-go/v2 v2.2.2 // indirect

app/go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431 h1:9/jM7e+kVALd
4646
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431/go.mod h1:I/47OIGG5H/IfAm+nz2c6hm6b/NkEhpvptAoiPcY7jQ=
4747
github.com/apernet/sing-tun v0.2.6-0.20250726070404-c99085f9af13 h1:gzets97c/u5iMj1zjanMBVkIYOdaVw+RXPzTT1xQoyM=
4848
github.com/apernet/sing-tun v0.2.6-0.20250726070404-c99085f9af13/go.mod h1:S5IydyLSN/QAfvY+r2GoomPJ6hidtXWm/Ad18sJVssk=
49-
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
50-
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
5149
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
5250
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
5351
github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE=

extras/go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ toolchain go1.24.2
77
require (
88
github.com/apernet/hysteria/core/v2 v2.0.0-00010101000000-000000000000
99
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431
10-
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6
1110
github.com/database64128/tfo-go/v2 v2.2.2
1211
github.com/hashicorp/golang-lru/v2 v2.0.5
1312
github.com/miekg/dns v1.1.59

extras/go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
22
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
33
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431 h1:9/jM7e+kVALd7Jfu1c27dcEpT/Fd/Gzq2OsQjKjakKI=
44
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431/go.mod h1:I/47OIGG5H/IfAm+nz2c6hm6b/NkEhpvptAoiPcY7jQ=
5-
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
6-
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
75
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
86
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
97
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=

extras/outbounds/dns_https.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,35 @@ package outbounds
22

33
import (
44
"crypto/tls"
5+
"fmt"
56
"net"
67
"net/http"
8+
"strings"
79
"time"
810

9-
"github.com/babolivier/go-doh-client"
11+
"github.com/apernet/hysteria/extras/v2/outbounds/tinydoh"
1012
)
1113

1214
// dohResolver is a PluggableOutbound DNS resolver that resolves hostnames
1315
// using the user-provided DNS-over-HTTPS server.
1416
type dohResolver struct {
15-
Resolver *doh.Resolver
17+
Resolver *tinydoh.Resolver
1618
Next PluggableOutbound
1719
}
1820

19-
func NewDoHResolver(host string, timeout time.Duration, sni string, insecure bool, next PluggableOutbound) PluggableOutbound {
21+
func NewDoHResolver(addr string, timeout time.Duration, sni string, insecure bool, next PluggableOutbound) PluggableOutbound {
22+
// User may provide just the IP address or full URL
23+
if !strings.HasSuffix(addr, "https://") {
24+
addr = fmt.Sprintf("https://%s/dns-query", addr)
25+
}
2026
tr := http.DefaultTransport.(*http.Transport).Clone()
2127
tr.TLSClientConfig = &tls.Config{
2228
ServerName: sni,
2329
InsecureSkipVerify: insecure,
2430
}
2531
return &dohResolver{
26-
Resolver: &doh.Resolver{
27-
Host: host,
28-
Class: doh.IN,
32+
Resolver: &tinydoh.Resolver{
33+
URL: addr,
2934
HTTPClient: &http.Client{
3035
Transport: tr,
3136
Timeout: timeoutOrDefault(timeout),
@@ -46,18 +51,18 @@ func (r *dohResolver) resolve(reqAddr *AddrEx) {
4651
}
4752
ch4, ch6 := make(chan lookupResult, 1), make(chan lookupResult, 1)
4853
go func() {
49-
recs, _, err := r.Resolver.LookupA(reqAddr.Host)
54+
ips, err := r.Resolver.LookupA(reqAddr.Host)
5055
var ip net.IP
51-
if err == nil && len(recs) > 0 {
52-
ip = net.ParseIP(recs[0].IP4).To4()
56+
if err == nil && len(ips) > 0 {
57+
ip = ips[0]
5358
}
5459
ch4 <- lookupResult{ip, err}
5560
}()
5661
go func() {
57-
recs, _, err := r.Resolver.LookupAAAA(reqAddr.Host)
62+
ips, err := r.Resolver.LookupAAAA(reqAddr.Host)
5863
var ip net.IP
59-
if err == nil && len(recs) > 0 {
60-
ip = net.ParseIP(recs[0].IP6).To16()
64+
if err == nil && len(ips) > 0 {
65+
ip = ips[0]
6166
}
6267
ch6 <- lookupResult{ip, err}
6368
}()
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package tinydoh
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"net"
8+
"net/http"
9+
"strings"
10+
11+
"golang.org/x/net/dns/dnsmessage"
12+
)
13+
14+
type Resolver struct {
15+
URL string
16+
HTTPClient *http.Client
17+
}
18+
19+
func (r *Resolver) lookup(dnsType dnsmessage.Type, host string) ([]dnsmessage.Resource, error) {
20+
url := r.URL
21+
if url == "" {
22+
return nil, errors.New("no DoH URL provided")
23+
}
24+
client := r.HTTPClient
25+
if client == nil {
26+
client = http.DefaultClient
27+
}
28+
if !strings.HasSuffix(host, ".") {
29+
host += "."
30+
}
31+
name, err := dnsmessage.NewName(host)
32+
if err != nil {
33+
return nil, fmt.Errorf("failed to parse host %s: %w", host, err)
34+
}
35+
36+
reqBuilder := dnsmessage.NewBuilder(nil, dnsmessage.Header{
37+
RecursionDesired: true,
38+
})
39+
reqBuilder.EnableCompression()
40+
err = reqBuilder.StartQuestions()
41+
if err != nil {
42+
return nil, fmt.Errorf("failed to start dns questions for host %s: %w", host, err)
43+
}
44+
err = reqBuilder.Question(dnsmessage.Question{
45+
Name: name,
46+
Type: dnsType,
47+
Class: dnsmessage.ClassINET,
48+
})
49+
if err != nil {
50+
return nil, fmt.Errorf("failed to build dns question for host %s: %w", host, err)
51+
}
52+
reqMsg, err := reqBuilder.Finish()
53+
if err != nil {
54+
return nil, fmt.Errorf("failed to finish dns message for host %s: %w", host, err)
55+
}
56+
httpReq, err := http.NewRequest("POST", url, strings.NewReader(string(reqMsg)))
57+
if err != nil {
58+
return nil, fmt.Errorf("failed to create http request for host %s: %w", host, err)
59+
}
60+
httpReq.Header.Set("Content-Type", "application/dns-message")
61+
62+
httpResp, err := client.Do(httpReq)
63+
if err != nil {
64+
return nil, fmt.Errorf("failed to perform http request for host %s: %w", host, err)
65+
}
66+
defer httpResp.Body.Close()
67+
if httpResp.StatusCode != http.StatusOK {
68+
return nil, fmt.Errorf("non-200 status-code=%d for host %s", httpResp.StatusCode, host)
69+
}
70+
if httpResp.Header.Get("Content-Type") != "application/dns-message" {
71+
return nil, fmt.Errorf("unexpected content-type=%s for host %s", httpResp.Header.Get("Content-Type"), host)
72+
}
73+
74+
// 64KB should be enough for all DNS response
75+
limitedBody := io.LimitReader(httpResp.Body, 65536)
76+
respMsg, err := io.ReadAll(limitedBody)
77+
if err != nil {
78+
return nil, fmt.Errorf("failed to read http response body for host %s: %w", host, err)
79+
}
80+
parser := dnsmessage.Parser{}
81+
header, err := parser.Start(respMsg)
82+
if err != nil {
83+
return nil, fmt.Errorf("failed to parse dns message header for host %s: %w", host, err)
84+
}
85+
if header.RCode != dnsmessage.RCodeSuccess {
86+
return nil, fmt.Errorf("dns query failed with %s for host %s", header.RCode, host)
87+
}
88+
err = parser.SkipAllQuestions()
89+
if err != nil {
90+
return nil, fmt.Errorf("failed to skip dns questions for host %s: %w", host, err)
91+
}
92+
answers, err := parser.AllAnswers()
93+
if err != nil {
94+
return nil, fmt.Errorf("failed to parse dns answers for host %s: %w", host, err)
95+
}
96+
return answers, nil
97+
}
98+
99+
func (r *Resolver) LookupA(host string) ([]net.IP, error) {
100+
answers, err := r.lookup(dnsmessage.TypeA, host)
101+
if err != nil {
102+
return nil, err
103+
}
104+
var results []net.IP
105+
for _, rr := range answers {
106+
if rr.Header.Type == dnsmessage.TypeA {
107+
a := rr.Body.(*dnsmessage.AResource)
108+
results = append(results, a.A[:])
109+
}
110+
}
111+
return results, nil
112+
}
113+
114+
func (r *Resolver) LookupAAAA(host string) ([]net.IP, error) {
115+
answers, err := r.lookup(dnsmessage.TypeAAAA, host)
116+
if err != nil {
117+
return nil, err
118+
}
119+
var results []net.IP
120+
for _, rr := range answers {
121+
if rr.Header.Type == dnsmessage.TypeAAAA {
122+
aaaa := rr.Body.(*dnsmessage.AAAAResource)
123+
results = append(results, aaaa.AAAA[:])
124+
}
125+
}
126+
return results, nil
127+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package tinydoh
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func TestResolver(t *testing.T) {
9+
r := &Resolver{
10+
URL: "https://1.1.1.1/dns-query",
11+
}
12+
ipv4, err := r.LookupA("www.wikipedia.org")
13+
if err != nil {
14+
t.Error(err)
15+
}
16+
fmt.Println(ipv4)
17+
ipv6, err := r.LookupAAAA("www.wikipedia.org")
18+
if err != nil {
19+
t.Error(err)
20+
}
21+
fmt.Println(ipv6)
22+
}

0 commit comments

Comments
 (0)