platform: modularize api/gui, add docs-tests-web foundation, and refresh root config

This commit is contained in:
beckline
2026-03-26 22:40:54 +03:00
parent 0e2d7f61ea
commit 6a56d734c2
562 changed files with 70151 additions and 16423 deletions

View File

@@ -0,0 +1,358 @@
package dnscfg
import (
"context"
"fmt"
"net"
"sort"
"strings"
"sync"
"time"
)
const (
BenchmarkProfileQuick = "quick"
BenchmarkProfileLoad = "load"
BenchmarkErrorNXDomain = "nxdomain"
BenchmarkErrorTimeout = "timeout"
BenchmarkErrorTemporary = "temporary"
BenchmarkErrorOther = "other"
)
var BenchmarkDefaultDomains = []string{
"cloudflare.com",
"google.com",
"telegram.org",
"github.com",
"youtube.com",
"twitter.com",
}
type BenchmarkOptions struct {
Profile string
LoadWorkers int
Rounds int
SyntheticPerDomain int
}
type BenchmarkResult struct {
Upstream string
Attempts int
OK int
Fail int
NXDomain int
Timeout int
Temporary int
Other int
AvgMS int
P95MS int
Score float64
Color string
}
func NormalizeBenchmarkProfile(raw string) string {
switch strings.ToLower(strings.TrimSpace(raw)) {
case "", BenchmarkProfileLoad:
return BenchmarkProfileLoad
case BenchmarkProfileQuick:
return BenchmarkProfileQuick
default:
return BenchmarkProfileLoad
}
}
func MakeDNSBenchmarkOptions(profile string, concurrency int) BenchmarkOptions {
if concurrency < 1 {
concurrency = 1
}
if profile == BenchmarkProfileQuick {
return BenchmarkOptions{
Profile: BenchmarkProfileQuick,
LoadWorkers: 1,
Rounds: 1,
SyntheticPerDomain: 0,
}
}
workers := concurrency * 2
if workers < 4 {
workers = 4
}
if workers > 16 {
workers = 16
}
return BenchmarkOptions{
Profile: BenchmarkProfileLoad,
LoadWorkers: workers,
Rounds: 3,
SyntheticPerDomain: 2,
}
}
func NormalizeBenchmarkUpstreamStrings(in []string, normalizeUpstream func(string, string) string) []string {
out := make([]string, 0, len(in))
seen := map[string]struct{}{}
for _, raw := range in {
n := strings.TrimSpace(raw)
if normalizeUpstream != nil {
n = normalizeUpstream(n, "53")
}
if n == "" {
continue
}
if _, ok := seen[n]; ok {
continue
}
seen[n] = struct{}{}
out = append(out, n)
}
return out
}
func NormalizeBenchmarkDomains(in []string) []string {
if len(in) == 0 {
return nil
}
out := make([]string, 0, len(in))
seen := map[string]struct{}{}
for _, raw := range in {
d := strings.TrimSuffix(strings.ToLower(strings.TrimSpace(raw)), ".")
if d == "" || strings.HasPrefix(d, "#") {
continue
}
if _, ok := seen[d]; ok {
continue
}
seen[d] = struct{}{}
out = append(out, d)
}
if len(out) > 100 {
out = out[:100]
}
return out
}
func BenchmarkDNSUpstream(
upstream string,
domains []string,
timeout time.Duration,
attempts int,
opts BenchmarkOptions,
lookupAOnce func(host, upstream string, timeout time.Duration) ([]string, error),
classifyErr func(error) string,
) BenchmarkResult {
res := BenchmarkResult{Upstream: upstream}
probes := BuildBenchmarkProbeHosts(domains, attempts, opts)
if len(probes) == 0 {
return res
}
durations := make([]int, 0, len(probes))
var mu sync.Mutex
jobs := make(chan string, len(probes))
workers := opts.LoadWorkers
if workers < 1 {
workers = 1
}
if workers > len(probes) {
workers = len(probes)
}
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for host := range jobs {
start := time.Now()
_, err := lookupAOnce(host, upstream, timeout)
elapsed := int(time.Since(start).Milliseconds())
if elapsed < 1 {
elapsed = 1
}
mu.Lock()
res.Attempts++
durations = append(durations, elapsed)
if err != nil {
res.Fail++
switch strings.ToLower(strings.TrimSpace(classifyErr(err))) {
case BenchmarkErrorNXDomain:
res.NXDomain++
case BenchmarkErrorTimeout:
res.Timeout++
case BenchmarkErrorTemporary:
res.Temporary++
default:
res.Other++
}
} else {
res.OK++
}
mu.Unlock()
}
}()
}
for _, host := range probes {
jobs <- host
}
close(jobs)
wg.Wait()
if len(durations) > 0 {
sort.Ints(durations)
sum := 0
for _, d := range durations {
sum += d
}
res.AvgMS = sum / len(durations)
idx := int(float64(len(durations)-1) * 0.95)
if idx < 0 {
idx = 0
}
res.P95MS = durations[idx]
}
total := res.Attempts
if total > 0 {
okRate := float64(res.OK) / float64(total)
answeredRate := float64(res.OK+res.NXDomain+res.Temporary+res.Other) / float64(total)
timeoutRate := float64(res.Timeout) / float64(total)
temporaryRate := float64(res.Temporary) / float64(total)
otherRate := float64(res.Other) / float64(total)
avg := float64(res.AvgMS)
if avg <= 0 {
avg = float64(timeout.Milliseconds())
}
p95 := float64(res.P95MS)
if p95 <= 0 {
p95 = avg
}
res.Score = answeredRate*100.0 + okRate*15.0 - timeoutRate*120.0 - temporaryRate*35.0 - otherRate*20.0 - (avg / 25.0) - (p95 / 45.0)
}
timeoutRate := 0.0
answeredRate := 0.0
if res.Attempts > 0 {
timeoutRate = float64(res.Timeout) / float64(res.Attempts)
answeredRate = float64(res.OK+res.NXDomain+res.Temporary+res.Other) / float64(res.Attempts)
}
switch {
case answeredRate < 0.85 || timeoutRate >= 0.10 || res.P95MS > 1800:
res.Color = "red"
case answeredRate >= 0.97 && timeoutRate <= 0.02 && res.P95MS <= 700:
res.Color = "green"
default:
res.Color = "yellow"
}
return res
}
func BuildBenchmarkProbeHosts(domains []string, attempts int, opts BenchmarkOptions) []string {
if len(domains) == 0 {
return nil
}
if attempts < 1 {
attempts = 1
}
rounds := opts.Rounds
if rounds < 1 {
rounds = 1
}
synth := opts.SyntheticPerDomain
if synth < 0 {
synth = 0
}
out := make([]string, 0, len(domains)*attempts*rounds*(1+synth))
for round := 0; round < rounds; round++ {
for _, host := range domains {
for i := 0; i < attempts; i++ {
out = append(out, host)
}
for n := 0; n < synth; n++ {
out = append(out, fmt.Sprintf("svpn-bench-%d-%d.%s", round+1, n+1, host))
}
}
}
if len(out) > 10000 {
out = out[:10000]
}
return out
}
func DNSLookupAOnce(
host string,
upstream string,
timeout time.Duration,
splitDNS func(string) (string, string),
isPrivateIPv4 func(string) bool,
) ([]string, error) {
if splitDNS == nil {
return nil, fmt.Errorf("splitDNS callback is nil")
}
server, port := splitDNS(upstream)
if server == "" {
return nil, fmt.Errorf("upstream empty")
}
if port == "" {
port = "53"
}
addr := net.JoinHostPort(server, port)
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{}
return d.DialContext(ctx, "udp", addr)
},
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
records, err := resolver.LookupHost(ctx, host)
cancel()
if err != nil {
return nil, err
}
seen := map[string]struct{}{}
out := make([]string, 0, len(records))
for _, ip := range records {
if isPrivateIPv4 != nil && isPrivateIPv4(ip) {
continue
}
if _, ok := seen[ip]; ok {
continue
}
seen[ip] = struct{}{}
out = append(out, ip)
}
if len(out) == 0 {
return nil, fmt.Errorf("no public ips")
}
return out, nil
}
func BenchmarkTopN(results []BenchmarkResult, n int, fallback []string) []string {
out := make([]string, 0, n)
for _, item := range results {
if item.OK <= 0 {
continue
}
out = append(out, item.Upstream)
if len(out) >= n {
return out
}
}
for _, item := range fallback {
if len(out) >= n {
break
}
dup := false
for _, e := range out {
if e == item {
dup = true
break
}
}
if !dup {
out = append(out, item)
}
}
return out
}