|
1 | 1 | package detector |
2 | 2 |
|
3 | | -import ( |
4 | | - "strings" |
5 | | -) |
| 3 | +import "github.com/daniellavrushin/b4/netprobe" |
6 | 4 |
|
7 | | -type tlsStage int |
| 5 | +type DomainStatus = netprobe.DomainStatus |
8 | 6 |
|
9 | 7 | const ( |
10 | | - stageConnect tlsStage = iota |
11 | | - stageHandshake |
12 | | - stageRead |
| 8 | + DomainOk = netprobe.DomainOk |
| 9 | + DomainTLSDPI = netprobe.DomainTLSDPI |
| 10 | + DomainTLSMITM = netprobe.DomainTLSMITM |
| 11 | + DomainTLSSpoof = netprobe.DomainTLSSpoof |
| 12 | + DomainTLSAlert = netprobe.DomainTLSAlert |
| 13 | + DomainTLSReset = netprobe.DomainTLSReset |
| 14 | + DomainTLSDrop = netprobe.DomainTLSDrop |
| 15 | + DomainSYNDrop = netprobe.DomainSYNDrop |
| 16 | + DomainTCP16 = netprobe.DomainTCP16 |
| 17 | + DomainISPPage = netprobe.DomainISPPage |
| 18 | + DomainBlocked = netprobe.DomainBlocked |
| 19 | + DomainDNSFake = netprobe.DomainDNSFake |
| 20 | + DomainTimeout = netprobe.DomainTimeout |
| 21 | + DomainError = netprobe.DomainError |
13 | 22 | ) |
14 | 23 |
|
| 24 | +type tlsStage = netprobe.TLSStage |
| 25 | + |
15 | 26 | const ( |
16 | | - tcp16MinBytes = 12 * 1024 |
17 | | - tcp16MaxBytes = 69 * 1024 |
| 27 | + stageConnect = netprobe.StageConnect |
| 28 | + stageHandshake = netprobe.StageHandshake |
| 29 | + stageRead = netprobe.StageRead |
18 | 30 | ) |
19 | 31 |
|
| 32 | +const tcp16MaxBytes = netprobe.TCP16MaxBytes |
| 33 | + |
20 | 34 | func ClassifyTLSError(err error) (DomainStatus, string) { |
21 | | - return ClassifyTLSErrorStaged(err, stageHandshake, 0) |
| 35 | + return netprobe.ClassifyTLSError(err) |
22 | 36 | } |
23 | 37 |
|
24 | 38 | func ClassifyTLSErrorStaged(err error, stage tlsStage, bytesRead int) (DomainStatus, string) { |
25 | | - if err == nil { |
26 | | - return DomainOk, "" |
27 | | - } |
28 | | - |
29 | | - msg := strings.ToLower(err.Error()) |
30 | | - |
31 | | - isTimeout := strings.Contains(msg, "timeout") || strings.Contains(msg, "deadline exceeded") || strings.Contains(msg, "timed out") |
32 | | - isEOF := strings.Contains(msg, "eof") |
33 | | - isReset := strings.Contains(msg, "connection reset") || strings.Contains(msg, "reset by peer") |
34 | | - |
35 | | - if stage == stageRead && isTimeout && bytesRead >= tcp16MinBytes && bytesRead <= tcp16MaxBytes { |
36 | | - return DomainTCP16, "Read stalled after TSPU fat-flow window (12-69KB)" |
37 | | - } |
38 | | - |
39 | | - if isTimeout { |
40 | | - switch stage { |
41 | | - case stageConnect: |
42 | | - return DomainSYNDrop, "TCP SYN dropped (no handshake)" |
43 | | - case stageHandshake: |
44 | | - return DomainTLSDrop, "TLS handshake timed out (drop)" |
45 | | - default: |
46 | | - return DomainTimeout, "Connection timed out" |
47 | | - } |
48 | | - } |
49 | | - |
50 | | - if strings.Contains(msg, "wrong version number") { |
51 | | - return DomainTLSSpoof, "Non-TLS response received (DPI replacement)" |
52 | | - } |
53 | | - for _, p := range []string{"record overflow", "oversized", "record layer failure", "decode error", "decoding error", "illegal parameter", "bad record mac", "decryption failed"} { |
54 | | - if strings.Contains(msg, p) { |
55 | | - return DomainTLSSpoof, "Garbage TLS response (DPI injection)" |
56 | | - } |
57 | | - } |
58 | | - |
59 | | - if strings.Contains(msg, "alert") || strings.Contains(msg, "unrecognized name") || strings.Contains(msg, "handshake failure") { |
60 | | - switch { |
61 | | - case strings.Contains(msg, "unrecognized name"): |
62 | | - return DomainTLSAlert, "SNI blocked (unrecognized name)" |
63 | | - case strings.Contains(msg, "protocol version"): |
64 | | - return DomainTLSAlert, "TLS protocol version alert" |
65 | | - default: |
66 | | - return DomainTLSAlert, "TLS alert (DPI disruption)" |
67 | | - } |
68 | | - } |
69 | | - |
70 | | - if isReset { |
71 | | - if stage == stageHandshake || stage == stageConnect { |
72 | | - return DomainTLSReset, "TCP RST during handshake (active reset)" |
73 | | - } |
74 | | - return DomainTLSReset, "TCP RST during transfer" |
75 | | - } |
76 | | - |
77 | | - if isEOF { |
78 | | - if stage == stageHandshake || bytesRead == 0 { |
79 | | - return DomainTLSReset, "Connection terminated (EOF injection)" |
80 | | - } |
81 | | - return DomainTLSReset, "Connection dropped during transfer (EOF)" |
82 | | - } |
83 | | - |
84 | | - for _, p := range []string{"self-signed", "self signed", "unknown authority", "certificate has expired", "certificate is not valid", "hostname mismatch", "name mismatch", "x509", "certificate"} { |
85 | | - if strings.Contains(msg, p) { |
86 | | - return DomainTLSMITM, "Certificate substitution (possible MITM)" |
87 | | - } |
88 | | - } |
89 | | - |
90 | | - if strings.Contains(msg, "no shared cipher") || strings.Contains(msg, "cipher") { |
91 | | - return DomainTLSMITM, "Cipher mismatch (possible MITM)" |
92 | | - } |
93 | | - |
94 | | - if strings.Contains(msg, "refused") { |
95 | | - return DomainBlocked, "Connection refused" |
96 | | - } |
97 | | - if strings.Contains(msg, "no such host") || strings.Contains(msg, "no address") { |
98 | | - return DomainError, "DNS resolution failed" |
99 | | - } |
100 | | - if strings.Contains(msg, "internal error") { |
101 | | - return DomainError, "TLS internal error" |
102 | | - } |
103 | | - |
104 | | - return DomainError, err.Error() |
| 39 | + return netprobe.ClassifyTLSErrorStaged(err, stage, bytesRead) |
105 | 40 | } |
106 | 41 |
|
107 | | -func ClassifyHTTPResponse(statusCode int, location string, body string) (DomainStatus, string) { |
108 | | - if statusCode == 451 { |
109 | | - return DomainISPPage, "HTTP 451 Unavailable For Legal Reasons" |
110 | | - } |
111 | | - |
112 | | - locLower := strings.ToLower(location) |
113 | | - for _, marker := range BlockMarkers { |
114 | | - if strings.Contains(locLower, marker) { |
115 | | - return DomainISPPage, "Redirect to ISP block page: " + location |
116 | | - } |
117 | | - } |
118 | | - |
119 | | - bodyLower := strings.ToLower(body) |
120 | | - for _, marker := range BodyBlockMarkers { |
121 | | - if strings.Contains(bodyLower, marker) { |
122 | | - return DomainISPPage, "ISP block page detected in response body" |
123 | | - } |
124 | | - } |
125 | | - |
126 | | - return DomainOk, "" |
| 42 | +func ClassifyHTTPResponse(statusCode int, location, body string) (DomainStatus, string) { |
| 43 | + return netprobe.ClassifyHTTPResponse(statusCode, location, body) |
127 | 44 | } |
0 commit comments