Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arp data for Linux, Windows and Macos #139

Merged
merged 10 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## 3.2.5

1. Added mac address support on Desktop (Linux, macOS, and Windows)

## 3.2.5

1. Bug fix for iOS listing all results in HostScanner

## 3.2.4
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
Network Tools Supported

1. Host Scanner
1. Search all devices on subnet
2. Get mac address of devices on Linux, macOS and Windows.
3. Search devices for a specific port open.

2. Port Scanner
1. Single
1. Single Port scan
2. Range
3. Custom

Expand Down
1 change: 1 addition & 0 deletions lib/src/host_scanner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ class HostScanner {
final activeHostFound =
ActiveHost.fromSendableActiveHost(sendableActiveHost: message);
await activeHostFound.resolveInfo();
log.fine("Found host: ${await activeHostFound.toStringFull()}");
yield activeHostFound;
} else if (message is String && message == 'Done') {
isolate.kill();
Expand Down
28 changes: 21 additions & 7 deletions lib/src/models/active_host.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'package:dart_ping/dart_ping.dart';
import 'package:network_tools/src/models/arp_data.dart';
import 'package:network_tools/src/models/arp_table.dart';
import 'package:network_tools/src/models/mdns_info.dart';
import 'package:network_tools/src/models/open_port.dart';
import 'package:network_tools/src/models/sendable_active_host.dart';
Expand Down Expand Up @@ -46,6 +48,8 @@ class ActiveHost extends Comparable<ActiveHost> {
}

deviceName = setDeviceName();
// fetch entry from in memory arp table
arpData = setARPData();
}
factory ActiveHost.buildWithAddress({
required String address,
Expand Down Expand Up @@ -84,13 +88,8 @@ class ActiveHost extends Comparable<ActiveHost> {
);
}

Future<void> resolveInfo() async {
await deviceName;
await mdnsInfo;
await hostName;
}

static const generic = 'Generic Device';

InternetAddress internetAddress;

/// The device specific number in the ip address. In IPv4 numbers after the
Expand All @@ -106,6 +105,10 @@ class ActiveHost extends Comparable<ActiveHost> {
/// Mdns information of this device
late Future<MdnsInfo?> mdnsInfo;

/// Resolve ARP data for this host.
/// only supported on Linux, Macos and Windows otherwise null
late Future<ARPData?> arpData;

/// List of all the open port of this device
List<OpenPort> openPorts;

Expand Down Expand Up @@ -165,6 +168,17 @@ class ActiveHost extends Comparable<ActiveHost> {
return null;
}

Future<void> resolveInfo() async {
await deviceName;
await mdnsInfo;
await hostName;
await arpData;
}

Future<ARPData?> setARPData() async {
return ARPTable.entryFor(address);
}

/// Try to find the mdns name of this device, if not exist mdns name will
/// be null
/// TODO: search mdns name for each device
Expand Down Expand Up @@ -204,6 +218,6 @@ class ActiveHost extends Comparable<ActiveHost> {
}

Future<String> toStringFull() async {
return 'Address: $address, HostId: $hostId Time: ${responseTime?.inMilliseconds}ms, DeviceName: ${await deviceName}, HostName: ${await hostName}, MdnsInfo: ${await mdnsInfo}';
return 'Address: $address, MAC: ${(await arpData)?.macAddress}, HostId: $hostId Time: ${responseTime?.inMilliseconds}ms, DeviceName: ${await deviceName}, HostName: ${await hostName}, MdnsInfo: ${await mdnsInfo}';
}
}
29 changes: 29 additions & 0 deletions lib/src/models/arp_data.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'dart:io';

class ARPData {
ARPData({
required this.host,
required this.iPAddress,
required this.macAddress,
required this.interfaceName,
required this.interfaceType,
});

final String? host;
final String? iPAddress;
final String? macAddress;
final String? interfaceName;
final String? interfaceType;

@override
String toString() {
if (Platform.isMacOS) {
return '$host ($iPAddress) at $macAddress on $interfaceName ifscope [$interfaceType]';
} else if (Platform.isLinux) {
return '$host ($iPAddress) at $macAddress [$interfaceType] on $interfaceName';
} else if (Platform.isWindows) {
return 'Internet Address: $iPAddress, Physical Address: $macAddress, Type: $interfaceType';
}
return '$host ($iPAddress) at $macAddress on $interfaceName type [$interfaceType]';
}
}
53 changes: 53 additions & 0 deletions lib/src/models/arp_table.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import 'dart:convert';
import 'dart:io';

import 'package:logging/logging.dart';
import 'package:network_tools/src/models/arp_data.dart';

class ARPTable {
static final arpLogger = Logger("arp-table-logger");
static final arpTable = <String, ARPData>{};
static bool resolved = false;

static Future<ARPData?> entryFor(String address) async {
if (resolved) return arpTable[address];
final result = await Process.run('arp', ['-a']);
final entries = const LineSplitter().convert(result.stdout.toString());
RegExp? pattern;
if (Platform.isMacOS) {
pattern = RegExp(
r'(?<host>[\w.?]*)\s\((?<ip>.*)\)\sat\s(?<mac>.*)\son\s(?<intf>\w+)\sifscope\s*(\w*)\s*\[(?<typ>.*)\]',
);
} else if (Platform.isLinux) {
pattern = RegExp(
r'(?<host>[\w.?]*)\s\((?<ip>.*)\)\sat\s(?<mac>.*)\s\[(?<typ>.*)\]\son\s(?<intf>\w+)',
);
} else {
pattern = RegExp(r'(?<ip>.*)\s(?<mac>.*)\s(?<typ>.*)');
}

for (final entry in entries) {
final match = pattern.firstMatch(entry);
if (match != null) {
final arpData = ARPData(
host: match.groupNames.contains('host')
? match.namedGroup("host")
: null,
iPAddress: match.namedGroup("ip"),
macAddress: match.namedGroup("mac"),
interfaceName: match.groupNames.contains('intf')
? match.namedGroup("intf")
: null,
interfaceType: match.namedGroup("typ"),
);
final key = arpData.iPAddress;
if (key != null) {
arpLogger.fine("Adding entry to table -> $arpData");
arpTable[key] = arpData;
}
}
}
resolved = true;
return arpTable[address];
}
}
7 changes: 3 additions & 4 deletions test/network_tools_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:async';

import 'package:logging/logging.dart';
import 'package:network_tools/network_tools.dart';
import 'package:test/test.dart';
Expand All @@ -10,9 +9,9 @@ void main() {

// Logger.root.level = Level.FINE;
// Logger.root.onRecord.listen((record) {
// print(
// '${DateFormat.Hms().format(record.time)}: ${record.level.name}: ${record.loggerName}: ${record.message}',
// );
// // print(
// // '${DateFormat.Hms().format(record.time)}: ${record.level.name}: ${record.loggerName}: ${record.message}',
// // );
// });

int port = 0;
Expand Down