-
Notifications
You must be signed in to change notification settings - Fork 5
/
ping.coffee
214 lines (190 loc) · 6.9 KB
/
ping.coffee
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
module.exports = (env) ->
# ##Dependencies
util = require 'util'
os = require 'os'
net = require 'net'
dns = require 'dns'
Promise = env.require 'bluebird'
assert = env.require 'cassert'
ping = env.ping or require("net-ping")
resolve4 = Promise.promisify (env.dns or require "dns").resolve4
resolve6 = Promise.promisify (env.dns or require "dns").resolve6
# ##The PingPlugin
class PingPlugin extends env.plugins.Plugin
init: (app, @framework, @config) =>
# ping package needs root access...
if os.platform() isnt 'win32' and process.getuid() != 0
throw new Error "ping-plugins needs root privileges. Please restart the framework as root!"
@deviceCount = 0
deviceConfigDef = require("./device-config-schema")
@framework.deviceManager.registerDeviceClass("PingPresence", {
configDef: deviceConfigDef.PingPresence,
createCallback: (config, lastState) =>
device = new PingPresence(config, lastState, @deviceCount)
@deviceCount++
return device
})
@framework.deviceManager.on('discover', (eventData) =>
interfaces = @listInterfaces()
# ping all devices in each net:
maxPings = 513
pingCount = 0
interfaces.forEach( (iface, ifNum) =>
@framework.deviceManager.discoverMessage(
'pimatic-ping', "Scanning #{iface.address}/24"
)
base = iface.address.match(/([0-9]+\.[0-9]+\.[0-9]+\.)[0-9]+/)[1]
i = 1
while i < 256
do (i) =>
if pingCount > maxPings then return
address = "#{base}#{i}"
sessionId = ((process.pid + (256*(ifNum+1)) + i) % 65535)
session = ping.createSession(
networkProtocol: ping.NetworkProtocol.IPv4
packetSize: 16
retries: 3
sessionId: sessionId
timeout: eventData.time
ttl: 128
)
session.pingHost(address, (error, target) =>
session.close()
unless error
dns.reverse(address, (error, hostnames) =>
displayName = (
if hostnames? and hostnames.length > 0 then hostnames[0] else address
)
config = {
class: 'PingPresence',
name: displayName,
host: displayName
}
@framework.deviceManager.discoveredDevice(
'pimatic-ping', "Presence of #{displayName}", config
)
)
)
i++
pingCount++
if pingCount > maxPings
@framework.deviceManager.discoverMessage(
'pimatic-ping', "Could not ping all networks, max ping cound reached."
)
)
)
# get all ip4 non local networks with /24 submask
listInterfaces : () ->
interfaces = []
ifaces = os.networkInterfaces()
Object.keys(ifaces).forEach( (ifname) ->
alias = 0
ifaces[ifname].forEach (iface) ->
if 'IPv4' isnt iface.family or iface.internal isnt false
# skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses
return
if iface.netmask isnt "255.255.255.0"
return
interfaces.push {name: ifname, address: iface.address}
return
)
return interfaces
pingPlugin = new PingPlugin
# ##PingPresence Sensor
class PingPresence extends env.devices.PresenceSensor
constructor: (@config, lastState, deviceNum) ->
@name = @config.name
@id = @config.id
@_presence = lastState?.presence?.value or false
@_resolve = resolve4
if @config.dnsRecordFamily is 6
@_resolve = resolve6
else if @config.dnsRecordFamily is 0
@_resolve = @_resolveAny
else if @config.dnsRecordFamily is 10
@_resolve = @_resolveHybrid
@session = ping.createSession(
networkProtocol: ping.NetworkProtocol.IPv4
packetSize: 16
retries: @config.retries
sessionId: ((process.pid + deviceNum) % 65535)
timeout: @config.timeout
ttl: 128
)
super()
pendingPingsCount = 0
lastError = null
doPing = ( =>
if @_destroyed then return
pendingPingsCount++
@_resolveHost(@config.host).then( (addresses) =>
Promise.any(@_pingHost address for address in addresses).then( =>
unless @_destroyed then @_setPresence yes
).catch( =>
unless @_destroyed then @_setPresence no
).finally( =>
pendingPingsCount-- if pendingPingsCount > 0
if @_destroyed then return
setTimeout(doPing, @config.interval) if pendingPingsCount is 0
)
).catch( (dnsError) =>
if lastError?.message isnt dnsError.message
env.logger.warn("Error on ip lookup of #{@config.host}: #{dnsError}")
lastError = dnsError
unless @_destroyed then @_setPresence(no)
pendingPingsCount-- if pendingPingsCount > 0
if @_destroyed then return
setTimeout(doPing, @config.interval) if pendingPingsCount is 0
)
)
doPing()
_resolveHost: (hostOrIP) ->
result = net.isIP hostOrIP
if result is 4 or result is 6
return Promise.resolve [hostOrIP]
else
@_resolve(hostOrIP)
_pingHost: (address) ->
return new Promise( (resolve, reject) =>
@session.pingHost(address, (error, target) =>
if pingPlugin.config.debug
if error?
errorMessage = if error.message? then error.message else error
env.logger.debug "Ping", address, if errorMessage? then errorMessage else "alive"
if error? then reject error else resolve target
)
)
_resolveHybrid: (hostOrIP) ->
# do both resolve4 and resolve6 queries, fails if both queries fail
result = []
return new Promise( (resolve, reject) =>
resolve4(hostOrIP).then( (addresses) =>
result = addresses
).catch(
# intentionally left empty
).finally( =>
resolve6(hostOrIP).then( (addresses) =>
resolve result.concat addresses
).catch( (error) =>
if result.length > 0
resolve result
else
reject error
)
)
)
_resolveAny: (hostOrIP) ->
return Promise.any([resolve4(hostOrIP), resolve6(hostOrIP)])
getPresence: ->
if @_presence?
return Promise.resolve @_presence
else
return new Promise( (resolve) =>
@once('presence', ( (state) -> resolve state ) )
).timeout(@config.timeout + 5*60*1000)
destroy: ->
super()
@session.close()
# For testing...
pingPlugin.PingPresence = PingPresence
return pingPlugin