CI / changes (push) Successful in 6s
CI / openapi (push) Successful in 1m2s
CI / go (push) Successful in 27s
CI / docker-web (push) Successful in 1m27s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 16s
CI / docker-go (push) Successful in 8m5s
- Added `DohResolverPolicy` schema to OpenAPI documentation, defining policies for domain resolution. - Updated module handling to support multiple DoH profiles via `doh_profile_ids` and introduced `doh_resolver_policy` in the API. - Refactored related functions to accommodate the new DoH profile structure, ensuring backward compatibility with existing `doh_profile_id`. - Enhanced UI components to allow selection and management of DoH profiles and policies in the web interface. - Updated database interactions to handle new fields and ensure proper data normalization.
132 lines
3.7 KiB
Go
132 lines
3.7 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/netip"
|
|
"strings"
|
|
"time"
|
|
|
|
"evobgp/internal/store"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
func loadModuleDohProfiles(st store.Backend, tenantID string, mod *store.Module) ([]*store.DohProfile, string, error) {
|
|
if mod == nil {
|
|
return nil, store.DohPolicyPrimaryOnly, nil
|
|
}
|
|
policy := store.NormalizeDohResolverPolicy(mod.DohResolverPolicy)
|
|
var profiles []*store.DohProfile
|
|
for _, id := range mod.EffectiveDohProfileIDs() {
|
|
prof, err := st.GetDohProfile(tenantID, id)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("get doh profile %s: %w", id, err)
|
|
}
|
|
profiles = append(profiles, prof)
|
|
}
|
|
return profiles, policy, nil
|
|
}
|
|
|
|
func resolveDomainIPsWithPolicy(ctx context.Context, hc *http.Client, profiles []*store.DohProfile, policy, fqdn string) ([]netip.Addr, error) {
|
|
policy = store.NormalizeDohResolverPolicy(policy)
|
|
if len(profiles) == 0 {
|
|
return resolveDomainIPs(ctx, hc, nil, fqdn)
|
|
}
|
|
if len(profiles) == 1 {
|
|
return resolveDomainIPs(ctx, hc, profiles[0], fqdn)
|
|
}
|
|
|
|
switch policy {
|
|
case store.DohPolicyUnion:
|
|
return resolveDomainIPsUnion(ctx, hc, profiles, fqdn)
|
|
case store.DohPolicyFailover:
|
|
return resolveDomainIPsFailover(ctx, hc, profiles, fqdn)
|
|
default:
|
|
return resolveDomainIPs(ctx, hc, profiles[0], fqdn)
|
|
}
|
|
}
|
|
|
|
func resolveDomainIPsUnion(ctx context.Context, hc *http.Client, profiles []*store.DohProfile, fqdn string) ([]netip.Addr, error) {
|
|
var merged []netip.Addr
|
|
var errs []error
|
|
for _, prof := range profiles {
|
|
if prof == nil {
|
|
continue
|
|
}
|
|
ips, err := resolveDomainIPsNoSystemFallback(ctx, hc, prof, fqdn)
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("%s: %w", strings.TrimSpace(prof.URL), err))
|
|
continue
|
|
}
|
|
merged = append(merged, ips...)
|
|
}
|
|
merged = uniqAddrs(merged)
|
|
if len(merged) > 0 {
|
|
return merged, nil
|
|
}
|
|
if len(errs) > 0 {
|
|
return nil, fmt.Errorf("doh union failed: %v", errs)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func resolveDomainIPsFailover(ctx context.Context, hc *http.Client, profiles []*store.DohProfile, fqdn string) ([]netip.Addr, error) {
|
|
var lastErr error
|
|
for _, prof := range profiles {
|
|
if prof == nil {
|
|
continue
|
|
}
|
|
ips, err := resolveDomainIPsNoSystemFallback(ctx, hc, prof, fqdn)
|
|
if err != nil {
|
|
lastErr = err
|
|
continue
|
|
}
|
|
if len(ips) > 0 {
|
|
return ips, nil
|
|
}
|
|
}
|
|
if lastErr != nil {
|
|
return nil, lastErr
|
|
}
|
|
return resolveDomainIPs(ctx, hc, nil, fqdn)
|
|
}
|
|
|
|
// resolveDomainIPsNoSystemFallback queries one DoH profile without falling back to OS resolver.
|
|
func resolveDomainIPsNoSystemFallback(ctx context.Context, hc *http.Client, profile *store.DohProfile, fqdn string) ([]netip.Addr, error) {
|
|
host := strings.TrimSpace(strings.TrimSuffix(fqdn, "."))
|
|
if host == "" {
|
|
return nil, nil
|
|
}
|
|
if profile == nil || strings.TrimSpace(profile.URL) == "" {
|
|
return nil, fmt.Errorf("empty doh profile")
|
|
}
|
|
|
|
timeout := dohProfileTimeout(profile)
|
|
dctx, cancel := context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
|
|
baseURL := strings.TrimSpace(profile.URL)
|
|
v4, err4 := resolveDomainWithDOHMessage(dctx, hc, baseURL, host, dns.TypeA)
|
|
v6, err6 := resolveDomainWithDOHMessage(dctx, hc, baseURL, host, dns.TypeAAAA)
|
|
if err4 != nil {
|
|
v4, err4 = resolveDomainWithDOHJSON(dctx, hc, baseURL, host, "A")
|
|
}
|
|
if err6 != nil {
|
|
v6, err6 = resolveDomainWithDOHJSON(dctx, hc, baseURL, host, "AAAA")
|
|
}
|
|
if err4 != nil && err6 != nil {
|
|
return nil, fmt.Errorf("doh failed for A and AAAA: %v; %v", err4, err6)
|
|
}
|
|
return uniqAddrs(append(v4, v6...)), nil
|
|
}
|
|
|
|
func dohProfileTimeout(profile *store.DohProfile) time.Duration {
|
|
timeout := 10 * time.Second
|
|
if profile != nil && profile.TimeoutMs != nil && *profile.TimeoutMs > 0 {
|
|
timeout = time.Duration(*profile.TimeoutMs) * time.Millisecond
|
|
}
|
|
return timeout
|
|
}
|