diff --git a/client/main.go b/client/main.go index 73dfd17..f3314f6 100644 --- a/client/main.go +++ b/client/main.go @@ -328,7 +328,7 @@ func ParseVkCaptchaError(errData map[string]interface{}) *VkCaptchaError { return nil } - // Extract session token if redirect_uri present + // Extract session token var sessionToken string if RedirectURI != "" { if parsed, err := neturl.Parse(RedirectURI); err == nil { @@ -338,6 +338,12 @@ func ParseVkCaptchaError(errData map[string]interface{}) *VkCaptchaError { return nil } } + // Fallback to top-level session_token field if not in redirect_uri + if sessionToken == "" { + if st, ok := errData["session_token"].(string); ok { + sessionToken = st + } + } // Extract is_sound_captcha_available isSound, ok := errData["is_sound_captcha_available"].(bool) @@ -1783,7 +1789,31 @@ func oneTurnConnectionLoop(ctx context.Context, turnParams *turnParams, peer *ne } } +func setupGlobalResolver() { + dialer := &net.Dialer{ + Timeout: 5 * time.Second, + KeepAlive: 30 * time.Second, + } + dnsServers := []string{"77.88.8.8:53", "77.88.8.1:53", "8.8.8.8:53", "8.8.4.4:53", "1.1.1.1:53", "1.0.0.1:53"} + + net.DefaultResolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + var lastErr error + for _, dns := range dnsServers { + conn, err := dialer.DialContext(ctx, "udp", dns) + if err == nil { + return conn, nil + } + lastErr = err + } + return nil, lastErr + }, + } +} + func main() { + setupGlobalResolver() ctx, cancel := context.WithCancel(context.Background()) globalAppCancel = cancel defer cancel() @@ -2047,12 +2077,15 @@ func runVLESSMode(ctx context.Context, tp *turnParams, peer *net.UDPAddr, listen if err != nil { log.Panicf("TCP listen: %s", err) } - context.AfterFunc(ctx, func() { _ = listener.Close() }) + + wrappedListener := listener + + context.AfterFunc(ctx, func() { _ = wrappedListener.Close() }) log.Printf("VLESS mode: listening on %s (round-robin across %d sessions)", listenAddr, numSessions) var wgConn sync.WaitGroup for { - tcpConn, err := listener.Accept() + tcpConn, err := wrappedListener.Accept() if err != nil { select { case <-ctx.Done(): diff --git a/client/manual_captcha.go b/client/manual_captcha.go index 826478c..61267f6 100644 --- a/client/manual_captcha.go +++ b/client/manual_captcha.go @@ -14,6 +14,7 @@ import ( "net/http/httputil" neturl "net/url" "os/exec" + "regexp" "runtime" "strings" "time" @@ -164,11 +165,68 @@ func rewriteProxyCookies(header http.Header) { } } +// htmlURLAttrDoubleRe matches src/href/action attributes with double-quoted absolute or protocol-relative URLs. +var htmlURLAttrDoubleRe = regexp.MustCompile(`(?i)((?:src|href|action)\s*=\s*)"((?:https?:)?//[^"]+)"`) + +// htmlURLAttrSingleRe matches src/href/action attributes with single-quoted absolute or protocol-relative URLs. +var htmlURLAttrSingleRe = regexp.MustCompile(`(?i)((?:src|href|action)\s*=\s*)'((?:https?:)?//[^']+)'`) + +// rewriteHTMLAttrsServerSide rewrites absolute and protocol-relative URLs in src/href/action +// attributes of raw HTML. URLs matching the upstream origin are redirected to localhost; +// all other absolute URLs are routed through /generic_proxy so that cross-domain resources +// (st.vk.com, userapi.com, etc.) load correctly through the proxy. +func rewriteHTMLAttrsServerSide(html string, targetURL *neturl.URL) string { + localOrigin := localCaptchaOrigin() + upstreamOrigin := targetOrigin(targetURL) + + rewriteURL := func(rawURL string) string { + // Normalise protocol-relative URL to absolute using the upstream scheme + absURL := rawURL + if strings.HasPrefix(rawURL, "//") { + absURL = targetURL.Scheme + ":" + rawURL + } + if strings.HasPrefix(absURL, upstreamOrigin) { + return localOrigin + absURL[len(upstreamOrigin):] + } + // Already points to local proxy — leave as-is + if strings.HasPrefix(absURL, localOrigin) { + return rawURL + } + // Any other absolute URL → route through generic_proxy + return "/generic_proxy?proxy_url=" + neturl.QueryEscape(absURL) + } + + html = htmlURLAttrDoubleRe.ReplaceAllStringFunc(html, func(match string) string { + groups := htmlURLAttrDoubleRe.FindStringSubmatch(match) + if len(groups) < 3 { + return match + } + return groups[1] + `"` + rewriteURL(groups[2]) + `"` + }) + + html = htmlURLAttrSingleRe.ReplaceAllStringFunc(html, func(match string) string { + groups := htmlURLAttrSingleRe.FindStringSubmatch(match) + if len(groups) < 3 { + return match + } + return groups[1] + `'` + rewriteURL(groups[2]) + `'` + }) + + return html +} + func rewriteCaptchaHTML(html string, targetURL *neturl.URL) string { localOrigin := localCaptchaOrigin() upstreamOrigin := targetOrigin(targetURL) + + // Step 1: plain text replacement for the primary upstream origin html = strings.ReplaceAll(html, upstreamOrigin, localOrigin) + // Step 2: rewrite all other absolute URLs in HTML attributes server-side. + // This is critical: the browser begins downloading `, localOrigin, upstreamOrigin) + // Step 3: inject the client-side script as early as possible — at the opening
tag + // so that XHR/fetch overrides are active before any inline