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
+9 -5
View File
@@ -1,10 +1,11 @@
module evobgp
go 1.22
go 1.24.0
require (
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.7.2
github.com/miekg/dns v1.1.72
github.com/prometheus/client_golang v1.20.5
modernc.org/sqlite v1.34.5
)
@@ -24,10 +25,13 @@ require (
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/tools v0.40.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
modernc.org/libc v1.55.3 // indirect
modernc.org/mathutil v1.6.0 // indirect
+16 -12
View File
@@ -28,6 +28,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
@@ -49,19 +51,21 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+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) {
+30 -14
View File
@@ -756,21 +756,37 @@
</div>
<div class="space-y-2">
{#each logEntries as entry, idx (`${job.job_id}-${idx}`)}
<div class="rounded-md border bg-muted/30 p-3">
<p class="break-words text-sm">{entry.message}</p>
<p class="text-muted-foreground mt-1 text-xs">
source: <span class="font-mono">{entry.source}</span>
·
kind: <span class="font-mono">{entry.kind}</span>
·
community: <span class="font-mono">{entry.community}</span>
·
prefixes: <span class="font-mono">{entry.prefix_count}</span>
</p>
<div class="bg-card/90 space-y-3 rounded-lg border border-border/70 p-4 shadow-sm">
<p class="text-sm font-medium break-words leading-relaxed">{entry.message}</p>
<div class="grid gap-2 md:grid-cols-2">
<div class="bg-muted/60 flex flex-col gap-1 rounded-md border px-2.5 py-2">
<span class="text-muted-foreground text-[11px] uppercase tracking-wide">source</span>
<span class="bg-background font-mono rounded px-1.5 py-0.5 text-xs break-all">{entry.source}</span>
</div>
<div class="bg-muted/60 flex flex-col gap-1 rounded-md border px-2.5 py-2">
<span class="text-muted-foreground text-[11px] uppercase tracking-wide">kind</span>
<span class="bg-background font-mono rounded px-1.5 py-0.5 text-xs break-all">{entry.kind}</span>
</div>
<div class="bg-muted/60 flex flex-col gap-1 rounded-md border px-2.5 py-2">
<span class="text-muted-foreground text-[11px] uppercase tracking-wide">community</span>
<span class="bg-background font-mono rounded px-1.5 py-0.5 text-xs break-all">{entry.community}</span>
</div>
<div class="bg-muted/60 flex flex-col gap-1 rounded-md border px-2.5 py-2">
<span class="text-muted-foreground text-[11px] uppercase tracking-wide">prefixes</span>
<span class="bg-background font-mono rounded px-1.5 py-0.5 text-xs break-all">{entry.prefix_count}</span>
</div>
</div>
{#if entry.sample && entry.sample.length > 0}
<p class="bg-background mt-2 break-all rounded border px-2 py-1 font-mono text-xs">
{entry.sample.join(', ')}
</p>
<div class="space-y-1.5">
<p class="text-muted-foreground text-[11px] uppercase tracking-wide">sample</p>
<ScrollArea class="max-h-28 rounded-md border bg-muted/35 p-2.5">
<div class="space-y-1.5">
{#each entry.sample as sampleValue, sampleIdx (`${job.job_id}-${idx}-sample-${sampleIdx}`)}
<p class="bg-background font-mono rounded px-2 py-1 text-xs break-all">{sampleValue}</p>
{/each}
</div>
</ScrollArea>
</div>
{/if}
</div>
{/each}