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>