classifyDialErr maps net.Dialer errors into the Outcome taxonomy.
(err error)
| 182 | |
| 183 | // classifyDialErr maps net.Dialer errors into the Outcome taxonomy. |
| 184 | func classifyDialErr(err error) (Outcome, string) { |
| 185 | if errors.Is(err, context.DeadlineExceeded) || isTimeout(err) { |
| 186 | return OutcomeTimeout, err.Error() |
| 187 | } |
| 188 | // syscall.ECONNREFUSED, syscall.ECONNRESET: infrastructure problems or |
| 189 | // an aggressive on-path RST injection. We split refused (no host) from |
| 190 | // reset (DPI) based on the surface error string. |
| 191 | if errors.Is(err, syscall.ECONNREFUSED) { |
| 192 | return OutcomeTCPRefused, err.Error() |
| 193 | } |
| 194 | if errors.Is(err, syscall.ECONNRESET) { |
| 195 | return OutcomeDPIReset, err.Error() |
| 196 | } |
| 197 | // On Windows Go surfaces connection reset as a string. |
| 198 | s := err.Error() |
| 199 | if strings.Contains(s, "connection reset") || strings.Contains(s, "reset by peer") { |
| 200 | return OutcomeDPIReset, s |
| 201 | } |
| 202 | return OutcomeTCPRefused, s |
| 203 | } |
| 204 | |
| 205 | // classifyTLSErr splits TLS-layer errors into "DPI interfered" vs "target |
| 206 | // rejected SNI" buckets. RSTs during handshake are classic DPI signatures. |