1515package main
1616
1717import (
18- "bytes"
1918 "context"
2019 "encoding/csv"
2120 "flag"
2221 "fmt"
2322 "log/slog"
2423 "os"
25- "os/exec"
2624 "path/filepath"
2725 "strconv"
28- "strings"
2926 "sync"
3027 "time"
3128
29+ "github.com/Jigsaw-Code/ech-research/internal/echtest"
3230 "github.com/Jigsaw-Code/ech-research/internal/tranco"
3331 "github.com/Jigsaw-Code/ech-research/internal/workspace"
3432 "golang.org/x/sync/semaphore"
3533)
3634
37- type TestResult struct {
38- Domain string
39- Rank int
40- ECHGrease bool
41- Error string
42- CurlExitCode int
43- CurlErrorName string
44- DNSLookup time.Duration
45- TCPConnection time.Duration
46- TLSHandshake time.Duration
47- ServerTime time.Duration
48- TotalTime time.Duration
49- HTTPStatus int
50- }
51-
52- // TODO: Deduplicate this with the unified ECH testing package (internal/echtest).
53- var curlExitCodeNames = map [int ]string {
54- 1 : "CURLE_UNSUPPORTED_PROTOCOL" ,
55- 2 : "CURLE_FAILED_INIT" ,
56- 3 : "CURLE_URL_MALFORMAT" ,
57- 4 : "CURLE_NOT_BUILT_IN" ,
58- 5 : "CURLE_COULDNT_RESOLVE_PROXY" ,
59- 6 : "CURLE_COULDNT_RESOLVE_HOST" ,
60- 7 : "CURLE_COULDNT_CONNECT" ,
61- 8 : "CURLE_WEIRD_SERVER_REPLY" ,
62- 9 : "CURLE_REMOTE_ACCESS_DENIED" ,
63- 11 : "CURLE_FTP_WEIRD_PASV_REPLY" ,
64- 13 : "CURLE_FTP_WEIRD_227_FORMAT" ,
65- 14 : "CURLE_FTP_CANT_GET_HOST" ,
66- 15 : "CURLE_FTP_CANT_RECONNECT" ,
67- 17 : "CURLE_FTP_COULDNT_SET_TYPE" ,
68- 18 : "CURLE_PARTIAL_FILE" ,
69- 19 : "CURLE_FTP_COULDNT_RETR_FILE" ,
70- 21 : "CURLE_QUOTE_ERROR" ,
71- 22 : "CURLE_HTTP_RETURNED_ERROR" ,
72- 23 : "CURLE_WRITE_ERROR" ,
73- 25 : "CURLE_UPLOAD_FAILED" ,
74- 26 : "CURLE_READ_ERROR" ,
75- 27 : "CURLE_OUT_OF_MEMORY" ,
76- 28 : "CURLE_OPERATION_TIMEDOUT" ,
77- 30 : "CURLE_FTP_PORT_FAILED" ,
78- 31 : "CURLE_FTP_COULDNT_USE_REST" ,
79- 33 : "CURLE_RANGE_ERROR" ,
80- 34 : "CURLE_HTTP_POST_ERROR" ,
81- 35 : "CURLE_SSL_CONNECT_ERROR" ,
82- 36 : "CURLE_BAD_DOWNLOAD_RESUME" ,
83- 37 : "CURLE_FILE_COULDNT_READ_FILE" ,
84- 38 : "CURLE_LDAP_CANNOT_BIND" ,
85- 39 : "CURLE_LDAP_SEARCH_FAILED" ,
86- 41 : "CURLE_FUNCTION_NOT_FOUND" ,
87- 42 : "CURLE_ABORTED_BY_CALLBACK" ,
88- 43 : "CURLE_BAD_FUNCTION_ARGUMENT" ,
89- 45 : "CURLE_INTERFACE_FAILED" ,
90- 47 : "CURLE_TOO_MANY_REDIRECTS" ,
91- 48 : "CURLE_UNKNOWN_OPTION" ,
92- 49 : "CURLE_TELNET_OPTION_SYNTAX" ,
93- 51 : "CURLE_PEER_FAILED_VERIFICATION" ,
94- 52 : "CURLE_GOT_NOTHING" ,
95- 53 : "CURLE_SSL_ENGINE_NOTFOUND" ,
96- 54 : "CURLE_SSL_ENGINE_SETFAILED" ,
97- 55 : "CURLE_SEND_ERROR" ,
98- 56 : "CURLE_RECV_ERROR" ,
99- 58 : "CURLE_SSL_CERTPROBLEM" ,
100- 59 : "CURLE_SSL_CIPHER" ,
101- 60 : "CURLE_SSL_CACERT" ,
102- 61 : "CURLE_BAD_CONTENT_ENCODING" ,
103- 62 : "CURLE_LDAP_INVALID_URL" ,
104- 63 : "CURLE_FILESIZE_EXCEEDED" ,
105- 64 : "CURLE_USE_SSL_FAILED" ,
106- 65 : "CURLE_SEND_FAIL_REWIND" ,
107- 66 : "CURLE_SSL_ENGINE_INITFAILED" ,
108- 67 : "CURLE_LOGIN_DENIED" ,
109- 68 : "CURLE_TFTP_NOTFOUND" ,
110- 69 : "CURLE_TFTP_PERM" ,
111- 70 : "CURLE_REMOTE_DISK_FULL" ,
112- 71 : "CURLE_TFTP_ILLEGAL" ,
113- 72 : "CURLE_TFTP_UNKNOWNID" ,
114- 73 : "CURLE_REMOTE_FILE_EXISTS" ,
115- 74 : "CURLE_TFTP_NOSUCHUSER" ,
116- 75 : "CURLE_CONV_FAILED" ,
117- 76 : "CURLE_CONV_REQD" ,
118- 77 : "CURLE_SSL_CACERT_BADFILE" ,
119- 78 : "CURLE_REMOTE_FILE_NOT_FOUND" ,
120- 79 : "CURLE_SSH" ,
121- 80 : "CURLE_SSL_SHUTDOWN_FAILED" ,
122- 81 : "CURLE_AGAIN" ,
123- 82 : "CURLE_SSL_CRL_BADFILE" ,
124- 83 : "CURLE_SSL_ISSUER_ERROR" ,
125- 84 : "CURLE_FTP_PRET_FAILED" ,
126- 85 : "CURLE_RTSP_CSEQ_ERROR" ,
127- 86 : "CURLE_RTSP_SESSION_ERROR" ,
128- 87 : "CURLE_FTP_BAD_FILE_LIST" ,
129- 88 : "CURLE_CHUNK_FAILED" ,
130- 89 : "CURLE_NO_CONNECTION_AVAILABLE" ,
131- 90 : "CURLE_SSL_PINNEDPUBKEYNOTMATCH" ,
132- 91 : "CURLE_SSL_INVALIDCERTSTATUS" ,
133- 92 : "CURLE_HTTP2_STREAM" ,
134- 93 : "CURLE_RECURSIVE_API_CALL" ,
135- 94 : "CURLE_AUTH_ERROR" ,
136- 95 : "CURLE_HTTP3" ,
137- 96 : "CURLE_QUIC_CONNECT_ERROR" ,
138- }
139-
140- func runTest (curlPath string , domain tranco.Domain , echGrease bool , maxTime time.Duration ) TestResult {
141- result := TestResult {
142- Domain : domain .Name ,
143- Rank : domain .Rank ,
144- ECHGrease : echGrease ,
145- }
146-
147- url := "https://" + domain .Name
148-
149- // curl -w "dnslookup:%{time_namelookup},tcpconnect:%{time_connect},tlsconnect:%{time_appconnect},servertime:%{time_starttransfer},total:%{time_total},httpstatus:%{http_code}" --head -s --ech grease https://example.com
150- args := []string {
151- "-w" ,
152- "dnslookup:%{time_namelookup},tcpconnect:%{time_connect},tlsconnect:%{time_appconnect},servertime:%{time_starttransfer},total:%{time_total},httpstatus:%{http_code}" ,
153- "--head" ,
154- "-s" , // silent
155- "--max-time" ,
156- strconv .FormatFloat (maxTime .Seconds (), 'f' , - 1 , 64 ),
157- }
158- if echGrease {
159- args = append (args , "--ech" , "grease" )
160- } else {
161- args = append (args , "--ech" , "false" )
162- }
163- args = append (args , url )
164-
165- slog .Debug ("running curl" , "path" , curlPath , "args" , args )
166- cmd := exec .Command (curlPath , args ... )
167- var out bytes.Buffer
168- var stderr bytes.Buffer
169- cmd .Stdout = & out
170- cmd .Stderr = & stderr
171-
172- err := cmd .Run ()
173- if err != nil {
174- result .Error = fmt .Sprintf ("failed to run curl: %v, stderr: %s" , err , stderr .String ())
175- if exitError , ok := err .(* exec.ExitError ); ok {
176- result .CurlExitCode = exitError .ExitCode ()
177- result .CurlErrorName = curlExitCodeNames [result .CurlExitCode ]
178- }
179- return result
180- }
181-
182- // parse the output
183- // dnslookup:0.001,tcpconnect:0.002,tlsconnect:0.003,servertime:0.004,total:0.005,httpstatus:200
184- parts := strings .Split (out .String (), "," )
185- for _ , part := range parts {
186- kv := strings .Split (part , ":" )
187- if len (kv ) != 2 {
188- continue
189- }
190- key := kv [0 ]
191- value := kv [1 ]
192-
193- switch key {
194- case "dnslookup" :
195- f , _ := strconv .ParseFloat (value , 64 )
196- result .DNSLookup = time .Duration (f * float64 (time .Second ))
197- case "tcpconnect" :
198- f , _ := strconv .ParseFloat (value , 64 )
199- result .TCPConnection = time .Duration (f * float64 (time .Second ))
200- case "tlsconnect" :
201- f , _ := strconv .ParseFloat (value , 64 )
202- result .TLSHandshake = time .Duration (f * float64 (time .Second ))
203- case "servertime" :
204- f , _ := strconv .ParseFloat (value , 64 )
205- result .ServerTime = time .Duration (f * float64 (time .Second ))
206- case "total" :
207- f , _ := strconv .ParseFloat (value , 64 )
208- result .TotalTime = time .Duration (f * float64 (time .Second ))
209- case "httpstatus" :
210- i , _ := strconv .Atoi (value )
211- result .HTTPStatus = i
212- }
213- }
214-
215- return result
35+ type DomainTestResult struct {
36+ Rank int
37+ echtest.TestResult
21638}
21739
21840func main () {
@@ -239,7 +61,7 @@ func main() {
23961 // Determine curl binary path.
24062 curlPath := * curlPathFlag
24163 if curlPath == "" {
242- curlPath = filepath .Join (workspaceDir , "output" , " bin" , "curl" )
64+ curlPath = filepath .Join (workspaceDir , "bin" , "curl" )
24365 }
24466
24567 // Ensure Tranco list is present.
@@ -274,18 +96,25 @@ func main() {
27496 os .Exit (1 )
27597 }
27698
277- resultsCh := make (chan TestResult , 2 * (* topNFlag ))
99+ resultsCh := make (chan DomainTestResult , 2 * (* topNFlag ))
278100
279101 var csvWg sync.WaitGroup
280102 csvWg .Add (1 )
281103 go func () {
282104 defer csvWg .Done ()
283105 for result := range resultsCh {
106+ errorStr := result .GoError
107+ if errorStr == "" && result .CurlErrorMessage != "" {
108+ errorStr = result .CurlErrorMessage
109+ } else if errorStr == "" && result .Stderr != "" && result .CurlExitCode != 0 {
110+ errorStr = result .Stderr
111+ }
112+
284113 record := []string {
285114 result .Domain ,
286115 strconv .Itoa (result .Rank ),
287116 strconv .FormatBool (result .ECHGrease ),
288- result . Error ,
117+ errorStr ,
289118 strconv .Itoa (result .CurlExitCode ),
290119 result .CurlErrorName ,
291120 strconv .FormatInt (result .DNSLookup .Milliseconds (), 10 ),
@@ -314,7 +143,8 @@ func main() {
314143 defer sem .Release (1 )
315144 defer wg .Done ()
316145 slog .Info ("Testing domain" , "rank" , d .Rank , "domain" , d .Name , "ech_grease" , false )
317- resultsCh <- runTest (curlPath , d , false , * maxTimeFlag )
146+ res := echtest .Run (curlPath , d .Name , false , * maxTimeFlag , "" , nil )
147+ resultsCh <- DomainTestResult {Rank : d .Rank , TestResult : res }
318148 }(domain )
319149
320150 if err := sem .Acquire (context .Background (), 1 ); err != nil {
@@ -325,7 +155,8 @@ func main() {
325155 defer sem .Release (1 )
326156 defer wg .Done ()
327157 slog .Info ("Testing domain" , "rank" , d .Rank , "domain" , d .Name , "ech_grease" , true )
328- resultsCh <- runTest (curlPath , d , true , * maxTimeFlag )
158+ res := echtest .Run (curlPath , d .Name , true , * maxTimeFlag , "" , nil )
159+ resultsCh <- DomainTestResult {Rank : d .Rank , TestResult : res }
329160 }(domain )
330161 }
331162
0 commit comments