1
2
3
4
5
6
7
8
9
10
11 import os
12 import time
13 import struct
14 import time
15 import urllib2
16 import re
17 from socket import socket, AF_INET, SOCK_DGRAM, SOL_SOCKET, SO_REUSEADDR, timeout as error_timeout
18 from struct import unpack
19
20 from datetime import datetime, date, timedelta
21 from kbutils import KBCapabilities, makeFCS, isIpAddr, KBInterfaceError
22
23 DEFAULT_IP = "10.10.10.2"
24 DEFAULT_GW = "10.10.10.1"
25 DEFAULT_UDP = 17754
26 TESTED_FW_VERS = ["0.5"]
27
28 NTP_DELTA = 70*365*24*60*60
29
30 '''
31 Convert the two parts of an NTP timestamp to a datetime object.
32 Similar code from Wireshark source:
33 575 /* NTP_BASETIME is in fact epoch - ntp_start_time */
34 576 #define NTP_BASETIME 2208988800ul
35 619 void
36 620 ntp_to_nstime(tvbuff_t *tvb, gint offset, nstime_t *nstime)
37 621 {
38 622 nstime->secs = tvb_get_ntohl(tvb, offset);
39 623 if (nstime->secs)
40 624 nstime->secs -= NTP_BASETIME;
41 625 nstime->nsecs = (int)(tvb_get_ntohl(tvb, offset+4)/(NTP_FLOAT_DENOM/1000000000.0));
42 626 }
43 '''
45 """convert a NTP time to system time"""
46 print "Secs:", secs, msecs
47 print "\tUTC:", datetime.utcfromtimestamp(secs - 2208988800)
48 return datetime.utcfromtimestamp(secs - 2208988800)
49
51 try:
52 html = urllib2.urlopen("http://{0}/".format(ip))
53 fw = re.search(r'Firmware version ([0-9.]+)', html.read())
54 if fw is not None:
55 return fw.group(1)
56 except Exception as e:
57 print("Unable to connect to IP {0} (error: {1}).".format(ip, e))
58 return None
59
61 '''
62 Returns a string for the MAC address of the sniffer.
63 '''
64 try:
65 html = urllib2.urlopen("http://{0}/".format(ip))
66
67
68 res = re.search(r'<!--#pindex-->[A-Z]+,((?:[0-9a-f]{2}:){5}[0-9a-f]{2})', html.read())
69 if res is None:
70 raise KBInterfaceError("Unable to parse the sniffer's MAC address.")
71 return res.group(1)
72 except Exception as e:
73 print("Unable to connect to IP {0} (error: {1}).".format(ip, e))
74 return None
75
78
81 '''
82 Instantiates the KillerBee class for the Wislab Sniffer.
83 @type dev: String
84 @param dev: IP address (ex 10.10.10.2)
85 @type recvport: Integer
86 @param recvport: UDP port to listen for sniffed packets on.
87 @type recvip: String
88 @param recvip: IP address of the host, where the sniffer will send sniffed packets to.
89 @return: None
90 @rtype: None
91 '''
92 self._channel = None
93 self._modulation = 0
94 self.handle = None
95 self.dev = dev
96
97
98
99
100 self.udp_recv_port = recvport
101 self.udp_recv_ip = recvip
102
103 self.__revision_num = getFirmwareVersion(self.dev)
104 if self.__revision_num not in TESTED_FW_VERS:
105 print("Warning: Firmware revision {0} reported by the sniffer is not currently supported. Errors may occur and dev_wislab.py may need updating.".format(self.__revision_num))
106
107 self.handle = socket(AF_INET, SOCK_DGRAM)
108 self.handle.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
109 self.handle.bind((self.udp_recv_ip, self.udp_recv_port))
110
111 self.__stream_open = False
112 self.capabilities = KBCapabilities()
113 self.__set_capabilities()
114
116 '''Actually close the receiving UDP socket.'''
117 self.sniffer_off()
118 self.handle.close()
119 self.handle = None
120
122 return self.capabilities.check(capab)
124 return self.capabilities.getlist()
136
137
139 '''
140 Returns device information in a list identifying the device.
141 @rtype: List
142 @return: List of 3 strings identifying device.
143 '''
144 return [self.dev, "Wislab Sniffer v{0}".format(self.__revision_num), getMacAddr(self.dev)]
145
147 '''
148 Wrapper to the sniffer's RESTful services.
149 Reports URL/HTTP errors as KBInterfaceErrors.
150 @rtype: If fetch==True, returns a String of the page. Otherwise, it
151 returns True if an HTTP 200 code was received.
152 '''
153 try:
154 html = urllib2.urlopen("http://{0}/{1}".format(self.dev, path))
155 if fetch:
156 return html.read()
157 else:
158 return (html.getcode() == 200)
159 except Exception as e:
160 raise KBInterfaceError("Unable to preform a call to {0}/{1} (error: {2}).".format(self.dev, path, e))
161
163 '''
164 Because the firmware accepts only toggle commands for sniffer on/off,
165 we need to check what state it's in before taking action. It's also
166 useful to make sure our command worked.
167 @rtype: Boolean
168 '''
169 html = self.__make_rest_call('')
170
171 res = re.search(r'<!--#pindex-->([A-Z]+),', html)
172 if res is None:
173 raise KBInterfaceError("Unable to parse the sniffer's current status.")
174
175 return (res.group(1) == "RUNNING")
176
178 '''
179 This updates the standard self.__stream_open variable based on the
180 status as reported from asking the remote sniffer.
181 '''
182 self.__stream_open = self.__sniffer_status()
183
185 '''
186 Because the firmware accepts only toggle commands for sniffer on/off,
187 we need to check what state it's in before taking action. It's also
188 useful to make sure our command worked.
189 @rtype: Boolean
190 '''
191 html = self.__make_rest_call('')
192
193
194 res = re.search(r'<!--#pindex-->[A-Z]+,[0-9a-f:]+,[0-9.]+,([0-9]+),', html)
195 if res is None:
196 raise KBInterfaceError("Unable to parse the sniffer's current channel.")
197 return int(res.group(1))
198
199
201 '''
202 Turns the sniffer on such that pnext() will start returning observed
203 data.
204 @type channel: Integer
205 @param channel: Sets the channel, optional
206 @rtype: None
207 '''
208 self.capabilities.require(KBCapabilities.SNIFF)
209
210
211
212 self.__sync_status()
213 if self.__stream_open == False:
214 if channel != None:
215 self.set_channel(channel)
216
217 if not self.__make_rest_call('status.cgi?p=2', fetch=False):
218 raise KBInterfaceError("Error instructing sniffer to start capture.")
219
220
221 self.__sync_status()
222 if not self.__stream_open:
223 raise KBInterfaceError("Sniffer did not turn on capture.")
224
225
227 '''
228 Turns the sniffer off.
229 @rtype: None
230 '''
231
232
233 self.__sync_status()
234 if self.__stream_open == True:
235 if not self.__make_rest_call('status.cgi?p=2', fetch=False):
236 raise KBInterfaceError("Error instructing sniffer to stop capture.")
237
238
239 self.__sync_status()
240 if self.__stream_open:
241 raise KBInterfaceError("Sniffer did not turn off capture.")
242
243 @staticmethod
245 '''
246 Return the Wislab-specific integer representing the modulation which
247 should be choosen to be IEEE 802.15.4 complinating for a given channel
248 number.
249 Captured values from sniffing Wislab web interface, unsure why these
250 are done as such.
251 Available modulations are listed at:
252 http://www.sewio.net/open-sniffer/develop/http-rest-interface/
253 @rtype: Integer, or None if unable to determine modulation
254 '''
255 if channel >= 11 or channel <= 26: return '0'
256 elif channel >= 1 or channel <= 10: return 'c'
257 elif channel >= 128 or channel <= 131: return '1c'
258 elif channel == 0: return '0'
259 else: return None
260
261
263 '''
264 Sets the radio interface to the specifid channel (limited to 2.4 GHz channels 11-26)
265 @type channel: Integer
266 @param channel: Sets the channel, optional
267 @rtype: None
268 '''
269 self.capabilities.require(KBCapabilities.SETCHAN)
270
271 if self.capabilities.is_valid_channel(channel):
272
273 curChannel = self.__sniffer_channel()
274 if channel != curChannel:
275 self.modulation = self.__get_default_modulation(channel)
276 print("Setting to channel {0}, modulation {1}.".format(channel, self.modulation))
277
278
279
280
281
282 self.__make_rest_call("settings.cgi?chn={0}&modul={1}&rxsens=0".format(channel, self.modulation), fetch=False)
283 self._channel = self.__sniffer_channel()
284 else:
285 self._channel = curChannel
286 else:
287 raise Exception('Invalid channel number ({0}) was provided'.format(channel))
288
289
290 - def inject(self, packet, channel=None, count=1, delay=0):
295
296 @staticmethod
298 '''
299 Parse the packet from the ZigBee encapsulation protocol version 2/3 and
300 return the fields desired for usage by pnext().
301 There is support here for some oddities specific to the Wislab
302 implementation of ZEP and the packet, such as CC24xx format FCS
303 headers being expected.
304
305 The ZEP protocol parsing is mainly based on Wireshark source at:
306 http://anonsvn.wireshark.org/wireshark/trunk/epan/dissectors/packet-zep.c
307 * ZEP v2 Header will have the following format (if type=1/Data):
308 * |Preamble|Version| Type |Channel ID|Device ID|CRC/LQI Mode|LQI Val|NTP Timestamp|Sequence#|Reserved|Length|
309 * |2 bytes |1 byte |1 byte| 1 byte | 2 bytes | 1 byte |1 byte | 8 bytes | 4 bytes |10 bytes|1 byte|
310 * ZEP v2 Header will have the following format (if type=2/Ack):
311 * |Preamble|Version| Type |Sequence#|
312 * |2 bytes |1 byte |1 byte| 4 bytes |
313 #define ZEP_PREAMBLE "EX"
314 #define ZEP_V2_HEADER_LEN 32
315 #define ZEP_V2_ACK_LEN 8
316 #define ZEP_V2_TYPE_DATA 1
317 #define ZEP_V2_TYPE_ACK 2
318 #define ZEP_LENGTH_MASK 0x7F
319 '''
320
321 (preamble, version, zeptype) = unpack('<HBB', data[:4])
322 if preamble != 22597 or version < 2:
323 raise Exception("Can not parse provided data as ZEP due to incorrect preamble or unsupported version.")
324 if zeptype == 1:
325 (ch, devid, crcmode, lqival, ntpsec, ntpnsec, seqnum, length) = unpack(">BHBBIII10xB", data[4:32])
326
327
328
329
330
331
332 recdtime = datetime.combine(date.today(), (datetime.now()).time())
333
334
335
336
337
338
339
340 frame = data[32:]
341
342
343 if crcmode == 0:
344 validcrc = ((ord(data[-1]) & 0x80) == 0x80)
345 rssi = ord(data[-2])
346
347
348 frame = frame[:-2] + makeFCS(frame[:-2])
349 else:
350 validcrc = False
351 rssi = None
352 return (frame, ch, validcrc, rssi, lqival, recdtime)
353 elif zeptype == 2:
354 frame = data[8:]
355 (seqnum) = unpack(">I", data[4:8])
356 recdtime = datetime.combine(date.today(), (datetime.now()).time())
357 validcrc = (frame[-2:] == makeFCS(frame[:-2]))
358 return (frame, None, validcrc, None, None, recdtime)
359 return None
360
361
362 - def pnext(self, timeout=100):
363 '''
364 Returns a dictionary containing packet data, else None.
365 @type timeout: Integer
366 @param timeout: Timeout to wait for packet reception in usec
367 @rtype: List
368 @return: Returns None is timeout expires and no packet received. When a packet is received, a dictionary is returned with the keys bytes (string of packet bytes), validcrc (boolean if a vaid CRC), rssi (unscaled RSSI), and location (may be set to None). For backwards compatibility, keys for 0,1,2 are provided such that it can be treated as if a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ]
369 '''
370 if self.__stream_open == False:
371 self.sniffer_on()
372
373
374 self.handle.settimeout(timeout / 1000000.0)
375
376 frame = None
377 donetime = datetime.now() + timedelta(microseconds=timeout)
378 while True:
379 try:
380 data, addr = self.handle.recvfrom(1024)
381 except error_timeout:
382 return None
383
384
385 if addr[0] != self.dev:
386 continue
387
388 (frame, ch, validcrc, rssi, lqival, recdtime) = self.__parse_zep_v2(data)
389 print "Valid CRC", validcrc, "LQI", lqival, "RSSI", rssi
390 if frame == None or (ch is not None and ch != self._channel):
391
392 print("ZEP parsing issue (bytes length={0}, channel={1}).".format(len(frame) if frame is not None else None, ch))
393 continue
394 break
395
396 if frame is None:
397 return None
398
399
400
401 result = {0:frame, 1:validcrc, 2:rssi, 'bytes':frame, 'validcrc':validcrc, 'rssi':rssi, 'dbm':None, 'location':None, 'datetime':recdtime}
402 if rssi is not None:
403 result['dbm'] = rssi - 45
404 return result
405
407 '''
408 Not yet implemented.
409 @type channel: Integer
410 @param channel: Sets the channel, optional
411 @rtype: None
412 '''
413 self.capabilities.require(KBCapabilities.PHYJAM)
414
422