Date: Fri, 17 Jul 2009 12:16:10 GMT From: Zachariah Riggle <zjriggl@FreeBSD.org> To: Perforce Change Reviews <perforce@FreeBSD.org> Subject: PERFORCE change 166198 for review Message-ID: <200907171216.n6HCGAD2054280@repoman.freebsd.org>
next in thread | raw e-mail | index | archive | help
http://perforce.freebsd.org/chv.cgi?CH=166198 Change 166198 by zjriggl@zjriggl_tcpregression on 2009/07/17 12:15:58 Periodic checkin Affected files ... .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/__init__.py#7 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/decorators.py#3 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/segmentBuffer.py#1 add .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcprecvdaemon.py#2 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpstatemachine.py#6 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpstates.py#3 edit Differences ... ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/__init__.py#7 (text+ko) ==== ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/decorators.py#3 (text+ko) ==== @@ -190,4 +190,86 @@ fget=ops.get('fget',lambda self:getattr(self, name)) fset=ops.get('fset',lambda self,value:setattr(self,name,value)) fdel=ops.get('fdel',lambda self:delattr(self,name)) - return property ( fget, fset, fdel, ops.get('doc','') )+ return property ( fget, fset, fdel, ops.get('doc',func.__doc__ or '') ) + +def boundedInt(func): + ''' + A bounded integer. See @prop for syntax. + Set the 'max' field to set a wrap-around value. + + >>> class example(object): + ... @boundedInt + ... def n(): + ... return {'max': 10} + ... _n = 0 + ... + >>> ex = example() + >>> l = [] + >>> for i in range(0,20): + ... l += [ex.n] + ... ex.n += 1 + ... + >>> l + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + ''' + ops = func() or {} + upperBound = ops.get('max',1) # default to bound it to 2 + wrapValue = upperBound + 1 + + name=ops.get('prefix','_')+func.__name__ # property name + fget = lambda self: getattr(self,name) % upperBound + fset = lambda self,value: setattr(self,name, (value % wrapValue)) + fdel = lambda self: delattr(self,value) + return property (fget, fset, fdel, ops.get('doc','') ) + +def uint32(func): + max = 2**32 + ops = func() or {} + name=ops.get('prefix','_')+func.__name__ # property name + fget = lambda self: getattr(self,name) + fset = lambda self,value: setattr(self, name, (value % max)) + fdel = lambda self: delattr(self,value) + return property (fget, fset, fdel, func.__doc__) + +def uint16(func): + max = 2**16 + ops = func() or {} + name=ops.get('prefix','_')+func.__name__ # property name + fget = lambda self: getattr(self,name) + fset = lambda self,value: setattr(self, name, (value % max)) + fdel = lambda self: delattr(self,value) + return property (fget, fset, fdel, func.__doc__) + + +def uint(max=2**32): + ''' + >>> from pcsextension.decorators import * + >>> lim = 5 + >>> class A(object): + ... @uint(lim) + ... def x(): pass + ... _x = 0 + ... + _x + 5 + >>> a = A() + >>> l = [] + >>> for i in range(0,lim*2): + ... a.x = i + ... l += [a.x] + ... + >>> print l + [0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + ''' + def unsignedInteger(func): + ops = func() or {} + name=ops.get('prefix','_')+func.__name__ # property name + + print name + print max + + fget = lambda self: getattr(self,name) + fset = lambda self,value: setattr(self, name, (value % max)) + fdel = lambda self: delattr(self, value) + return property(fget, fset, fdel) + return unsignedInteger ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcprecvdaemon.py#2 (text+ko) ==== @@ -7,7 +7,7 @@ import threading import loggable import time -from tcpstatemachine import TcpStateMachine +# from tcpstatemachine import TcpStateMachine class TcpRecvDaemon( threading.Thread ): ''' @@ -16,83 +16,84 @@ just creating a thread object, is to provide [1] separation of duties and [2] to ease further expansion. Maybe later we want to change it to a process or something. - - The flow of packets is as follows: - - - Packet arrives on interface - - Packet is queued by PCAP library - - IF processing is disabled, sleep, and continue loop. - - Packet is pulled of PCAP queue by thread funning recvThread() method - - Packets are validated (i.e. ensure checksums) - - Packet is pushed onto the 'queuedPackets' list if validated. - - The packet is inspected to see if its sequence number is the sequence number that - the TCP state machine was expecting. - -- IF the sequence # is NOT the expected sequence, continue loop. - -- The sequence # matches. Iterate through all packets in queuedPackets. - --- If the iterated packet matches the next sequence #, send it to the - '_handleRecvdPacket' method, and push it onto the 'queuedPackets' list. - ''' +# The flow of packets is as follows: +# +# - Packet arrives on interface +# - Packet is queued by PCAP library +# - IF processing is disabled, sleep, and continue loop. +# - Packet is pulled of PCAP queue by thread funning recvThread() method +# - Packets are validated (i.e. ensure checksums) +# - Packet is pushed onto the 'queuedPackets' list if validated. +# - The packet is inspected to see if its sequence number is the sequence number that +# the TCP state machine was expecting. +# -- IF the sequence # is NOT the expected sequence, continue loop. +# -- The sequence # matches. Iterate through all packets in queuedPackets. +# --- If the iterated packet matches the next sequence #, send it to the +# '_handleRecvdPacket' method, and push it onto the 'queuedPackets' list. +# +# ''' def __init__( self, target ): self.log = loggable.tcplog( self ) threading.Thread.__init__( self, None, self.recvThread, None, ( target, ) ) + self.log.info('Starting receive thread for object %s' % repr(target)) - # List of packets that have been pulled off the interface, but were received out- - # of-order. - queuedPackets = [] +# # List of packets that have been pulled off the interface, but were received out- +# # of-order. +# queuedPackets = [] +# +# # List of in-order packets to be returned by calls to recv() by a test-writer +# orderedPackets = [] +# +# # List of in-order packets to be processed. +# packetsToBeProcessed = [] - # List of in-order packets to be returned by calls to recv() by a test-writer - orderedPackets = [] - - # List of in-order packets to be processed. - packetsToBeProcessed = [] - - def recvThread( self, tx ): + def recvThread( self, t ): ''' Takes a TCP State Machine object as an argument. Performs the collection and organization of packets. ''' - t = TcpStateMachine() - # Only process packets as long as we are told to. while True: # If we are not supposed to be processing packets, DON'T PROCESS PACKETS. if not t.processPackets: - time.sleep( 0.5 ) + time.sleep( 0.05 ) continue # Get the next packet from PCAP packet = t.recvRawTcp() + + # 'Arrive' it + t.segmentArrives(packet) - # Iterate over all of the packets in our list. If we find ONE packet - # that is the 'next' packet that the TCP State Machine is expecting, then - # we have to re-process all of them. - repeat = True - while repeat: - # By default, don't repeat. - repeat = False - - # List of packets to remove. We cannot modify the list of packets while - # iterating over it. - toRemove = [] - - # Iterate over the packets backwards. We will almost always find the packet - # that we want at the back of the list, as it will be the most recently-added. - for packet in self.packets.__reversed__(): - - # Found a match? - if packet.sequence == tsm.rcv_nxt: - # Handle it. - t._handleRecvdPacket( packet ) - - # Queue the item for removal - toRemove.append( packet ) - - # Have to re-iterate over everything. - repeat = True - - - # Remove everything that needs to be removed - for packet in toRemove: - self.packets.remove( packet ) +# # Iterate over all of the packets in our list. If we find ONE packet +# # that is the 'next' packet that the TCP State Machine is expecting, then +# # we have to re-process all of them. +# repeat = True +# while repeat: +# # By default, don't repeat. +# repeat = False +# +# # List of packets to remove. We cannot modify the list of packets while +# # iterating over it. +# toRemove = [] +# +# # Iterate over the packets backwards. We will almost always find the packet +# # that we want at the back of the list, as it will be the most recently-added. +# for packet in self.packets.__reversed__(): +# +# # Found a match? +# if packet.sequence == tsm.rcv_nxt: +# # Handle it. +# t.segmentArrives( packet ) +# # Queue the item for removal +# toRemove.append( packet ) +# +# # Have to re-iterate over everything. +# repeat = True +# +# +# # Remove everything that needs to be removed +# for packet in toRemove: +# self.packets.remove( packet ) ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpstatemachine.py#6 (text+ko) ==== @@ -6,7 +6,7 @@ from loggable import tcplog from pcs.packets import ethernet, ipv4, tcp -from pcsextension.decorators import prop, validateTypes +from pcsextension.decorators import prop, validateTypes, uint16, uint32 from pcsextension.hwAddress import HwAddress from pcsextension.ipAddress import IpAddress from pcsextension.networkPort import NetworkPort @@ -25,6 +25,7 @@ import testconfig import time from tcprecvdaemon import TcpRecvDaemon +import timer # Valid state transitions, as defined by the diagram on RFC 793 pp. 23: # September 1981 @@ -81,6 +82,40 @@ # # [Page 23] +def seq(x): + # It is essential to remember that the actual sequence number space is + # finite, though very large. This space ranges from 0 to 2**32 - 1. + # Since the space is finite, all arithmetic dealing with sequence + # numbers must be performed modulo 2**32. This unsigned arithmetic + # preserves the relationship of sequence numbers as they cycle from + # 2**32 - 1 to 0 again. + return x % (2**32) + +class sequenced(str): + ''' + This class exists to encapsulate sequenced items in the TCP stream. Each + byte (octet) is assigned a sequence number, as is the original SYN and final + FIN. + ''' + syn = False + fin = False + + def __str__(self): + if self.syn: + return "SYN" + if self.fin: + return "FIN" + + return str.__str__(self) + + def __repr__(self): + if self.syn: + return "SYN".__repr__() + if self.fin: + return "FIN".__repr__() + return str.__repr__(self) + + class TcpStateMachine( object ): ''' Enumerates the various states of a TCP connection as defined by RFC 793, @@ -117,55 +152,91 @@ __connector = tcpFilter( testconfig.interface ) __recvThread = None - snd_nxt = 0 # Next available send sequence # - snd_una = 0 # Unacknowledge send sequence # - snd_wnd = 128 * 1024 # Send window - snd_up = 0 # Seng urgent pointer - snd_wl1 = 0 # Sequence number used for last window update - snd_wl2 = 0 # Ack number used for last window update - iss = -1 # Initial sequence number - - rcv_wnd = 128 * 1024 # Recv window size - rcv_up = 0 # Recv urgent pointer - irs = 0 # Initial receive sequence number - rcv_nxt = irs # Expected next recv sequence # + @uint32 + def snd_nxt(): '''Next sequence to be sent (SND.NXT) ''' + _snd_nxt = 0 + + @uint32 + def snd_una(): ''' First (i.e. oldest) unacknowledged sequence (SND.UNA) ''' + _snd_una = 0 + + @uint16 + def snd_wnd(): ''' Send window size (SND.WND) ''' + _snd_wnd = 0 + + @uint16 + def snd_up(): ''' Send urgent pointer ''' + _snd_up = 0 + + @uint32 + def snd_wl1(): ''' Sequence number used for last window update. ''' + _snd_wl1 = 0 + + @uint32 + def snd_wl2(): ''' Ack number used for last window update ''' + _snd_wl2 = 0 + + @uint32 + def iss(): ''' Initial Send Sequence (ISS) ''' + _iss = 0 + + @uint16 + def rcv_wnd(): ''' Receive Window (RCV.WND) ''' + _rcv_wnd = 2**16 - 1 + + @uint16 + def rcv_up(): ''' Receive Urgent Pointer ''' + _rcv_up = 0 + + @uint32 + def irs(): ''' Initial Receive Sequence (IRS) ''' + _irs = 0 + + @uint32 + def rcv_nxt(): ''' Sequence expected in next segment (RCV.NXT) ''' + _rcv_nxt = 0 msl = 2 * 60 # Maximum Segment Lifetime. Arbitrarily defined in the RFC to 2 minutes timeout = 2 * msl # Timeout - # Flag used to stop the recv'er thread from processing additional packets. - processPackets = True + @prop + def processPackets(): ''' Flag used to stop the recv'er thread from processing additional packets. ''' + _processPackets = True # Ethernet stuff @prop - def localEthernet(): - return {'doc': 'Local hardware ethernet address'} + def localEthernet(): ''' Local hardware ethernet address ''' _localEthernet = HwAddress( default = testconfig.localMAC ) @prop - def remoteEthernet(): - return {'doc': 'Remote hardware ethernet address'} - remoteEthernet = HwAddress( default = testconfig.remoteMAC ) + def remoteEthernet(): '''Remote hardware ethernet address''' + _remoteEthernet = HwAddress( default = testconfig.remoteMAC ) @prop - def localIP(): - return {'doc': 'Local IP address.'} + def localIP(): '''Local IP address.''' _localIP = IpAddress( default = testconfig.localIP ) @prop - def remoteIP(): - return {'doc': 'Remote IP address.' } + def remoteIP(): '''Remote IP address.''' _remoteIP = IpAddress( default = testconfig.remoteIP ) @prop - def localPort(): - return {'doc': 'Local port.'} + def localPort(): '''Local port.''' _localPort = NetworkPort( default = testconfig.localPort ) @prop - def remotePort(): - return {'doc': 'Remote port.'} + def remotePort(): '''Remote port.''' _remotePort = NetworkPort( default = testconfig.remotePort ) + + # Interface is actually a shortcut to the connector's interface field, + # which is itself a property. Setting a TcpStateMachine's interface will + # effectively trigger the tcpFilter object to switch interfaces to the + # specified interface. + @prop + def interface(): + '''Interface to use for sending/recving data''' + return {'fget': lambda self: self.__connector.interface, + 'fset': lambda self,x: setattr(self.__connector,'interface',x)} def setLoopback( self, lb = True ): ''' @@ -176,16 +247,21 @@ self.__constructor.loopback = lb # Override the interface - if lb and self.interface not in self.loopbackInterfaces: + if lb and (self.interface not in self.loopbackInterfaces): self.log.warn( 'Overriding interface to be %s' % self.loopbackInterfaces[0] ) - self.interface = self.loopbackInterfaces[0] + + devs = (dev[i] for dev in pcap.findalldevs()) + loInterface = (iface for iface in self.loopbackInterfaces if iface 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 the connector is already active AND it is not on the loopback interface, - # re-open it on the loopback interface. - if self.__connector is not None and self.__connector.interface is not self.interface: - self.__connector = tcpFilter( self.interface ) - - # Used by setLoopback + # Used by setLoopback, this should be a list of all known loopback interface names. loopbackInterfaces = ['lo0', 'lo'] @prop @@ -193,12 +269,21 @@ return {'doc': 'Maximum Tranmission Unit'} _mtu = testconfig.mtu + def isSynchronized(self): + ''' + Is the connection in a synchronized state? + Return True if yes, otherwise False. + + @see tcpstates.synchronizedStates + ''' + return self.state in synchronizedStates + @prop def generate(): - return {'doc': 'What fields of outgoing TCP packets should be auto-generated,' - ' and various packet-generation toggles. Accepted Values:\n' - '%s - Outgoing packet TCP Checksum\n' - '%s - } + ''' + Dictionary of fields of outgoing TCP packets should be auto-generated, + and various packet-generation toggles. + ''' _generate = {tcp.f_checksum: True, tcp.f_offset: True, tcp.f_sequence: True, @@ -210,12 +295,14 @@ @prop def validate(): - return {'doc': 'Fields to be validated. Non - valid packets are dropped, non - valid' - 'settings throw an error. Accepted Values:\n' - ' % s - Incoming packet TCP Checksum\n' - ' % s - Incoming packet TCP Sequence Number\n' - ' % s - Incoming packet TCP Ack number\n' - ' % s - TCP State Machine transition\n'} + ''' + Dictionary of fields to be validated. Non - valid packets are dropped, non - valid + settings throw an error. Accepted Values: + %s - Incoming packet TCP Checksum + %s - Incoming packet TCP Sequence Number + %s - Incoming packet TCP Ack number + %s - TCP State Machine transition + ''' _validate = { tcp.f_checksum: True, tcp.f_sequence: True, tcp.f_ack_number: True, @@ -223,36 +310,91 @@ #tcp.f_dport: True, 'transition': True } - + def generateISS(self): + ''' + Generates a new Initial Sequence Number (ISS). + ''' + return seq(0) + @prop - def packetsToSend(): - return {'doc': 'List of all packets to be sent.' } - _packetsToSend = [] + def outboundSequences(): + ''' + List of all outbound sequences. This includes data sent from ISS onward. + + The first sequence should be an instance of a 'sequenced' object, with syn=1. + The last sequence should be an instance of a 'sequenced' object, with fin=1. + + All other sequences can be either a string object, or a 'sequenced' object (which + is simply a string with a 'syn' and 'fin' property). + + The first item in the list is the sequence with SEQ=ISS (the SYN sequence). + Note that this implementation does not gracefully deal with wrap, i.e. when the + sequence number overflows 2**32. + ''' + _outboundSequences = [] + @prop - def packetsSent(): - return {'doc': 'List of all packets the have been sent.' } - _packetsSent = [] - + # def recvd(): + def inboundSequences(): + ''' + List of all received sequences. This includes data recv'd fron IRS onward. + @see outboundSequences for more information. + ''' + _inboundSequences = [] + @prop - def packetsSentAcked(): - return {'doc': 'List of all packets the have been sent, for which ' - 'an ACKnowledgement message has not been received.'} - _packetsSentAcked = [] - + def retransmissionQ(): + ''' + Sent data that has not been acknowledged. + ''' + return {'fget': lambda self: self.outboundData[self.snd_una:]} + @prop - def packetsRecvd(): - return {'doc': 'List of all packets the have been received, but have not been ACKnowledged. ' - 'Upon receiving, a packet will be put into this buffer. If its sequence number is rcv_nxt, ' - 'it is moved to packetsRecvdAcked, and rcv_next is updated.'} - _packetsRecvd = [] - _packetsRecvdOffset = 0 + def recvBuffer(): + ''' + 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 + ''' + return {'fget': + lambda self: + [octet for octet in self.inboundSequences[seq( self._recvBufferOffset + self.irs ):] \ + if type(octet) != sequenced or not (octet.syn or octet.ack) ] + } + + @uint32 + def _recvBufferOffset(): + ''' + Offset to the 'read' pointer in recvBuffer. This offset is relative to IRS, + which is the first item in inboundSequences + ''' + __recvBufferOffset = 0 - @prop - def packetsRecvdAcked(): - return {'doc': 'List of all packets the have been received, but have not ' - 'been ACKnowledged.'} - _packetsRecvdAcked = [] +# @prop +# def packetsSent(): +# return {'doc': 'List of all packets the have been sent.' } +# _packetsSent = [] +# +# @prop +# def packetsSentAcked(): +# return {'doc': 'List of all packets the have been sent, for which ' +# 'an ACKnowledgement message has not been received.'} +# _packetsSentAcked = [] +# +# @prop +# def packetsRecvd(): +# return {'doc': 'List of all packets the have been received, but have not been ACKnowledged. ' +# 'Upon receiving, a packet will be put into this buffer. If its sequence number is rcv_nxt, ' +# 'it is moved to packetsRecvdAcked, and rcv_next is updated.'} +# _packetsRecvd = [] +# _packetsRecvdOffset = 0 +# +# @prop +# def packetsRecvdAcked(): +# return {'doc': 'List of all packets the have been received, but have not ' +# 'been ACKnowledged.'} +# _packetsRecvdAcked = [] @prop @@ -260,6 +402,7 @@ return {'fset': lambda self, x: self.setState( x ), 'doc': 'The current state of the TCP State Machine'} _state = CLOSED + _lastState = None # Used to store the previous state. def status( self ): ''' @@ -282,9 +425,18 @@ connName = 'Connection naming not supported' recvWindow = str( self.rcv_wnd ) sendWindow = str( self.snd_wnd ) - state = self.state.name - nbaAck = str( len( self.packetsSent ) ) - nbpRecpt = str( len( self.packetsRecvd ) ) + state = str(self.state) + nbaAck = len(self.retransmissionQ) + # TODO TODO TODO + # This should be the number of octets that have been received, but have + # not been accounted for in an 'ACK' message to the other side. + # TODO TODO TODO + nbpRecpt = 0 + + # TODO TODO TODO + # Urgent state is not currently supported, and will be added later as + # tests call for its use. + # TODO TODO TODO urgState = () precedence = 'Precedence not supported' security = 'Security not supported' @@ -293,6 +445,11 @@ return ( localSocket, remoteSocket, connName, recvWindow, sendWindow, state, nbaAck, nbpRecpt, urgState, precedence, security, timeout ) + def __repr__(self): + + return self.__class__.__name__ + '( (%s,%s), (%s,%s) )' % \ + ( self.localIP, self.localPort, self.remoteIP, self.remotePort ) + def __str__( self ): ''' Prints out the annotated status. @@ -321,7 +478,6 @@ ''' Sets the current state of the state machine. ''' - self.log.debug( state ) # Quick bail-out if self.state == state: return @@ -331,17 +487,20 @@ # Is the state a valid state? if state in tcpStates: - if ( not self.validate['transition'] ) or ( state in self.state.next ): + # if ( not self.validate['transition'] ) or ( state in self.state.next ): # if ( not validateTransition ) or ( state in self.state.next ): - action = "Setting" - if validateTransition: - action = "Advancing" + action = "Setting" + if validateTransition: + action = "Advancing" - self.log.state( "%s state from %s to %s" % ( action, self.state, state ) ) - self.__state = state - else: - self.log.state( "Attempted invalid state transition from %s to %s" % - ( self.state, state ) ) + self.log.state( "%s state from %s to %s" % ( action, self.state, state ) ) + + self._lastState = self._state + self._state = state + + if not state in self.state.next: + self.log.state( "Performed non-valid state transition from %s to %s" % + ( self._lastState, self.state ) ) else: self.log.error( 'Attempted to change to invalid state %s' % state ) @@ -350,6 +509,8 @@ Open the socket connection. This is synonymous with the 'connect' function used by normal UNIX sockets. ''' + + # CLOSED STATE (i.e., TCB does not exist) if self.state == CLOSED or self.state == LISTEN: # "Create a new transmission control block (TCB) to hold connection # state information. Fill in local socket identifier, foreign @@ -357,34 +518,42 @@ # information." # Layman's Terms: Reset the connection state self.reset() - + # "if active and the foreign socket is # specified, issue a SYN segment. An initial send sequence number - # (ISS) is selected. A SYN segment of the form <SEQ=ISS><CTL=SYN> - # is sent. Set SND.UNA to ISS, SND.NXT to ISS+1, enter SYN-SENT + # (ISS) is selected. + self.iss = self.generateISS() + + # A SYN segment of the form <SEQ=ISS><CTL=SYN> is sent. + synPacket = self.newPacket( {tcp.f_syn:1, tcp.f_sequence: self.iss} ) + self.sendPacket(synPackets) + + # Set SND.UNA to ISS, SND.NXT to ISS+1, enter SYN-SENT # state, and return." - # Layman's Terms: Send the SYN packet. The snd.una and snd.nxt bits - # are handled by the above reset() call. - synPacket = self.newPacket( {tcp.f_syn:1} ) - self.log.generated( "Sending generated SYN packet to initiate connection: %s" % repr( synPacket ) ) - self.packetsToSend.append( synPacket ) - self.sendQueuedPackets() + self.snd_una = self.iss + self.snd_nxt = self.iss + 1 + self.state = SYN_SENT + + t = timer(5.0) + while self.state != ESTABLISHED and not t.expired(): + self.sendPacket(synPackets) + time.sleep(0.5) # Recv the SYN-ACK packet. - start = time() - while self.state == SYN_SENT: - synAck = findTcpLayer( self.recv() ) - - self.log.debug( 'received packet %s' % repr( synAck ) ) - - if synAck.ack and synAck.syn and synAck.ack_number == ( synPacket.sequence + 1 ): - self.log.info( 'received SYN/ACK packet' ) - self.state = ESTABLISHED - - elif time() > ( start + testconfig.timeout ): - self.log.info( 'open() timeout after %s seconds' % testconfig.timeout ) - return False +# start = time() +# while self.state == SYN_SENT: +# synAck = findTcpLayer( self.recv() ) +# +# self.log.debug( 'received packet %s' % repr( synAck ) ) +# +# if synAck.ack and synAck.syn and synAck.ack_number == ( synPacket.sequence + 1 ): +# self.log.info( 'received SYN/ACK packet' ) +# self.state = ESTABLISHED +# +# elif time() > ( start + testconfig.timeout ): +# self.log.info( 'open() timeout after %s seconds' % testconfig.timeout ) +# return False # Send the ACK packet. ackPacket = self.newPacket( {tcp.f_ack:1, 'seq': synPacket.sequence + 1, tcp.f_ack_number: synAck.ack_number + 1} ) @@ -392,9 +561,7 @@ self.snd_nxt = synPacket.sequence + 1 self.rcv_nxt = synAck.ack_number + 1 - self.log.generated( "Sending generated ACK packet in response to SYN/ACK: %s" % repr( ackPacket ) ) - self.packetsToSend.append( appPacket ) - self.sendQueuedPackets() + self.state = ESTABLISHED return True else: @@ -403,11 +570,14 @@ # Default... return False + + def connect(self): + self.open() + def reset( self, iss = None ): ''' Resets all of the internal variables, sets the state to CLOSED. - @param iss Override the default ISS. ''' # ...if active and the foreign socket is # specified, issue a SYN segment. An initial send sequence number @@ -415,17 +585,10 @@ # is sent. Set SND.UNA to ISS, SND.NXT to ISS+1, enter SYN-SENT # state, and return. self.log.state( "Resetting state" ) - if iss is not None: - self.iss = iss - else: - if testconfig.randomISS: - self.iss = randint( 0, ( 2 ** 32 ) - 1 ) - else: - self.iss = testconfig.staticISS - self.logGenerated( self, 'iss' ) - - self.snd_una = self.iss - self.snd_nxt = self.iss + 1 + + self.iss = 0 + self.snd_una = 0 + self.snd_nxt = 0 self.snd_up = 0 self.snd_wl2 = 0 self.snd_wnd = 0xffff @@ -434,10 +597,15 @@ self.rcv_nxt = 0 self.rcv_up = 0 self.rcv_wnd = testconfig.recvWindow + + self.outboundData = [] + self.inboundSequences = [] + self._recvBufferOffset = 0 self.state = CLOSED - def send( self, packet ): + + def sendPacket( self, packet ): ''' Inform the TCP State Machine about packets that have been transmitted. This is necessary to keep the state up - to - date. @@ -490,7 +658,7 @@ if tcpLayer.fin: self.state = FIN_WAIT_1 - if self.state in [FIN_WAIT_1, FIN_WAIT_2, CLOSING, LAST_ACK, TIME_WAIT]: + if self.state in (FIN_WAIT_1, FIN_WAIT_2, CLOSING, LAST_ACK, TIME_WAIT): self.log.error( 'connection closing' ) # Send all queued packets @@ -511,6 +679,73 @@ self.__connector.write( packet.bytes ) pass + + def send(self, data): + ''' + Sends the specified data. + Returns -1 on errors, or else the number of bytes sent. + ''' + if self.state == CLOSED: + self.log.error('connection does not exist') + return -1 + + if self.state == LISTEN: + self.open() + + if self.state in (SYN_RECEIVED, SYN_SENT): + self.outboundSequences += [x for x in data] + + if self.state in (ESTABLISHED, CLOSE_WAIT): + pkt = self.newPacket({tcp.f_data: payload(data)}) + self.snd_nxt += len(data) + + firstOctetSequence = seq(pkt.sequence) + lastOctetSequence = seq(self.snd_nxt - 1) + + + seq = pkt.sequence + end = seq + len(data) + + self.sendPacket(pkt) + + + self.retransmissionQ[seq:end] = [byte for byte in bytes] + + self.outboundData[firstOctetSequence : lastOctetSequence] = + + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + # TODO + + # This is a blocking call. Sleep until all of the data has + # been acknowledged. + while self.snd_una <= end: + time.sleep(0.05) + + if self.state in (FIN_WAIT_1, FIN_WAIT_2, CLOSING, LAST_ACK, TIME_WAIT): + self.log.error('connection closing') + def sendQueuedPackets( self ): ''' @@ -584,7 +819,7 @@ t = tcp.tcp() # Generate all the fields that are set up... - t.syn = t.fin = t.rst = t.push = t.ack = t.urgent = 0 + t.syn = t.fin = t.reset = t.push = t.ack = t.urgent = 0 # set the defaults. don't generate the checksum yet. for field in self.tcpFieldsToGenerate(): @@ -609,21 +844,33 @@ # self.log.debug( " % s % s" % ( fieldname, repr( packet ) ) ) rv = None if fieldname == tcp.f_checksum: + # The checksum will always be the same rv = tcpChecksum( packet, src = self.localIP, dst = self.remoteIP ) elif fieldname == tcp.f_window: + # This will always be true rv = self.snd_wl1 elif fieldname == tcp.f_dport: + # This will always be true rv = self.remotePort.getInteger() elif fieldname == tcp.f_sport: + # This will always be true rv = self.localPort.getInteger() elif fieldname == tcp.f_sequence: + # This will always be true rv = self.snd_nxt + elif fieldname == tcp.f_ack: + # The ACK flag is *always* set, except on the VERY first SYN. + if self.state is not CLOSED: + rv = 1 elif fieldname == tcp.f_ack_number: + # Same with ack_num, ALWAYS set. rv = self.rcv_nxt elif fieldname == tcp.f_offset: + # The offset is constant until we support TCP options rv = 5 elif fieldname == tcp.f_urg_pointer: # TODO + # URG is unsupported right now rv = 0 else: self.log.warn( 'generateField not defined for %s' % fieldname ) @@ -647,14 +894,83 @@ return sequence + ( dataLength - headerLength ) + 1 - def ack( self, sequence ): + def sendAck(self, fields={}): + ''' + Sends an acknowledgment packet: + <SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK> + + Optionally, specify field-override values. + ''' + # self.snd_una = sequence + 1 + + ackFields = {tcp.f_sequence: self.snd_nxt, + tcp.f_acknum: self.rcv_nxt, + tcp.f_ack: 1} >>> TRUNCATED FOR MAIL (1000 lines) <<<
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200907171216.n6HCGAD2054280>