up
Some checks failed
Go CI / test (push) Has been cancelled

This commit is contained in:
Dev
2025-09-13 12:30:01 +03:00
commit 4d51c65060
14 changed files with 1227 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
package clouddetect
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"time"
)
// Result represents the outcome of a cloud proxy detection.
type Result struct {
IsProxy bool `json:"is_proxy"`
Provider string `json:"provider,omitempty"`
Details string `json:"details,omitempty"`
}
// knownCIDRs maps providers to a slice of CIDR strings that represent their IP ranges.
// In a production implementation these would be loaded from data files; for this prototype
// a small representative subset is hardcoded.
var knownCIDRs = map[string][]string{
"Cloudflare": {
"103.21.244.0/22",
"104.16.0.0/12",
"2606:4700::/32",
},
"AWS CloudFront": {
"13.32.0.0/15",
"13.54.0.0/15",
"2600:9000::/28",
},
"Azure Front Door": {
"40.112.0.0/13",
"52.239.0.0/16",
"2603:1030::/32",
},
}
// Detect attempts to determine whether the target is behind a known cloud proxy.
// It performs DNS resolution, checks the resolved IPs against known CIDR ranges,
// and inspects HTTP response headers for provider signatures.
func Detect(target string) (*Result, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Resolve IP addresses for the target.
// Strip any port component if present (e.g., "example.com:443").
host := target
if strings.Contains(target, ":") {
if h, _, err := net.SplitHostPort(target); err == nil {
host = h
}
}
var ips []net.IP
ips, err := net.DefaultResolver.LookupIP(ctx, "ip", host)
if err != nil {
// DNS lookup failed; continue without IPs.
ips = nil
}
// Duplicate error handling removed the previous block already handles DNS failures.
// Prepare CIDR parsers.
parsedCIDRs := make(map[string][]*net.IPNet)
for prov, cidrs := range knownCIDRs {
for _, cidr := range cidrs {
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
continue // skip malformed entries
}
parsedCIDRs[prov] = append(parsedCIDRs[prov], ipnet)
}
}
// Load additional CIDR ranges from external file (cloud_ranges.txt).
if extCIDRs, err := LoadCIDRs("internal/clouddetect/cloud_ranges.txt"); err == nil {
for prov, nets := range extCIDRs {
parsedCIDRs[prov] = append(parsedCIDRs[prov], nets...)
}
}
// Check each resolved IP against the CIDR tables.
for _, ip := range ips {
for prov, nets := range parsedCIDRs {
for _, net := range nets {
if net.Contains(ip) {
return &Result{
IsProxy: true,
Provider: prov,
Details: fmt.Sprintf("IP %s matches %s range", ip, prov),
}, nil
}
}
}
}
// If no CIDR match, attempt an HTTP HEAD request to look for provider headers.
// Use the original target (host) for the request to avoid port issues.
url := fmt.Sprintf("http://%s", host)
req, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil)
if err == nil {
client := &http.Client{
Timeout: 5 * time.Second,
}
resp, err := client.Do(req)
if err == nil {
defer resp.Body.Close()
serverHeader := resp.Header.Get("Server")
viaHeader := resp.Header.Get("Via")
// Simple heuristics based on common header values.
if strings.Contains(strings.ToLower(serverHeader), "cloudflare") ||
strings.Contains(strings.ToLower(viaHeader), "cloudflare") {
return &Result{IsProxy: true, Provider: "Cloudflare", Details: "Detected via HTTP headers"}, nil
}
if strings.Contains(strings.ToLower(serverHeader), "amazon") ||
strings.Contains(strings.ToLower(viaHeader), "aws") {
return &Result{IsProxy: true, Provider: "AWS CloudFront", Details: "Detected via HTTP headers"}, nil
}
if strings.Contains(strings.ToLower(serverHeader), "azure") ||
strings.Contains(strings.ToLower(viaHeader), "azure") {
return &Result{IsProxy: true, Provider: "Azure Front Door", Details: "Detected via HTTP headers"}, nil
}
}
}
// No proxy indicators found.
return &Result{IsProxy: false}, nil
}