PERFORCE change 167953 for review
Zachariah Riggle
zjriggl at FreeBSD.org
Sat Aug 29 15:43:53 UTC 2009
http://perforce.freebsd.org/chv.cgi?CH=167953
Change 167953 by zjriggl at zjriggl_tcpregression on 2009/08/29 15:43:23
Added better comments
Affected files ...
.. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/loggable.py#8 edit
.. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpconstructor.py#2 edit
.. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpsenddaemon.py#3 edit
.. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpstatemachine.py#11 edit
.. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tests/tcpFilterTest.py#3 edit
.. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tests/testTcpConstructor.py#2 edit
Differences ...
==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/loggable.py#8 (text+ko) ====
@@ -1,3 +1,52 @@
+# Copyright 1994-2009 The FreeBSD Project. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# Redistributions of source code must retain the above copyright notice, this
+# list of conditions and the following disclaimer. Redistributions in binary
+# form must reproduce the above copyright notice, this list of conditions and
+# the following disclaimer in the documentation and/or other materials provided
+# with the distribution. THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``AS
+# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and documentation are
+# those of the authors and should not be interpreted as representing official
+# policies, either expressed or implied, of the FreeBSD Project.
+
+'''
+tcpregression.loggable
+
+Implements the logging interface used by the TCP regression framework.
+The tcplog class provides several convenience functions to allow access to
+different log-levels not defined by the standard logging library.
+
+ Sub-Info
+ - PACKET_RECEIVED
+ Logs packets that are destined for our TCP stack as they arrive.
+ - PACKET_SENT
+ Logs packets that are sent from our TCP stack as they are injected
+ into the network.
+ - STATE_CHANGE
+ Logs changes in the TCP state as defined by the TCP RFC
+ (i.e. CLOSED -> SYN-SENT -> ESTABLISHED -> FIN-WAIT-1 -> FIN-WAIT-2
+ -> TIME-WAIT -> CLOSED )
+
+ Sub-Debug
+ - FIELD_GENERATED
+ This log statement is printed every time an individual TCP packet field
+ is auto-generated.
+
+'''
+
import logging
import pcsextension
import inspect
@@ -16,8 +65,8 @@
break
# -------- Set Up New Loglevel Names --------
-( logging.FIELD_CHANGE,
- logging.RESPONSE_GENERATION ) = range( logging.DEBUG - 2, logging.DEBUG )
+( logging.FIELD_GENERATION,
+ logging.FIELD_CHANGE ) = range( logging.DEBUG - 2, logging.DEBUG )
( logging.PACKET_TRANSMIT,
logging.PACKET_RECEIVED,
@@ -64,7 +113,7 @@
# -------- Loglevel names --------
logging.addLevelName( logging.FIELD_CHANGE, "FIELD" )
-logging.addLevelName( logging.RESPONSE_GENERATION, colorText( "GENERATE", fgCyan ) )
+logging.addLevelName( logging.FIELD_GENERATION, colorText( "GENERATE", fgCyan ) )
logging.addLevelName( logging.PACKET_RECEIVED, colorText( "RECVD", fgYellow ) )
logging.addLevelName( logging.PACKET_SENT, colorText( "SENT", fgGreen ) )
logging.addLevelName( logging.STATE_CHANGE, colorText( "STATE", fgBlue ) )
@@ -72,6 +121,7 @@
class tcplog( object ):
'''
Provides rapid access to logging mechanisms for derived classes.
+ Also includes the name of the caller when printing a DEBUG statement.
Provides all of the standard log levels:
debug
@@ -80,10 +130,10 @@
error
fatal/critical
As well as some custom-defined ones:
- state - State changes
- packet - Packet send/recv
- response - Response generation
- field - Field change
+ state - State changes (i.e. CLOSED->SYN SENT)
+ packet - Packet send/recv
+ generate - TCP field, or PCS.Chain generation
+ field - TCP field, non-generated
>>> from loggable import Loggable
>>> class A(object):
@@ -100,9 +150,9 @@
>>> a.log.pktsent('test')
2009-05-24 12:48:26,402 - __main__.A - PACKET_SENT - test
>>> a.log.generated('test')
- 2009-05-24 12:48:49,516 - __main__.A - RESPONSE_GEN - test
+ 2009-05-24 12:48:49,516 - __main__.A - GENERATE - test
>>> a.log.field('test')
- 2009-05-24 12:48:52,475 - __main__.A - FIELD_CHANGE - test
+ 2009-05-24 12:48:52,475 - __main__.A - FIELD - test
'''
def __init__( self, parent ):
@@ -121,5 +171,5 @@
state = lambda self, x: self.logger.log( logging.STATE_CHANGE, x )
pktsent = lambda self, x: self.logger.log( logging.PACKET_SENT, x )
pktrecv = lambda self, x: self.logger.log( logging.PACKET_RECEIVED, x )
- generated = lambda self, x: self.logger.log( logging.RESPONSE_GENERATION, x )
+ generated = lambda self, x: self.logger.log( logging.FIELD_GENERATION, x )
field = lambda self, x: self.logger.log( logging.FIELD_CHANGE, x )
==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpconstructor.py#2 (text+ko) ====
@@ -3,7 +3,7 @@
@author: zach
'''
-
+import logging
import socket
import testconfig
from random import randint
@@ -20,13 +20,14 @@
from loggable import tcplog
-class tcpConstructor( object ):
+class TcpConstructor( object ):
'''
- Used to construct a packet complete with hardware and IP layers.
- Currently supports:
- - Ethernet
- - IPv4
- TODO IPv6
+ Generates a complete packet chain, given a TCP packet and information
+ regarding the underlying IP and Ethernet/Loopback levels.
+
+ This helper class simply creates a valid pcs.Chain object, complete
+ with hardware and IP-level pcs.Packet objects, given a
+ pcs.packets.tcp.tcp object.
'''
ipVersion = IPPROTO_IPV4
@@ -34,7 +35,7 @@
@prop
def loopback():
'''
- Set to 'True' to generate loopback packets for the hardware layer
+ Set to 'True' to use loopback packets for the hardware layer
instead of Ethernet packets.
'''
_loopback = False
@@ -83,7 +84,7 @@
dest = ( testconfig.remoteMAC, testconfig.remoteIP ),
loopback = False ):
'''
- Initialize the tcpConstructor object.
+ Initialize the TcpConstructor object.
@param src:
Tuple containing (HwAddress, IpAddress) for the SOURCE IP and
@@ -170,7 +171,7 @@
- dst = remote IP address (@see setRemoteIP)
- id = Random, between 0 and 65535 inclusive
- Other values are filled in with default values that will likely be changed.
+ Other values are filled in with sane default values.
'''
ip = ipv4()
ip.version = 4
@@ -190,7 +191,7 @@
Genreates a pcs.packets.ipv6.ipv6 object
TODO: Not supported.
'''
- pass
+ raise NotImplementedError( 'Not implemented' )
def generateIP( self ):
'''
@@ -216,6 +217,12 @@
return ether
def generateLoopback( self ):
+ '''
+ Generates a loopback-layer packet.
+
+ @note:
+ Supports IPv4 and IPv6 network layers.
+ '''
lb = localhost()
if self.ipVersion == IPPROTO_IPV6:
lb.type = socket.htonl( socket.AF_INET6 )
@@ -223,7 +230,11 @@
lb.type = socket.htonl( socket.AF_INET )
return lb
- def generateHardware( self ):
+ def generateDataLink( self ):
+ '''
+ Generates a data-link layer packet, using on self.loopback to
+ determine whether to use Loopback/NULL or Ethernet.
+ '''
if self.loopback:
return self.generateLoopback()
else:
@@ -231,23 +242,31 @@
def generateChain( self, tcpPacket ):
'''
- Generates a pcs.Chain complete with Ethernet and IP levels, that uses the provided
- pcs.packets.tcp.tcp object as the TCP layer.
- NOTE: This method makes no modifications to the TCP packet, and as such does not
- perform any validation of auto-generation of ANY TCP fields. The length of the
- TCP header + data is necessary to set the IP length field.
+ Generates a chain given a TCP packet.
+
+ @param tcpPacket:
+ TCP-layer packet to be used in the chain.
+
+ @note:
+ 'tcpPacket' can realisitacally be any implementation of
+ pcs.Packet, but it was designed to be used with
+ pcs.packets.tcp.tcp
'''
- hw = self.generateHardware()
+
+ dl = self.generateDataLink()
ip = self.generateIP()
- hw.data = ip
+ dl.data = ip
ip.data = tcpPacket
# Set the proper IP length and checksum
ip.length = ip.length + len( tcpPacket.chain().bytes )
ip.checksum = ipChecksum( ip )
- chain = hw.chain()
- self.log.generated( repr( chain ) )
+ # Build the chain.
+ chain = dl.chain()
+
+ if self.log.logger.isEnabledFor( logging.FIELD_GENERATION ):
+ self.log.generated( repr( chain ) )
return chain
==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpsenddaemon.py#3 (text+ko) ====
@@ -6,6 +6,7 @@
import threading
import loggable
+import logging
from pcs.packets import tcp
from pcs.packets import payload
import timer
@@ -14,30 +15,46 @@
class TcpSendDaemon( threading.Thread ):
'''
- This class is responsible for automatically re-sending unacknowledged
- data after some configured delay.
+ Thread daemon that polls a TcpStateMachine object's outboundPackets
+ queue and re-sends all unacknowledged packets every 'timeout' seconds.
'''
timeout = None
log = None
__killed = False
- def __init__( self, target ):
+ def __init__( self, tcpStateMachine, pollEvery = 1 ):
'''
- @param target TcpStateMachine that this daemon is sending for.
+ @param tcpStateMachine:
+ TcpStateMachine that this daemon is sending for.
+ @param pollEvery:
+ Polling period, in seconds.
'''
self.log = loggable.tcplog( self )
- self.timeout = timer.timer( 1 )
- threading.Thread.__init__( self, None, self.sendThread, None, ( target, ) )
- self.log.info( 'Starting send thread for object %s' % repr( target ) )
- self.daemon = True
+ self.timeout = timer.timer( pollEvery )
+ threading.Thread.__init__( self, None, self.sendThread, None, ( tcpStateMachine, ) )
+ self.log.info( 'Starting send thread for object %s' % repr( tcpStateMachine ) )
def kill( self ):
+ '''
+ Terminates the thread.
+
+ @note:
+ The thread will die the next time it polls. If using blocking
+ Pcap I/O, this may not be until after the next packet is recv'd.
+ '''
self.__killed = True
def sendThread( self, t ):
+ '''
+ Thread that performs the actual work
+
+ @note:
+ The thread will auto-die when the TCP state enters CLOSED or
+ TIME_WAIT, or when the kill() method is called.
+ '''
- while t.state not in ( CLOSED, TIME_WAIT ):
+ while t.state not in ( CLOSED, TIME_WAIT ) and not self.__killed:
# Restart the timer
self.timeout.reset()
@@ -48,8 +65,7 @@
# Are we supposed to be sending stuff?
if not t.autoResendUnackedSequences:
- self.log.debug( "Auto-resend disabled" )
- continue
+ self.log.debug( "Auto-send disabled" )
# Is there anything to send?
if len( t.outboundSequences ) == 0:
@@ -63,41 +79,67 @@
# Grab all available data, up to MSS. Only send one MSS' worth of data.
sequencesToSend = t.outboundSequences[:t.snd_una + t.mss]
- self.log.debug( "Getting sequences %i to %i in %s" % ( t.snd_una, t.snd_una + t.mss, sequencesToSend ) )
- self.log.debug( "Got sequences %i to %i: %s" %
- ( t.snd_una, t.snd_una + len( sequencesToSend ), repr( sequencesToSend ) ) )
+ if self.log.logger.isEnabledFor( logging.DEBUG ):
+ self.log.debug( "Getting sequences %i to %i in %s" %
+ ( t.snd_una, t.snd_una + t.mss, sequencesToSend ) )
+ self.log.debug( "Got sequences %i to %i: %s" %
+ ( t.snd_una, t.snd_una + len( sequencesToSend ), repr( sequencesToSend ) ) )
# Len...
if len( sequencesToSend ) == 0:
self.log.warn( "outboundSequences is not empty, but there is no data to send." )
continue
- # Check for a SYN or FIN
- syn = 1 if ( type( sequencesToSend[0] ) == Sequenced and sequencesToSend[0].syn ) else 0
- fin = 1 if ( type( sequencesToSend[0] ) == Sequenced and sequencesToSend[0].fin ) else 0
- if syn: self.log.debug( "Sending SYN!" )
- if fin: self.log.debug( "Sending FIN!" )
+ # Check for a SYN or FIN in the sequences that we selected
+ # syn = 1 if ( type( sequencesToSend[0] ) == Sequenced and sequencesToSend[0].syn ) else 0
+ # fin = 1 if ( type( sequencesToSend[0] ) == Sequenced and sequencesToSend[0].fin ) else 0
+
+ synOffsets = ( sequencesToSend.index( x ) for x in sequencesToSend \
+ if x.syn )
+ finOffsets = ( sequencesToSend.index( x ) for x in sequencesToSend \
+ if x.syn )
+ syn = 0 in synOffsets
+ fin = 0 in finOffsets
+
+ if synOffsets:
+ if syn:
+ self.log.debug( "Sending SYN, not sending any other data." )
+ else:
+ self.log.debug( "SYN in outbound data, sending all data up to that offset." )
+ if finOffsets:
+ if fin:
+ self.log.debug( "Sending FIN, not sending any other data" )
+ else:
+ self.log.debug( "FIN in outbound data, sending all data up to that offset." )
+
+ # Find out what the 'last byte' is that we're sending.
+ lastByte = len( sequencesToSend )
+ if not ( fin or syn ) and ( synOffsets or finOffsets ):
+ lastByte = min( synOffsets + finOffsets )
bytes = ''
- if not fin and not syn:
- bytes = ''.join( sequencesToSend )
+ if not ( syn or fin ):
+ bytes = ''.join( sequencesToSend[:lastByte] )
- self.log.debug( "Sending %i bytes" % len( bytes ) )
+ if self.log.logger.isEnabledFor( logging.DEBUG ):
+ self.log.debug( "Sending %i bytes of daat" % len( bytes ) )
# TCP Fields
tcpFields = {tcp.f_data: payload.payload( bytes ),
tcp.f_sequence: t.snd_una,
tcp.f_syn: syn,
tcp.f_fin: fin }
- if not syn:
+ if not ( syn or fin ):
tcpFields[tcp.f_ack] = 1
tcpFields[tcp.f_ack_number] = t.rcv_nxt
- self.log.debug( "Set outgoing packet ACK to %i" % t.rcv_nxt )
+
+ if self.log.logger.isEnabledFor( logging.DEBUG ):
+ self.log.debug( "Set outgoing packet ACK to %i" % t.rcv_nxt )
# Create the packet
tcpPacket = t.newPacket( tcpFields )
# Send the packet. Note that we use 'sendRaw' so that it is ALWAYS sent.
- self.log.debug( "Sending packet." )
t.sendRawTcp( tcpPacket )
+
self.log.debug( 'Quitting send thread' )
==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpstatemachine.py#11 (text+ko) ====
@@ -1,34 +1,60 @@
+# Copyright 1994-2009 The FreeBSD Project. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# Redistributions of source code must retain the above copyright notice, this
+# list of conditions and the following disclaimer. Redistributions in binary
+# form must reproduce the above copyright notice, this list of conditions and
+# the following disclaimer in the documentation and/or other materials provided
+# with the distribution. THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``AS
+# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and documentation are
+# those of the authors and should not be interpreted as representing official
+# policies, either expressed or implied, of the FreeBSD Project.
+
'''
-Created on May 24, 2009
-
- at author: zach
+Provides the TcpStateMachine class.
'''
from loggable import tcplog
from pcs.packets import tcp, payload
from pcsextension import findTcpLayer
from pcsextension.checksum import tcpChecksum
-from pcsextension.decorators import prop, validateTypes, uint16, uint32, synchronized
+from pcsextension.decorators import prop, validateTypes, uint16, uint32, \
+ synchronized
from pcsextension.hwaddress import HwAddress
from pcsextension.ipaddress import IpAddress
from pcsextension.networkport import NetworkPort
from segmentbuffer import SegmentBuffer
from sequence import seq, Sequenced, seq_fin, seq_finack, seq_syn, seq_synack
-from tcpconstructor import tcpConstructor
+from tcpconstructor import TcpConstructor
from tcpfilter import TcpFilter
from tcprecvdaemon import TcpRecvDaemon
from tcpsenddaemon import TcpSendDaemon
-from tcpstates import CLOSE_WAIT, CLOSED, CLOSING, ESTABLISHED, FIN_WAIT_1, FIN_WAIT_2, \
- LAST_ACK, LISTEN, SYN_RECEIVED, SYN_SENT, TIME_WAIT, TcpState, synchronizedStates, tcpStates
+from tcpstates import CLOSE_WAIT, CLOSED, CLOSING, ESTABLISHED, FIN_WAIT_1, \
+ FIN_WAIT_2, LAST_ACK, LISTEN, SYN_RECEIVED, SYN_SENT, TIME_WAIT, TcpState, \
+ synchronizedStates, tcpStates
+from threading import RLock
from time import time
-from threading import RLock
+from timer import timer
+import logging
+import pcs
import pcs.pcap as pcap
-import pcs
+import random
import testconfig
+import threading
import time
-from timer import timer
-import random
-import threading
+
# Valid state transitions, as defined by the diagram on RFC 793 pp. 23:
# September 1981
# Transmission Control Protocol
@@ -86,34 +112,64 @@
class TcpStateMachine( object ):
'''
- Enumerates the various states of a TCP connection as defined by RFC 793,
- pages 21-22.
+ Encapsulates a 'TCP control block', as well as the corresponding logic
+ for accepting, starting, and closing a connection, and transferring data
+ between two hosts using TCP.
- Allows arbitrary state assignment with the 'state' property and the 'setState' method
- (state=X is the same as setState(X)).
+ Usage example
+ =============
+ Consider an echo server running on localhost port 7.
+ The below shows the TCP State Machine being set up in a manner very
+ similar to regular sockets programming, and sending a few bytes of
+ data, then recv'ing those bytes back. The connection is then closed.
+
+ >>> # These lines will disable all logging. Remove them to enable the
+ >>> # Very-verbose log statements that show each step of the process.
+ >>> import logging
+ >>> logging.getLogger('tcpregression').disabled = True
+ >>> from tcpregression.tcpstatemachine import TcpStateMachine
+ >>> tsm = TcpStateMachine(remotehost=('127.0.0.1',7))
+ >>> tsm.state
+ Closed
+ >>> tsm.open()
+ True
+ >>> tsm.state
+ Established
+ >>> tsm.send('asdfasdf')
+ 8
+ >>> data = tsm.recv()
+ >>> data
+ 'asdfasdf'
+ >>> tsm.close()
+ True
+ >>> tsm.state
+ Time-Wait
+ >>> import time
+ >>> time.sleep(tsm.timeout)
+ >>> tsm.state
+ Closed
- Allows for checked state transitions with the 'advanceState' method.
+ Advanced usage
+ ==============
+ All parts of the TCP State Machine can be directly modified --
+ nothing is off limits. For example, throwing an extra SYN on the
+ outbound transmit queue is as simple as the below example. See
+ sequence.py for more information the the Sequenced class and
+ >>> tsm.outboundSequences += [seq_syn]
- >>> from tcpstatemachine import *
- >>> t = TcpStateMachine()
- >>> t.state
- 0
- >>> t.state = CLOSED
- 2009-05-24 14:46:37,198 - tcpstatemachine.TcpStateMachine - STATE_CHANGE - Changing state from None to None
- >>> t.state
- 0
- >>> t.state = LISTEN
- 2009-05-24 14:46:42,621 - tcpstatemachine.TcpStateMachine - STATE_CHANGE - Changing state from None to Listen
- >>> t.advanceSt ate(SYN_RECEIVED)
- 2009-05-24 14:46:52,939 - tcpstatemachine.TcpStateMachine - STATE_CHANGE - Changing state from Listen to Syn-Recvd
- True
- >>> t.state
- 3
- >>> t.advanceState(SYN_SENT)
- 2009-05-24 14:46:57,922 - tcpstatemachine.TcpStateMachine - STATE_CHANGE - Attempted invalid state transition from Syn-Recvd to Syn-Sent
- False
- >>> t.state
- 3
+ Additionally, disabling of the auto-processing mechanism allows for
+ fully manual control over the flow of packets. For example:
+ >>> tsm.processPacketsOnArrival = False
+ >>> tsm.reset()
+ >>> tsm.outboundSequences = [seq_syn]
+ >>> tsm.state = SYN_SENT
+ >>> while True:
+ ... p = tsm.recvRaw()
+ ... if p.syn and p.ack and p.ack_num == (tsm.iss + 1):
+ ... break
+ >>> tsm.rcv_nxt = p.sequence + 1
+ >>> tsm.sendAck()
+ >>> tsm.state = ESTABLISHED
'''
__constructor = None
@@ -131,12 +187,12 @@
remote host must be provided as a 2 - tuple, a string and an integer.
Example:
- >>> t = TcpStateMachine( ( "127.0.0.1", 10000 ), ( "192.168.0.1", 80 ) )
+ >>> t = TcpStateMachine( locahost=( "127.0.0.1", 10000 ),
+ ... remotehost=( "192.168.0.1", 80 ) )
'''
# Required objects.
- # self.lock = RLock()
- self.__constructor = tcpConstructor()
+ self.__constructor = TcpConstructor()
self.__tcpFilter = TcpFilter( testconfig.interface )
self._inboundSequences = SegmentBuffer()
self._outboundSequences = SegmentBuffer()
@@ -169,14 +225,15 @@
# Reset the internal state.
self.reset()
- # @uint32
@prop
def snd_nxt():
'''
- Next sequence to be sent (SND.NXT).
- This is automatically calculated based on the next-available index
- in outboundSequences. However, if the value is set directly, it is overridden.
- Set it to None to re-enable auto-calculation
+ Next sequence to be sent (SND.NXT in the RFC).
+ This is automatically calculated based on the next available index
+ in outboundSequences.
+
+ If the value is set directly, the provided value is used.
+ Set it to None to re-enable auto-calculation.
'''
return {'fget': lambda self: self._snd_nxt or self.outboundSequences.nextIndex }
@@ -186,49 +243,58 @@
def snd_una():
'''
First (i.e. oldest) unacknowledged sequence (SND.UNA).
- This is automatically calculated based on the first index in outboundSequences.
+ This is automatically calculated based on the first index in
+ outboundSequences.
+
Setting this value sets the 'base' member of that same list, which will
remove any information from the retransmit queue with a lower SEQ #.
'''
return {'fget': lambda self: self.outboundSequences.base,
'fset': lambda self, x: self.outboundSequences.setBase( x ) }
-# _snd_una = # Not necessary...
@uint16
def snd_wnd(): ''' Send window size (SND.WND) '''
_snd_wnd = 0
@uint16
- def snd_up(): ''' Send urgent pointer '''
+ def snd_up(): ''' Send urgent pointer (SND.UP) '''
_snd_up = 0
@uint32
- def snd_wl1(): ''' Sequence number used for last window update. '''
+ def snd_wl1(): ''' Sequence number used for last window update (SND.WL1) '''
_snd_wl1 = 0
@uint32
- def snd_wl2(): ''' Ack number used for last window update '''
+ def snd_wl2(): ''' Ack number used for last window update (SND.WL2)'''
_snd_wl2 = 0
@prop
def iss():
- ''' Initial Send Sequence (ISS) '''
- return {'fset': lambda self, x: self.setISS( x ) }
+ '''
+ Initial Send Sequence (ISS). This value is set via self.generateISS()
+ when self.reset() is called, and when establishing a new connection via
+ self.open().
+
+ To define a *static* ISS (i.e. the same ISS is always used), do the
+ following (this example uses 1234):
+ object.generateISS = lambda self: 1234
+
+ @attention:
+ The ISS is linked to the outboundSequences queue, and modifying iss
+ will set outboundSequences.base=iss and delete the contents of
+ the outboundSequences list.
+ '''
+ return {'fset': lambda self, x: self.__setISS( x ) }
_iss = 0
- def setISS( self, iss ):
- '''
- Sets the ISS, and also updates the outboundSequences base to reflect
- the new ISS. Any information in outboundSequences is deleted.
- '''
+ def __setISS( self, iss ):
self._iss = iss
self.snd_una = self.iss
- # self.outboundSequences.base = iss
self.outboundSequences = []
self.outboundSequences.base = iss
@uint16
- def rcv_wnd(): ''' Receive Window (RCV.WND) '''
+ def rcv_wnd(): ''' Receive Window (RCV.WND). '''
_rcv_wnd = 2 ** 16 - 1
@uint16
@@ -238,12 +304,12 @@
@prop
def irs():
''' Initial Receive Sequence (IRS) '''
- return {'fset': lambda self, x: self.setIRS( x )}
+ return {'fset': lambda self, x: self.__setIRS( x )}
_irs = 0
- def setIRS( self, irs ):
+ def __setIRS( self, irs ):
'''
- @see setISS
+ @see: TcpStateMachine.__setISS
'''
self._irs = irs
self.inboundSequences.base = irs
@@ -274,58 +340,95 @@
'''
Flag used to start/stop the recv'er thread from processing additional packets.
Default is True.
+
+ @type:
+ bool
'''
_processPacketsOnArrival = True
@prop
def autoResendUnackedSequences():
'''
- Flag used to start/stop the resender thread from automatically retransmit
- un-acknowledged packets.
+ Flag used to start/stop the resender thread from automatically
+ retransmit un-acknowledged packets.
Default is True.
+
+ @type:
+ bool
'''
_autoResendUnackedSequences = True
# Ethernet stuff
@prop
- def localEthernet(): ''' Local hardware ethernet address '''
+ def localEthernet():
+ '''
+ Local hardware ethernet address
+ @type:
+ tcpregression.pcsextension.HwAddress
+ '''
_localEthernet = HwAddress( default = testconfig.localMAC )
@prop
- def remoteEthernet(): '''Remote hardware ethernet address'''
+ def remoteEthernet():
+ '''
+ Remote hardware ethernet address
+ @type:
+ tcpregression.pcsextension.HwAddress
+ '''
_remoteEthernet = HwAddress( default = testconfig.remoteMAC )
@prop
def localIP():
- '''Local IP address.'''
+ '''
+ Local IP address.
+ @type:
+ tcpregression.pcsextension.IpAddress
+ '''
return {'fset': lambda self, x: self.localIP.set( x ) }
@prop
def remoteIP():
- '''Remote IP address.'''
+ '''
+ Remote IP address.
+ @type:
+ tcpregression.pcsextension.IpAddress
+ '''
return {'fset': lambda self, x: self.remoteIP.set( x ) }
@prop
def localPort():
- '''Local port.'''
+ '''
+ Local port.
+ @type:
+ tcpregression.pcsextension.NetworkPort
+ '''
return {'fset': lambda self, x: self.localPort.set( x ) }
@prop
def remotePort():
- '''Remote port.'''
+ '''
+ Remote port.
+ @type:
+ tcpregression.pcsextension.NetworkPort
+ '''
return {'fset': lambda self, x: self.remotePort.set( x ) }
- def setRemotePort( self, x ):
- import traceback
- traceback.print_stack()
- self._remotePort = x
-
@prop
- def userTimer(): ''' User timeout timer '''
+ def userTimer():
+ '''
+ User timeout timer
+ @type:
+ threading.Timer
+ '''
_userTimer = None
@prop
- def timeWaitTimer(): ''' Time-wait timer '''
+ def timeWaitTimer():
+ '''
+ Time-wait timer
+ @type:
+ threading.Timer
+ '''
_timeWaitTimer = None
# Interface is actually a shortcut to the connector's interface field,
@@ -334,40 +437,69 @@
# specified interface.
@prop
def interface():
- '''Interface to use for sending/recving data'''
+ '''
+ Interface to use for sending/recving data
+ @type:
+ str
+ '''
return {'fget': lambda self: self.__tcpFilter.interface,
'fset': lambda self, x: setattr( self.__tcpFilter, 'interface', x )}
def getConnector( self ):
'''
- Retrieves the connector used to send and receive packets.
- Note: The name 'connector' is simply for consistency. The object that is
- returned is actually a TcpFilter object.
+ Retrieves the TcpFilter object used to send and receive packets.
+
+ @return:
+ A TcpFilter object, or None.
'''
return self.__tcpFilter
- def setLoopback( self, lb = True ):
+ def setLoopback( self, lb = True, iface = None ):
'''
- Call with lb=True to omit the ethernet layer with a loopback layer in its place.
- This should be done when using lo0 instead of eth0, or the source and destination
- IP address are the same.
+ Sets the TcpStateMachine into loopback mode. This call is more of
+ a helper function, as loopback vs. ethernet doesn't have anything
+ to do with TCP.
+
+ This function searches for a list of loopback interfaces from pcap,
+ and automatically sets the TcpFilter to use the first loopback IF
+ it finds, or 'iface' if it is set.
+
+ Additionally, it tells the TcpConstructor to use Loopback frames
+ instead of Ethernet frames.
+
+ If 'lb' is False, the TcpStateMachine sets the TcpConstructor to
+ use Ethernet frames, and uses [1] the first non-loopback interface
+ it finds, or 'iface' if it is set.
+
+ If lb=True and the current interface is already loopback interface,
+ the interface is not changed unless specified by 'iface'.
+
+ @param lb:
+ True/False value, specify whether to use loopback or not.
+ @param iface:
+ Override the interface selection. Synonymous with:
+ self.interface = iface
'''
self.__constructor.loopback = lb
+ loInterface = iface
+
# Override the interface
if lb and ( self.interface not in self.loopbackInterfaces ):
- self.log.warn( 'Overriding interface to be %s' % self.loopbackInterfaces[0] )
devs = [dev[0] for dev in pcap.findalldevs()]
- loInterface = ( iface for iface in self.loopbackInterfaces if iface in devs )
+ if not iface:
+ iface = ( _if for _if in self.loopbackInterfaces if _if in devs )
if len( loInterface ) < 1:
self.log.error( 'cannot set loopback, could not identify any '
' loopback interfaces in available interfaces (%s) out '
' of known loopback interfaces (%s)' % ( devs, self.loopbackInterfaces ) )
- else :
- # Select the first interface
- self.interface = loInterface[0]
+
+ if lo
+ self.interface = iface or (loInterface[0]
+
+
# Used by setLoopback, this should be a list of all known loopback interface names.
# findalldevs() returns a tuple where the first item is the interface name,
@@ -383,7 +515,7 @@
Is the connection in a synchronized state?
Return True if yes, otherwise False.
- @see tcpstates.synchronizedStates
+ @see: tcpstates.synchronizedStates
'''
return self.state in synchronizedStates
@@ -393,6 +525,7 @@
Dictionary of fields of outgoing TCP packets should be auto-generated,
and various packet-generation toggles.
'''
+ return {'fset': lambda self, x: self.__updateGenerateDictionary( x )}
_generate = {tcp.f_checksum: True,
tcp.f_offset: True,
tcp.f_sequence: True,
@@ -402,6 +535,10 @@
tcp.f_window: True,
tcp.f_urg_pointer: True }
+ def __updateGenerateDictionary( self, newDict ):
+ self._generate = newDict
+ self.__fieldsToGenerateUpdated = True
+
def generateISS( self ):
'''
Generates a new Initial Sequence Number (ISS).
@@ -429,8 +566,9 @@
@prop
def inboundSequences():
'''
- List of all received sequences. This includes data recv'd from IRS onward.
- @see outboundSequences for more information.
+ List of all received sequences. This includes data recv'd from IRS
+ onward.
+ @see: TcpStateMachine.outboundSequences
'''
return { 'fset': lambda self, x: self.inboundSequences.update( x ) }
@@ -446,7 +584,7 @@
'''
Recv buffer of octets waiting for the user to call recv().
Note that this buffer will explicitly exclude all SYN and FIN sequences.
- @see inboundSequences
+ @see: TcpStateMachine.inboundSequences
'''
return {'fget': lambda self: [octet for octet in self.inboundSequences ] }
@@ -592,8 +730,11 @@
def close( self ):
'''
- Close the socket connection. This is synonymous with the 'close' function used
- by normal UNIX sockets.
+ Close the socket connection. This is synonymous with the 'close'
+ function used by normal UNIX sockets.
+
+ @return:
+ True on success; False otherwise.
'''
self.log.info( "Closing connection" )
@@ -604,7 +745,7 @@
#
# Otherwise, return "error: connection does not exist".
self.log.error( 'connection does not exist' )
- return - 1
+ return False
#LISTEN STATE
elif self.state is LISTEN:
# Any outstanding RECEIVEs are returned with "error: closing"
@@ -618,7 +759,6 @@
# queued SENDs, or RECEIVEs.
self.state = CLOSED
self.reset()
-
#SYN-RECEIVED STATE
elif self.state is SYN_RECEIVED:
# If no SENDs have been issued and there is no pending data to send,
@@ -633,7 +773,6 @@
# state.
self.outboundSequences += [seq_fin]# Sequenced(fin=1)]
self.state = FIN_WAIT_1
-
#FIN-WAIT-1 STATE
#FIN-WAIT-2 STATE
>>> TRUNCATED FOR MAIL (1000 lines) <<<
More information about the p4-projects
mailing list