PERFORCE change 166198 for review
Zachariah Riggle
zjriggl at FreeBSD.org
Fri Jul 17 12:16:11 UTC 2009
http://perforce.freebsd.org/chv.cgi?CH=166198
Change 166198 by zjriggl at 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) <<<
More information about the p4-projects
mailing list