mirror of
https://github.com/hackdiaz-dev/free-turn-proxy.git
synced 2026-10-12 09:41:53 +02:00
fix(dnsdial): динамический фоллбэк на DoH при отказе UDP/53 после пробы
This commit is contained in:
@@ -468,6 +468,10 @@ func udpDNSDial(ctx context.Context, _ string, _ string) (net.Conn, error) {
|
||||
//
|
||||
// Просто dial-timeout не годится: UDP "dial" безсоединительный и всегда успешен
|
||||
// мгновенно. Единственный способ узнать — реально отправить запрос и ждать ответ.
|
||||
//
|
||||
// Проба one-shot: если сеть сменилась после неё (Wi-Fi→мобилка, UDP/53 серверы
|
||||
// стали недостижимы), ловим это по ошибке udpDNSDial и динамически залипаем на
|
||||
// DoH — иначе процесс навсегда застрял бы на мёртвом UDP.
|
||||
func autoDial(r *DohResolver) dialFunc {
|
||||
var (
|
||||
probed sync.Once
|
||||
@@ -486,7 +490,23 @@ func autoDial(r *DohResolver) dialFunc {
|
||||
if useDoH.Load() {
|
||||
return doh(ctx, network, addr)
|
||||
}
|
||||
return udpDNSDial(ctx, network, addr)
|
||||
conn, err := udpDNSDial(ctx, network, addr)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return nil, err
|
||||
}
|
||||
// UDP/53 отвалился (смена сети) — пробуем DoH; залипаем только если он
|
||||
// реально поднялся, иначе отдаём исходную UDP-ошибку.
|
||||
dohConn, dohErr := doh(ctx, network, addr)
|
||||
if dohErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
if useDoH.CompareAndSwap(false, true) {
|
||||
Log.Warnf("[DNS] UDP/53 dial failed (%v); sticky-switching to DoH", err)
|
||||
}
|
||||
return dohConn, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,70 @@ func readWire(t *testing.T, r io.Reader) []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
func TestAutoDial_DynamicFallbackOnUDPDialError(t *testing.T) {
|
||||
// DoH backend всегда отвечает валидным wire-ответом.
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body := readWire(t, r.Body)
|
||||
w.Header().Set("Content-Type", "application/dns-message")
|
||||
_, _ = w.Write(dohAnswer(t, body, net.ParseIP("9.9.9.9"), 300)) //nolint:errcheck
|
||||
}))
|
||||
defer srv.Close()
|
||||
resolver := newDohResolverWithClient(
|
||||
[]DohEndpoint{{URL: srv.URL, Hostname: "mock", BootstrapIPs: []string{"127.0.0.1"}}},
|
||||
srv.Client(),
|
||||
)
|
||||
|
||||
// Локальный UDP DNS-ответчик: проба (udpProbe) пройдёт.
|
||||
pc, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
||||
if err != nil {
|
||||
t.Fatalf("listen udp: %v", err)
|
||||
}
|
||||
defer func() { _ = pc.Close() }()
|
||||
go func() {
|
||||
buf := make([]byte, 512)
|
||||
for {
|
||||
n, from, rerr := pc.ReadFromUDP(buf)
|
||||
if rerr != nil {
|
||||
return
|
||||
}
|
||||
_, _ = pc.WriteToUDP(dohAnswer(t, buf[:n], net.ParseIP("1.2.3.4"), 60), from) //nolint:errcheck
|
||||
}
|
||||
}()
|
||||
|
||||
old := udpDNSServersPtr.Load()
|
||||
good := []string{pc.LocalAddr().String()}
|
||||
udpDNSServersPtr.Store(&good)
|
||||
defer func() { udpDNSServersPtr.Store(old) }()
|
||||
|
||||
dial := autoDial(resolver)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Первый вызов: проба OK + реальный UDP-dial OK.
|
||||
conn1, err := dial(ctx, "udp", "unused")
|
||||
if err != nil {
|
||||
t.Fatalf("first dial (UDP): %v", err)
|
||||
}
|
||||
_ = conn1.Close()
|
||||
|
||||
// Сеть "сменилась": UDP/53 серверы недостижимы → dial должен упасть и
|
||||
// динамически переключиться на DoH.
|
||||
bad := []string{"not-a-valid-host-port"}
|
||||
udpDNSServersPtr.Store(&bad)
|
||||
conn2, err := dial(ctx, "udp", "unused")
|
||||
if err != nil {
|
||||
t.Fatalf("second dial must fall back to DoH, got: %v", err)
|
||||
}
|
||||
_ = conn2.Close()
|
||||
|
||||
// После фоллбэка процесс залип на DoH: третий вызов с битыми UDP проходит.
|
||||
conn3, err := dial(ctx, "udp", "unused")
|
||||
if err != nil {
|
||||
t.Fatalf("third dial must stay on DoH, got: %v", err)
|
||||
}
|
||||
_ = conn3.Close()
|
||||
}
|
||||
|
||||
func TestAutoDial_StickyAfterUDPFailure(t *testing.T) {
|
||||
// DoH backend: always responds with a valid wire-format reply.
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user