From owner-p4-projects@FreeBSD.ORG Fri Jul 10 01:35:43 2009 Return-Path: Delivered-To: p4-projects@freebsd.org Received: by hub.freebsd.org (Postfix, from userid 32767) id A46F81065674; Fri, 10 Jul 2009 01:35:43 +0000 (UTC) Delivered-To: perforce@FreeBSD.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 63D10106566B for ; Fri, 10 Jul 2009 01:35:43 +0000 (UTC) (envelope-from zjriggl@FreeBSD.org) Received: from repoman.freebsd.org (repoman.freebsd.org [IPv6:2001:4f8:fff6::29]) by mx1.freebsd.org (Postfix) with ESMTP id 504268FC0A for ; Fri, 10 Jul 2009 01:35:43 +0000 (UTC) (envelope-from zjriggl@FreeBSD.org) Received: from repoman.freebsd.org (localhost [127.0.0.1]) by repoman.freebsd.org (8.14.3/8.14.3) with ESMTP id n6A1Zh1f033993 for ; Fri, 10 Jul 2009 01:35:43 GMT (envelope-from zjriggl@FreeBSD.org) Received: (from perforce@localhost) by repoman.freebsd.org (8.14.3/8.14.3/Submit) id n6A1Zh1W033991 for perforce@freebsd.org; Fri, 10 Jul 2009 01:35:43 GMT (envelope-from zjriggl@FreeBSD.org) Date: Fri, 10 Jul 2009 01:35:43 GMT Message-Id: <200907100135.n6A1Zh1W033991@repoman.freebsd.org> X-Authentication-Warning: repoman.freebsd.org: perforce set sender to zjriggl@FreeBSD.org using -f From: Zachariah Riggle To: Perforce Change Reviews Cc: Subject: PERFORCE change 165891 for review X-BeenThere: p4-projects@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: p4 projects tree changes List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 10 Jul 2009 01:35:44 -0000 http://perforce.freebsd.org/chv.cgi?CH=165891 Change 165891 by zjriggl@zjriggl_tcpregression on 2009/07/10 01:35:37 Just a random commit. Realized that I hadn't in a while... Affected files ... .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/echoServer.py#4 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/loggable.py#4 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/logging.conf#4 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/StringField.py#2 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/__init__.py#6 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/backup.tar#2 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/decorators.py#2 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/hwAddress.py#3 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/ipAddress.py#4 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/networkPort.py#3 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/payload.py#3 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/pseudoipv4.py#2 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/sniffLocalhost.py#4 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpConstructor.py#5 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpFilter.py#6 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpHandshake.py#4 delete .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpstatemachine.py#5 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpstates.py#2 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/test.html#4 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/testconfig.py#6 edit Differences ... ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/echoServer.py#4 (text+ko) ==== ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/loggable.py#4 (text+ko) ==== @@ -1,7 +1,31 @@ import logging import pcsextension +import inspect + +# Configure logging +logging.config.fileConfig( "logging.conf" ) + +( logging.FIELD_CHANGE, + logging.RESPONSE_GENERATION, + logging.PACKET_TRANSMIT, + logging.PACKET_RECEIVED, + logging.PACKET_SENT, + logging.VALIDATE, + logging.STATE_CHANGE ) = range( logging.INFO - 7, logging.INFO ) + +logging.addLevelName( logging.FIELD_CHANGE, "FIELD" ) +logging.addLevelName( logging.RESPONSE_GENERATION, "\033[1;36mGENERATE\033[1;m" ) +logging.addLevelName( logging.PACKET_TRANSMIT, "XMIT" ) +logging.addLevelName( logging.PACKET_RECEIVED, "\033[1;31mRECVD\033[1;m" ) +logging.addLevelName( logging.PACKET_SENT, "\033[1;31mSENT\033[1;m" ) +logging.addLevelName( logging.VALIDATE, "VALIDATE" ) +logging.addLevelName( logging.STATE_CHANGE, "STATE" ) + +# print '\033[1;36mGENERATE\033[1;m' +# print '\033[1;36mCyan like Caribbean\033[1;m' +# '\033[1;31mRed like Radish\033[1;m' -class tcplog(object): +class tcplog( object ): ''' Provides rapid access to logging mechanisms for derived classes. @@ -36,22 +60,24 @@ >>> a.log.field('test') 2009-05-24 12:48:52,475 - __main__.A - FIELD_CHANGE - test ''' - - def __init__(self,parent): + + def __init__( self, parent ): # self._log = logging.getLogger(parent.__class__.__module__ + '.' + parent.__class__.__name__) - self._log = logging.getLogger(parent.__class__.__name__) - - debug = lambda self,x: self._log.debug(x) - info = lambda self,x: self._log.info(x) - error = lambda self,x: self._log.error(x) - critical = lambda self,x: self._log.critical(x) - fatal = lambda self,x: self._log.fatal(x) - warning = lambda self,x: self._log.warning(x) - warn = lambda self,x: self._log.warn(x) - state = lambda self,x: self._log.log(logging.STATE_CHANGE, x) - validate = lambda self,x: self._log.log(logging.VALIDATE, x) - pktsent = lambda self,x: self._log.log(logging.PACKET_SENT, x) - pktrecv = lambda self,x: self._log.log(logging.PACKET_RECEIVED, x) - generated = lambda self,x: self._log.log(logging.RESPONSE_GENERATION, x) - field = lambda self,x: self._log.log(logging.FIELD_CHANGE, x) - + self._log = logging.getLogger( parent.__class__.__name__ ) + + def caller( self ): + return inspect.stack()[2][3] + + debug = lambda self, x: self._log.debug( "%s - %s" % ( self.caller(), x ) ) + info = lambda self, x: self._log.info( x ) + error = lambda self, x: self._log.error( x ) + critical = lambda self, x: self._log.critical( x ) + fatal = lambda self, x: self._log.fatal( x ) + warning = lambda self, x: self._log.warning( x ) + warn = lambda self, x: self._log.warn( x ) + state = lambda self, x: self._log.log( logging.STATE_CHANGE, x ) + validate = lambda self, x: self._log.log( logging.VALIDATE, x ) + pktsent = lambda self, x: self._log.log( logging.PACKET_SENT, x ) + pktrecv = lambda self, x: self._log.log( logging.PACKET_RECEIVED, x ) + generated = lambda self, x: self._log.log( logging.RESPONSE_GENERATION, x ) + field = lambda self, x: self._log.log( logging.FIELD_CHANGE, x ) ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/logging.conf#4 (text+ko) ==== ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/StringField.py#2 (text+ko) ==== ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/__init__.py#6 (text+ko) ==== @@ -8,25 +8,6 @@ from pcs.packets.tcp import tcp from pcs.packets.tcpv6 import tcpv6 -# Configure logging -logging.config.fileConfig( "logging.conf" ) - -( logging.FIELD_CHANGE, - logging.RESPONSE_GENERATION, - logging.PACKET_TRANSMIT, - logging.PACKET_RECEIVED, - logging.PACKET_SENT, - logging.VALIDATE, - logging.STATE_CHANGE ) = range( logging.INFO - 7, logging.INFO ) - -logging.addLevelName( logging.FIELD_CHANGE, "FIELD" ) -logging.addLevelName( logging.RESPONSE_GENERATION, "GENERATE" ) -logging.addLevelName( logging.PACKET_TRANSMIT, "XMIT" ) -logging.addLevelName( logging.PACKET_RECEIVED, "RECEIVED" ) -logging.addLevelName( logging.PACKET_SENT, "SENT" ) -logging.addLevelName( logging.VALIDATE, "VALIDATE" ) -logging.addLevelName( logging.STATE_CHANGE, "STATE" ) - # Find the TCP layer in a packet. def findTcpLayer( packet ): return findPacketLayer( packet, tcp ) @@ -35,6 +16,11 @@ return findPacketLayer( packet, ipv4 ) def findPacketLayer( packet, _class ): + # Quick return if there's an exact match + if type( packet ) == _class: + return packet + + # Otherwise search through the layers p = packet while p is not None: if isinstance( p, _class ): ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/backup.tar#2 (binary) ==== ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/decorators.py#2 (text+ko) ==== ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/hwAddress.py#3 (text+ko) ==== ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/ipAddress.py#4 (text+ko) ==== ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/networkPort.py#3 (text+ko) ==== ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/payload.py#3 (text+ko) ==== ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/pseudoipv4.py#2 (text+ko) ==== ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/sniffLocalhost.py#4 (text+ko) ==== ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpConstructor.py#5 (text+ko) ==== @@ -10,10 +10,12 @@ from pcs.packets.ipv4 import ipv4 from pcs.packets.tcp import tcp from pcs.packets.ethernet import * +from pcs.packets.localhost import * from socket import IPPROTO_IPV4, IPPROTO_IPV6, IPPROTO_TCP from pcsextension.ipAddress import IpAddress from pcsextension.hwAddress import HwAddress -from pcsextension.checksum import * +from pcsextension.checksum import * +from loggable import tcplog class tcpConstructor: ''' @@ -23,7 +25,11 @@ - IPv4 TODO IPv6 ''' + + loopback = False + ipVersion = IPPROTO_IPV4 + localIP = IpAddress() remoteIP = IpAddress() @@ -31,6 +37,8 @@ remoteHw = HwAddress() def __init__( self ): + self.log = tcplog( self ) + self.localIP.setAscii( testconfig.localIP ) self.remoteIP.setAscii( testconfig.remoteIP ) @@ -81,10 +89,10 @@ ip = ipv4() ip.version = 4 ip.hlen = 5 - ip.tos = 0 # Normal + ip.tos = 0x10 # Normal ip.length = 20 ip.id = randint( 0, 65535 ) # See RFC4413, pp. 20 - ip.flags = 0x4 # 'Dont fragment' + ip.flags = 2 # 'Dont fragment' ip.ttl = 64 ip.protocol = IPPROTO_TCP ip.src = self.localIP.getPCS() @@ -121,6 +129,20 @@ ether.dst = self.remoteHw.getPCS() return ether + def generateLoopback( self ): + lb = localhost() + if self.ipVersion == IPPROTO_IPV6: + lb.type = socket.htonl( socket.AF_INET6 ) + else: + lb.type = socket.htonl( socket.AF_INET ) + return lb + + def generateHardware( self ): + if self.loopback: + return self.generateLoopback() + else: + return self.generateEthernet() + def generateChain( self, tcpPacket ): ''' Generates a pcs.Chain complete with Ethernet and IP levels, that uses the provided @@ -129,14 +151,17 @@ 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. ''' - ether = self.generateEthernet() + hw = self.generateHardware() ip = self.generateIP() - ether.data = ip + hw.data = ip ip.data = tcpPacket # Set the proper IP length and checksum ip.length = ip.length + len( tcpPacket.chain().bytes ) ip.checksum = ipChecksum( ip ) - return ether.chain() + chain = hw.chain() + self.log.generated( repr( chain ) ) + + return chain ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpFilter.py#6 (text+ko) ==== @@ -13,28 +13,45 @@ from pcsextension.ipAddress import IpAddress from pcsextension.networkPort import NetworkPort from pcsextension import findIpLayer, findTcpLayer - +import pprint class tcpFilter( object ): log = None pcapHandle = None doRead = False + @prop + def interface(): + return {'doc': 'Interface used with the TCP Filter. If this interface is different from the' + ' current interface, the current one is closed, and the new one is opened automatically.', + 'fset': lambda self, x: self.openInterface( x ) } + self._interface = None + def __init__( self, interfaceName ): self.log = tcplog( self ) - self.openInterface( interfaceName ) + self.interface = interfaceName def openInterface( self, interfaceName ): + # Is it already opened with this interface? + if self.interface is interfaceName: + log.info( 'Tried to re - open same interface: % s' % self.interface ) + return + else: + self._interface = interfaceName + + # Open the interface try: - self.pcapHandle = PcapConnector( interfaceName ) + self.pcapHandle = PcapConnector( self.interface ) + self.log.info( "Opened %s" % self.interface ) # self.pcapHandle = IP4Connector(); except: - self.log.error( "Could not open interface %s" % interfaceName ) + self.log.error( "Could not open interface %s" % self.interface ) def read( self ): return self.pcapHandle.readpkt() def write( self, bytes ): + self.log.pktsent( pprint.pformat( bytes ) ) self.pcapHandle.write( bytes, len( bytes ) ) def readFilteredByIP( self, ip ): @@ -55,9 +72,6 @@ tmp.setAscii( ip ) ip = tmp - srcIP = IpAddress() - dstIP = IpAddress() - while True: packet = self.read() @@ -66,11 +80,8 @@ if ipLayer == None: continue - srcIP.setPCS( ipLayer.src ) - dstIP.setPCS( ipLayer.dst ) - - print "Saw %s" % str( srcIP ) - print "Saw %s" % str( dstIP ) + # srcIP.setPCS( ipLayer.src ) + # dstIP.setPCS( ipLayer.dst ) if ipLayer.src == ip.getPCS() or ipLayer.dst == ip.getPCS(): return packet @@ -90,13 +101,6 @@ while True: packet = self.readFilteredByIP( ipAddress ) - tcpLayer = findTcpLayer( packet ) - if tcpLayer == None: - continue - - print tcpLayer.sport - print port.getNetworkInteger() - - if tcpLayer.sport == port.getInteger() or tcpLayer.dport == port.getInteger(): + if tcpLayer and ( tcpLayer.sport == port.getInteger() or tcpLayer.dport == port.getInteger() ): return packet ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpstatemachine.py#5 (text+ko) ==== @@ -12,6 +12,7 @@ from pcsextension.networkPort import NetworkPort from pcsextension.pseudoipv4 import pseudoipv4, ipv4_cksum from pcsextension.checksum import tcpChecksum +from multiprocessing import Process, Queue from random import randint from socket import IPPROTO_TCP from tcpFilter import tcpFilter @@ -22,6 +23,8 @@ import binhex import pcs import testconfig +import time +from tcprecvdaemon import TcpRecvDaemon # Valid state transitions, as defined by the diagram on RFC 793 pp. 23: # September 1981 @@ -110,11 +113,9 @@ 3 ''' - _constructor = tcpConstructor() - _connector = tcpFilter( testconfig.interface ) - - def generateInitialSequence( self ): - return 0 + __constructor = tcpConstructor() + __connector = tcpFilter( testconfig.interface ) + __recvThread = None snd_nxt = 0 # Next available send sequence # snd_una = 0 # Unacknowledge send sequence # @@ -129,17 +130,19 @@ irs = 0 # Initial receive sequence number rcv_nxt = irs # Expected next recv sequence # - ack_nxt = 0 # Next ACK number to send. - ack_lst = 0 # Last ACK number sent. - 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 + # Ethernet stuff + @prop def localEthernet(): return {'doc': 'Local hardware ethernet address'} _localEthernet = HwAddress( default = testconfig.localMAC ) + @prop def remoteEthernet(): return {'doc': 'Remote hardware ethernet address'} remoteEthernet = HwAddress( default = testconfig.remoteMAC ) @@ -164,6 +167,27 @@ return {'doc': 'Remote port.'} _remotePort = NetworkPort( default = testconfig.remotePort ) + def setLoopback( self, lb = True ): + ''' + 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. + ''' + self.__constructor.loopback = lb + + # Override the interface + 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] + + # 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 + loopbackInterfaces = ['lo0', 'lo'] + @prop def mtu(): return {'doc': 'Maximum Tranmission Unit'} @@ -171,26 +195,35 @@ @prop def generate(): - return {'doc': 'What fields of outgoing TCP packets should be auto-generated.'} - _generate = {'cksum': True, - 'off': True, - 'sequence': True, - 'ack_number': True, - 'sport': True, - 'dport': True, - 'window': True, - 'urg_pointer': True } + 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 - } + _generate = {tcp.f_checksum: True, + tcp.f_offset: True, + tcp.f_sequence: True, + tcp.f_ack_number: True, + tcp.f_sport: True, + tcp.f_dport: True, + tcp.f_window: True, + tcp.f_urg_pointer: True } @prop def validate(): - return {'doc': 'What fields of incoming TCP packets should be validated.'} - _validate = { 'cksum': True, - 'sequence': True, - 'ack_number': True, - 'sport': True, - 'dport': True, + 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'} + _validate = { tcp.f_checksum: True, + tcp.f_sequence: True, + tcp.f_ack_number: True, + #tcp.f_sport: True, + #tcp.f_dport: True, 'transition': True } + @prop def packetsToSend(): return {'doc': 'List of all packets to be sent.' } @@ -213,6 +246,7 @@ '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(): @@ -240,7 +274,7 @@ number of buffers pending receipt, urgent state, precedence, - security/compartment, + security / compartment, and transmission timeout ) ''' localSocket = self.localIP.getAscii() + ':' + self.localPort.getAscii() @@ -262,7 +296,7 @@ def __str__( self ): ''' Prints out the annotated status. - + ''' statusNames = ['local socket', 'foreign socket', @@ -287,23 +321,15 @@ ''' Sets the current state of the state machine. ''' + self.log.debug( state ) # Quick bail-out if self.state == state: return validateTypes( {state:TcpState} ) - if ( state == LISTEN or - state == SYN_SENT or - state == SYN_RECEIVED or - state == ESTABLISHED or - state == FIN_WAIT_1 or - state == FIN_WAIT_2 or - state == CLOSE_WAIT or - state == CLOSING or - state == CLOSED or - state == TIME_WAIT or - state == LAST_ACK ): + # Is the state a valid state? + if state in tcpStates: if ( not self.validate['transition'] ) or ( state in self.state.next ): # if ( not validateTransition ) or ( state in self.state.next ): @@ -312,42 +338,35 @@ action = "Advancing" self.log.state( "%s state from %s to %s" % ( action, self.state, state ) ) - self._state = state + self.__state = state else: self.log.state( "Attempted invalid state transition from %s to %s" % ( self.state, state ) ) - -# -# def advanceState(self, state): -# ''' -# Sets the currents state of the state machine, checking to make -# sure that the provided state is a valid state to transition to, -# given the current state. -# ''' -# validateTypes({state:TcpState}) -# # validateTypes(state,TcpState) -# -# if state in self.state.next: -# self.state = state -# else: -# self.log.state("Attempted invalid state transition from %s to %s" % -# (self.state.name, state.name)) -# -# return self.state == state + else: + self.log.error( 'Attempted to change to invalid state %s' % state ) def open( self ): ''' Open the socket connection. This is synonymous with the 'connect' function used by normal UNIX sockets. - S ''' if self.state == CLOSED or self.state == LISTEN: - # Reset the connection state + # "Create a new transmission control block (TCB) to hold connection + # state information. Fill in local socket identifier, foreign + # socket, precedence, security/compartment, and user timeout + # information." + # Layman's Terms: Reset the connection state self.reset() - # Send the SYN packet. - synPacket = self.newPacket( {'syn':1} ) - self.log.generated( "Sending generated SYN packet to initiate connection: %s" % synPacket ) + # "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 + # is sent. 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.state = SYN_SENT @@ -355,18 +374,25 @@ # Recv the SYN-ACK packet. start = time() while self.state == SYN_SENT: - synAck = self.recv() + synAck = findTcpLayer( self.recv() ) + + self.log.debug( 'received packet %s' % repr( synAck ) ) - if synAck.ack and synAck.syn and synAck.ack_number == synPacket.sequence: - state = ESTABLISHED + 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 ): + elif time() > ( start + testconfig.timeout ): self.log.info( 'open() timeout after %s seconds' % testconfig.timeout ) return False # Send the ACK packet. - ackPacket = self.newPacket( {'ack':1} ) - self.log.generated( "Sending generated ACK packet in response to SYN/ACK: %s" % ackPacket ) + ackPacket = self.newPacket( {tcp.f_ack:1, 'seq': synPacket.sequence + 1, tcp.f_ack_number: synAck.ack_number + 1} ) + + 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 @@ -401,9 +427,9 @@ self.snd_una = self.iss self.snd_nxt = self.iss + 1 self.snd_up = 0 - self.snd_wl1 = 0 self.snd_wl2 = 0 self.snd_wnd = 0xffff + self.snd_wl1 = self.snd_wnd self.rcv_nxt = 0 self.rcv_up = 0 @@ -411,18 +437,18 @@ self.state = CLOSED - def recv( self ): - self._connector.readFilteredByTuple( self.localIP, self.localPort ) - def send( self, packet ): ''' Inform the TCP State Machine about packets that have been transmitted. - This is necessary to keep the state up-to-date. + This is necessary to keep the state up - to - date. @param packet pcs.Packet object to send. Must be, or contain, a TCP packet. ''' # Check the arg type validateTypes( {packet:pcs.Packet} ) + # Log + self.log.debug( repr( packet ) ) + # Get the TCP layer tcpLayer = findTcpLayer( packet ) if tcpLayer == None: @@ -452,7 +478,7 @@ # acknowledgment (acknowledgment value = RCV.NXT). If there is # insufficient space to remember this buffer, simply return "error: # insufficient resources". - if self.generate['ack_number']: + if self.generate.get( tcp.f_ack_number ): packet.ack_number = self.rcv_nxt # If the urgent flag is set, then SND.UP <- SND.NXT-1 and set the @@ -470,61 +496,99 @@ # Send all queued packets self.sendQueuedPackets() + def recvRawTcp( self ): + return findTcpLayer( self.__connector.readFilteredByTuple( self.remoteIP, self.remotePort ) ) + def sendRawTcp( self, tcpLayer ): + self.log.debug( repr( tcpLayer ) ) + if not validateTypes( {tcpLayer:tcp.tcp} ): return - # self._socket.sendto(tcpLayer.chain().bytes, (self.remoteIP().getAscii(),self.remotePort().getAscii())) - packet = self._constructor - self._connector.send( tcpLayer.chain().bytes ) + # self.__socket.sendto(tcpLayer.chain().bytes, (self.remoteIP().getAscii(),self.remotePort().getAscii())) + packet = self.__constructor.generateChain( tcpLayer ) + self.log.pktsent( repr ( packet ) ) + self.__connector.write( packet.bytes ) pass def sendQueuedPackets( self ): + ''' + Sends all packets in the queue. + ''' + self.log.debug( "called" ) for tcpLayer in self.packetsToSend: send = False # If the packet is the next packet that we are supposed to send, send it. - if self.snd_una == tcpLayer.sequence: + if self.snd_una < tcpLayer.sequence or not self.generate.get( tcp.f_sequence ): # Make sure we are allowed to send it (i.e. smaller than rcv window) - # Set the ACK to acknowledge any packets that we've received - if self.generate['ack_number'] and self.ack_nxt > self.ack_lst: - # if self.isAutomaticAckEnabled() and self.ack_nxt > self.ack_lst: - tcpLayer.ack_number = self.rcv_nxt - tcpLayer.ack = 1 +# # Set the ACK to acknowledge any packets that we've received +# if self.generate[tcp.f_ack_number] and self.ack_nxt > self.ack_lst: +# # if self.isAutomaticAckEnabled() and self.ack_nxt > self.ack_lst: +# tcpLayer.ack_number = self.rcv_nxt +# tcpLayer.ack = 1 +# +# # Set the correct checksum +# if self.generate[tcp.f_checksum]: +# # if self.isChecksumValidationEnabled(): +# tcpLayer.cksum = tcpChecksum( tcpLayer, src = self.localIP, dst = self.remoteIP ) + #self.generateChecksum( tcpLayer ) - # Set the correct checksum - if self.generate['checksum']: - # if self.isChecksumValidationEnabled(): - tcpLayer.cksum = tcpChecksum( tcpLayer, src = self.localIP, dst = self.remoteIP ) #self.generateChecksum( tcpLayer ) + self.sendRawTcp( tcpLayer ) + # Update internal stuff. +# length = 0 # TODO TODO TODO TODO +# if self.snd_nxt + else: + self.log.error( "Did not send packet because sequence (%s) > snd_una (%s): %s" + % ( tcpLayer.sequence, self.snd_una, repr( tcpLayer ) ) ) - self.sendRawTcp( tcpLayer ) + def logGenerated( self, packet = None, fieldname = None ): + ''' + Helper method to log generated field data. + ''' + # If a packet was specified, print the packet. + if packet: + self.log.generated( "Automatically set field %s to %s" % ( fieldname, hex( getattr( packet, fieldname ) ) ) ) - def logGenerated( self, packet, fieldname ): - self.log.generated( "Automatically set field %s to %s" % ( fieldname, hex( getattr( packet, fieldname ) ) ) ) + # Otherwise, assume it was some internal setting to the state, i.e. ISS, rcv_nxt, et. al + else: + self.log.generated( "Automatically set %s to %s" % ( fieldname, hex( getattr( self, fieldname ) ) ) ) def logField( self, packet, fieldname ): - self.log.field( "Set field %s to %s" % ( fieldname, hex( getattr( packet, fieldname ) ) ) ) + ''' + Helper method to log user - specified field changes. + ''' + self.log.field( "Set field % s to % s" % ( fieldname, hex( getattr( packet, fieldname ) ) ) ) def fieldsToGenerate( self ): - return [k for k in self.generate.keys() if self.generate[k]] + ''' + Returns an array of fields that need to be generated for a TCP packet. + ''' + return [k for k in self.generate.iterkeys() if hasattr( self, k ) and self.generate.get( k )] + + def tcpFieldsToGenerate( self ): + ''' + Returns an array of fields that need to be generated for a TCP packet. + ''' + return [k for k in self.generate.iterkeys() if hasattr( tcp, k ) and self.generate.get( k )] def newPacket( self, fields = None ): ''' Creates a new packet with the specified fields. Any fields that - are not specified are auto-generated if auto-generation is turned + are not specified are auto - generated if auto - generation is turned on for that field. @see #generate ''' + self.log.debug( " % s" % fields ) t = tcp.tcp() # Generate all the fields that are set up... t.syn = t.fin = t.rst = t.push = t.ack = t.urgent = 0 # set the defaults. don't generate the checksum yet. - for field in [k for k in self.fieldsToGenerate() if k != 'cksum']: + for field in self.tcpFieldsToGenerate(): setattr( t, field, self.generateField( t, field ) ) - self.logGenerated( t, field ) # setattr( t, field, value ) # set the user-provided values @@ -534,49 +598,407 @@ self.logField( t, field ) # Do the checksum after user-provided values - if 'cksum' in self.generate: - t.cksum = tcpChecksum( t, src = self.localIP, dst = self.remoteIP ) - self.logGenerated( t, 'cksum' ) + if self.generate.get( tcp.f_checksum ): + t.checksum = self.generateField( t, tcp.f_checksum ) + #t.checksum = tcpChecksum( t, src = self.localIP, dst = self.remoteIP ) + #self.logGenerated( t, tcp.f_checksum ) return t def generateField( self, packet, fieldname ): - if fieldname == 'cksum': - return tcpChecksum( packet, src = self.localIP, dst = self.remoteIP ) - if fieldname == 'window': - return self.snd_wl1 - if fieldname == 'dport': - return self.remotePort.getInteger() - if fieldname == 'sport': - return self.localPort.getInteger() - if fieldname == 'sequence': - return self.snd_nxt - if fieldname == 'ack_number': - return self.rcv_nxt - if fieldname == 'off': - return 5 - if fieldname == 'urg_pointer': + # self.log.debug( " % s % s" % ( fieldname, repr( packet ) ) ) + rv = None + if fieldname == tcp.f_checksum: + rv = tcpChecksum( packet, src = self.localIP, dst = self.remoteIP ) + elif fieldname == tcp.f_window: + rv = self.snd_wl1 + elif fieldname == tcp.f_dport: + rv = self.remotePort.getInteger() + elif fieldname == tcp.f_sport: + rv = self.localPort.getInteger() + elif fieldname == tcp.f_sequence: + rv = self.snd_nxt + elif fieldname == tcp.f_ack_number: + rv = self.rcv_nxt + elif fieldname == tcp.f_offset: + rv = 5 + elif fieldname == tcp.f_urg_pointer: # TODO + rv = 0 + else: + self.log.warn( 'generateField not defined for %s' % fieldname ) + + self.log.generated( '{%s: %s}' % ( fieldname, rv ) ) + return rv + + def getAckNum( self, packet ): + ''' + Returns the ack_number that should be used to ACK the successful + receipt of the provided packet. + ''' + p = findTcpLayer( packet ) + if p is None: return 0 + sequence = p.sequence + headerLength = len( p.getbytes() ) + data = p.chain().bytes + dataLength = len( data ) + + return sequence + ( dataLength - headerLength ) + 1 + + def ack( self, sequence ): + ''' + Modify the internal state to consider the provided sequence number + as having been successfully acknowledged. + ''' + self.snd_una = sequence + 1 + + def recv( self, packet = None, timeout = None ): + ''' + Get the next packet that has been received. Packets are guaranteed + to be valid, in - order, and non - duplicated. - def recv( self, packet = None ): + @param packet Specify a packet to 'simulate' the receipt of. + @param timeout Optionally, specify a timeout for receiving a packet. + @return + - If param 'packet' specified, the packet or None if an error occurred. + - Otherwise, the next packet or None if there were none available in + the timeout period. + ''' + + # If the user provides a packet, we are 'simulating' receipt of + # a packet. Pass it off to the packet-receipt handler. Return + # the packet if it is processed successfully. + if packet: + self.log.debug( repr( packet ) ) + if self._handleRecvdPacket( packet ) == packet: + return packet + else: + return None + + # Otherwise, pull the packet off of the front of the packetsRecvd list. + else: + start = time.time() + + # Wait until there is a 'new' packet available. + while self.packetsRecvd.size() <= self._packetsRecvdOffset: + time.sleep( 0.05 ) + + # Check the timeout + if timeout and time.time() > start + timeout: + return None + + # Return the next packet. + self._packetsRecvdOffset += 1 + return self.packetsRecvd[self._packetsRecvdOffset] + + # Return 'None' + return None + + def _handleRecvdPacket( self, packet = None ): ''' Inform the TCP State Machine about packets that have been received. - This is necessary to keep the state up-to-date. + This is necessary to keep the state up - to - date. ''' - if packet == None: - packet = self._connector.readFilteredByTuple( self.remoteIP, self.remotePort ) + # Make sure that there is TCP data in the packet. + seg = findTcpLayer( packet ) + + if seg is None: + return None + + # Set to 'True' to queue the packet in 'self.packetsRecvd' at the end of the method. + queuePacket = False + + # If the state is CLOSED (i.e., TCB does not exist) then + # all data in the incoming segment is discarded. An incoming + # segment containing a RST is discarded. An incoming segment not + # containing a RST causes a RST to be sent in response. The + # acknowledgment and sequence field values are selected to make the + # reset sequence acceptable to the TCP that sent the offending + # segment. + if self.state == CLOSED and not seg.reset: + + log.warn( 'Called recv with state == %s' % CLOSED ) + + # If the ACK bit is off, sequence number zero is used, + # + if seg.ack: + pkt = self.newPacket( {tcp.f_sequence: seg.sequence, 'reset': 1} ) + + # If the ACK bit is on, + # + else: + pkt = self.newPacket( {tcp.f_sequence:0, tcp.f_ack: 1, 'reset': 1, tcp.f_ack_number: seg.ack_number} ) + + self.sendRawTcp( pkt ) + return None + + # If the state is LISTEN then first check for an RST + + elif self.state == LISTEN: + # An incoming RST should be ignored. Return. + if seg.reset: + return None + + # second check for an ACK + # Any acknowledgment is bad if it arrives on a connection still in + # the LISTEN state. An acceptable reset segment should be formed + # for any arriving ACK-bearing segment. The RST should be + # formatted as follows: + # + # Return. + if seg.ack: + pkt = self.newPacket( {tcp.f_sequence:seg.ack_number, 'reset': 1} ) + self.sendRawTcp( pkt ) + return None + + # third check for a SYN + # + # If the SYN bit is set, check the security. If the + # security/compartment on the incoming segment does not exactly + # match the security/compartment in the TCB then send a reset and + # return. + # + # >>> TRUNCATED FOR MAIL (1000 lines) <<<