1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// Local TLS + WebSocket mock, the Dart twin of tests/mock_server.py.
// It answers the probe's upgrade with 101, reads the masked VLESS frame, then
// answers according to [MockMode]. Test-only: bound to 127.0.0.1, with the
// self-signed certificate in test/fixtures/.
import 'dart:async';
import 'dart:io';
enum MockMode {
/// 101, then "HTTP/1.1 204 No Content": a clean IP.
success204,
/// 101, then "HTTP/1.1 200 OK": reachable but not clean.
wrong200,
/// "HTTP/1.1 400": the upgrade is refused.
upgradeRefused,
/// Closes right after the TLS handshake, before any reply.
closeBeforeUpgrade,
}
class MockVlessServer {
MockVlessServer(this.mode);
final MockMode mode;
SecureServerSocket? _server;
StreamSubscription<SecureSocket>? _accepting;
/// Number of probes that were answered with a 204.
int accepted = 0;
int get port => _server!.port;
static SecurityContext _context() {
return SecurityContext()
..useCertificateChain('test/fixtures/mock_cert.pem')
..usePrivateKey('test/fixtures/mock_key.pem');
}
Future<void> start() async {
_server = await SecureServerSocket.bind(InternetAddress.loopbackIPv4, 0, _context());
_accepting = _server!.listen(_handle);
}
Future<void> close() async {
await _accepting?.cancel();
await _server?.close();
}
void _handle(SecureSocket client) {
final pending = BytesBuilder(copy: false);
var upgraded = false;
StreamSubscription<List<int>>? sub;
sub = client.listen(
(List<int> data) async {
if (!upgraded) {
pending.add(data);
final text = String.fromCharCodes(pending.toBytes());
if (!text.contains('\r\n\r\n')) {
return;
}
upgraded = true;
if (mode == MockMode.closeBeforeUpgrade) {
client.destroy();
return;
}
if (mode == MockMode.upgradeRefused) {
client.add('HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n'.codeUnits);
await client.flush();
client.destroy();
return;
}
client.add(
'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\n'
'Connection: Upgrade\r\n\r\n'
.codeUnits,
);
await client.flush();
return;
}
// The masked VLESS frame arrived: answer, then close.
final success = mode == MockMode.success204;
final reply = success
? 'HTTP/1.1 204 No Content\r\nContent-Length: 0\r\n\r\n'
: 'HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n';
client.add(reply.codeUnits);
await client.flush();
if (success) {
accepted++;
}
await sub?.cancel();
client.destroy();
},
onError: (Object _) {},
cancelOnError: true,
);
}
}