Line data Source code
1 : import 'dart:async';
2 : import 'dart:isolate';
3 : import 'dart:math';
4 :
5 : import 'package:dart_ping/dart_ping.dart';
6 : import 'package:network_tools/network_tools.dart';
7 : import 'package:network_tools/src/injection.dart';
8 : import 'package:network_tools/src/network_tools_utils.dart';
9 : import 'package:network_tools/src/repository/repository.dart';
10 : import 'package:universal_io/io.dart';
11 :
12 : /// Scans for all hosts in a subnet.
13 : class HostScannerServiceImpl extends HostScannerService {
14 : /// Scans for all hosts in a particular subnet (e.g., 192.168.1.0/24)
15 : /// Set maxHost to higher value if you are not getting results.
16 : /// It won't firstHostId again unless previous scan is completed due to heavy
17 : /// resource consumption.
18 : /// Use hostIds to limit subnet scan to hosts given.
19 : /// [resultsInAddressAscendingOrder] = false will return results faster but not in
20 : /// ascending order and without [progressCallback].
21 1 : @override
22 : Stream<ActiveHost> getAllPingableDevices(
23 : String subnet, {
24 : int firstHostId = HostScannerService.defaultFirstHostId,
25 : int lastHostId = HostScannerService.defaultLastHostId,
26 : List<int> hostIds = const [],
27 : int timeoutInSeconds = 1,
28 : ProgressCallback? progressCallback,
29 : bool resultsInAddressAscendingOrder = true,
30 : }) async* {
31 1 : final stream = getAllSendablePingableDevices(
32 : subnet,
33 : firstHostId: firstHostId,
34 : lastHostId: lastHostId,
35 : hostIds: hostIds,
36 : timeoutInSeconds: timeoutInSeconds,
37 : progressCallback: progressCallback,
38 : resultsInAddressAscendingOrder: resultsInAddressAscendingOrder,
39 : );
40 2 : await for (final sendableActiveHost in stream) {
41 1 : final activeHost = ActiveHost.fromSendableActiveHost(
42 : sendableActiveHost: sendableActiveHost,
43 : );
44 :
45 1 : await activeHost.resolveInfo();
46 :
47 : yield activeHost;
48 : }
49 : }
50 :
51 : /// Same as [getAllPingableDevices] but can be called or run inside isolate.
52 1 : @override
53 : Stream<SendableActiveHost> getAllSendablePingableDevices(
54 : String subnet, {
55 : int firstHostId = HostScannerService.defaultFirstHostId,
56 : int lastHostId = HostScannerService.defaultLastHostId,
57 : List<int> hostIds = const [],
58 : int timeoutInSeconds = 1,
59 : ProgressCallback? progressCallback,
60 : bool resultsInAddressAscendingOrder = true,
61 : }) async* {
62 1 : final int lastValidSubnet = validateAndGetLastValidSubnet(
63 : subnet,
64 : firstHostId,
65 : lastHostId,
66 : );
67 1 : final List<Future<SendableActiveHost?>> activeHostsFuture = [];
68 : final StreamController<SendableActiveHost> activeHostsController =
69 1 : StreamController<SendableActiveHost>();
70 :
71 1 : final List<int> pinged = [];
72 2 : for (int i = firstHostId; i <= lastValidSubnet; i++) {
73 2 : if (hostIds.isEmpty || hostIds.contains(i)) {
74 1 : pinged.add(i);
75 1 : activeHostsFuture.add(
76 1 : getHostFromPing(
77 : activeHostsController: activeHostsController,
78 1 : host: '$subnet.$i',
79 : timeoutInSeconds: timeoutInSeconds,
80 : ),
81 : );
82 : }
83 : }
84 :
85 : if (!resultsInAddressAscendingOrder) {
86 0 : yield* activeHostsController.stream;
87 : }
88 :
89 : int i = 0;
90 2 : for (final Future<SendableActiveHost?> host in activeHostsFuture) {
91 : final SendableActiveHost? tempHost = await host;
92 :
93 0 : progressCallback?.call(
94 0 : (pinged[i] - firstHostId) * 100 / (lastValidSubnet - firstHostId),
95 : );
96 1 : i++;
97 :
98 : if (tempHost == null) {
99 : continue;
100 : }
101 : yield tempHost;
102 : }
103 : }
104 :
105 1 : Future<SendableActiveHost?> getHostFromPing({
106 : required String host,
107 : required StreamController<SendableActiveHost> activeHostsController,
108 : int timeoutInSeconds = 1,
109 : }) async {
110 : SendableActiveHost? tempSendableActivateHost;
111 :
112 2 : await for (final PingData pingData in Ping(
113 : host,
114 : count: 1,
115 : timeout: timeoutInSeconds,
116 1 : forceCodepage: Platform.isWindows,
117 2 : ).stream) {
118 1 : final PingResponse? response = pingData.response;
119 1 : final PingError? pingError = pingData.error;
120 : if (response != null) {
121 : // Check if ping succeeded
122 : if (pingError == null) {
123 1 : final Duration? time = response.time;
124 : if (time != null) {
125 3 : logger.fine("Pingable device found: $host");
126 1 : tempSendableActivateHost = SendableActiveHost(
127 : host,
128 : pingData: pingData,
129 : );
130 : } else {
131 0 : logger.fine("Non pingable device found: $host");
132 : }
133 : }
134 : }
135 : if (tempSendableActivateHost == null) {
136 : // Check if it's there in arp table
137 :
138 3 : final data = await getIt<Repository<ARPData>>().entryFor(host);
139 :
140 : if (data != null) {
141 0 : logger.fine("Successfully fetched arp entry for $host as $data");
142 0 : tempSendableActivateHost = SendableActiveHost(
143 : host,
144 : pingData: pingData,
145 : );
146 : } else {
147 3 : logger.fine("Problem in fetching arp entry for $host");
148 : }
149 : }
150 :
151 : if (tempSendableActivateHost != null) {
152 3 : logger.fine("Successfully added to result $host");
153 1 : activeHostsController.add(tempSendableActivateHost);
154 : }
155 : }
156 :
157 : return tempSendableActivateHost;
158 : }
159 :
160 1 : @override
161 : int validateAndGetLastValidSubnet(
162 : String subnet,
163 : int firstHostId,
164 : int lastHostId,
165 : ) {
166 1 : final int maxEnd = maxHost;
167 1 : if (firstHostId > lastHostId ||
168 1 : firstHostId < HostScannerService.defaultFirstHostId ||
169 1 : lastHostId < HostScannerService.defaultFirstHostId ||
170 1 : firstHostId > maxEnd ||
171 1 : lastHostId > maxEnd) {
172 : throw 'Invalid subnet range or firstHostId < lastHostId is not true';
173 : }
174 1 : return min(lastHostId, maxEnd);
175 : }
176 :
177 : /// Works same as [getAllPingableDevices] but does everything inside
178 : /// isolate out of the box.
179 1 : @override
180 : Stream<ActiveHost> getAllPingableDevicesAsync(
181 : String subnet, {
182 : int firstHostId = HostScannerService.defaultFirstHostId,
183 : int lastHostId = HostScannerService.defaultLastHostId,
184 : List<int> hostIds = const [],
185 : int timeoutInSeconds = 1,
186 : ProgressCallback? progressCallback,
187 : bool resultsInAddressAscendingOrder = true,
188 : }) async* {
189 : const int scanRangeForIsolate = 51;
190 1 : final int lastValidSubnet = validateAndGetLastValidSubnet(
191 : subnet,
192 : firstHostId,
193 : lastHostId,
194 : );
195 : for (
196 : int i = firstHostId;
197 1 : i <= lastValidSubnet;
198 2 : i += scanRangeForIsolate + 1
199 : ) {
200 2 : final limit = min(i + scanRangeForIsolate, lastValidSubnet);
201 3 : logger.fine('Scanning from $i to $limit');
202 :
203 1 : final receivePort = ReceivePort();
204 1 : final isolate = await Isolate.spawn(
205 : _startSearchingDevices,
206 1 : receivePort.sendPort,
207 : );
208 :
209 2 : await for (final message in receivePort) {
210 1 : if (message is SendPort) {
211 2 : message.send(<String>[
212 : subnet,
213 1 : i.toString(),
214 1 : limit.toString(),
215 1 : timeoutInSeconds.toString(),
216 1 : resultsInAddressAscendingOrder.toString(),
217 1 : dbDirectory,
218 2 : enableDebugging.toString(),
219 1 : hostIds.join(','),
220 : ]);
221 1 : } else if (message is SendableActiveHost) {
222 1 : final activeHostFound = ActiveHost.fromSendableActiveHost(
223 : sendableActiveHost: message,
224 : );
225 1 : await activeHostFound.resolveInfo();
226 2 : final j = int.tryParse(activeHostFound.hostId) ?? i;
227 0 : progressCallback?.call(
228 0 : (j - firstHostId) * 100 / (lastValidSubnet - firstHostId),
229 : );
230 : yield activeHostFound;
231 2 : } else if (message is String && message == 'Done') {
232 1 : isolate.kill();
233 : break;
234 : }
235 : }
236 : }
237 : }
238 :
239 : /// Will search devices in the network inside new isolate
240 1 : @pragma('vm:entry-point')
241 : static Future<void> _startSearchingDevices(SendPort sendPort) async {
242 1 : final port = ReceivePort();
243 2 : sendPort.send(port.sendPort);
244 :
245 2 : await for (final message in port) {
246 1 : if (message is List<String>) {
247 1 : final String subnetIsolate = message[0];
248 2 : final int firstSubnetIsolate = int.parse(message[1]);
249 2 : final int lastSubnetIsolate = int.parse(message[2]);
250 2 : final int timeoutInSeconds = int.parse(message[3]);
251 2 : final bool resultsInAddressAscendingOrder = message[4] == "true";
252 1 : final String dbDirectory = message[5];
253 2 : final bool enableDebugging = message[6] == "true";
254 1 : final String joinedIds = message[7];
255 : final List<int> hostIds = joinedIds
256 1 : .split(',')
257 3 : .where((e) => e.isNotEmpty)
258 1 : .map(int.parse)
259 1 : .toList();
260 : // configure again
261 1 : await configureNetworkTools(
262 : dbDirectory,
263 : enableDebugging: enableDebugging,
264 : );
265 :
266 : /// Will contain all the hosts that got discovered in the network, will
267 : /// be use inorder to cancel on dispose of the page.
268 : final Stream<SendableActiveHost> hostsDiscoveredInNetwork =
269 2 : HostScannerService.instance.getAllSendablePingableDevices(
270 : subnetIsolate,
271 : firstHostId: firstSubnetIsolate,
272 : lastHostId: lastSubnetIsolate,
273 : hostIds: hostIds,
274 : timeoutInSeconds: timeoutInSeconds,
275 : resultsInAddressAscendingOrder: resultsInAddressAscendingOrder,
276 : );
277 :
278 1 : await for (final SendableActiveHost activeHostFound
279 1 : in hostsDiscoveredInNetwork) {
280 1 : sendPort.send(activeHostFound);
281 : }
282 1 : sendPort.send('Done');
283 : }
284 : }
285 : }
286 :
287 : /// Scans for all hosts that have the specific port that was given.
288 : /// [resultsInAddressAscendingOrder] = false will return results faster but not in
289 : /// ascending order and without [progressCallback].
290 1 : @override
291 : Stream<ActiveHost> scanDevicesForSinglePort(
292 : String subnet,
293 : int port, {
294 : int firstHostId = HostScannerService.defaultFirstHostId,
295 : int lastHostId = HostScannerService.defaultLastHostId,
296 : Duration timeout = const Duration(milliseconds: 2000),
297 : ProgressCallback? progressCallback,
298 : bool resultsInAddressAscendingOrder = true,
299 : }) async* {
300 1 : final int lastValidSubnet = validateAndGetLastValidSubnet(
301 : subnet,
302 : firstHostId,
303 : lastHostId,
304 : );
305 1 : final List<Future<ActiveHost?>> activeHostOpenPortList = [];
306 : final StreamController<ActiveHost> activeHostsController =
307 1 : StreamController<ActiveHost>();
308 :
309 2 : for (int i = firstHostId; i <= lastValidSubnet; i++) {
310 1 : final host = '$subnet.$i';
311 1 : activeHostOpenPortList.add(
312 2 : PortScannerService.instance.connectToPort(
313 : address: host,
314 : port: port,
315 : timeout: timeout,
316 : activeHostsController: activeHostsController,
317 : ),
318 : );
319 : }
320 :
321 : if (!resultsInAddressAscendingOrder) {
322 0 : yield* activeHostsController.stream;
323 : }
324 :
325 : int counter = firstHostId;
326 : for (final Future<ActiveHost?> openPortActiveHostFuture
327 2 : in activeHostOpenPortList) {
328 : final ActiveHost? activeHost = await openPortActiveHostFuture;
329 : if (activeHost != null) {
330 : yield activeHost;
331 : }
332 0 : progressCallback?.call(
333 0 : (counter - firstHostId) * 100 / (lastValidSubnet - firstHostId),
334 : );
335 1 : counter++;
336 : }
337 : }
338 :
339 : /// Defines total number of subnets in class A network
340 : final classASubnets = 16777216;
341 :
342 : /// Defines total number of subnets in class B network
343 : final classBSubnets = 65536;
344 :
345 : /// Defines total number of subnets in class C network
346 : final classCSubnets = 256;
347 :
348 : /// Minimum value of first octet in IPv4 address used by getMaxHost
349 : final int minNetworkId = 1;
350 :
351 : /// Maximum value of first octect in IPv4 address used by getMaxHost
352 : final int maxNetworkId = 223;
353 :
354 : /// returns the max number of hosts a subnet can have excluding network Id and broadcast Id
355 0 : @Deprecated(
356 : "Implementation is wrong, since we only append in last octet, max host can only be 254. Use maxHost getter",
357 : )
358 : int getMaxHost(String subnet) {
359 0 : if (subnet.isEmpty) {
360 0 : throw ArgumentError('Invalid subnet address, address can not be empty.');
361 : }
362 0 : final List<String> firstOctetStr = subnet.split('.');
363 0 : if (firstOctetStr.isEmpty) {
364 0 : throw ArgumentError(
365 : 'Invalid subnet address, address should be in IPv4 format x.x.x',
366 : );
367 : }
368 :
369 0 : final int firstOctet = int.parse(firstOctetStr[0]);
370 :
371 0 : if (firstOctet >= minNetworkId && firstOctet < 128) {
372 0 : return classASubnets;
373 0 : } else if (firstOctet >= 128 && firstOctet < 192) {
374 0 : return classBSubnets;
375 0 : } else if (firstOctet >= 192 && firstOctet <= maxNetworkId) {
376 0 : return classCSubnets;
377 : }
378 : // Out of range for first octet
379 0 : throw RangeError.range(
380 : firstOctet,
381 0 : minNetworkId,
382 0 : maxNetworkId,
383 : 'subnet',
384 : 'Out of range for first octet',
385 : );
386 : }
387 :
388 1 : int get maxHost => HostScannerService.defaultLastHostId;
389 : }
|