/
index.js
181 lines (156 loc) 路 4.27 KB
/
index.js
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
var ip = require('ip')
var os = require('os')
var net = require('net')
var cp = require('mz/child_process')
var getIPRange = require('get-ip-range')
var parseLinux = require('./parser/linux')
var parseWin32 = require('./parser/win32')
var parseRow = require('./parser')
var servers
var lock = {}
const TEN_MEGA_BYTE = 1024 * 1024 * 10
const ONE_MINUTE = 60 * 1000
const options = {
maxBuffer: TEN_MEGA_BYTE,
timeout: ONE_MINUTE
}
/**
* Finds all local devices (ip and mac address) connected to the current network.
*/
module.exports = function findLocalDevices (address, skipNameResolution = false) {
var key = String(address)
if (isRange(address)) {
try {
servers = getIPRange(key)
} catch (error) {
// Note: currently doesn't throw the intended error message from the package maintainer
// It will still be caught, PR here though: https://github.com/JoeScho/get-ip-range/pull/6
return error
}
} else {
servers = getServers()
}
if (!lock[key]) {
if (!address || isRange(key)) {
lock[key] = pingServers().then((servers) => arpAll(servers, skipNameResolution)).then(unlock(key))
} else {
lock[key] = pingServer(address).then(arpOne).then(unlock(key))
}
}
return lock[key]
}
function isRange (address) {
return address && new RegExp('/|-').test(address)
}
/**
* Gets the current list of possible servers in the local networks.
*/
function getServers () {
var interfaces = os.networkInterfaces()
var result = []
for (var key in interfaces) {
var addresses = interfaces[key]
for (var i = addresses.length; i--;) {
var address = addresses[i]
if (address.family === 'IPv4' && !address.internal) {
var subnet = ip.subnet(address.address, address.netmask)
var current = ip.toLong(subnet.firstAddress)
var last = ip.toLong(subnet.lastAddress) - 1
while (current++ < last) result.push(ip.fromLong(current))
}
}
}
return result
}
/**
* Sends a ping to all servers to update the arp table.
*/
function pingServers () {
return Promise.all(servers.map(pingServer))
}
/**
* Pings an individual server to update the arp table.
*/
function pingServer (address) {
return new Promise(function (resolve) {
var socket = new net.Socket()
socket.setTimeout(1000, close)
socket.connect(80, address, close)
socket.once('error', close)
function close () {
socket.destroy()
resolve(address)
}
})
}
/**
* Reads the arp table.
*/
function arpAll (_, skipNameResolution = false) {
const cmd = skipNameResolution ? 'arp -an' : 'arp -a'
return cp.exec(cmd, options).then(parseAll)
}
/**
* Parses arp scan data into a useable collection.
*/
function parseAll (data) {
if (!data || !data[0]) {
return []
}
if (process.platform.includes('linux')) {
var rows = data[0].split('\n')
return rows.map(function (row) {
return parseLinux(row, servers)
}).filter(Boolean)
} else if (process.platform.includes('win32')) {
var winRows = data[0].split('\n').splice(1)
return winRows.map(function (row) {
return parseWin32(row, servers)
}).filter(Boolean)
}
return data[0]
.trim()
.split('\n')
.map(function (row) {
return parseRow(row, servers)
})
.filter(Boolean)
}
/**
* Reads the arp table for a single address.
*/
function arpOne (address) {
if (!ip.isV4Format(address) && !ip.isV6Format(address)) {
return Promise.reject(new Error('Invalid IP address provided.'))
}
return cp.exec('arp -n ' + address, options).then(parseOne)
}
/**
* Parses a single row of arp data.
*/
function parseOne (data) {
if (!data || !data[0]) {
return
}
if (process.platform.includes('linux')) {
// ignore unresolved hosts (can happen when parseOne returns only one unresolved host)
if (data[0].indexOf('no entry') >= 0) {
return
}
// remove first row (containing "headlines")
var rows = data[0].split('\n').slice(1)[0]
return parseLinux(rows, servers, true)
} else if (process.platform.includes('win32')) {
return // currently not supported
}
return parseRow(data[0], servers)
}
/**
* Clears the current promise and unlocks (will ping servers again).
*/
function unlock (key) {
return function (data) {
lock[key] = null
return data
}
}