Skip to content

Context Done Not Propagated in Goproxy Extension Limiter During HTTPS/CONNECT #633

@alessiodallapiazza

Description

@alessiodallapiazza

The Goproxy extension limiter fails to propagate the context Done signal correctly during HTTPS/CONNECT requests. This causes the extension to block SSL connections, resulting in undesired behavior. Steps to reproduce and potential fixes would be appreciated.

Here's an example of a minimal but sufficient piece of code to reproduce the issue:

package main

import (
	"log"
	"net/http"
	"net/url"
	"time"
	"crypto/tls"
	"context"

	"github.com/elazarl/goproxy"
)

const maxConcurrentRequests = 1

func concurrentRequests(limit int) goproxy.ReqHandler {
	if limit <= 0 {
		return goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
			return req, nil
		})
	}

	limitation := make(chan struct{}, limit)
	return goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
		log.Printf("Request received: %s %s - queue size: %d", req.Method, req.URL.String(), len(limitation))

		reqCtx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
		req = req.WithContext(reqCtx)

		select {
		case limitation <- struct{}{}:
			go func() {
				select {
				case <-reqCtx.Done():
					log.Printf("Request finished or timed out: %s %s", req.Method, req.URL.String())
					<-limitation
					cancel()
				}
			}()
			return req, nil
		case <-reqCtx.Done():
			log.Printf("Request timed out while waiting: %s %s", req.Method, req.URL.String())
			cancel()
			return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusServiceUnavailable, "Request timed out while waiting for available slot")
		}
	})
}

func startProxyServer() {
	proxy := goproxy.NewProxyHttpServer()
	proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
	proxy.Verbose = false

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
		DisableKeepAlives: true,
		ForceAttemptHTTP2: false,
	}
	proxy.Tr = tr

	proxy.OnRequest().Do(concurrentRequests(maxConcurrentRequests))

	log.Println("Starting proxy server on port 8099")
	log.Fatal(http.ListenAndServe(":8099", proxy))
}

func simulateClientRequests(proxyURL string) {
	proxy, err := url.Parse(proxyURL)
	if err != nil {
		log.Fatalf("Error parsing proxy URL: %v", err)
	}

	client := &http.Client{
		Timeout: 10 * time.Second,
		Transport: &http.Transport{
			Proxy: http.ProxyURL(proxy),
			MaxConnsPerHost: 1,
			DisableKeepAlives: true,
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
	}

	for i := 1; i <= 5; i++ {
		go func(i int) {
			req, err := http.NewRequest("GET", "https://google.com/get", nil)
			if err != nil {
				log.Fatalf("Error creating request: %v", err)
			}

			log.Printf("Client sending request %d", i)

			resp, err := client.Do(req)
			if err != nil {
				log.Printf("Request %d failed: %v", i, err)
				return
			}
			defer resp.Body.Close()

			log.Printf("Client received response for request %d: %d", i, resp.StatusCode)
		}(i)

		time.Sleep(500 * time.Millisecond)
	}
}

func main() {
	go startProxyServer()
	time.Sleep(2 * time.Second)

	proxyURL := "http://localhost:8099"
	simulateClientRequests(proxyURL)
	time.Sleep(500 * time.Second)
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions