Files
elmprodvpn/selective-vpn-api/app/dns_settings_benchmark_handler.go

141 lines
3.3 KiB
Go

package app
import (
"encoding/json"
"io"
"net/http"
dnscfgpkg "selective-vpn-api/app/dnscfg"
"sort"
"sync"
"time"
)
func handleDNSBenchmark(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
var req DNSBenchmarkRequest
if r.Body != nil {
defer r.Body.Close()
if err := json.NewDecoder(io.LimitReader(r.Body, 1<<20)).Decode(&req); err != nil && err != io.EOF {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
}
upstreams := normalizeBenchmarkUpstreams(req.Upstreams)
if len(upstreams) == 0 {
pool := loadDNSUpstreamPoolState()
if len(pool) > 0 {
tmp := make([]DNSBenchmarkUpstream, 0, len(pool))
for _, item := range pool {
tmp = append(tmp, DNSBenchmarkUpstream{Addr: item.Addr, Enabled: item.Enabled})
}
upstreams = normalizeBenchmarkUpstreams(tmp)
}
if len(upstreams) == 0 {
cfg := loadDNSUpstreamsConf()
upstreams = dnscfgpkg.NormalizeBenchmarkUpstreamStrings([]string{
cfg.Default1,
cfg.Default2,
cfg.Meta1,
cfg.Meta2,
}, normalizeDNSUpstream)
}
}
if len(upstreams) == 0 {
http.Error(w, "no upstreams", http.StatusBadRequest)
return
}
domains := dnscfgpkg.NormalizeBenchmarkDomains(req.Domains)
if len(domains) == 0 {
domains = append(domains, dnsBenchmarkDefaultDomains...)
}
timeoutMS := req.TimeoutMS
if timeoutMS <= 0 {
timeoutMS = 1800
}
if timeoutMS < 300 {
timeoutMS = 300
}
if timeoutMS > 5000 {
timeoutMS = 5000
}
attempts := req.Attempts
if attempts <= 0 {
attempts = 1
}
if attempts > 3 {
attempts = 3
}
profile := dnscfgpkg.NormalizeBenchmarkProfile(req.Profile)
if profile == dnsBenchmarkProfileLoad && attempts < 2 {
// Load profile should emulate real resolver pressure.
attempts = 2
}
concurrency := req.Concurrency
if concurrency <= 0 {
concurrency = 6
}
if concurrency < 1 {
concurrency = 1
}
if concurrency > 32 {
concurrency = 32
}
if concurrency > len(upstreams) {
concurrency = len(upstreams)
}
opts := dnscfgpkg.MakeDNSBenchmarkOptions(profile, concurrency)
results := make([]DNSBenchmarkResult, 0, len(upstreams))
var mu sync.Mutex
var wg sync.WaitGroup
sem := make(chan struct{}, concurrency)
timeout := time.Duration(timeoutMS) * time.Millisecond
for _, upstream := range upstreams {
wg.Add(1)
sem <- struct{}{}
go func(upstream string) {
defer wg.Done()
defer func() { <-sem }()
result := benchmarkDNSUpstream(upstream, domains, timeout, attempts, opts)
mu.Lock()
results = append(results, result)
mu.Unlock()
}(upstream)
}
wg.Wait()
sort.Slice(results, func(i, j int) bool {
if results[i].Score == results[j].Score {
if results[i].AvgMS == results[j].AvgMS {
if results[i].OK == results[j].OK {
return results[i].Upstream < results[j].Upstream
}
return results[i].OK > results[j].OK
}
return results[i].AvgMS < results[j].AvgMS
}
return results[i].Score > results[j].Score
})
resp := DNSBenchmarkResponse{
Results: results,
DomainsUsed: domains,
TimeoutMS: timeoutMS,
AttemptsPerDomain: attempts,
Profile: profile,
}
resp.RecommendedDefault = benchmarkTopN(results, 2, upstreams)
resp.RecommendedMeta = benchmarkTopN(results, 2, upstreams)
writeJSON(w, http.StatusOK, resp)
}