classifyTLSErr splits TLS-layer errors into "DPI interfered" vs "target rejected SNI" buckets. RSTs during handshake are classic DPI signatures.
(err error)
| 205 | // classifyTLSErr splits TLS-layer errors into "DPI interfered" vs "target |
| 206 | // rejected SNI" buckets. RSTs during handshake are classic DPI signatures. |
| 207 | func classifyTLSErr(err error) (Outcome, string) { |
| 208 | if errors.Is(err, context.DeadlineExceeded) || isTimeout(err) { |
| 209 | return OutcomeTimeout, err.Error() |
| 210 | } |
| 211 | if errors.Is(err, syscall.ECONNRESET) || errors.Is(err, io.ErrUnexpectedEOF) { |
| 212 | return OutcomeDPIReset, err.Error() |
| 213 | } |
| 214 | s := err.Error() |
| 215 | switch { |
| 216 | case strings.Contains(s, "connection reset"), |
| 217 | strings.Contains(s, "reset by peer"), |
| 218 | strings.Contains(s, "EOF"): |
| 219 | return OutcomeDPIReset, s |
| 220 | case strings.Contains(s, "unrecognized name"), |
| 221 | strings.Contains(s, "unknown_ca"), |
| 222 | strings.Contains(s, "bad_certificate"): |
| 223 | return OutcomeTLSError, s |
| 224 | } |
| 225 | return OutcomeTLSError, s |
| 226 | } |
| 227 | |
| 228 | func isTimeout(err error) bool { |
| 229 | var ne net.Error |