From ed17c2135d7fd77089b3b3a37eebc86e8dd790ae Mon Sep 17 00:00:00 2001 From: lordixir Date: Sat, 6 Dec 2025 23:29:06 +0300 Subject: [PATCH 1/4] feat(request): add proxy and custom DNS support with lazy initialization - Add HTTP/HTTPS/SOCKS5 proxy support via config.ProxyURL - Implement custom DNS resolver using config.DNSServers - Replace init() with lazy initialization via GetHTTPClient() - Auto-recreate HTTP client when proxy or DNS config changes - Add proper fallback mechanisms for DNS resolution --- modules/request.go | 145 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 125 insertions(+), 20 deletions(-) diff --git a/modules/request.go b/modules/request.go index 59e9cf3..7eab349 100644 --- a/modules/request.go +++ b/modules/request.go @@ -7,26 +7,128 @@ import ( "ipmap/config" "net" "net/http" + "net/url" "strconv" "strings" + "sync" "time" "github.com/corpix/uarand" ) -// Reusable HTTP client with connection pooling -var httpClient *http.Client +// HTTP client with lazy initialization +var ( + httpClient *http.Client + httpClientOnce sync.Once + lastProxyURL string + lastDNSServers string + clientMu sync.RWMutex +) + +// GetHTTPClient returns the HTTP client, creating or recreating if config changed +func GetHTTPClient() *http.Client { + clientMu.RLock() + currentProxy := config.ProxyURL + currentDNS := strings.Join(config.DNSServers, ",") + needsRecreate := httpClient != nil && (lastProxyURL != currentProxy || lastDNSServers != currentDNS) + clientMu.RUnlock() + + if needsRecreate { + clientMu.Lock() + // Double-check after acquiring write lock + if lastProxyURL != currentProxy || lastDNSServers != currentDNS { + httpClient = createHTTPClientWithConfig() + lastProxyURL = currentProxy + lastDNSServers = currentDNS + config.VerboseLog("HTTP client recreated with new config (Proxy: %s, DNS: %s)", currentProxy, currentDNS) + } + clientMu.Unlock() + return httpClient + } + + httpClientOnce.Do(func() { + clientMu.Lock() + defer clientMu.Unlock() + httpClient = createHTTPClientWithConfig() + lastProxyURL = config.ProxyURL + lastDNSServers = strings.Join(config.DNSServers, ",") + }) + + return httpClient +} + +// createCustomDialer creates a dialer with optional custom DNS servers +func createCustomDialer() *net.Dialer { + dialer := &net.Dialer{ + Timeout: 10 * time.Second, + KeepAlive: 30 * time.Second, + } + return dialer +} + +// createDialContext creates a DialContext function with optional custom DNS +func createDialContext() func(ctx context.Context, network, addr string) (net.Conn, error) { + dialer := createCustomDialer() + + if len(config.DNSServers) == 0 { + return dialer.DialContext + } + + // Custom DNS resolver + resolver := &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + d := net.Dialer{Timeout: 5 * time.Second} + // Use first available custom DNS server + for _, dns := range config.DNSServers { + dnsAddr := strings.TrimSpace(dns) + if !strings.Contains(dnsAddr, ":") { + dnsAddr = dnsAddr + ":53" + } + conn, err := d.DialContext(ctx, "udp", dnsAddr) + if err == nil { + return conn, nil + } + } + // Fallback to default + return d.DialContext(ctx, network, address) + }, + } + + config.VerboseLog("Using custom DNS servers: %v", config.DNSServers) + + return func(ctx context.Context, network, addr string) (net.Conn, error) { + // Split host and port + host, port, err := net.SplitHostPort(addr) + if err != nil { + return dialer.DialContext(ctx, network, addr) + } + + // Resolve using custom DNS + ips, err := resolver.LookupIPAddr(ctx, host) + if err != nil || len(ips) == 0 { + // Fallback to normal resolution + return dialer.DialContext(ctx, network, addr) + } -func init() { - httpClient = createHTTPClient() + // Try each resolved IP + for _, ip := range ips { + conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) + if err == nil { + return conn, nil + } + } + + // Fallback + return dialer.DialContext(ctx, network, addr) + } } -func createHTTPClient() *http.Client { +func createHTTPClientWithConfig() *http.Client { transport := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, MinVersion: tls.VersionTLS12, - // Allow more cipher suites for compatibility CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, @@ -36,21 +138,25 @@ func createHTTPClient() *http.Client { tls.TLS_RSA_WITH_AES_128_GCM_SHA256, }, }, - // Connection pooling - MaxIdleConns: 100, - MaxIdleConnsPerHost: 10, - IdleConnTimeout: 90 * time.Second, - // Timeouts + MaxIdleConns: 100, + MaxIdleConnsPerHost: 10, + IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ResponseHeaderTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, - // Custom dialer with timeout - DialContext: (&net.Dialer{ - Timeout: 10 * time.Second, - KeepAlive: 30 * time.Second, - }).DialContext, - // Enable HTTP/2 - ForceAttemptHTTP2: true, + DialContext: createDialContext(), + ForceAttemptHTTP2: true, + } + + // Configure proxy if specified + if config.ProxyURL != "" { + proxyURL, err := url.Parse(config.ProxyURL) + if err != nil { + config.ErrorLog("Invalid proxy URL '%s': %v", config.ProxyURL, err) + } else { + transport.Proxy = http.ProxyURL(proxyURL) + config.VerboseLog("Using proxy: %s", config.ProxyURL) + } } return &http.Client{ @@ -59,7 +165,6 @@ func createHTTPClient() *http.Client { if len(via) >= 10 { return http.ErrUseLastResponse } - // Preserve headers on redirect for key, val := range via[0].Header { if _, ok := req.Header[key]; !ok { req.Header[key] = val @@ -118,7 +223,7 @@ func RequestFuncWithRetry(ip string, url string, timeout int, maxRetries int) [] req.Header.Set("Sec-Ch-Ua-Mobile", "?0") req.Header.Set("Sec-Ch-Ua-Platform", `"Windows"`) - resp, err := httpClient.Do(req) + resp, err := GetHTTPClient().Do(req) if err != nil { cancel() // Cancel on error From cc8f0329ac0524b2e63d05ae42920afa153cc458 Mon Sep 17 00:00:00 2001 From: lordixir Date: Sat, 6 Dec 2025 23:29:39 +0300 Subject: [PATCH 2/4] feat(resolve_site): integrate rate limiter for request throttling - Create rate limiter based on config.RateLimit value - Call rateLimiter.Wait() before each request - Add verbose logging for rate limit status - Use burst size of 2x rate limit --- modules/resolve_site.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/resolve_site.go b/modules/resolve_site.go index b268600..212a789 100644 --- a/modules/resolve_site.go +++ b/modules/resolve_site.go @@ -17,6 +17,12 @@ func ResolveSite(IPAddress []string, Websites [][]string, DomainTitle string, IP config.VerboseLog("Starting scan with %d concurrent workers", workerCount) sem := make(chan struct{}, workerCount) + // Create rate limiter + rateLimiter := NewRateLimiter(config.RateLimit, config.RateLimit*2) + if rateLimiter.IsEnabled() { + config.VerboseLog("Rate limiting enabled: %d requests/second", config.RateLimit) + } + // Create progress bar bar := progressbar.NewOptions(len(IPAddress), progressbar.OptionEnableColorCodes(true), @@ -41,6 +47,9 @@ func ResolveSite(IPAddress []string, Websites [][]string, DomainTitle string, IP defer wg.Done() defer func() { <-sem }() + // Wait for rate limiter before making request + rateLimiter.Wait() + site := GetSite(ip, domain, timeout) if len(site) > 0 { From 0c2b268db69d57cd09d60a3f607f0073758ffdcb Mon Sep 17 00:00:00 2001 From: lordixir Date: Sat, 6 Dec 2025 23:29:51 +0300 Subject: [PATCH 3/4] feat(dns_resolver): add custom DNS server support for reverse lookups - Use config.DNSServers for reverse DNS lookups - Try each configured DNS server with fallback - Add proper port handling (default :53) --- modules/dns_resolver.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/modules/dns_resolver.go b/modules/dns_resolver.go index bccb2a3..e50fed5 100644 --- a/modules/dns_resolver.go +++ b/modules/dns_resolver.go @@ -4,6 +4,7 @@ import ( "context" "ipmap/config" "net" + "strings" "time" ) @@ -18,6 +19,19 @@ func ReverseDNS(ip string) string { d := net.Dialer{ Timeout: time.Second * 2, } + // Use custom DNS servers if configured + if len(config.DNSServers) > 0 { + for _, dns := range config.DNSServers { + dnsAddr := strings.TrimSpace(dns) + if !strings.Contains(dnsAddr, ":") { + dnsAddr = dnsAddr + ":53" + } + conn, err := d.DialContext(ctx, network, dnsAddr) + if err == nil { + return conn, nil + } + } + } return d.DialContext(ctx, network, address) }, } From debd4db9791bb80638ab4c0536cee83d5801768a Mon Sep 17 00:00:00 2001 From: lordixir Date: Sat, 6 Dec 2025 23:30:18 +0300 Subject: [PATCH 4/4] refactor(tools): replace global variables with local to prevent race conditions - Convert IPBlocks, IPAddress, Websites from global to local variables - Use lowercase naming convention (ipBlocks, ipAddress, websites) - Prevents data accumulation across multiple runs - Eliminates potential race condition risk --- tools/find_asn.go | 27 +++++++++++++-------------- tools/find_ip.go | 18 +++++++++++------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/tools/find_asn.go b/tools/find_asn.go index 3886bf5..6ce486d 100644 --- a/tools/find_asn.go +++ b/tools/find_asn.go @@ -8,37 +8,36 @@ import ( "time" ) -var ( - IPBlocks []string - IPAddress []string - Websites [][]string -) - func FindASN(asn string, domain string, domainTitle string, con bool, export bool, timeout int, interruptData *modules.InterruptData) { + // Use local variables instead of global to avoid race conditions + var ipBlocks []string + var ipAddress []string + var websites [][]string + re := regexp.MustCompile(`(?m)route:\s+([0-9\.\/]+)$`) for _, match := range re.FindAllStringSubmatch(modules.FindIPBlocks(asn), -1) { - IPBlocks = append(IPBlocks, match[1]) + ipBlocks = append(ipBlocks, match[1]) } - for _, block := range IPBlocks { + for _, block := range ipBlocks { ips, err := modules.CalcIPAddress(block) if err != nil { return } - IPAddress = append(IPAddress, ips...) + ipAddress = append(ipAddress, ips...) } // Update interrupt data with IP blocks if interruptData != nil { - interruptData.IPBlocks = IPBlocks + interruptData.IPBlocks = ipBlocks } fmt.Println("ASN: " + asn + - "\nIP Block: " + strconv.Itoa(len(IPBlocks)) + - "\nIP Address: " + strconv.Itoa(len(IPAddress)) + + "\nIP Block: " + strconv.Itoa(len(ipBlocks)) + + "\nIP Address: " + strconv.Itoa(len(ipAddress)) + "\nStart Time: " + time.Now().Local().String() + - "\nEnd Time: " + time.Now().Add((time.Millisecond*time.Duration(timeout))*time.Duration(len(IPAddress))).Local().String()) + "\nEnd Time: " + time.Now().Add((time.Millisecond*time.Duration(timeout))*time.Duration(len(ipAddress))).Local().String()) - modules.ResolveSite(IPAddress, Websites, domainTitle, IPBlocks, domain, con, export, timeout, interruptData) + modules.ResolveSite(ipAddress, websites, domainTitle, ipBlocks, domain, con, export, timeout, interruptData) } diff --git a/tools/find_ip.go b/tools/find_ip.go index 4c8cb8a..bbacf08 100644 --- a/tools/find_ip.go +++ b/tools/find_ip.go @@ -7,20 +7,24 @@ import ( "time" ) -func FindIP(IPBlocks []string, domain string, domainTitle string, con bool, export bool, timeout int, interruptData *modules.InterruptData) { - for _, block := range IPBlocks { +func FindIP(ipBlocks []string, domain string, domainTitle string, con bool, export bool, timeout int, interruptData *modules.InterruptData) { + // Use local variables instead of global to avoid race conditions + var ipAddress []string + var websites [][]string + + for _, block := range ipBlocks { ips, err := modules.CalcIPAddress(block) if err != nil { return } - IPAddress = append(IPAddress, ips...) + ipAddress = append(ipAddress, ips...) } - fmt.Println("IP Block: " + strconv.Itoa(len(IPBlocks)) + - "\nIP Address: " + strconv.Itoa(len(IPAddress)) + + fmt.Println("IP Block: " + strconv.Itoa(len(ipBlocks)) + + "\nIP Address: " + strconv.Itoa(len(ipAddress)) + "\nStart Time: " + time.Now().Local().String() + - "\nEnd Time: " + time.Now().Add((time.Millisecond*time.Duration(timeout))*time.Duration(len(IPAddress))).Local().String()) + "\nEnd Time: " + time.Now().Add((time.Millisecond*time.Duration(timeout))*time.Duration(len(ipAddress))).Local().String()) - modules.ResolveSite(IPAddress, Websites, domainTitle, IPBlocks, domain, con, export, timeout, interruptData) + modules.ResolveSite(ipAddress, websites, domainTitle, ipBlocks, domain, con, export, timeout, interruptData) }