From owner-p4-projects@FreeBSD.ORG Thu Jun 25 13:32:58 2009 Return-Path: Delivered-To: p4-projects@freebsd.org Received: by hub.freebsd.org (Postfix, from userid 32767) id 790501065673; Thu, 25 Jun 2009 13:32:58 +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 38946106566C for ; Thu, 25 Jun 2009 13:32:58 +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 24D138FC13 for ; Thu, 25 Jun 2009 13:32:58 +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 n5PDWw9g099231 for ; Thu, 25 Jun 2009 13:32:58 GMT (envelope-from zjriggl@FreeBSD.org) Received: (from perforce@localhost) by repoman.freebsd.org (8.14.3/8.14.3/Submit) id n5PDWw23099229 for perforce@freebsd.org; Thu, 25 Jun 2009 13:32:58 GMT (envelope-from zjriggl@FreeBSD.org) Date: Thu, 25 Jun 2009 13:32:58 GMT Message-Id: <200906251332.n5PDWw23099229@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 165159 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: Thu, 25 Jun 2009 13:32:59 -0000 http://perforce.freebsd.org/chv.cgi?CH=165159 Change 165159 by zjriggl@zjriggl_tcpregression on 2009/06/25 13:32:01 Periodic commit Affected files ... .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/__init__.py#5 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/ipAddress.py#3 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpFilter.py#5 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpstatemachine.py#4 edit .. //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/testconfig.py#5 edit Differences ... ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/__init__.py#5 (text+ko) ==== @@ -9,49 +9,49 @@ from pcs.packets.tcpv6 import tcpv6 # Configure logging -logging.config.fileConfig("logging.conf") +logging.config.fileConfig( "logging.conf" ) -(logging.FIELD_CHANGE, +( 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.STATE_CHANGE ) = range( logging.INFO - 7, logging.INFO ) -logging.addLevelName(logging.FIELD_CHANGE, "FIELD") -logging.addLevelName(logging.RESPONSE_GENERATION, "RESPONSE") -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") +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) +def findTcpLayer( packet ): + return findPacketLayer( packet, tcp ) -def findIpLayer(packet): - return findPacketLayer(packet,ipv4) +def findIpLayer( packet ): + return findPacketLayer( packet, ipv4 ) -def findPacketLayer(packet, _class): +def findPacketLayer( packet, _class ): p = packet while p is not None: - if isinstance(p,_class): + if isinstance( p, _class ): return p p = p.data return None - - - -def inet_lton(integer): - return struct.pack(">L",integer) + + + +def inet_lton( integer ): + return struct.pack( ">L", integer ) + +def inet_ltoa( integer ): + return socket.inet_ntoa( inet_lton( integer ) ) -def inet_ltoa(integer): - return socket.inet_ntoa(inet_lton(integer)) - -def inet_ntol(byteString): - return struct.unpack(">L",byteString)[0] +def inet_ntol( byteString ): + return struct.unpack( ">L", byteString )[0] -def inet_atol(ipString): - return inet_ntol(socket.inet_aton(ipString))+def inet_atol( ipString ): + return inet_ntol( socket.inet_aton( ipString ) ) ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/pcsextension/ipAddress.py#3 (text+ko) ==== @@ -52,7 +52,7 @@ return htonl( unpack( "!L", self.nbo )[0] ) def getPCS( self ): - return self.getNetworkInteger() + return self.getInteger() def setPCS( self, x ): - self.setNetworkInteger( x ) + self.setInteger( x ) ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpFilter.py#5 (text+ko) ==== @@ -14,65 +14,68 @@ from pcsextension.networkPort import NetworkPort from pcsextension import findIpLayer, findTcpLayer -class tcpFilter(object): +class tcpFilter( object ): log = None pcapHandle = None doRead = False - - def __init__(self, interfaceName): - self.log = tcplog(self) - self.openInterface(interfaceName) + + def __init__( self, interfaceName ): + self.log = tcplog( self ) + self.openInterface( interfaceName ) - def openInterface(self, interfaceName): + def openInterface( self, interfaceName ): try: - self.pcapHandle = PcapConnector(interfaceName) + self.pcapHandle = PcapConnector( interfaceName ) # self.pcapHandle = IP4Connector(); except: - self.log.error("Could not open interface %s" % interfaceName) - - def read(self): + self.log.error( "Could not open interface %s" % interfaceName ) + + def read( self ): return self.pcapHandle.readpkt() - - def write(self,bytes): - self.pcapHandle.write(bytes,len(bytes)) - - def readFilteredByIP(self, ip): + + def write( self, bytes ): + self.pcapHandle.write( bytes, len( bytes ) ) + + def readFilteredByIP( self, ip ): """ Reads packets until a packet is found going either to or from the specified IP address is discovered. Returns the first matching packet. @param ip IpAddress or dotted-quad string @return A pcs.Packet object """ - + # if isinstance(ipAddress,int): # ipAddress = intToBytes(ipAddress) # If the IP address is a string ("127.0.0.1") or byte array ('\x7f\x00\x00\x01') # we need to convert it into an integer representation of the same. - if isinstance(ip,str): + if isinstance( ip, str ): tmp = IpAddress() - tmp.setAscii(ip) + tmp.setAscii( ip ) ip = tmp srcIP = IpAddress() dstIP = IpAddress() - + while True: packet = self.read() - - ipLayer = findIpLayer(packet) - + + ipLayer = findIpLayer( packet ) + if ipLayer == None: continue - - srcIP.setNetworkInteger(ipLayer.src) - dstIP.setNetworkInteger(ipLayer.dst) - + + srcIP.setPCS( ipLayer.src ) + dstIP.setPCS( ipLayer.dst ) + + print "Saw %s" % str( srcIP ) + print "Saw %s" % str( dstIP ) + if ipLayer.src == ip.getPCS() or ipLayer.dst == ip.getPCS(): - return packet - - def readFilteredByTuple(self, ipAddress, port): + return packet + + def readFilteredByTuple( self, ipAddress, port ): """ Reads packets until a packet is found going either to or from [1] the specified IP address and [2] the specified port. Returns the first matching packet. @@ -81,21 +84,19 @@ @return A pcs.Packet object. @see readFilteredByIP """ - - if isinstance(port,int): - tmp = NetworkPort() - tmp.setInteger(port) - port = tmp - + + if isinstance( port, int ): + port = NetworkPort( port ) + while True: - packet = self.readFilteredByIP(ipAddress) - - tcpLayer = findTcpLayer(packet) + 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(): - return packet+ return packet ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/tcpstatemachine.py#4 (text+ko) ==== @@ -11,10 +11,13 @@ from pcsextension.ipAddress import IpAddress from pcsextension.networkPort import NetworkPort from pcsextension.pseudoipv4 import pseudoipv4, ipv4_cksum +from pcsextension.checksum import tcpChecksum +from random import randint from socket import IPPROTO_TCP from tcpFilter import tcpFilter from tcpConstructor import tcpConstructor -from tcpstates import +from tcpstates import * +from time import time import binascii import binhex import pcs @@ -123,7 +126,7 @@ rcv_wnd = 128 * 1024 # Recv window size rcv_up = 0 # Recv urgent pointer - irs = -1 # Initial receive sequence number + irs = 0 # Initial receive sequence number rcv_nxt = irs # Expected next recv sequence # ack_nxt = 0 # Next ACK number to send. @@ -166,31 +169,27 @@ return {'doc': 'Maximum Tranmission Unit'} _mtu = testconfig.mtu + @prop def generate(): return {'doc': 'What fields of outgoing TCP packets should be auto-generated.'} _generate = {'cksum': True, 'off': True, - 'seq': True, - 'acknum': True, + 'sequence': True, + 'ack_number': True, 'sport': True, 'dport': True, 'window': True, - 'urg': True, - 'ack': True, - 'syn': True, - 'fin': True, - 'rst': True, - 'psh': True, - 'urgp': True } + 'urg_pointer': True } @prop def validate(): return {'doc': 'What fields of incoming TCP packets should be validated.'} _validate = { 'cksum': True, - 'seq': True, - 'acknum': True, + 'sequence': True, + 'ack_number': True, 'sport': True, - 'dport': True } + 'dport': True, + 'transition': True } @prop def packetsToSend(): @@ -336,10 +335,90 @@ # # return self.state == 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 + self.reset() + + # Send the SYN packet. + synPacket = self.newPacket( {'syn':1} ) + self.log.generated( "Sending generated SYN packet to initiate connection: %s" % synPacket ) + self.packetsToSend.append( synPacket ) + self.sendQueuedPackets() + self.state = SYN_SENT + + # Recv the SYN-ACK packet. + start = time() + while self.state == SYN_SENT: + synAck = self.recv() + + if synAck.ack and synAck.syn and synAck.ack_number == synPacket.sequence: + 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( {'ack':1} ) + self.log.generated( "Sending generated ACK packet in response to SYN/ACK: %s" % ackPacket ) + self.packetsToSend.append( appPacket ) + self.sendQueuedPackets() + self.state = ESTABLISHED + return True + else: + self.log.error( "connection already exists" ) + return False + + # Default... + return False + + 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 + # (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. + 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.snd_up = 0 + self.snd_wl1 = 0 + self.snd_wl2 = 0 + self.snd_wnd = 0xffff + + self.rcv_nxt = 0 + self.rcv_up = 0 + self.rcv_wnd = testconfig.recvWindow + + 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. + @param packet pcs.Packet object to send. Must be, or contain, a TCP packet. ''' # Check the arg type validateTypes( {packet:pcs.Packet} ) @@ -349,28 +428,47 @@ if tcpLayer == None: self.log.error( "Could not find TCP layer in packet %s" % packet ) - # Update list of packets sent - self.packetsSent += [tcpLayer] + # Check the state + if self.state == CLOSED: + # Otherwise, return "error: connection does not exist". + self.log.error( "connection does not exist" ) + + if self.state == LISTEN: + # If the foreign socket is specified, then change the connection + # from passive to active, select an ISS. Send a SYN segment, set + # SND.UNA to ISS, SND.NXT to ISS+1. Enter SYN-SENT state. Data + # associated with SEND may be sent with SYN segment or queued for + # transmission after entering ESTABLISHED state. + self.open() + + if self.state == SYN_SENT or self.state == SYN_RECEIVED: + # Queue the data for transmission after entering ESTABLISHED state. + # If no space to queue, respond with "error: insufficient + # resources". + self.packetsToSend.append( tcpLayer ) - # Get the bytes... - bytes = tcpLayer.chain().bytes + if self.state == ESTABLISHED or self.state == CLOSE_WAIT: + # Segmentize the buffer and send it with a piggybacked + # acknowledgment (acknowledgment value = RCV.NXT). If there is + # insufficient space to remember this buffer, simply return "error: + # insufficient resources". + if self.generate['ack_number']: + packet.ack_number = self.rcv_nxt - # Get the data length. - dataLength = ( len( bytes ) - tcpLayer.off ) + # If the urgent flag is set, then SND.UP <- SND.NXT-1 and set the + # urgent pointer in the outgoing segments. + # TODO: Handle URG pointer. + self.packetsToSend.append( tcpLayer ) - # Log packet - self.log.pktsent( packet ) + # Are we closing the connection? + if tcpLayer.fin: + self.state = FIN_WAIT_1 - # Set the correct sequence number. - x = self.snd_nxt - if self.validate['sequence']: - # if self.isValidateSeqEnabled(): - tcpLayer.seq = self.snd_nxt - self.snd_nxt += len( tcpLayer.data.chain() ) + if self.state in [FIN_WAIT_1, FIN_WAIT_2, CLOSING, LAST_ACK, TIME_WAIT]: + self.log.error( 'connection closing' ) - # Add the packet to the outgoing queue. - self.packetsToSend += [tcpLayer] - self._sendPackets() + # Send all queued packets + self.sendQueuedPackets() def sendRawTcp( self, tcpLayer ): if not validateTypes( {tcpLayer:tcp.tcp} ): @@ -382,35 +480,35 @@ pass - def _sendPackets( self ): + def sendQueuedPackets( self ): for tcpLayer in self.packetsToSend: send = False - # If the packet is the next packet that we are supposed to send, - # and it is LARGER than the window size, send it. - if self.snd_una == tcpLayer.seq: - send = True - else: - packetLen = len( tcpLayer.chain().bytes ) - if tcpLayer.seq + packetLen > ( self.snd_una + self.snd_wnd ): - send = True + # If the packet is the next packet that we are supposed to send, send it. + if self.snd_una == tcpLayer.sequence: + # Make sure we are allowed to send it (i.e. smaller than rcv window) - if send: # Set the ACK to acknowledge any packets that we've received - if self.generate['acknum'] and self.ack_nxt > self.ack_lst: + if self.generate['ack_number'] and self.ack_nxt > self.ack_lst: # if self.isAutomaticAckEnabled() and self.ack_nxt > self.ack_lst: - tcpLayer.acknum = self.rcv_nxt + tcpLayer.ack_number = self.rcv_nxt tcpLayer.ack = 1 # Set the correct checksum if self.generate['checksum']: # if self.isChecksumValidationEnabled(): - tcpLayer.cksum = tcpChecksum( tcpLayer ) #self.generateChecksum( tcpLayer ) + tcpLayer.cksum = tcpChecksum( tcpLayer, src = self.localIP, dst = self.remoteIP ) #self.generateChecksum( tcpLayer ) self.sendRawTcp( tcpLayer ) def logGenerated( self, packet, fieldname ): self.log.generated( "Automatically set field %s to %s" % ( fieldname, hex( getattr( packet, fieldname ) ) ) ) + def logField( self, packet, fieldname ): + 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]] + def newPacket( self, fields = None ): ''' Creates a new packet with the specified fields. Any fields that @@ -421,57 +519,55 @@ t = tcp.tcp() # Generate all the fields that are set up... - t.syn = t.fin = t.rst = t.psh = t.ack = t.urg = 0 + t.syn = t.fin = t.rst = t.push = t.ack = t.urgent = 0 - # Get the defaults... - defaults = {'window': 0xffff, - 'dport': self.remotePort.getInteger(), - 'sport': self.localPort.getInteger(), - 'seq': self.snd_nxt, - 'acknum': self.rcv_nxt, - 'off': 5} + # set the defaults. don't generate the checksum yet. + for field in [k for k in self.fieldsToGenerate() if k != 'cksum']: + setattr( t, field, self.generateField( t, field ) ) + self.logGenerated( t, field ) + # setattr( t, field, value ) - # set the defaults - for ( field, value ) in defaults.items(): - if field in self.generate: + # set the user-provided values + if fields is not None: + for ( field, value ) in fields.items(): setattr( t, field, value ) - self.logGenerated( t, field ) + self.logField( t, field ) - # set the user-provided values - for ( field, value ) in fields.items(): - setattr( t, field, value ) - - # Do the checksum LAST + # Do the checksum after user-provided values if 'cksum' in self.generate: - t.cksum = tcpChecksum( t ) + t.cksum = tcpChecksum( t, src = self.localIP, dst = self.remoteIP ) self.logGenerated( t, 'cksum' ) - def generateChecksum( self, packet ): - """Calculate and store the checksum for the TCP segment - when encapsulated as an IPv4 payload with the given header.""" - raise NotImplementedError + return t - bytes = packet.chain().bytes - pip = pseudoipv4() - pip.src = self.localIP - pip.dst = self.remoteIP - pip.protocol = IPPROTO_TCP - pip.length = len( bytes ) - tmpbytes = pip.getbytes() + bytes + 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': + # TODO + return 0 - cksum = ipv4_cksum( tmpbytes ) - # Log the checksum - hexCksum = binascii.hexlify( struct.pack( "!H", cksum ) ) - self.log.debug( "Generated cksum: %s" % hexCksum ) - - return cksum - - def recv( self, packet ): + def recv( self, packet = None ): ''' Inform the TCP State Machine about packets that have been received. This is necessary to keep the state up-to-date. ''' + if packet == None: + packet = self._connector.readFilteredByTuple( self.remoteIP, self.remotePort ) + self.packetsRecvd += [packet] self.log.pktrecv( packet ) @@ -563,3 +659,4 @@ # self.packetsToSend = [] self._connector = tcpFilter( testconfig.interface ) + self.reset() ==== //depot/projects/soc2009/zjriggl_tcpregression/src/tcpregression/testconfig.py#5 (text+ko) ==== @@ -1,10 +1,11 @@ -localPort = 46001 -localIP = "127.0.0.1" + +localPort = 54321 +localIP = "172.16.0.10" localIPv6 = "::1" localMAC = "00:1b:63:06:82:b2" -remotePort = 46002 -remoteIP = "127.0.0.1" +remotePort = 12345 +remoteIP = "172.16.0.10" remoteIPv6 = "::1" remoteMAC = "00:21:29:a5:a9:3f" @@ -12,3 +13,14 @@ interface = "en1" +# Timeout for connect operations, in seconds +timeout = 5 + +# Receive window size +recvWindow = 0xffff + +# Initial sequence number randomization. If randomISS is true, +# the ISS is initialized to a random value. Otherwise, staticISS +# is used as the ISS for all communication. +randomISS = True +staticISS = 0