spb/internetpressure
Public
TypeScript 36.3%
Python 31.8%
Go 18%
JavaScript 9.8%
Shell 1.9%
SQL 1.4%
CSS 0.5%
1package http23import (4 "context"5 "crypto/tls"6 "crypto/x509"7 "errors"8 "io"9 "net"10 "net/http"11 "net/http/httptest"12 "net/url"13 "os"14 "strings"15 "syscall"16 "testing"1718 "internetpressure.io/probe-agent/internal/protocol"19)2021type timeoutErr struct{}2223func (timeoutErr) Error() string { return "i/o timeout" }24func (timeoutErr) Timeout() bool { return true }25func (timeoutErr) Temporary() bool { return true }2627func TestClassify(t *testing.T) {28 wrap := func(e error) error { return &url.Error{Op: "Get", URL: "https://x/", Err: e} }29 cases := []struct {30 name string31 err error32 st stage33 want string34 }{35 {"dns", wrap(&net.DNSError{Err: "no such host", IsNotFound: true}), stageDNS, protocol.ErrDNSFail},36 {"dns timeout", wrap(&net.DNSError{Err: "timeout", IsTimeout: true}), stageDNS, protocol.ErrDNSFail},37 {"refused", wrap(&net.OpError{Op: "dial", Err: &os.SyscallError{Syscall: "connect", Err: syscall.ECONNREFUSED}}), stageConnect, protocol.ErrTCPRefused},38 {"connect timeout", wrap(timeoutErr{}), stageConnect, protocol.ErrTCPTimeout},39 {"tls timeout", wrap(timeoutErr{}), stageTLS, protocol.ErrTLSFail},40 {"http timeout", wrap(context.DeadlineExceeded), stageResponse, protocol.ErrHTTPTimeout},41 {"client timeout string", wrap(errors.New("net/http: request canceled (Client.Timeout exceeded while awaiting headers)")), stageResponse, protocol.ErrHTTPTimeout},42 {"reset early", wrap(syscall.ECONNRESET), stageConnect, protocol.ErrTCPReset},43 {"reset late", wrap(syscall.ECONNRESET), stageResponse, protocol.ErrReset},44 {"cert unknown authority", wrap(x509.UnknownAuthorityError{}), stageTLS, protocol.ErrTLSCert},45 {"cert hostname", wrap(x509.HostnameError{Host: "x"}), stageTLS, protocol.ErrTLSCert},46 {"cert verification", wrap(&tls.CertificateVerificationError{Err: errors.New("expired")}), stageTLS, protocol.ErrTLSCert},47 {"tls record", wrap(tls.RecordHeaderError{Msg: "first record does not look like a TLS handshake"}), stageTLS, protocol.ErrTLSFail},48 {"tls alert", wrap(tls.AlertError(40)), stageTLS, protocol.ErrTLSFail},49 {"eof during tls", wrap(io.EOF), stageTLS, protocol.ErrTLSFail},50 {"eof during response", wrap(io.ErrUnexpectedEOF), stageResponse, protocol.ErrReset},51 {"unknown", wrap(errors.New("something odd")), stageResponse, protocol.ErrOther},52 {"nil", nil, stageResponse, ""},53 }54 for _, c := range cases {55 if got := classify(c.err, c.st); got != c.want {56 t.Errorf("%s: got %q want %q", c.name, got, c.want)57 }58 }59}6061// End-to-end against a local TLS server: status codes, no-redirect, body cap and cert error mapping.62func TestRunAgainstLocalServer(t *testing.T) {63 big := strings.Repeat("x", 1<<20)64 srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {65 if !strings.HasPrefix(r.UserAgent(), "InternetPressureProbe/") {66 t.Errorf("unexpected UA %q", r.UserAgent())67 }68 switch r.URL.Path {69 case "/redirect":70 http.Redirect(w, r, "/elsewhere", http.StatusFound)71 case "/boom":72 w.WriteHeader(503)73 case "/missing":74 w.WriteHeader(404)75 default:76 w.WriteHeader(200)77 io.WriteString(w, big)78 }79 }))80 srv.EnableHTTP2 = true81 srv.StartTLS()82 defer srv.Close()8384 u, _ := url.Parse(srv.URL)85 host, port, _ := net.SplitHostPort(u.Host)86 c := &Checker{UserAgent: "InternetPressureProbe/test (+https://www.internetpressure.io/probes)"}8788 // 1. Untrusted certificate → tls_cert, not ok.89 target := protocol.Target{TargetID: "local", Hostname: host, URL: srv.URL + "/", Port: 443}90 m := c.Run(context.Background(), target)91 if m.OK || m.Error != protocol.ErrTLSCert {92 t.Fatalf("expected tls_cert, got ok=%v error=%q", m.OK, m.Error)93 }94 if m.TCPMs == nil || m.TotalMs == nil {95 t.Fatalf("tcp/total should be measured even when TLS fails: %+v", m)96 }9798 // Trust the test CA for the remaining cases by swapping the run function's TLS root via a helper.99 pool := x509.NewCertPool()100 pool.AddCert(srv.Certificate())101 run := func(path string) protocol.Measurement {102 return runWithRoots(c, protocol.Target{TargetID: "local", Hostname: host, URL: srv.URL + path, Port: atoi(port)}, pool)103 }104105 m = run("/")106 if !m.OK || m.Error != "" || m.HTTPStatus == nil || *m.HTTPStatus != 200 {107 t.Fatalf("200: %+v", m)108 }109 if m.HTTPProto != "HTTP/2.0" {110 t.Errorf("expected HTTP/2.0, got %q", m.HTTPProto)111 }112 if m.TLSVersion != "TLS1.3" {113 t.Errorf("expected TLS1.3, got %q", m.TLSVersion)114 }115 if m.TCPMs == nil || m.TLSMs == nil || m.TTFBMs == nil || m.TotalMs == nil || m.ResolvedIP != host {116 t.Errorf("timings incomplete: %+v", m)117 }118 if *m.TotalMs > 5000 {119 t.Errorf("body cap not applied, total=%v ms", *m.TotalMs)120 }121 if m = run("/redirect"); !m.OK || *m.HTTPStatus != 302 {122 t.Errorf("redirect must not be followed: %+v", m)123 }124 if m = run("/boom"); m.OK || m.Error != protocol.ErrHTTP5xx || *m.HTTPStatus != 503 {125 t.Errorf("5xx: %+v", m)126 }127 if m = run("/missing"); !m.OK || m.Error != protocol.ErrHTTP4xx {128 t.Errorf("4xx should be ok with http_4xx code: %+v", m)129 }130131 // Connection refused.132 srv2 := httptest.NewServer(http.NotFoundHandler())133 refusedURL := srv2.URL134 srv2.Close()135 m = c.Run(context.Background(), protocol.Target{TargetID: "dead", Hostname: host, URL: refusedURL + "/"})136 if m.OK || m.Error != protocol.ErrTCPRefused {137 t.Errorf("refused: %+v", m)138 }139}140141func atoi(s string) int {142 n := 0143 for _, r := range s {144 n = n*10 + int(r-'0')145 }146 return n147}148