(error: ssl.SSLError, bytes_read: int, stage: str = "unknown")
| 66 | # ── Классификаторы ошибок ───────────────────────────────────────────────────── |
| 67 | |
| 68 | def classify_ssl_error(error: ssl.SSLError, bytes_read: int, stage: str = "unknown") -> Tuple[str, str, int]: |
| 69 | msg = str(error).lower() |
| 70 | full_text = collect_error_text(error) |
| 71 | |
| 72 | if "pop from an empty deque" in full_text or "brokenresourceerror" in full_text: |
| 73 | return ("[bold red]TLS RST[/bold red]", "Активный сброс (TCP RST)", bytes_read) |
| 74 | |
| 75 | if "wrong version number" in msg: |
| 76 | return ("[bold red]TLS SPOOF[/bold red]", "Подмена ответа (Wrong Version)", bytes_read) |
| 77 | if any(x in msg for x in ["record overflow", "oversized", "record layer failure", "decode error", "decoding error", "illegal parameter"]): |
| 78 | return ("[bold red]TLS SPOOF[/bold red]", "Подмена ответа (Garbage Data)", bytes_read) |
| 79 | |
| 80 | if "alert" in msg: |
| 81 | if "unrecognized_name" in msg or "unrecognized name" in msg: |
| 82 | return ("[bold red]TLS ALERT[/bold red]", "SNI Block (Unrecognized Name)", bytes_read) |
| 83 | if "handshake_failure" in msg or "handshake failure" in msg: |
| 84 | return ("[bold red]TLS ALERT[/bold red]", "DPI Alert (Handshake Failure)", bytes_read) |
| 85 | if "protocol_version" in msg: |
| 86 | # Если мы проверяем TLS 1.3, а прилетает этот алерт - возможно, блок версии |
| 87 | return ("[bold red]TLS BLOCK[/bold red]", "Protocol Version Alert", bytes_read) |
| 88 | return ("[bold red]TLS ALERT[/bold red]", "Поддельный TLS Alert", bytes_read) |
| 89 | |
| 90 | dpi_interruption = ["eof", "unexpected eof", "eof occurred", "operation did not complete", "want_read"] |
| 91 | if any(m in msg for m in dpi_interruption): |
| 92 | # Если это произошло во время хендшейка - в 99% это активный RST, замаскированный ОС под EOF |
| 93 | if bytes_read == 0 or stage == "tls_handshake": |
| 94 | return ("[bold red]TLS RST[/bold red]", "Активный сброс (TCP RST)", bytes_read) |
| 95 | |
| 96 | detail = "Обрыв при передаче (EOF)" if bytes_read > 0 else "Тихий обрыв (Handshake EOF)" |
| 97 | return ("[bold red]TLS EOF[/bold red]", detail, bytes_read) |
| 98 | |
| 99 | if isinstance(error, ssl.SSLCertVerificationError) or "certificate" in msg or "unknown ca" in msg: |
| 100 | verify_code = getattr(error, 'verify_code', None) |
| 101 | if verify_code == 10 or "expired" in msg: |
| 102 | return ("[bold red]TLS MITM[/bold red]", "Cert expired", bytes_read) |
| 103 | elif verify_code in (18, 19) or "self-signed" in msg: |
| 104 | return ("[bold red]TLS MITM[/bold red]", "Self-signed cert", bytes_read) |
| 105 | elif verify_code == 62 or "hostname mismatch" in msg: |
| 106 | return ("[bold red]TLS MITM[/bold red]", "Hostname mismatch", bytes_read) |
| 107 | return ("[bold red]TLS MITM[/bold red]", "Подмена сертификата", bytes_read) |
| 108 | |
| 109 | if "version" in msg or "protocol version" in msg: |
| 110 | return ("[bold red]NO TLS1.3[/bold red]", "Server has no TLS 1.3", bytes_read) |
| 111 | |
| 112 | if "internal error" in msg: |
| 113 | return ("[red]SSL ERR[/red]", "Internal error", bytes_read) |
| 114 | |
| 115 | return ("[red]SSL ERR[/red]", clean_detail(str(error)[:40]), bytes_read) |
| 116 | |
| 117 | |
| 118 | def classify_connect_error(error: Exception, bytes_read: int, stage: str = "unknown") -> Tuple[str, str, int]: |
no test coverage detected