feat: update Go module dependencies and enhance DNS resolution in refresh pipeline. Upgrade Go version to 1.24.0, add support for DNS over HTTPS using RFC8484 format, and improve error handling for domain resolution. Update UI components for better log entry presentation in Svelte.
CI / changes (push) Successful in 6s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 1m36s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m4s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m4s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 16s
CI / docker-go-prime (push) Failing after 35s
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Has been skipped
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Has been skipped
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Has been skipped
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Has been skipped
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Has been skipped
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Has been skipped
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Has been skipped
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Has been skipped

This commit is contained in:
Denozordec
2026-04-06 17:59:52 +07:00
parent 05bc5c435e
commit 655d7507e5
4 changed files with 167 additions and 39 deletions
+112 -8
View File
@@ -1,13 +1,16 @@
package pipeline
import (
"bytes"
"context"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/netip"
"net"
"net/http"
"net/netip"
"net/url"
"os"
"sort"
@@ -20,6 +23,7 @@ import (
"evobgp/internal/store"
"github.com/google/uuid"
"github.com/miekg/dns"
)
const (
@@ -264,7 +268,7 @@ func resolveDomainIPs(ctx context.Context, hc *http.Client, profile *store.DohPr
return nil, nil
}
if profile == nil || strings.TrimSpace(profile.URL) == "" {
return nil, nil
return resolveDomainWithSystemDNS(ctx, host)
}
timeout := 10 * time.Second
@@ -274,13 +278,113 @@ func resolveDomainIPs(ctx context.Context, hc *http.Client, profile *store.DohPr
dctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// RFC8484 endpoint with JSON mode: ?name=<fqdn>&type=A/AAAA
v4, err4 := resolveDomainWithDOHJSON(dctx, hc, strings.TrimSpace(profile.URL), host, "A")
v6, err6 := resolveDomainWithDOHJSON(dctx, hc, strings.TrimSpace(profile.URL), host, "AAAA")
if err4 != nil && err6 != nil {
return nil, fmt.Errorf("doh failed for A and AAAA: %v; %v", err4, err6)
baseURL := strings.TrimSpace(profile.URL)
// Prefer RFC8484 dns-message transport. Some providers don't support dns-json.
v4, err4 := resolveDomainWithDOHMessage(dctx, hc, baseURL, host, dns.TypeA)
v6, err6 := resolveDomainWithDOHMessage(dctx, hc, baseURL, host, dns.TypeAAAA)
if err4 != nil {
// Fallback to JSON mode for providers that only expose dns-json.
v4, err4 = resolveDomainWithDOHJSON(dctx, hc, baseURL, host, "A")
}
return uniqAddrs(append(v4, v6...)), nil
if err6 != nil {
v6, err6 = resolveDomainWithDOHJSON(dctx, hc, baseURL, host, "AAAA")
}
if err4 != nil && err6 != nil {
// Some DoH providers return non-JSON responses (RFC8484 dns-message, HTML error pages, etc.).
// Fall back to system resolver to avoid failing the whole module refresh.
ips, fallbackErr := resolveDomainWithSystemDNS(dctx, host)
if fallbackErr != nil {
return nil, fmt.Errorf("doh failed for A and AAAA: %v; %v; fallback dns failed: %w", err4, err6, fallbackErr)
}
return ips, nil
}
out := uniqAddrs(append(v4, v6...))
if len(out) > 0 {
return out, nil
}
// If DoH succeeds but returns no A/AAAA records, attempt system resolver as best-effort fallback.
ips, err := resolveDomainWithSystemDNS(dctx, host)
if err != nil {
return nil, err
}
return ips, nil
}
func resolveDomainWithDOHMessage(ctx context.Context, hc *http.Client, baseURL, host string, qtype uint16) ([]netip.Addr, error) {
msg := new(dns.Msg)
msg.SetQuestion(dns.Fqdn(host), qtype)
wire, err := msg.Pack()
if err != nil {
return nil, err
}
u, err := url.Parse(baseURL)
if err != nil {
return nil, err
}
q := u.Query()
q.Set("dns", base64.RawURLEncoding.EncodeToString(wire))
u.RawQuery = q.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", "application/dns-message")
resp, err := hc.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
return nil, fmt.Errorf("doh dns-message status %s: %s", resp.Status, strings.TrimSpace(string(body)))
}
raw, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20))
if err != nil {
return nil, err
}
parsed := new(dns.Msg)
if err := parsed.Unpack(raw); err != nil {
return nil, err
}
if parsed.Rcode != dns.RcodeSuccess {
return nil, fmt.Errorf("doh rcode=%s", dns.RcodeToString[parsed.Rcode])
}
var out []netip.Addr
for _, rr := range parsed.Answer {
switch x := rr.(type) {
case *dns.A:
if qtype == dns.TypeA {
if ip, ok := netip.AddrFromSlice(x.A.To4()); ok {
out = append(out, ip.Unmap())
}
}
case *dns.AAAA:
if qtype == dns.TypeAAAA {
if ip, ok := netip.AddrFromSlice(x.AAAA.To16()); ok {
out = append(out, ip.Unmap())
}
}
}
}
// Some providers may return JSON even on dns-message request.
if len(out) == 0 && bytes.Contains(bytes.ToLower(raw), []byte(`"answer"`)) {
qs := "A"
if qtype == dns.TypeAAAA {
qs = "AAAA"
}
return resolveDomainWithDOHJSON(ctx, hc, baseURL, host, qs)
}
return uniqAddrs(out), nil
}
func resolveDomainWithSystemDNS(ctx context.Context, host string) ([]netip.Addr, error) {
ips, err := net.DefaultResolver.LookupNetIP(ctx, "ip", host)
if err != nil {
return nil, err
}
return uniqAddrs(ips), nil
}
func resolveDomainWithDOHJSON(ctx context.Context, hc *http.Client, baseURL, host, qtype string) ([]netip.Addr, error) {