Line data Source code
1 : import 'dart:async';
2 : import 'dart:isolate';
3 : import 'dart:math';
4 :
5 : import 'package:network_tools/network_tools.dart';
6 : import 'package:universal_io/io.dart';
7 :
8 : /// Scans open port for a target Address or domain.
9 : class PortScannerServiceImpl extends PortScannerService {
10 : /// Checks if the single [port] is open or not for the [target].
11 : ///
12 : /// Returns an [ActiveHost] if the port is open, otherwise null.
13 : /// Throws if the port is out of range or the target cannot be resolved.
14 1 : @override
15 : Future<ActiveHost?> isOpen(
16 : String target,
17 : int port, {
18 : Duration timeout = const Duration(milliseconds: 2000),
19 : }) async {
20 2 : if (port < 0 || port > 65535) {
21 : throw 'Provide a valid port range between '
22 : '0 to 65535 or startPort < endPort is not true';
23 : }
24 1 : final List<InternetAddress> address = await InternetAddress.lookup(
25 : target,
26 : type: InternetAddressType.IPv4,
27 : );
28 1 : if (address.isNotEmpty) {
29 2 : final String hostAddress = address[0].address;
30 1 : return connectToPort(
31 1 : activeHostsController: StreamController<ActiveHost>(),
32 : address: hostAddress,
33 : port: port,
34 : timeout: timeout,
35 : );
36 : } else {
37 : throw 'Name can not be resolved';
38 : }
39 : }
40 :
41 : /// Scans only the ports listed in [portList] for a [target].
42 : ///
43 : /// Progress can be retrieved by [progressCallback].
44 : /// Tries connecting ports until [timeout] is reached for each port.
45 : /// If [resultsInAddressAscendingOrder] is false, results may be returned faster but not in ascending order and without [progressCallback].
46 : /// If [async] is true, scanning is performed in isolates for better performance on large port lists.
47 1 : @override
48 : Stream<ActiveHost> customDiscover(
49 : String target, {
50 : List<int> portList = PortScannerService.commonPorts,
51 : ProgressCallback? progressCallback,
52 : Duration timeout = const Duration(milliseconds: 2000),
53 : bool resultsInAddressAscendingOrder = true,
54 : bool async = false,
55 : }) async* {
56 : if (!async) {
57 : // If async is false then run on main isolate
58 1 : yield* _customDiscover<ActiveHost>(
59 : target,
60 : portList: portList,
61 : progressCallback: progressCallback,
62 : timeout: timeout,
63 : resultsInAddressAscendingOrder: resultsInAddressAscendingOrder,
64 : );
65 : } else {
66 : const int scanRangeForIsolate = 1000;
67 4 : for (int i = 0; i <= portList.length; i += scanRangeForIsolate + 1) {
68 3 : final limit = min(i + scanRangeForIsolate, portList.length);
69 1 : final receivePort = ReceivePort();
70 1 : final isolate = await Isolate.spawn(
71 1 : _startSearchingPorts,
72 1 : receivePort.sendPort,
73 : );
74 :
75 2 : await for (final message in receivePort) {
76 1 : if (message is SendPort) {
77 2 : message.send(<dynamic>[
78 : target,
79 1 : portList.sublist(i, limit),
80 : timeout,
81 1 : resultsInAddressAscendingOrder.toString(),
82 1 : dbDirectory,
83 2 : enableDebugging.toString(),
84 : ]);
85 1 : } else if (message is SendableActiveHost) {
86 0 : progressCallback?.call(i * 100 / (portList.length));
87 1 : final activeHostFound = ActiveHost.fromSendableActiveHost(
88 : sendableActiveHost: message,
89 : );
90 1 : await activeHostFound.resolveInfo();
91 : yield activeHostFound;
92 2 : } else if (message is String && message == 'Done') {
93 1 : isolate.kill();
94 : break;
95 : }
96 : }
97 : }
98 : }
99 : }
100 :
101 1 : @pragma('vm:entry-point')
102 : Future<void> _startSearchingPorts(SendPort sendPort) async {
103 1 : final port = ReceivePort();
104 2 : sendPort.send(port.sendPort);
105 :
106 2 : await for (final message in port) {
107 1 : if (message is List<dynamic>) {
108 1 : final String target = message[0] as String;
109 1 : final List<int> portList = message[1] as List<int>;
110 1 : final Duration timeout = message[2] as Duration;
111 2 : final bool resultsInAddressAscendingOrder = message[3] == "true";
112 1 : final String dbDirectory = message[4] as String;
113 2 : final bool enableDebugging = message[5] == "true";
114 : // configure again
115 1 : await configureNetworkTools(
116 : dbDirectory,
117 : enableDebugging: enableDebugging,
118 : );
119 1 : final openPortsForTarget = _customDiscover<SendableActiveHost>(
120 : target,
121 : portList: portList,
122 : timeout: timeout,
123 : resultsInAddressAscendingOrder: resultsInAddressAscendingOrder,
124 : );
125 :
126 1 : await for (final SendableActiveHost activeHostFound
127 1 : in openPortsForTarget) {
128 1 : sendPort.send(activeHostFound);
129 : }
130 1 : sendPort.send('Done');
131 : }
132 : }
133 : }
134 :
135 1 : Stream<T> _customDiscover<T>(
136 : String target, {
137 : List<int> portList = PortScannerService.commonPorts,
138 : ProgressCallback? progressCallback,
139 : Duration timeout = const Duration(milliseconds: 2000),
140 : bool resultsInAddressAscendingOrder = true,
141 : }) async* {
142 1 : final List<InternetAddress> address = await InternetAddress.lookup(
143 : target,
144 : type: InternetAddressType.IPv4,
145 : );
146 1 : if (address.isNotEmpty) {
147 2 : final String hostAddress = address[0].address;
148 1 : final List<Future<T?>> openPortList = [];
149 1 : final StreamController<T> activeHostsController = StreamController<T>();
150 :
151 3 : for (int k = 0; k < portList.length; k++) {
152 4 : if (portList[k] >= 0 && portList[k] <= 65535) {
153 1 : openPortList.add(
154 1 : _connectToPort<T>(
155 : address: hostAddress,
156 1 : port: portList[k],
157 : timeout: timeout,
158 : activeHostsController: activeHostsController,
159 : ),
160 : );
161 : }
162 : }
163 :
164 : if (!resultsInAddressAscendingOrder) {
165 0 : yield* activeHostsController.stream;
166 : }
167 :
168 : int counter = 0;
169 :
170 2 : for (final Future<T?> openPortFuture in openPortList) {
171 : final T? openPort = await openPortFuture;
172 : if (openPort == null) {
173 : continue;
174 : }
175 0 : progressCallback?.call(counter * 100 / portList.length);
176 : yield openPort;
177 1 : counter++;
178 : }
179 : } else {
180 : throw 'Name can not be resolved';
181 : }
182 : }
183 :
184 : /// Scans ports from [startPort] to [endPort] for a [target].
185 : ///
186 : /// Progress can be retrieved by [progressCallback].
187 : /// Tries connecting ports until [timeout] is reached for each port.
188 : /// If [resultsInAddressAscendingOrder] is false, results may be returned faster but not in ascending order and without [progressCallback].
189 : /// If [async] is true, scanning is performed in isolates for better performance on large port ranges.
190 1 : @override
191 : Stream<ActiveHost> scanPortsForSingleDevice(
192 : String target, {
193 : int startPort = PortScannerService.defaultStartPort,
194 : int endPort = PortScannerService.defaultEndPort,
195 : ProgressCallback? progressCallback,
196 : Duration timeout = const Duration(milliseconds: 2000),
197 : bool resultsInAddressAscendingOrder = true,
198 : bool async = false,
199 : }) async* {
200 1 : if (startPort < 0 ||
201 1 : endPort < 0 ||
202 1 : startPort > 65535 ||
203 1 : endPort > 65535 ||
204 1 : startPort > endPort) {
205 : throw 'Provide a valid port range between 0 to 65535 or startPort <'
206 : ' endPort is not true';
207 : }
208 :
209 1 : final List<int> portList = [];
210 :
211 2 : for (int i = startPort; i <= endPort; ++i) {
212 1 : portList.add(i);
213 : }
214 :
215 1 : yield* customDiscover(
216 : target,
217 : portList: portList,
218 : progressCallback: progressCallback,
219 : timeout: timeout,
220 : resultsInAddressAscendingOrder: resultsInAddressAscendingOrder,
221 : async: async,
222 : );
223 : }
224 :
225 : /// Attempts to connect to a specific [port] on an [address] within a [timeout].
226 : ///
227 : /// Returns an [ActiveHost] if the port is open, otherwise null. Used internally.
228 1 : @override
229 : Future<ActiveHost?> connectToPort({
230 : required String address,
231 : required int port,
232 : required Duration timeout,
233 : required StreamController<ActiveHost> activeHostsController,
234 : int recursionCount = 0,
235 : }) async {
236 1 : return await _connectToPort<ActiveHost>(
237 : address: address,
238 : port: port,
239 : timeout: timeout,
240 : activeHostsController: activeHostsController,
241 : );
242 : }
243 :
244 1 : Future<T?> _connectToPort<T>({
245 : required String address,
246 : required int port,
247 : required Duration timeout,
248 : required StreamController<T> activeHostsController,
249 : int recursionCount = 0,
250 : }) async {
251 : try {
252 1 : final Socket s = await Socket.connect(address, port, timeout: timeout);
253 1 : s.destroy();
254 :
255 1 : if (T == SendableActiveHost) {
256 1 : final SendableActiveHost sendableActiveHost = SendableActiveHost(
257 : address,
258 2 : openPorts: [OpenPort(port)],
259 : );
260 1 : activeHostsController.add(sendableActiveHost as T);
261 : return sendableActiveHost as T;
262 : }
263 1 : final ActiveHost activeHost = ActiveHost.buildWithAddress(
264 : address: address,
265 2 : openPorts: [OpenPort(port)],
266 : );
267 1 : activeHostsController.add(activeHost as T);
268 : return activeHost as T;
269 : } catch (e) {
270 1 : if (e is! SocketException) {
271 : rethrow;
272 : }
273 :
274 : // Check if connection timed out or we got one of predefined errors
275 5 : if (e.osError == null || _errorCodes.contains(e.osError?.errorCode)) {
276 : return null;
277 : }
278 :
279 : // Error 23,24: Too many open files in system
280 : // e.osError can't be null here so `!` can be used
281 : // Do no more than 2 retries to prevent infinite loops
282 0 : if (recursionCount < 3 &&
283 0 : (e.osError!.errorCode == 23 || e.osError!.errorCode == 24)) {
284 : // Hotfix: Wait for the timeout (+ a little more) to complete and retry
285 : // -> Other connections must be closed now and the file handles available again
286 :
287 0 : await Future.delayed(timeout + const Duration(milliseconds: 250));
288 :
289 0 : return _connectToPort<T>(
290 : address: address,
291 : port: port,
292 : timeout: timeout,
293 : activeHostsController: activeHostsController,
294 0 : recursionCount: recursionCount + 1,
295 : );
296 : }
297 :
298 : rethrow;
299 : }
300 : }
301 :
302 : final _errorCodes = [13, 49, 61, 64, 65, 101, 110, 111, 113, 10060];
303 : }
|