From 540378735bc828820db566ca60870143fd786a09 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Fri, 27 Apr 2018 13:16:50 -0700 Subject: [PATCH 1/2] Add Go 1.8 TLS tracing support & refactor timing code To have accurate measurement of TLS and support skipped steps (DNS, TCP and TLS reuse), the timing code has been refactored to use Go 1.8 support for TLS tracing as well as a more descriptive variable namings for steps time. This commit removes support for Go versions lower than 1.8. --- main.go | 73 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/main.go b/main.go index b06298f..cb70a47 100644 --- a/main.go +++ b/main.go @@ -221,29 +221,30 @@ func dialContext(network string) func(ctx context.Context, network, addr string) func visit(url *url.URL) { req := newRequest(httpMethod, url, postBody) - var t0, t1, t2, t3, t4, t5, t6 time.Time + var tStart, tDNSStart, tDNSEnd, tConnectStart, tConnectEnd, tTLSStart, tTLSEnd, tConnected, tTTBF, tDone time.Time trace := &httptrace.ClientTrace{ - DNSStart: func(_ httptrace.DNSStartInfo) { t0 = time.Now() }, - DNSDone: func(_ httptrace.DNSDoneInfo) { t1 = time.Now() }, + GetConn: func(_ string) { tStart = time.Now() }, + DNSStart: func(_ httptrace.DNSStartInfo) { tDNSStart = time.Now() }, + DNSDone: func(_ httptrace.DNSDoneInfo) { tDNSEnd = time.Now() }, ConnectStart: func(_, _ string) { - if t1.IsZero() { + if tConnectStart.IsZero() { // connecting to IP - t1 = time.Now() + tConnectStart = time.Now() } }, ConnectDone: func(net, addr string, err error) { if err != nil { log.Fatalf("unable to connect to host %v: %v", addr, err) } - t2 = time.Now() + tConnectEnd = time.Now() printf("\n%s%s\n", color.GreenString("Connected to "), color.CyanString(addr)) }, - GotConn: func(_ httptrace.GotConnInfo) { t3 = time.Now() }, - GotFirstResponseByte: func() { t4 = time.Now() }, - TLSHandshakeStart: func() { t5 = time.Now() }, - TLSHandshakeDone: func(_ tls.ConnectionState, _ error) { t6 = time.Now() }, + TLSHandshakeStart: func() { tTLSStart = time.Now() }, + TLSHandshakeDone: func(_ tls.ConnectionState, _ error) { tTLSEnd = time.Now() }, + GotConn: func(_ httptrace.GotConnInfo) { tConnected = time.Now() }, + GotFirstResponseByte: func() { tTTBF = time.Now() }, } req = req.WithContext(httptrace.WithClientTrace(context.Background(), trace)) @@ -300,10 +301,18 @@ func visit(url *url.URL) { bodyMsg := readResponseBody(req, resp) resp.Body.Close() - t7 := time.Now() // after read body - if t0.IsZero() { - // we skipped DNS - t0 = t1 + tDone = time.Now() // after read body + if tDNSStart.IsZero() { + tDNSStart = tStart + tDNSEnd = tStart + } + if tConnectStart.IsZero() { + tConnectStart = tStart + tConnectEnd = tStart + } + if tTLSStart.IsZero() { + tTLSStart = tStart + tTLSEnd = tStart } // print status line and headers @@ -341,27 +350,27 @@ func visit(url *url.URL) { switch url.Scheme { case "https": printf(colorize(HTTPSTemplate), - fmta(t1.Sub(t0)), // dns lookup - fmta(t2.Sub(t1)), // tcp connection - fmta(t6.Sub(t5)), // tls handshake - fmta(t4.Sub(t3)), // server processing - fmta(t7.Sub(t4)), // content transfer - fmtb(t1.Sub(t0)), // namelookup - fmtb(t2.Sub(t0)), // connect - fmtb(t3.Sub(t0)), // pretransfer - fmtb(t4.Sub(t0)), // starttransfer - fmtb(t7.Sub(t0)), // total + fmta(tDNSEnd.Sub(tDNSStart)), // dns lookup + fmta(tConnectEnd.Sub(tConnectStart)), // tcp connection + fmta(tTLSEnd.Sub(tTLSStart)), // tls handshake + fmta(tTTBF.Sub(tConnected)), // server processing + fmta(tDone.Sub(tTTBF)), // content transfer + fmtb(tDNSEnd.Sub(tStart)), // namelookup + fmtb(tConnectEnd.Sub(tStart)), // connect + fmtb(tConnected.Sub(tStart)), // pretransfer + fmtb(tTTBF.Sub(tStart)), // starttransfer + fmtb(tDone.Sub(tStart)), // total ) case "http": printf(colorize(HTTPTemplate), - fmta(t1.Sub(t0)), // dns lookup - fmta(t3.Sub(t1)), // tcp connection - fmta(t4.Sub(t3)), // server processing - fmta(t7.Sub(t4)), // content transfer - fmtb(t1.Sub(t0)), // namelookup - fmtb(t3.Sub(t0)), // connect - fmtb(t4.Sub(t0)), // starttransfer - fmtb(t7.Sub(t0)), // total + fmta(tDNSEnd.Sub(tDNSStart)), // dns lookup + fmta(tConnectEnd.Sub(tConnectStart)), // tcp connection + fmta(tTTBF.Sub(tConnected)), // server processing + fmta(tDone.Sub(tTTBF)), // content transfer + fmtb(tDNSEnd.Sub(tStart)), // namelookup + fmtb(tConnectEnd.Sub(tStart)), // connect + fmtb(tTTBF.Sub(tStart)), // starttransfer + fmtb(tDone.Sub(tStart)), // total ) } From 77390fc97ba06fa1f75f701e8b65ac07f5aa162b Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Fri, 4 May 2018 18:52:35 -0700 Subject: [PATCH 2/2] Add JSON output support Example output (whitespace and carriage returns added for readibility): { "Address": "128.199.248.140:443", "Header": { "Connection": [ "keep-alive" ], "Content-Type": [ "text/html; charset=UTF-8" ], "Date": [ "Sat, 05 May 2018 03:15:15 GMT" ], "Link": [ "; rel=\"https://api.w.org/\"" ], "Server": [ "nginx/1.2.1" ], "Strict-Transport-Security": [ "max-age=15768000" ], "X-Powered-By": [ "PHP/5.4.45-0+deb7u11" ] }, "Proto": "HTTP/1.1", "Status": "200 OK", "Timing": { "DNS": 98, "TCP": 191, "TLS": 604, "Server": 747, "Transfer": 392, "Lookup": 98, "Connect": 290, "PreTransfer": 895, "StartTransfer": 1643, "Total": 2036 } } --- main.go | 218 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 132 insertions(+), 86 deletions(-) diff --git a/main.go b/main.go index cb70a47..18e747f 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,10 @@ package main import ( + "bytes" "context" "crypto/tls" + "encoding/json" "encoding/pem" "flag" "fmt" @@ -16,6 +18,7 @@ import ( "net/url" "os" "path" + "reflect" "runtime" "sort" "strconv" @@ -27,25 +30,47 @@ import ( "github.com/fatih/color" ) +type Report struct { + Address string + Header http.Header + Proto string + Status string + Timing Timing +} + +type Timing struct { + DNS int + TCP int + TLS int + Server int + Transfer int + + Lookup int + Connect int + PreTransfer int + StartTransfer int + Total int +} + const ( HTTPSTemplate = `` + ` DNS Lookup TCP Connection TLS Handshake Server Processing Content Transfer` + "\n" + - `[%s | %s | %s | %s | %s ]` + "\n" + + `[ %>DNS | %>TCP | %>TLS | %>Server | %>Transfer ]` + "\n" + ` | | | | |` + "\n" + - ` namelookup:%s | | | |` + "\n" + - ` connect:%s | | |` + "\n" + - ` pretransfer:%s | |` + "\n" + - ` starttransfer:%s |` + "\n" + - ` total:%s` + "\n" + ` namelookup:%DNS | %>TCP | %>Server | %>Transfer ]` + "\n" + + ` | | | |` + "\n" + + ` namelookup:%= 'a' && b[end] <= 'z') || (b[end] >= 'A' && b[end] <= 'Z')); end++ { + } + vnam := string(b[idx+2 : end]) + copy(b[idx:end], bytes.Repeat([]byte{' '}, end-idx)) + val := rvars.FieldByName(vnam) + if !val.IsValid() { + panic("invalid template variable: " + vnam) + } + v := strconv.Itoa(val.Interface().(int)) + "ms" + vlen := len(v) + v = color.CyanString(v) + switch dir { + case '>': + b = append(append(append([]byte{}, b[:end-vlen]...), []byte(v)...), b[end:]...) + case '<': + b = append(append(append([]byte{}, b[:idx]...), []byte(v)...), b[idx+vlen:]...) + default: + panic("invalid direction: " + string(dir)) + } + idx = end + } + print(string(b)) +} + func isRedirect(resp *http.Response) bool { return resp.StatusCode > 299 && resp.StatusCode < 400 }