blob: 59f5a48328a4a9bc751256befe5290f05e14c4c0 [file] [log] [blame]
Jeremy Songster1f39bf02016-01-20 17:17:25 -08001#!/usr/bin/env python
2"""
32015-2016
Jeremy Ronquillob27ce4c2017-07-17 12:41:28 -07004Copyright 2016 Open Networking Foundation (ONF)
Jeremy Songsterae01bba2016-07-11 15:39:17 -07005
6Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>,
7the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>,
8or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg>
Jeremy Songster1f39bf02016-01-20 17:17:25 -08009
10TestON is free software: you can redistribute it and/or modify
11it under the terms of the GNU General Public License as published by
12the Free Software Foundation, either version 2 of the License, or
13( at your option ) any later version.
14
15TestON is distributed in the hope that it will be useful,
16but WITHOUT ANY WARRANTY; without even the implied warranty of
17MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with TestON. If not, see <http://www.gnu.org/licenses/>.
22
23ScapyCliDriver is the basic driver which will handle the Scapy functions
24
25TODO: Add Explanation on how to install scapy
26"""
27import pexpect
28import re
29import sys
30import types
31import os
32from drivers.common.cli.emulatordriver import Emulator
33
34
35class ScapyCliDriver( Emulator ):
36
37 """
38 ScapyCliDriver is the basic driver which will handle
39 the Scapy functions"""
40 def __init__( self ):
Devin Limdc78e202017-06-09 18:30:07 -070041 super( ScapyCliDriver, self ).__init__()
Jeremy Songster1f39bf02016-01-20 17:17:25 -080042 self.handle = self
43 self.name = None
44 self.home = None
45 self.wrapped = sys.modules[ __name__ ]
46 self.flag = 0
47 # TODO: Refactor driver to use these everywhere
48 self.hostPrompt = "~#"
49 self.bashPrompt = "\$"
50 self.scapyPrompt = ">>>"
51
52 def connect( self, **connectargs ):
53 """
54 Here the main is the TestON instance after creating
55 all the log handles."""
56 try:
57 for key in connectargs:
58 vars( self )[ key ] = connectargs[ key ]
59 self.home = "~/mininet"
60 self.name = self.options[ 'name' ]
61 for key in self.options:
62 if key == "home":
63 self.home = self.options[ 'home' ]
64 break
65 if self.home is None or self.home == "":
66 self.home = "~/mininet"
67
68 try:
69 if os.getenv( str( self.ip_address ) ) is not None:
70 self.ip_address = os.getenv( str( self.ip_address ) )
71 else:
72 main.log.info( self.name +
73 ": Trying to connect to " +
74 self.ip_address )
75
76 except KeyError:
77 main.log.info( "Invalid host name," +
78 " connecting to local host instead" )
79 self.ip_address = 'localhost'
80 except Exception as inst:
81 main.log.error( "Uncaught exception: " + str( inst ) )
82
83 self.handle = super(
84 ScapyCliDriver,
85 self ).connect(
86 user_name=self.user_name,
87 ip_address=self.ip_address,
88 port=None,
89 pwd=self.pwd )
90
91 if self.handle:
92 main.log.info( "Connection successful to the host " +
93 self.user_name +
94 "@" +
95 self.ip_address )
96 return main.TRUE
97 else:
98 main.log.error( "Connection failed to the host " +
99 self.user_name +
100 "@" +
101 self.ip_address )
102 main.log.error( "Failed to connect to the Mininet Host" )
103 return main.FALSE
104 except pexpect.EOF:
105 main.log.error( self.name + ": EOF exception found" )
106 main.log.error( self.name + ": " + self.handle.before )
107 main.cleanup()
108 main.exit()
109 except Exception:
110 main.log.exception( self.name + ": Uncaught exception!" )
111 main.cleanup()
112 main.exit()
113
114 def disconnect( self ):
115 """
116 Called at the end of the test to stop the scapy component and
117 disconnect the handle.
118 """
119 self.handle.sendline( '' )
120 i = self.handle.expect( [ 'mininet>', pexpect.EOF, pexpect.TIMEOUT ],
121 timeout=2 )
122 response = main.TRUE
123 if i == 0:
124 response = self.stopNet()
125 elif i == 1:
126 return main.TRUE
127 # print "Disconnecting Mininet"
128 if self.handle:
129 self.handle.sendline( "exit" )
130 self.handle.expect( "exit" )
131 self.handle.expect( "(.*)" )
132 else:
133 main.log.error( "Connection failed to the host" )
134 return response
135
136 def stopNet( self, fileName="", timeout=5 ):
137 """
138 Stops mininet.
139 Returns main.TRUE if the mininet successfully stops and
140 main.FALSE if the pexpect handle does not exist.
141
142 Will cleanup and exit the test if scapy fails to stop
143 """
144 main.log.info( self.name + ": Stopping scapy..." )
145 response = ''
146 if self.handle:
147 try:
148 self.handle.sendline( "" )
149 i = self.handle.expect( [ '>>>',
Devin Limdc78e202017-06-09 18:30:07 -0700150 self.prompt,
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800151 pexpect.EOF,
152 pexpect.TIMEOUT ],
153 timeout )
154 if i == 0:
155 main.log.info( "Exiting scapy..." )
156 response = self.execute(
157 cmd="exit",
158 prompt="(.*)",
159 timeout=120 )
160 main.log.info( self.name + ": Stopped" )
161 response = main.TRUE
162
163 if i == 1:
164 main.log.info( " Mininet trying to exit while not " +
165 "in the mininet prompt" )
166 elif i == 2:
167 main.log.error( "Something went wrong exiting mininet" )
168 elif i == 3: # timeout
169 main.log.error( "Something went wrong exiting mininet " +
170 "TIMEOUT" )
171
172 if fileName:
173 self.handle.sendline( "" )
Devin Limdc78e202017-06-09 18:30:07 -0700174 self.handle.expect( self.prompt )
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800175 self.handle.sendline(
176 "sudo kill -9 \`ps -ef | grep \"" +
177 fileName +
178 "\" | grep -v grep | awk '{print $2}'\`" )
179 except pexpect.EOF:
180 main.log.error( self.name + ": EOF exception found" )
181 main.log.error( self.name + ": " + self.handle.before )
182 main.cleanup()
183 main.exit()
184 else:
185 main.log.error( self.name + ": Connection failed to the host" )
186 response = main.FALSE
187 return response
188
189 def createHostComponent( self, name ):
190 """
191 Creates a new mininet cli component with the same parameters as self.
192 This new component is intended to be used to login to the hosts created
193 by mininet.
194
195 Arguments:
196 name - The string of the name of this component. The new component
197 will be assigned to main.<name> .
198 In addition, main.<name>.name = str( name )
199 """
200 try:
201 # look to see if this component already exists
202 getattr( main, name )
203 except AttributeError:
204 # namespace is clear, creating component
205 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
206 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
207 main.componentInit( name )
208 except Exception:
209 main.log.exception( self.name + ": Uncaught exception!" )
210 main.cleanup()
211 main.exit()
212 else:
213 # namespace is not clear!
214 main.log.error( name + " component already exists!" )
215 main.cleanup()
216 main.exit()
217
218 def removeHostComponent( self, name ):
219 """
220 Remove host component
221 Arguments:
222 name - The string of the name of the component to delete.
223 """
224 try:
225 # Get host component
226 component = getattr( main, name )
227 except AttributeError:
228 main.log.error( "Component " + name + " does not exist." )
229 return main.FALSE
230 try:
231 # Disconnect from component
232 component.disconnect()
233 # Delete component
234 delattr( main, name )
235 # Delete component from ComponentDictionary
236 del( main.componentDictionary[name] )
237 return main.TRUE
238 except Exception:
239 main.log.exception( self.name + ": Uncaught exception!" )
240 main.cleanup()
241 main.exit()
242
243 def startHostCli( self, host=None ):
244 """
245 Use the mininet m utility to connect to the host's cli
246 """
247 # These are fields that can be used by scapy packets. Initialized to None
248 self.hostIp = None
249 self.hostMac = None
250 try:
251 if not host:
252 host = self.name
253 self.handle.sendline( self.home + "/util/m " + host )
Jeremy Ronquillo0f2008a2017-06-23 15:32:51 -0700254 self.handle.sendline( "cd" )
255 self.handle.expect( self.hostPrompt )
256 self.handle.sendline( "" )
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800257 self.handle.expect( self.hostPrompt )
258 return main.TRUE
259 except pexpect.TIMEOUT:
260 main.log.exception( self.name + ": Command timed out" )
261 return main.FALSE
262 except pexpect.EOF:
263 main.log.exception( self.name + ": connection closed." )
264 main.cleanup()
265 main.exit()
266 except Exception:
267 main.log.exception( self.name + ": Uncaught exception!" )
268 main.cleanup()
269 main.exit()
270
271 def startScapy( self, mplsPath="" ):
272 """
273 Start the Scapy cli
274 optional:
275 mplsPath - The path where the MPLS class is located
276 NOTE: This can be a relative path from the user's home dir
277 """
278 mplsLines = ['import imp',
279 'imp.load_source( "mplsClass", "{}mplsClass.py" )'.format(mplsPath),
280 'from mplsClass import MPLS',
281 'bind_layers(Ether, MPLS, type = 0x8847)',
282 'bind_layers(MPLS, MPLS, bottom_of_label_stack = 0)',
283 'bind_layers(MPLS, IP)']
284
285 try:
286 self.handle.sendline( "scapy" )
287 self.handle.expect( self.scapyPrompt )
288 self.handle.sendline( "conf.color_theme = NoTheme()" )
289 self.handle.expect( self.scapyPrompt )
290 if mplsPath:
291 main.log.info( "Adding MPLS class" )
292 main.log.info( "MPLS class path: " + mplsPath )
293 for line in mplsLines:
294 main.log.info( "sending line: " + line )
295 self.handle.sendline( line )
296 self.handle.expect( self.scapyPrompt )
297 return main.TRUE
298 except pexpect.TIMEOUT:
299 main.log.exception( self.name + ": Command timed out" )
300 return main.FALSE
301 except pexpect.EOF:
302 main.log.exception( self.name + ": connection closed." )
303 main.cleanup()
304 main.exit()
305 except Exception:
306 main.log.exception( self.name + ": Uncaught exception!" )
307 main.cleanup()
308 main.exit()
309
310 def stopScapy( self ):
311 """
312 Exit the Scapy cli
313 """
314 try:
315 self.handle.sendline( "exit()" )
316 self.handle.expect( self.hostPrompt )
317 return main.TRUE
318 except pexpect.TIMEOUT:
319 main.log.exception( self.name + ": Command timed out" )
320 return main.FALSE
321 except pexpect.EOF:
322 main.log.exception( self.name + ": connection closed." )
323 main.cleanup()
324 main.exit()
325 except Exception:
326 main.log.exception( self.name + ": Uncaught exception!" )
327 main.cleanup()
328 main.exit()
329
330 def buildEther( self, **kwargs ):
331 """
332 Build an Ethernet frame
333
334 Will create a frame class with the given options. If a field is
335 left blank it will default to the below value unless it is
336 overwritten by the next frame.
337 Default frame:
338 ###[ Ethernet ]###
339 dst= ff:ff:ff:ff:ff:ff
340 src= 00:00:00:00:00:00
341 type= 0x800
342
343 Returns main.TRUE or main.FALSE on error
344 """
345 try:
346 # Set the Ethernet frame
347 cmd = 'ether = Ether( '
348 options = []
349 for key, value in kwargs.iteritems():
350 if isinstance( value, str ):
351 value = '"' + value + '"'
352 options.append( str( key ) + "=" + str( value ) )
353 cmd += ", ".join( options )
354 cmd += ' )'
355 self.handle.sendline( cmd )
356 self.handle.expect( self.scapyPrompt )
357 if "Traceback" in self.handle.before:
358 # KeyError, SyntaxError, ...
359 main.log.error( "Error in sending command: " + self.handle.before )
360 return main.FALSE
361 self.handle.sendline( "packet = ether" )
362 self.handle.expect( self.scapyPrompt )
363 if "Traceback" in self.handle.before:
364 # KeyError, SyntaxError, ...
365 main.log.error( "Error in sending command: " + self.handle.before )
366 return main.FALSE
367 return main.TRUE
368 except pexpect.TIMEOUT:
369 main.log.exception( self.name + ": Command timed out" )
370 return main.FALSE
371 except pexpect.EOF:
372 main.log.exception( self.name + ": connection closed." )
373 main.cleanup()
374 main.exit()
375 except Exception:
376 main.log.exception( self.name + ": Uncaught exception!" )
377 main.cleanup()
378 main.exit()
379
380 def buildIP( self, **kwargs ):
381 """
382 Build an IP frame
383
384 Will create a frame class with the given options. If a field is
385 left blank it will default to the below value unless it is
386 overwritten by the next frame.
387 Default frame:
388 ###[ IP ]###
389 version= 4
390 ihl= None
391 tos= 0x0
392 len= None
393 id= 1
394 flags=
395 frag= 0
396 ttl= 64
397 proto= hopopt
398 chksum= None
399 src= 127.0.0.1
400 dst= 127.0.0.1
401 \options\
402
403 Returns main.TRUE or main.FALSE on error
404 """
405 try:
406 # Set the IP frame
407 cmd = 'ip = IP( '
408 options = []
409 for key, value in kwargs.iteritems():
410 if isinstance( value, str ):
411 value = '"' + value + '"'
412 options.append( str( key ) + "=" + str( value ) )
413 cmd += ", ".join( options )
414 cmd += ' )'
415 self.handle.sendline( cmd )
416 self.handle.expect( self.scapyPrompt )
417 if "Traceback" in self.handle.before:
418 # KeyError, SyntaxError, ...
419 main.log.error( "Error in sending command: " + self.handle.before )
420 return main.FALSE
421 self.handle.sendline( "packet = ether/ip" )
422 self.handle.expect( self.scapyPrompt )
423 if "Traceback" in self.handle.before:
424 # KeyError, SyntaxError, ...
425 main.log.error( "Error in sending command: " + self.handle.before )
426 return main.FALSE
427 return main.TRUE
428 except pexpect.TIMEOUT:
429 main.log.exception( self.name + ": Command timed out" )
430 return main.FALSE
431 except pexpect.EOF:
432 main.log.exception( self.name + ": connection closed." )
433 main.cleanup()
434 main.exit()
435 except Exception:
436 main.log.exception( self.name + ": Uncaught exception!" )
437 main.cleanup()
438 main.exit()
439
440 def buildIPv6( self, **kwargs ):
441 """
442 Build an IPv6 frame
443
444 Will create a frame class with the given options. If a field is
445 left blank it will default to the below value unless it is
446 overwritten by the next frame.
447 Default frame:
448 ###[ IPv6 ]###
449 version= 6
450 tc= 0
451 fl= 0
452 plen= None
453 nh= No Next Header
454 hlim= 64
455 src= ::1
456 dst= ::1
457
458 Returns main.TRUE or main.FALSE on error
459 """
460 try:
461 # Set the IPv6 frame
462 cmd = 'ipv6 = IPv6( '
463 options = []
464 for key, value in kwargs.iteritems():
465 if isinstance( value, str ):
466 value = '"' + value + '"'
467 options.append( str( key ) + "=" + str( value ) )
468 cmd += ", ".join( options )
469 cmd += ' )'
470 self.handle.sendline( cmd )
471 self.handle.expect( self.scapyPrompt )
472 if "Traceback" in self.handle.before:
473 # KeyError, SyntaxError, ...
474 main.log.error( "Error in sending command: " + self.handle.before )
475 return main.FALSE
476 self.handle.sendline( "packet = ether/ipv6" )
477 self.handle.expect( self.scapyPrompt )
478 if "Traceback" in self.handle.before:
479 # KeyError, SyntaxError, ...
480 main.log.error( "Error in sending command: " + self.handle.before )
481 return main.FALSE
482 return main.TRUE
483 except pexpect.TIMEOUT:
484 main.log.exception( self.name + ": Command timed out" )
485 return main.FALSE
486 except pexpect.EOF:
487 main.log.exception( self.name + ": connection closed." )
488 main.cleanup()
489 main.exit()
490 except Exception:
491 main.log.exception( self.name + ": Uncaught exception!" )
492 main.cleanup()
493 main.exit()
494
495 def buildTCP( self, ipVersion=4, **kwargs ):
496 """
497 Build an TCP frame
498
499 Will create a frame class with the given options. If a field is
500 left blank it will default to the below value unless it is
501 overwritten by the next frame.
502
503 NOTE: Some arguments require quotes around them. It's up to you to
504 know which ones and to add them yourself. Arguments with an asterisk
505 do not need quotes.
506
507 Options:
508 ipVersion - Either 4 (default) or 6, indicates what Internet Protocol
509 frame to use to encapsulate into
510 Default frame:
511 ###[ TCP ]###
512 sport= ftp_data *
513 dport= http *
514 seq= 0
515 ack= 0
516 dataofs= None
517 reserved= 0
518 flags= S
519 window= 8192
520 chksum= None
521 urgptr= 0
522 options= {}
523
524 Returns main.TRUE or main.FALSE on error
525 """
526 try:
527 # Set the TCP frame
528 cmd = 'tcp = TCP( '
529 options = []
530 for key, value in kwargs.iteritems():
531 options.append( str( key ) + "=" + str( value ) )
532 cmd += ", ".join( options )
533 cmd += ' )'
534 self.handle.sendline( cmd )
535 self.handle.expect( self.scapyPrompt )
536 if "Traceback" in self.handle.before:
537 # KeyError, SyntaxError, ...
538 main.log.error( "Error in sending command: " + self.handle.before )
539 return main.FALSE
540 if str( ipVersion ) is '4':
541 self.handle.sendline( "packet = ether/ip/tcp" )
542 elif str( ipVersion ) is '6':
543 self.handle.sendline( "packet = ether/ipv6/tcp" )
544 else:
545 main.log.error( "Unrecognized option for ipVersion, given " +
546 repr( ipVersion ) )
547 return main.FALSE
548 self.handle.expect( self.scapyPrompt )
549 if "Traceback" in self.handle.before:
550 # KeyError, SyntaxError, ...
551 main.log.error( "Error in sending command: " + self.handle.before )
552 return main.FALSE
553 return main.TRUE
554 except pexpect.TIMEOUT:
555 main.log.exception( self.name + ": Command timed out" )
556 return main.FALSE
557 except pexpect.EOF:
558 main.log.exception( self.name + ": connection closed." )
559 main.cleanup()
560 main.exit()
561 except Exception:
562 main.log.exception( self.name + ": Uncaught exception!" )
563 main.cleanup()
564 main.exit()
565
566 def buildUDP( self, ipVersion=4, **kwargs ):
567 """
568 Build an UDP frame
569
570 Will create a frame class with the given options. If a field is
571 left blank it will default to the below value unless it is
572 overwritten by the next frame.
573
574 NOTE: Some arguments require quotes around them. It's up to you to
575 know which ones and to add them yourself. Arguments with an asterisk
576 do not need quotes.
577
578 Options:
579 ipVersion - Either 4 (default) or 6, indicates what Internet Protocol
580 frame to use to encapsulate into
581 Default frame:
582 ###[ UDP ]###
583 sport= domain *
584 dport= domain *
585 len= None
586 chksum= None
587
588 Returns main.TRUE or main.FALSE on error
589 """
590 try:
591 # Set the UDP frame
592 cmd = 'udp = UDP( '
593 options = []
594 for key, value in kwargs.iteritems():
595 options.append( str( key ) + "=" + str( value ) )
596 cmd += ", ".join( options )
597 cmd += ' )'
598 self.handle.sendline( cmd )
599 self.handle.expect( self.scapyPrompt )
600 if "Traceback" in self.handle.before:
601 # KeyError, SyntaxError, ...
602 main.log.error( "Error in sending command: " + self.handle.before )
603 return main.FALSE
604 if str( ipVersion ) is '4':
605 self.handle.sendline( "packet = ether/ip/udp" )
606 elif str( ipVersion ) is '6':
607 self.handle.sendline( "packet = ether/ipv6/udp" )
608 else:
609 main.log.error( "Unrecognized option for ipVersion, given " +
610 repr( ipVersion ) )
611 return main.FALSE
612 self.handle.expect( self.scapyPrompt )
613 if "Traceback" in self.handle.before:
614 # KeyError, SyntaxError, ...
615 main.log.error( "Error in sending command: " + self.handle.before )
616 return main.FALSE
617 return main.TRUE
618 except pexpect.TIMEOUT:
619 main.log.exception( self.name + ": Command timed out" )
620 return main.FALSE
621 except pexpect.EOF:
622 main.log.exception( self.name + ": connection closed." )
623 main.cleanup()
624 main.exit()
625 except Exception:
626 main.log.exception( self.name + ": Uncaught exception!" )
627 main.cleanup()
628 main.exit()
629
alisone14d7b02016-07-06 10:31:51 -0700630 def buildSCTP( self, ipVersion=4, **kwargs ):
631 """
632 Build an SCTP frame
633
634 Will create a frame class with the given options. If a field is
635 left blank it will default to the below value unless it is
636 overwritten by the next frame.
637
638 NOTE: Some arguments require quotes around them. It's up to you to
639 know which ones and to add them yourself. Arguments with an asterisk
640 do not need quotes.
641
642 Options:
643 ipVersion - Either 4 (default) or 6, indicates what Internet Protocol
644 frame to use to encapsulate into
645 Default frame:
646 ###[ SCTP ]###
647 sport= domain *
648 dport= domain *
649 tag = None
650 chksum = None
651
652 Returns main.TRUE or main.FALSE on error
653 """
654 try:
655 # Set the SCTP frame
656 cmd = 'sctp = SCTP( '
657 options = [ ]
658 for key, value in kwargs.iteritems( ):
659 options.append( str( key ) + "=" + str( value ) )
660 cmd += ", ".join( options )
661 cmd += ' )'
662 self.handle.sendline( cmd )
663 self.handle.expect( self.scapyPrompt )
664 if "Traceback" in self.handle.before:
665 # KeyError, SyntaxError, ...
666 main.log.error( "Error in sending command: " + self.handle.before )
667 return main.FALSE
668 if str( ipVersion ) is '4':
669 self.handle.sendline( "packet = ether/ip/sctp" )
670 elif str( ipVersion ) is '6':
671 self.handle.sendline( "packet = ether/ipv6/sctp" )
672 else:
673 main.log.error( "Unrecognized option for ipVersion, given " +
674 repr( ipVersion ) )
675 return main.FALSE
676 self.handle.expect( self.scapyPrompt )
677 if "Traceback" in self.handle.before:
678 # KeyError, SyntaxError, ...
679 main.log.error( "Error in sending command: " + self.handle.before )
680 return main.FALSE
681 return main.TRUE
682 except pexpect.TIMEOUT:
683 main.log.exception( self.name + ": Command timed out" )
684 return main.FALSE
685 except pexpect.EOF:
686 main.log.exception( self.name + ": connection closed." )
687 main.cleanup( )
688 main.exit( )
689 except Exception:
690 main.log.exception( self.name + ": Uncaught exception!" )
691 main.cleanup( )
692 main.exit( )
693
694 def buildARP( self, **kwargs ):
695 """
696 Build an ARP frame
697
698 Will create a frame class with the given options. If a field is
699 left blank it will default to the below value unless it is
700 overwritten by the next frame.
701
702 NOTE: Some arguments require quotes around them. It's up to you to
703 know which ones and to add them yourself. Arguments with an asterisk
704 do not need quotes.
705
706 Default frame:
707 ###[ ARP ]###
708 hwtype : XShortField = (1)
709 ptype : XShortEnumField = (2048)
710 hwlen : ByteField = (6)
711 plen : ByteField = (4)
712 op : ShortEnumField = (1)
713 hwsrc : ARPSourceMACField = (None)
714 psrc : SourceIPField = (None)
715 hwdst : MACField = ('00:00:00:00:00:00')
716 pdst : IPField = ('0.0.0.0')
717
718 Returns main.TRUE or main.FALSE on error
719 """
720 try:
721 # Set the ARP frame
722 cmd = 'arp = ARP( '
723 options = []
724 for key, value in kwargs.iteritems( ):
725 if isinstance( value, str ):
726 value = '"' + value + '"'
727 options.append( str( key ) + "=" + str( value ) )
728 cmd += ", ".join( options )
729 cmd += ' )'
730 self.handle.sendline( cmd )
731 self.handle.expect( self.scapyPrompt )
732 if "Traceback" in self.handle.before:
733 # KeyError, SyntaxError, ...
734 main.log.error( "Error in sending command: " + self.handle.before )
735 return main.FALSE
736 self.handle.sendline( "packet = ether/arp" )
737 self.handle.expect( self.scapyPrompt )
738 if "Traceback" in self.handle.before:
739 # KeyError, SyntaxError, ...
740 main.log.error( "Error in sending command: " + self.handle.before )
741 return main.FALSE
742 return main.TRUE
743 except pexpect.TIMEOUT:
744 main.log.exception( self.name + ": Command timed out" )
745 return main.FALSE
746 except pexpect.EOF:
747 main.log.exception( self.name + ": connection closed." )
748 main.cleanup( )
749 main.exit( )
750 except Exception:
751 main.log.exception( self.name + ": Uncaught exception!" )
752 main.cleanup( )
753 main.exit( )
754
755 def buildICMP( self, ipVersion=4, **kwargs ):
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800756 """
757 Build an ICMP frame
758
759 Will create a frame class with the given options. If a field is
760 left blank it will default to the below value unless it is
761 overwritten by the next frame.
762 Default frame:
763 ###[ ICMP ]###
764 type= echo-request
765 code= 0
766 chksum= None
767 id= 0x0
768 seq= 0x0
769
alisone14d7b02016-07-06 10:31:51 -0700770 Options:
771 ipVersion - Either 4 (default) or 6, indicates what Internet Protocol
772 frame to use to encapsulate into
773
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800774 Returns main.TRUE or main.FALSE on error
775 """
776 try:
777 # Set the ICMP frame
alisone14d7b02016-07-06 10:31:51 -0700778 if str( ipVersion ) is '4':
779 cmd = 'icmp = ICMP( '
780 elif str( ipVersion ) is '6':
781 cmd = 'icmp6 = ICMPv6EchoReply( '
782 else:
783 main.log.error( "Unrecognized option for ipVersion, given " +
784 repr( ipVersion ) )
785 return main.FALSE
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800786 options = []
alisone14d7b02016-07-06 10:31:51 -0700787 for key, value in kwargs.iteritems( ):
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800788 if isinstance( value, str ):
789 value = '"' + value + '"'
790 options.append( str( key ) + "=" + str( value ) )
791 cmd += ", ".join( options )
792 cmd += ' )'
793 self.handle.sendline( cmd )
794 self.handle.expect( self.scapyPrompt )
795 if "Traceback" in self.handle.before:
796 # KeyError, SyntaxError, ...
797 main.log.error( "Error in sending command: " + self.handle.before )
798 return main.FALSE
alisone14d7b02016-07-06 10:31:51 -0700799
800 if str( ipVersion ) is '4':
801 self.handle.sendline( "packet = ether/ip/icmp" )
802 elif str( ipVersion ) is '6':
803 self.handle.sendline( "packet = ether/ipv6/icmp6" )
804 else:
805 main.log.error( "Unrecognized option for ipVersion, given " +
806 repr( ipVersion ) )
807 return main.FALSE
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800808 self.handle.expect( self.scapyPrompt )
809 if "Traceback" in self.handle.before:
810 # KeyError, SyntaxError, ...
811 main.log.error( "Error in sending command: " + self.handle.before )
812 return main.FALSE
813 return main.TRUE
814 except pexpect.TIMEOUT:
815 main.log.exception( self.name + ": Command timed out" )
816 return main.FALSE
817 except pexpect.EOF:
818 main.log.exception( self.name + ": connection closed." )
alisone14d7b02016-07-06 10:31:51 -0700819 main.cleanup( )
820 main.exit( )
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800821 except Exception:
822 main.log.exception( self.name + ": Uncaught exception!" )
alisone14d7b02016-07-06 10:31:51 -0700823 main.cleanup( )
824 main.exit( )
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800825
826 def sendPacket( self, iface=None, packet=None, timeout=1 ):
827 """
828 Send a packet with either the given scapy packet command, or use the
829 packet saved in the variable 'packet'.
830
831 Examples of a valid string for packet:
832
833 Simple IP packet
834 packet='Ether(dst="a6:d9:26:df:1d:4b")/IP(dst="10.0.0.2")'
835
836 A Ping with two vlan tags
837 packet='Ether(dst='ff:ff:ff:ff:ff:ff')/Dot1Q(vlan=1)/Dot1Q(vlan=10)/
838 IP(dst='255.255.255.255', src='192.168.0.1')/ICMP()'
839
840 Returns main.TRUE or main.FALSE on error
841 """
842 try:
843 # TODO: add all params, or use kwargs
844 sendCmd = 'srp( '
845 if packet:
846 sendCmd += packet
847 else:
848 sendCmd += "packet"
849 if iface:
850 sendCmd += ", iface='{}'".format( iface )
851
852 sendCmd += ', timeout=' + str( timeout ) + ')'
853 self.handle.sendline( sendCmd )
854 self.handle.expect( self.scapyPrompt )
Jon Halla510a8a2016-05-04 15:09:28 -0700855 # main.log.warn( "Send packet response: {}".format( self.handle.before ) )
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800856 if "Traceback" in self.handle.before:
857 # KeyError, SyntaxError, ...
858 main.log.error( "Error in sending command: " + self.handle.before )
859 return main.FALSE
860 # TODO: Check # of packets sent?
861 return main.TRUE
862 except pexpect.TIMEOUT:
863 main.log.exception( self.name + ": Command timed out" )
864 return main.FALSE
865 except pexpect.EOF:
866 main.log.exception( self.name + ": connection closed." )
867 main.cleanup()
868 main.exit()
869 except Exception:
870 main.log.exception( self.name + ": Uncaught exception!" )
871 main.cleanup()
872 main.exit()
873
874 def startFilter( self, ifaceName=None, sniffCount=1, pktFilter="ip" ):
875 """
876 Listen for packets using the given filters
877
878 Options:
879 ifaceName - the name of the interface to listen on. If none is given,
880 defaults to <host name>-eth0
881 pktFilter - A string in Berkeley Packet Filter (BPF) format which
882 specifies which packets to sniff
883 sniffCount - The number of matching packets to capture before returning
884
885 Returns main.TRUE or main.FALSE on error
886 """
887 try:
888 # TODO: add all params, or use kwargs
889 ifaceName = str( ifaceName ) if ifaceName else self.name + "-eth0"
890 # Set interface
891 self.handle.sendline( ' conf.iface = "' + ifaceName + '"' )
892 self.handle.expect( self.scapyPrompt )
893 cmd = 'pkt = sniff(count = ' + str( sniffCount ) +\
894 ', filter = "' + str( pktFilter ) + '")'
Jon Halla510a8a2016-05-04 15:09:28 -0700895 main.log.info( "Filter on " + self.name + ' > ' + cmd )
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800896 self.handle.sendline( cmd )
897 self.handle.expect( '"\)\r\n' )
898 # TODO: parse this?
899 return main.TRUE
900 except pexpect.TIMEOUT:
901 main.log.exception( self.name + ": Command timed out" )
902 return main.FALSE
903 except pexpect.EOF:
904 main.log.exception( self.name + ": connection closed." )
905 main.cleanup()
906 main.exit()
907 except Exception:
908 main.log.exception( self.name + ": Uncaught exception!" )
909 main.cleanup()
910 main.exit()
911
912 def checkFilter( self, timeout=10 ):
913 """
914 Check that a filter returned and returns the reponse
915 """
916 try:
917 i = self.handle.expect( [ self.scapyPrompt, pexpect.TIMEOUT ], timeout=timeout )
918 if i == 0:
919 return main.TRUE
920 else:
921 return main.FALSE
922 except pexpect.EOF:
923 main.log.exception( self.name + ": connection closed." )
924 main.cleanup()
925 main.exit()
926 except Exception:
927 main.log.exception( self.name + ": Uncaught exception!" )
928 main.cleanup()
929 main.exit()
930
931 def killFilter( self ):
932 """
933 Kill a scapy filter
934 """
935 try:
936 self.handle.send( "\x03" ) # Send a ctrl-c to kill the filter
937 self.handle.expect( self.scapyPrompt )
938 return self.handle.before
939 except pexpect.TIMEOUT:
940 main.log.exception( self.name + ": Command timed out" )
941 return None
942 except pexpect.EOF:
943 main.log.exception( self.name + ": connection closed." )
944 main.cleanup()
945 main.exit()
946 except Exception:
947 main.log.exception( self.name + ": Uncaught exception!" )
948 main.cleanup()
949 main.exit()
950
951 def readPackets( self ):
952 """
953 Read all the packets captured by the previous filter
954 """
955 try:
956 self.handle.sendline( "for p in pkt: p \n")
957 self.handle.expect( "for p in pkt: p \r\n... \r\n" )
958 self.handle.expect( self.scapyPrompt )
959 except pexpect.TIMEOUT:
960 main.log.exception( self.name + ": Command timed out" )
961 return None
962 except pexpect.EOF:
963 main.log.exception( self.name + ": connection closed." )
964 main.cleanup()
965 main.exit()
966 except Exception:
967 main.log.exception( self.name + ": Uncaught exception!" )
968 main.cleanup()
969 main.exit()
970 return self.handle.before
971
alisone14d7b02016-07-06 10:31:51 -0700972 def updateSelf( self, IPv6=False ):
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800973 """
974 Updates local MAC and IP fields
975 """
976 self.hostMac = self.getMac()
alisone14d7b02016-07-06 10:31:51 -0700977 if IPv6:
978 self.hostIp = self.getIp( IPv6=True )
979 else:
980 self.hostIp = self.getIp()
Jeremy Songster1f39bf02016-01-20 17:17:25 -0800981
982 def getMac( self, ifaceName=None ):
983 """
984 Save host's MAC address
985 """
986 try:
987 ifaceName = str( ifaceName ) if ifaceName else self.name + "-eth0"
988 cmd = 'get_if_hwaddr("' + str( ifaceName ) + '")'
989 self.handle.sendline( cmd )
990 self.handle.expect( self.scapyPrompt )
991 pattern = r'(([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))'
992 match = re.search( pattern, self.handle.before )
993 if match:
994 return match.group()
995 else:
996 # the command will have an exception if iface doesn't exist
997 return None
998 except pexpect.TIMEOUT:
999 main.log.exception( self.name + ": Command timed out" )
1000 return None
1001 except pexpect.EOF:
1002 main.log.exception( self.name + ": connection closed." )
1003 main.cleanup()
1004 main.exit()
1005 except Exception:
1006 main.log.exception( self.name + ": Uncaught exception!" )
1007 main.cleanup()
1008 main.exit()
1009
alisone14d7b02016-07-06 10:31:51 -07001010 def getIp( self, ifaceName=None, IPv6=False ):
Jeremy Songster1f39bf02016-01-20 17:17:25 -08001011 """
1012 Save host's IP address
1013
1014 Returns the IP of the first interface that is not a loopback device.
1015 If no IP could be found then it will return 0.0.0.0.
alisone14d7b02016-07-06 10:31:51 -07001016
1017 If IPv6 is equal to True, returns IPv6 of the first interface that is not a loopback device.
1018 If no IPv6 could be found then it will return :: .
1019
Jeremy Songster1f39bf02016-01-20 17:17:25 -08001020 """
1021 def getIPofInterface( ifaceName ):
1022 cmd = 'get_if_addr("' + str( ifaceName ) + '")'
alisone14d7b02016-07-06 10:31:51 -07001023 if IPv6:
1024 cmd = 'get_if_raw_addr6("' + str( ifaceName ) + '")'
Jeremy Songster1f39bf02016-01-20 17:17:25 -08001025 self.handle.sendline( cmd )
1026 self.handle.expect( self.scapyPrompt )
1027
1028 pattern = r'(((2[0-5]|1[0-9]|[0-9])?[0-9]\.){3}((2[0-5]|1[0-9]|[0-9])?[0-9]))'
alisone14d7b02016-07-06 10:31:51 -07001029 if IPv6:
1030 pattern = r'(\\x([0-9]|[a-f]|[A-F])([0-9]|[a-f]|[A-F])){16}'
Jeremy Songster1f39bf02016-01-20 17:17:25 -08001031 match = re.search( pattern, self.handle.before )
1032 if match:
1033 # NOTE: The command will return 0.0.0.0 if the iface doesn't exist
alisone14d7b02016-07-06 10:31:51 -07001034 if IPv6 != True:
1035 if match.group() == '0.0.0.0':
1036 main.log.warn( 'iface {0} has no IPv4 address'.format( ifaceName ) )
Jeremy Songster1f39bf02016-01-20 17:17:25 -08001037 return match.group()
1038 else:
1039 return None
1040 try:
1041 if not ifaceName:
1042 # Get list of interfaces
1043 ifList = self.getIfList()
alisone14d7b02016-07-06 10:31:51 -07001044 if IPv6:
1045 for ifaceName in ifList:
1046 if ifaceName == "lo":
1047 continue
1048 ip = getIPofInterface( ifaceName )
1049 if ip != None:
1050 newip =ip
1051 tmp = newip.split( "\\x" )
1052 ip = ""
1053 counter = 0
1054 for i in tmp:
1055 if i != "":
1056 counter = counter + 1;
1057 if counter % 2 == 0 and counter < 16:
1058 ip = ip + i + ":"
1059 else:
1060 ip = ip + i
1061 return ip
1062 return "::"
1063 else:
1064 for ifaceName in ifList:
1065 if ifaceName == "lo":
1066 continue
1067 ip = getIPofInterface( ifaceName )
1068 if ip != "0.0.0.0":
1069 return ip
1070 return "0.0.0.0"
Jeremy Songster1f39bf02016-01-20 17:17:25 -08001071 else:
1072 return getIPofInterface( ifaceName )
1073
1074 except pexpect.TIMEOUT:
1075 main.log.exception( self.name + ": Command timed out" )
1076 return None
1077 except pexpect.EOF:
1078 main.log.exception( self.name + ": connection closed." )
1079 main.cleanup()
1080 main.exit()
1081 except Exception:
1082 main.log.exception( self.name + ": Uncaught exception!" )
1083 main.cleanup()
1084 main.exit()
1085
1086 def getIfList( self ):
1087 """
1088 Return List of Interfaces
1089 """
1090 try:
1091 self.handle.sendline( 'get_if_list()' )
1092 self.handle.expect( self.scapyPrompt )
1093 ifList = self.handle.before.split( '\r\n' )
1094 ifList = ifList[ 1 ].replace( "'","" )[ 1:-1 ].split( ', ' )
1095 return ifList
1096
1097 except pexpect.TIMEOUT:
1098 main.log.exception( self.name + ": Command timed out" )
1099 return None
1100 except pexpect.EOF:
1101 main.log.exception( self.name + ": connection closed." )
1102 main.cleanup()
1103 main.exit()
1104 except Exception:
1105 main.log.exception( self.name + ": Uncaught exception!" )
1106 main.cleanup()
1107 main.exit()
1108
1109if __name__ != "__main__":
1110 sys.modules[ __name__ ] = ScapyCliDriver()