Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 29 Aug 2009 15:43:52 GMT
From:      Zachariah Riggle <zjriggl@FreeBSD.org>
To:        Perforce Change Reviews <perforce@FreeBSD.org>
Subject:   PERFORCE change 167953 for review
Message-ID:  <200908291543.n7TFhq0h021266@repoman.freebsd.org>

next in thread | raw e-mail | index | archive | help
http://perforce.freebsd.org/chv.cgi?CH=167953

Change 167953 by zjriggl@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
-
-@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) <<<



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200908291543.n7TFhq0h021266>