mirror of
https://github.com/qdm12/gluetun.git
synced 2026-05-07 04:20:12 +02:00
4a78989d9d
- main reason being it's a burden to always define sentinel errors at global scope, wrap them with `%w` instead of using a string directly - only use sentinel errors when it has to be checked using `errors.Is` - replace all usage of these sentinel errors in `fmt.Errorf` with direct strings that were in the sentinel error - exclude the sentinel error definition requirement from .golangci.yml - update unit tests to use ContainersError instead of ErrorIs so it stays as a "not a change detector test" without requiring a sentinel error
129 lines
2.9 KiB
Go
129 lines
2.9 KiB
Go
package dns
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"math/rand/v2"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
func leakCheck(ctx context.Context, client *http.Client) (report string, err error) {
|
|
const sessionLength = 40
|
|
session := generateRandomString(sessionLength)
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
type result struct {
|
|
dnsToCount map[string]uint
|
|
err error
|
|
}
|
|
resultsCh := make(chan result)
|
|
|
|
const requestsCount = 5
|
|
for range requestsCount {
|
|
go func() {
|
|
dnsToCount, err := triggerDNSQuery(ctx, client, session)
|
|
resultsCh <- result{dnsToCount: dnsToCount, err: err}
|
|
}()
|
|
}
|
|
|
|
dnsToCount := make(map[string]uint)
|
|
for range requestsCount {
|
|
result := <-resultsCh
|
|
if result.err != nil {
|
|
if err == nil {
|
|
cancel()
|
|
err = fmt.Errorf("request failed: %w", result.err)
|
|
}
|
|
continue
|
|
}
|
|
for dns, count := range result.dnsToCount {
|
|
dnsToCount[dns] += count
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return formatPercentages(dnsToCount), nil
|
|
}
|
|
|
|
func generateRandomString(length uint) string {
|
|
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
b := make([]byte, length)
|
|
for i := range b {
|
|
b[i] = charset[rand.IntN(len(charset))] //nolint:gosec
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func triggerDNSQuery(ctx context.Context, client *http.Client, session string) (
|
|
dnsToCount map[string]uint, err error,
|
|
) {
|
|
const randomLength = 12
|
|
randomPart := generateRandomString(randomLength)
|
|
url := fmt.Sprintf("https://%s-%s.ipleak.net/dnsdetection/", session, randomPart)
|
|
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating request: %w", err)
|
|
}
|
|
|
|
response, err := client.Do(request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("performing request: %w", err)
|
|
}
|
|
defer response.Body.Close()
|
|
|
|
type ipLeakData struct {
|
|
Session string `json:"session"`
|
|
IP map[string]uint `json:"ip"`
|
|
}
|
|
|
|
decoder := json.NewDecoder(response.Body)
|
|
var data ipLeakData
|
|
err = decoder.Decode(&data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decoding response: %w", err)
|
|
} else if data.Session != session {
|
|
return nil, fmt.Errorf("ipleak.net session mismatch: expected %s, got %s", session, data.Session)
|
|
}
|
|
|
|
return data.IP, nil
|
|
}
|
|
|
|
func formatPercentages(data map[string]uint) string {
|
|
if len(data) == 0 {
|
|
return ""
|
|
}
|
|
|
|
var total uint
|
|
keys := make([]string, 0, len(data))
|
|
for k, v := range data {
|
|
total += v
|
|
keys = append(keys, k)
|
|
}
|
|
|
|
sort.Slice(keys, func(i, j int) bool {
|
|
if data[keys[i]] == data[keys[j]] {
|
|
return keys[i] < keys[j] // Tie-breaker: alphabetical
|
|
}
|
|
return data[keys[i]] > data[keys[j]]
|
|
})
|
|
|
|
results := make([]string, len(keys))
|
|
for i, key := range keys {
|
|
var pct float64
|
|
if total > 0 {
|
|
pct = math.Ceil((float64(data[key]) / float64(total)) * 100) //nolint:mnd
|
|
}
|
|
results[i] = fmt.Sprintf("%s (%.0f%%)", key, pct)
|
|
}
|
|
|
|
return strings.Join(results, ", ")
|
|
}
|