arblant
firefox-vpn-client / core / internal / proxy / session_test.go
session_test.go101 lines · 4.0 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
package proxy

import (
	"context"
	"errors"
	"net/http"
	"testing"
	"time"
)

func TestRoundTripWithOpenTimeout(t *testing.T) {
	t.Parallel()

	_, _, err := roundTripWithOpenTimeout(20*time.Millisecond, func(ctx context.Context) (*http.Response, error) {
		<-ctx.Done()
		return nil, ctx.Err()
	})
	var timeoutErr *tunnelOpenTimeoutError
	if !errors.As(err, &timeoutErr) {
		t.Fatalf("expected tunnelOpenTimeoutError, got %v", err)
	}
}

func TestShouldRebuildProxySession(t *testing.T) {
	t.Parallel()

	if shouldRebuildProxySession(&proxyConnectHTTPError{statusCode: http.StatusBadGateway, status: "502 Bad Gateway"}) {
		t.Fatal("expected target/proxy CONNECT 502 to avoid session rebuild")
	}
	if !shouldRebuildProxySession(&proxyConnectHTTPError{statusCode: http.StatusProxyAuthRequired, status: "407 Proxy Authentication Required"}) {
		t.Fatal("expected proxy auth failure to rebuild session")
	}
	if shouldRebuildProxySession(&tunnelOpenTimeoutError{timeout: time.Second}) {
		t.Fatal("expected CONNECT timeout to avoid session rebuild")
	}
	if !shouldRebuildProxySession(errors.New("transport closed")) {
		t.Fatal("expected transport errors to rebuild session")
	}
}

func TestSessionFailureTrackerRequiresDistinctTargets(t *testing.T) {
	t.Parallel()

	tracker := &sessionFailureTracker{}
	timeoutErr := &tunnelOpenTimeoutError{timeout: time.Second}
	for i := 0; i < 5; i++ {
		if err := tracker.observe("same.example:443", timeoutErr); errors.Is(err, errProxySessionUnhealthy) {
			t.Fatal("repeated failure for one target should not retire the session")
		}
	}
	if err := tracker.observe("second.example:443", timeoutErr); errors.Is(err, errProxySessionUnhealthy) {
		t.Fatal("two failed targets should not retire the session")
	}
	if err := tracker.observe("third.example:443", timeoutErr); !errors.Is(err, errProxySessionUnhealthy) {
		t.Fatalf("expected three distinct timeout targets to retire the session, got %v", err)
	}

	tracker.observe("healthy.example:443", nil)
	badGateway := &proxyConnectHTTPError{statusCode: http.StatusBadGateway, status: "502 Bad Gateway"}
	if err := tracker.observe("one.example:443", badGateway); errors.Is(err, errProxySessionUnhealthy) {
		t.Fatal("one 502 target should not retire the session")
	}
	if err := tracker.observe("two.example:443", badGateway); errors.Is(err, errProxySessionUnhealthy) {
		t.Fatal("two 502 targets should not retire the session")
	}
	if err := tracker.observe("three.example:443", badGateway); !errors.Is(err, errProxySessionUnhealthy) {
		t.Fatalf("expected three distinct 502 targets to retire the session, got %v", err)
	}
}

// Mozilla's proxy answers 502 for ports it does not connect to; such
// answers neither retire the session nor reset the count of real failures.
func TestRefusedPortDoesNotRetireSession(t *testing.T) {
	t.Parallel()

	tracker := &sessionFailureTracker{}
	badGateway := &proxyConnectHTTPError{statusCode: http.StatusBadGateway, status: "502 Bad Gateway"}
	for _, target := range []string{"a.example:9898", "b.example:22", "c.example:3000", "203.0.113.5:81", "[2001:db8::1]:8000"} {
		if !portRefused(target, badGateway) {
			t.Fatalf("%s: expected a refused port", target)
		}
		if err := tracker.observe(target, badGateway); errors.Is(err, errProxySessionUnhealthy) {
			t.Fatalf("%s: a refused port must not retire the session", target)
		}
	}
	for _, target := range []string{"web.example:80", "web.example:443", "web.example:8080", "web.example:8443"} {
		if portRefused(target, badGateway) {
			t.Fatalf("%s: a web port is not refused by policy", target)
		}
	}
	if portRefused("a.example:9898", &proxyConnectHTTPError{statusCode: http.StatusForbidden}) {
		t.Fatal("only 502 means a refused port")
	}

	tracker.observe("one.example:443", badGateway)
	tracker.observe("two.example:443", badGateway)
	tracker.observe("other.example:9898", badGateway)
	if err := tracker.observe("three.example:443", badGateway); !errors.Is(err, errProxySessionUnhealthy) {
		t.Fatalf("a refused port in between must not reset real failures, got %v", err)
	}
}