Skip to content

Commit dca3db9

Browse files
authored
feat: add soaxreport tool for ECH testing via SOAX proxies (#5)
1 parent 206086a commit dca3db9

7 files changed

Lines changed: 832 additions & 0 deletions

File tree

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,24 @@ go 1.24.8
44

55
require (
66
github.com/miekg/dns v1.1.70
7+
golang.getoutline.org/sdk/x v0.1.0
78
golang.org/x/sync v0.19.0
89
)
910

1011
require (
12+
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
13+
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
14+
github.com/onsi/ginkgo/v2 v2.12.0 // indirect
15+
github.com/quic-go/qpack v0.5.1 // indirect
16+
github.com/quic-go/quic-go v0.48.1 // indirect
17+
go.uber.org/mock v0.4.0 // indirect
18+
golang.getoutline.org/sdk v0.0.21 // indirect
19+
golang.org/x/crypto v0.46.0 // indirect
20+
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
1121
golang.org/x/mod v0.31.0 // indirect
1222
golang.org/x/net v0.48.0 // indirect
1323
golang.org/x/sys v0.39.0 // indirect
24+
golang.org/x/text v0.32.0 // indirect
1425
golang.org/x/tools v0.40.0 // indirect
1526
)
1627

go.sum

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,70 @@
1+
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
2+
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
3+
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
4+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7+
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
8+
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
9+
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
10+
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
11+
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
12+
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
113
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
214
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
15+
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
16+
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
17+
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
18+
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
19+
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
320
github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
421
github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
22+
github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI=
23+
github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ=
24+
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
25+
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
26+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
27+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
28+
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
29+
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
30+
github.com/quic-go/quic-go v0.48.1 h1:y/8xmfWI9qmGTc+lBr4jKRUWLGSlSigv847ULJ4hYXA=
31+
github.com/quic-go/quic-go v0.48.1/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
32+
github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28=
33+
github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM=
34+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
35+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
36+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
37+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
38+
github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
39+
github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ=
40+
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
41+
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
42+
golang.getoutline.org/sdk v0.0.21 h1:zgtenz5DMbnIPOsuAOHNiWdrri81fHyBxhSfRi6Dk8s=
43+
golang.getoutline.org/sdk v0.0.21/go.mod h1:raUAs4PYbEaT/cLTK6PviiKSh7gjEj7JJczFFFr41zc=
44+
golang.getoutline.org/sdk/x v0.1.0 h1:8ykaCEC8Eoi3h/2MdGW7uaMAt2BWFCRhrSvuJ0Y/IU0=
45+
golang.getoutline.org/sdk/x v0.1.0/go.mod h1:Vw7FWpLbYifHFYbbo0mXOCkhR14d1ADwjiF7uBQKyzM=
46+
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
47+
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
48+
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
49+
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
550
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
651
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
752
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
853
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
954
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
1055
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
56+
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1157
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
1258
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
59+
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
60+
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
61+
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
62+
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
1363
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
1464
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
65+
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
66+
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
67+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
68+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
69+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
70+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

greasereport/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type TestResult struct {
4949
HTTPStatus int
5050
}
5151

52+
// TODO: Deduplicate this with the unified ECH testing package (internal/echtest).
5253
var curlExitCodeNames = map[int]string{
5354
1: "CURLE_UNSUPPORTED_PROTOCOL",
5455
2: "CURLE_FAILED_INIT",

internal/echtest/run.go

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package echtest
16+
17+
import (
18+
"bytes"
19+
"fmt"
20+
"os"
21+
"os/exec"
22+
"path/filepath"
23+
"strconv"
24+
"strings"
25+
"time"
26+
)
27+
28+
type TestResult struct {
29+
Domain string
30+
ECHGrease bool
31+
Error string
32+
CurlExitCode int
33+
CurlErrorName string
34+
DNSLookup time.Duration
35+
TCPConnection time.Duration
36+
TLSHandshake time.Duration
37+
ServerTime time.Duration
38+
TotalTime time.Duration
39+
HTTPStatus int
40+
Stderr string
41+
}
42+
43+
// curlExitCodeNames maps curl exit codes to their CURL_* string representations.
44+
var curlExitCodeNames = map[int]string{
45+
1: "CURLE_UNSUPPORTED_PROTOCOL",
46+
2: "CURLE_FAILED_INIT",
47+
3: "CURLE_URL_MALFORMAT",
48+
4: "CURLE_NOT_BUILT_IN",
49+
5: "CURLE_COULDNT_RESOLVE_PROXY",
50+
6: "CURLE_COULDNT_RESOLVE_HOST",
51+
7: "CURLE_COULDNT_CONNECT",
52+
8: "CURLE_WEIRD_SERVER_REPLY",
53+
9: "CURLE_REMOTE_ACCESS_DENIED",
54+
11: "CURLE_FTP_WEIRD_PASV_REPLY",
55+
13: "CURLE_FTP_WEIRD_227_FORMAT",
56+
14: "CURLE_FTP_CANT_GET_HOST",
57+
15: "CURLE_FTP_CANT_RECONNECT",
58+
17: "CURLE_FTP_COULDNT_SET_TYPE",
59+
18: "CURLE_PARTIAL_FILE",
60+
19: "CURLE_FTP_COULDNT_RETR_FILE",
61+
21: "CURLE_QUOTE_ERROR",
62+
22: "CURLE_HTTP_RETURNED_ERROR",
63+
23: "CURLE_WRITE_ERROR",
64+
25: "CURLE_UPLOAD_FAILED",
65+
26: "CURLE_READ_ERROR",
66+
27: "CURLE_OUT_OF_MEMORY",
67+
28: "CURLE_OPERATION_TIMEDOUT",
68+
30: "CURLE_FTP_PORT_FAILED",
69+
31: "CURLE_FTP_COULDNT_USE_REST",
70+
33: "CURLE_RANGE_ERROR",
71+
34: "CURLE_HTTP_POST_ERROR",
72+
35: "CURLE_SSL_CONNECT_ERROR",
73+
36: "CURLE_BAD_DOWNLOAD_RESUME",
74+
37: "CURLE_FILE_COULDNT_READ_FILE",
75+
38: "CURLE_LDAP_CANNOT_BIND",
76+
39: "CURLE_LDAP_SEARCH_FAILED",
77+
41: "CURLE_FUNCTION_NOT_FOUND",
78+
42: "CURLE_ABORTED_BY_CALLBACK",
79+
43: "CURLE_BAD_FUNCTION_ARGUMENT",
80+
45: "CURLE_INTERFACE_FAILED",
81+
47: "CURLE_TOO_MANY_REDIRECTS",
82+
48: "CURLE_UNKNOWN_OPTION",
83+
49: "CURLE_TELNET_OPTION_SYNTAX",
84+
51: "CURLE_PEER_FAILED_VERIFICATION",
85+
52: "CURLE_GOT_NOTHING",
86+
53: "CURLE_SSL_ENGINE_NOTFOUND",
87+
54: "CURLE_SSL_ENGINE_SETFAILED",
88+
55: "CURLE_SEND_ERROR",
89+
56: "CURLE_RECV_ERROR",
90+
58: "CURLE_SSL_CERTPROBLEM",
91+
59: "CURLE_SSL_CIPHER",
92+
60: "CURLE_SSL_CACERT",
93+
61: "CURLE_BAD_CONTENT_ENCODING",
94+
62: "CURLE_LDAP_INVALID_URL",
95+
63: "CURLE_FILESIZE_EXCEEDED",
96+
64: "CURLE_USE_SSL_FAILED",
97+
65: "CURLE_SEND_FAIL_REWIND",
98+
66: "CURLE_SSL_ENGINE_INITFAILED",
99+
67: "CURLE_LOGIN_DENIED",
100+
68: "CURLE_TFTP_NOTFOUND",
101+
69: "CURLE_TFTP_PERM",
102+
70: "CURLE_REMOTE_DISK_FULL",
103+
71: "CURLE_TFTP_ILLEGAL",
104+
72: "CURLE_TFTP_UNKNOWNID",
105+
73: "CURLE_REMOTE_FILE_EXISTS",
106+
74: "CURLE_TFTP_NOSUCHUSER",
107+
75: "CURLE_CONV_FAILED",
108+
76: "CURLE_CONV_REQD",
109+
77: "CURLE_SSL_CACERT_BADFILE",
110+
78: "CURLE_REMOTE_FILE_NOT_FOUND",
111+
79: "CURLE_SSH",
112+
80: "CURLE_SSL_SHUTDOWN_FAILED",
113+
81: "CURLE_AGAIN",
114+
82: "CURLE_SSL_CRL_BADFILE",
115+
83: "CURLE_SSL_ISSUER_ERROR",
116+
84: "CURLE_FTP_PRET_FAILED",
117+
85: "CURLE_RTSP_CSEQ_ERROR",
118+
86: "CURLE_RTSP_SESSION_ERROR",
119+
87: "CURLE_FTP_BAD_FILE_LIST",
120+
88: "CURLE_CHUNK_FAILED",
121+
89: "CURLE_NO_CONNECTION_AVAILABLE",
122+
90: "CURLE_SSL_PINNEDPUBKEYNOTMATCH",
123+
91: "CURLE_SSL_INVALIDCERTSTATUS",
124+
92: "CURLE_HTTP2_STREAM",
125+
93: "CURLE_RECURSIVE_API_CALL",
126+
94: "CURLE_AUTH_ERROR",
127+
95: "CURLE_HTTP3",
128+
96: "CURLE_QUIC_CONNECT_ERROR",
129+
}
130+
131+
// Run executes a curl command against the specified domain.
132+
func Run(
133+
curlPath string,
134+
domain string,
135+
echGrease bool,
136+
maxTime time.Duration,
137+
proxyURL string,
138+
proxyHeaders []string,
139+
) TestResult {
140+
result := TestResult{
141+
Domain: domain,
142+
ECHGrease: echGrease,
143+
}
144+
145+
targetURL := "https://" + domain
146+
147+
args := []string{
148+
"-w",
149+
"dnslookup:%{time_namelookup},tcpconnect:%{time_connect},tlsconnect:%{time_appconnect},servertime:%{time_starttransfer},total:%{time_total},httpstatus:%{http_code}",
150+
"--head",
151+
"--max-time",
152+
strconv.FormatFloat(maxTime.Seconds(), 'f', -1, 64),
153+
}
154+
155+
// Handle proxy options
156+
if proxyURL != "" {
157+
args = append(args, "--proxy", proxyURL)
158+
for _, h := range proxyHeaders {
159+
args = append(args, "--proxy-header", h)
160+
}
161+
// If using a proxy with headers, we usually need verbose mode to see the proxy response.
162+
// If proxy headers are provided, we assume the caller wants to read them from stderr.
163+
if len(proxyHeaders) > 0 {
164+
args = append(args, "-v")
165+
} else {
166+
args = append(args, "-s")
167+
}
168+
} else {
169+
args = append(args, "-s")
170+
}
171+
172+
if echGrease {
173+
args = append(args, "--ech", "grease")
174+
} else {
175+
args = append(args, "--ech", "false")
176+
}
177+
args = append(args, targetURL)
178+
179+
cmd := exec.Command(curlPath, args...)
180+
181+
// Setup environment for custom curl (matching internal/curl/runner.go)
182+
binDir := filepath.Dir(curlPath)
183+
libDir := filepath.Join(filepath.Dir(binDir), "lib")
184+
if libStat, err := os.Stat(libDir); err == nil && libStat.IsDir() {
185+
cmd.Env = append(os.Environ(), "LD_LIBRARY_PATH="+libDir)
186+
}
187+
188+
var stdout bytes.Buffer
189+
var stderr bytes.Buffer
190+
cmd.Stdout = &stdout
191+
cmd.Stderr = &stderr
192+
193+
err := cmd.Run()
194+
result.Stderr = stderr.String() // Always capture stderr for caller
195+
196+
if err != nil {
197+
if exitError, ok := err.(*exec.ExitError); ok {
198+
result.CurlExitCode = exitError.ExitCode()
199+
result.CurlErrorName = curlExitCodeNames[result.CurlExitCode]
200+
} else {
201+
result.Error = fmt.Sprintf("failed to execute curl: %v", err)
202+
return result
203+
}
204+
} else {
205+
// Even if err is nil, there might be curl-level errors recorded in stderr
206+
// that the caller might be interested in, though standard execution succeeded.
207+
}
208+
209+
// parse the stdout stats
210+
parts := strings.SplitSeq(stdout.String(), ",")
211+
for part := range parts {
212+
kv := strings.Split(part, ":")
213+
if len(kv) != 2 {
214+
continue
215+
}
216+
key := kv[0]
217+
value := kv[1]
218+
219+
switch key {
220+
case "dnslookup":
221+
f, _ := strconv.ParseFloat(value, 64)
222+
result.DNSLookup = time.Duration(f * float64(time.Second))
223+
case "tcpconnect":
224+
f, _ := strconv.ParseFloat(value, 64)
225+
result.TCPConnection = time.Duration(f * float64(time.Second))
226+
case "tlsconnect":
227+
f, _ := strconv.ParseFloat(value, 64)
228+
result.TLSHandshake = time.Duration(f * float64(time.Second))
229+
case "servertime":
230+
f, _ := strconv.ParseFloat(value, 64)
231+
result.ServerTime = time.Duration(f * float64(time.Second))
232+
case "total":
233+
f, _ := strconv.ParseFloat(value, 64)
234+
result.TotalTime = time.Duration(f * float64(time.Second))
235+
case "httpstatus":
236+
i, _ := strconv.Atoi(value)
237+
result.HTTPStatus = i
238+
}
239+
}
240+
241+
return result
242+
}

0 commit comments

Comments
 (0)