Spencer Poisseroux

<–– Back

[Python] TCP transfer clone over UDP

Purpose

To provide a TCP-like transport protocol over UDP with reliable transfer, relevant headers, and connection establishment
This results in a faster transfer with the same reliability as TCP

Created with Python 2.X using no external libraries
In collaboration with Jack Bonnelycke

[github]


Receiver Design


Setup (Linux environment required)

  • To begin using our TCP-like Transport Protocol over UDP, please clone this repository into wherever you would like it saved.
  • Open a terminal in the location you would like to receive the file, with the repository cloned.
  • Copy the file you would like to send into the cloned repository’s project3/src/.
  • In the receiving terminal, run the following python3 ./server.py 5000 > RECEIVED_FILE
  • In the sending terminal, run the following cat FILENAME | python3 ./client.py HOSTNAME-OR-IP 5000
  • Compare the two with: diff FILENAME RECEIVED_FILE

[github]


Code

Client.py

#!/usr/bin/env python
from socket import *
import sys
from sys import getsizeof
import time
import struct

#print("first line")

host = sys.argv[1]
port = int(sys.argv[2])
total_kb = 0 #keep track for stats
buf = 512
addr = (host,port)
s = socket(AF_INET,SOCK_DGRAM)
end_time=0
seqNum = 42
conID = 0
payloadSize = 0
finished = False
#s.bind((host,port))

#start timer
global start_time
start_time = time.time()

def resend(packet):
    #resend lost packet
    global s
    global addr

    flag = ''
    sentSeqNum, sentAck, sentConID, sentFlag, sentPayload = struct.unpack('iihh512s', packet)
    if (sentFlag == 4):
        flag = "ACK"
    elif (sentFlag == 2):
        flag = "SYN"
    elif (sentFlag == 6):
        flag = "ACK SYN"
    elif (sentFlag == 1):
        flag = "FIN"
    elif (sentFlag == 5):
        flag = "ACK FIN"
    print("DROP " + str(sentSeqNum) + " " + str(sentAck) + " " + str(sentConID) + " " + flag)
    print("SEND " + str(sentSeqNum) + " " + str(sentAck) + " " + str(sentConID) + " " + flag + " DUP")

    s.sendto(packet,addr)
    receive(packet) #go to receive lost packet

def resendSYNACK(packet):
    global s
    global addr

    flag = ''
    sentSeqNum, sentAck, sentConID, sentFlag, sentPayload = struct.unpack('iihh512s', packet)
    if (sentFlag == 4):
        flag = "ACK"
    elif (sentFlag == 2):
        flag = "SYN"
    elif (sentFlag == 6):
        flag = "ACK SYN"
    elif (sentFlag == 1):
        flag = "FIN"
    elif (sentFlag == 5):
        flag = "ACK FIN"
    print("DROP " + str(sentSeqNum) + " " + str(sentAck) + " " + str(sentConID) + " " + flag)
    print("SEND " + str(sentSeqNum) + " " + str(sentAck) + " " + str(sentConID) + " " + flag + " DUP")

    s.sendto(packet,addr)
    receiveSYNACK(packet)

def receiveSYNACK(packet):
    global s
    global buf
    global host
    global port
    global addr
    global seqNum
    global conID

    try:
        s.settimeout(0.5)
        data, addr = s.recvfrom(buf)
        recvdSeqNum, recvdAck, conID, recvdFlag = struct.unpack('iihh', data)

        flag = ''
        if (recvdFlag == 4):
            flag = "ACK"
        elif (recvdFlag == 2):
            flag = "SYN"
        elif (recvdFlag == 6):
            flag = "ACK SYN"
        elif (recvdFlag == 1):
            flag = "FIN"
        elif (recvdFlag == 5):
            flag = "ACK FIN"
        print("RECV " + str(recvdSeqNum) + " " + str(recvdAck) + " " + str(conID) + " " + flag)

        if (recvdFlag != 6 or recvdAck != seqNum+1):
            resendSYNACK(packet)
    except timeout:
        resendSYNACK(packet)

def receive(packet, timer=0.5):
    global s
    global buf
    global host
    global port
    global addr
    global seqNum
    global conID
    global payloadSize
    global finished

    #print("in receive()")
    try:
        if (timer != 0.5):
            s.settimeout(timer)
        else:
            s.settimeout(timer)
        expectedSeqNum = seqNum
        data,addr = s.recvfrom(buf)
        # TODO split header + payload decoded
        recvdSeqNum, recvdAck, recvdConID, recvdFlag = struct.unpack('iihh', data)
        sentSeqNum, sentAck, sentConID, sentFlag, sentPayload = struct.unpack('iihh512s', packet)

        flag = ''
        if (recvdFlag == 4):
            flag = "ACK"
        elif (recvdFlag == 2):
            flag = "SYN"
        elif (recvdFlag == 6):
            flag = "ACK SYN"
        elif (recvdFlag == 1):
            flag = "FIN"
        elif (recvdFlag == 5):
            flag = "ACK FIN"
        print("RECV " + str(recvdSeqNum) + " " + str(recvdAck) + " " + str(recvdConID) + " " + flag)


        if (recvdFlag == 1):
            seqNum = expectedSeqNum
            return
        if (sentFlag == 5):
            seqNum = expectedSeqNum
            return
        if (sentFlag == 1):
            return
            #if ((recvdAck != expectedSeqNum) or recvdConID != conID) or (recvdFlag != 5):
            #    resend(packet)
            #seqNum = expectedSeqNum
            #return
        if (recvdFlag == 0 or recvdFlag == 4):
            expectedSeqNum = seqNum
        if (expectedSeqNum >= 204801): #after 204800 is zero
            expectedSeqNum = abs(204800 - expectedSeqNum)
        #check if received correct data (ACK)
        #print("receive if state (recvdSeq, seqNum, recvdAck, expected SeqNum)" + str(recvdSeqNum) + " " + str(seqNum) + " " +  str(recvdAck) + " " + str(expectedSeqNum))
        if ((recvdAck != expectedSeqNum)
            or (recvdConID != conID) or (recvdFlag != 4)):
            #print("if final\n)")
            resend(packet)
    except timeout: #ACK got lost
        if (timer == 0.5):
            resend(packet)
    seqNum = expectedSeqNum

def send(packet):
    global s
    global total_kb
    global host
    global port
    global buf
    global addr
    global seqNum

    flag = ''
    sentSeqNum, sentAck, sentConID, sentFlag, sentPayload = struct.unpack('iihh512s',packet)
    if (sentFlag == 4):
        flag = "ACK"
    elif (sentFlag == 2):
        flag = "SYN"
    elif (sentFlag == 6):
        flag = "ACK SYN"
    elif (sentFlag == 1):
        flag = "FIN"
    elif (sentFlag == 5):
        flag = "ACK FIN"
    print("SEND " + str(sentSeqNum) + " " + str(sentAck) + " " + str(sentConID) + " " + flag)

    s.sendto(packet,addr)
    total_kb += buf #track kb sent

def main():
    #get data, call send, recv after getting data in loop
    global buf
    global end_time
    global seqNum
    global payloadSize
    global finished

    #print("in getdata() init")

    completeHandshakePacket = handshake()
    receive(completeHandshakePacket)
    data = sys.stdin.read(buf)
    #print("got data")
    packet = buildStandardHeader(data)
    seqNum = seqNum + payloadSize
    #print(seqNum)

    try:
        while (data):
            #print("in getdata() while loop")
            s.settimeout(10)
            #seqNum = seqNum + 1
            send(packet) #send data
            receive(packet) #receive ack for sent data
            #seqNum = seqNum + payloadSize
            if (seqNum >= 204800): #make sure seqnum never goes above 9
                seqNum = abs(204800 - seqNum) #TODO IS THIS RIGHT?
            data = sys.stdin.read(buf)
            packet = buildStandardHeader(data)
            seqNum = seqNum + payloadSize
        packet = buildFinHeader()
        send(packet)
        receive(packet)
        finTimerStart = time.time()
        finTimerDiff = time.time()
        while ((finTimerDiff - finTimerStart) <= 2):
            finTimerDiff = time.time()
            receive(packet, 2)
            packet = buildFinAckHeader()
            send(packet)
        # try w/ timeout 2
        # receive ack from server
        # receive fin's from server and ack em

    except timeout:
        s.close()
        sys.exit(1)
    s.close()
    sys.exit(0)

def printStats():
    #end timer

    elapsed_time = end_time - start_time

    #statistics
    if (elapsed_time == 0):
        elapsed_time = 0.001
    kbRate = (total_kb / 125) / elapsed_time
    kbRate = float("%0.2f" % (kbRate))
    elapsed_time = float("%0.3f" % (elapsed_time))

def buildStandardHeader(payload):
    global conID
    global seqNum
    global payloadSize

    payloadSize = len(payload)
    payload = payload.encode()
    header = struct.pack('iihh512s', seqNum, 0, conID, 0, payload)

    return header

def buildFirstHandshakeHeader():
    global seqNum
    emptyPayload = bytearray(512)
    header = struct.pack('iihh512s', seqNum, 0, 0, 2, emptyPayload)
    #print("FIRST HANDSHAKE HEADER")
    return header

def buildThirdHandshakeHeader(payload):
    global conID
    global seqNum
    global payloadSize

    #payload = bytes(payload, 'utf-8')

    payload = payload.encode()
    payloadSize = len(payload)
    header = struct.pack('iihh512s', seqNum, 0, conID, 4, payload)
    #print("THIRD HANDSHAKE HEADER")
    return header

def buildFinHeader():
    global conID
    global seqNum

    emptyPayload = bytearray(512)
    header = struct.pack('iihh512s', seqNum, 0, conID, 1, emptyPayload)
    #print("FIN HEADER")
    return header

def buildFinAckHeader():
    global conID
    global seqNum

    emptyPayload = bytearray(512)
    header = struct.pack('iihh512s', seqNum, seqNum + 1, conID, 5, emptyPayload)
    #TODO WHAT IS SEQNUM
    #print("FIN ACK HEADER")
    return header

def handshake():
    global seqNum
    initpacket = buildFirstHandshakeHeader()
    send(initpacket)
    #print("init" + str(seqNum))
    receiveSYNACK(initpacket)
    data = sys.stdin.read(buf)
    seqNum = seqNum + 1
    secondpacket = buildThirdHandshakeHeader(data)
    send(secondpacket)
    #print("init2" + str(seqNum) + " data " + str(data))
    seqNum = seqNum + payloadSize
    return secondpacket

main()

Server.py

from socket import *
import sys, os
import select
import struct
import random

host = "127.0.0.1"
port = int(sys.argv[1])
s = socket(AF_INET,SOCK_DGRAM)
s.bind((host,port))
addr = (host,port)
buf=524
seqNum = 42
conID = 0
payloadSize = 0
prevFlag = 0
recvdFlag = 0
prevSize = 1
prevSeq = 0
firstHandshakeResend = False

def recieve():
    global s
    global buf
    global addr
    global seqNum
    global conID
    global prevFlag
    global recvdFlag
    global prevSize
    global prevSeq
    global firstHandshakeResend

    data,addr = s.recvfrom(buf)
    prevFlag = recvdFlag
    recvdPayload = struct.unpack_from('512s', data, 12)[0]
    recvdPayload = recvdPayload.decode().rstrip('\x00')
    recvdSeqNum, recvdAck, recvdConID, recvdFlag, recvdPayloadNull = struct.unpack('iihh512s', data)

    flag = ''
    if (recvdFlag == 4):
        flag = "ACK"
    elif (recvdFlag == 2):
        flag = "SYN"
    elif (recvdFlag == 6):
        flag = "ACK SYN"
    elif (recvdFlag == 1):
        flag = "FIN"
    elif (recvdFlag == 5):
        flag = "ACK FIN"
    recvPrint = "RECV " + str(recvdSeqNum) + " " + str(recvdAck) + " " + str(recvdConID) + " " + flag

    #recvdPayload = recvdPayload.decode()
    if (prevFlag == 1 and recvdFlag == 5):
        s.close()
        sys.exit(0)
    if (recvdFlag == 2): # first handshake
        if (firstHandshakeResend == True):
            sys.stderr.write("DROP " + str(recvdSeqNum) + " " + str(recvdAck) + " " + str(recvdConID) + flag + "\n")
            seqNum = seqNum - 1
            recvPrint = recvPrint + " DUP"
        conID = random.randint(1, 20)
        prevSize = 1
        firstHandshakeResend = True
    # TODO elif recvdFlag ==4 and prevFlag == 1
    sys.stderr.write(recvPrint + "\n")
    if (recvdFlag == 1):
        finning()
    elif ((recvdFlag == 4 or recvdFlag == 0) and (conID == recvdConID)):
        if (recvdSeqNum == seqNum):
            sys.stdout.write(recvdPayload)
        elif (recvdSeqNum == prevSeq):
            seqNum = prevSeq
            sys.stderr.write("DROP " + str(recvdSeqNum) + " " + str(recvdAck) + " " + str(recvdConID) + flag + "\n")
            # retransmission
        prevSize = len(recvdPayload)
    # elif recvdFlag == 1 TODO
    prevSeq = seqNum
    # TODO prevFlag = recvdFlag

def finning():
    global addr
    global prevFlag
    global recvdFlag

    packet = buildFinAckHeader()

    flag = ''
    sentSeqNum, sentAck, sentConID, sentFlag = struct.unpack('iihh', packet)
    if (sentFlag == 4):
        flag = "ACK"
    elif (sentFlag == 2):
        flag = "SYN"
    elif (sentFlag == 6):
        flag = "ACK SYN"
    elif (sentFlag == 1):
        flag = "FIN"
    elif (sentFlag == 5):
        flag = "ACK FIN"
    sys.stderr.write("SEND " + str(sentSeqNum) + " " + str(sentAck) + " " + str(sentConID) + " " + flag + "\n")

    s.sendto(packet, addr)
    #recv all the stuff
    packet = buildFinHeader()

    flag = ''
    sentSeqNum, sentAck, sentConID, sentFlag = struct.unpack('iihh', packet)
    if (sentFlag == 4):
        flag = "ACK"
    elif (sentFlag == 2):
        flag = "SYN"
    elif (sentFlag == 6):
        flag = "ACK SYN"
    elif (sentFlag == 1):
        flag = "FIN"
    elif (sentFlag == 5):
        flag = "ACK FIN"
    sys.stderr.write("SEND " + str(sentSeqNum) + " " + str(sentAck) + " " + str(sentConID) + " " + flag + "\n")

    s.sendto(packet,addr)
    recieve()

def buildFinHeader():
    global conID
    global seqNum

    header = struct.pack('iihh', seqNum, 0, conID, 1)

    return header

def buildFinAckHeader():
    global conID
    global seqNum

    ackNum = seqNum + prevSize
    header = struct.pack('iihh', seqNum, ackNum, conID, 5)
    return header

def send():
    global s
    global total_kb
    global host
    global port
    global buf
    global addr
    global recvdFlag
    global prevSize
    global conID
    global seqNum

    ackNum = seqNum + prevSize
    if (ackNum >= 204800): ackNum = abs(204800 - ackNum)

    #dummyPayload = bytearray(512)
    if (recvdFlag == 2):
        packet = struct.pack('iihh', seqNum, ackNum, conID, 6)
    elif (recvdFlag == 1): #final
        pass #TODO
        # define and send ack
        # packet = defined fin
    #elif recvdFlag == 4 and prev == 1 then return
    elif (recvdFlag == 0 or recvdFlag == 4):
        packet = struct.pack('iihh', seqNum, ackNum, conID, 4)

    flag = ''
    sentSeqNum, sentAck, sentConID, sentFlag = struct.unpack('iihh', packet)
    if (sentFlag == 4):
        flag = "ACK"
    elif (sentFlag == 2):
        flag = "SYN"
    elif (sentFlag == 6):
        flag = "ACK SYN"
    elif (sentFlag == 1):
        flag = "FIN"
    elif (sentFlag == 5):
        flag = "ACK FIN"
    sys.stderr.write("SEND " + str(sentSeqNum) + " " + str(sentAck) + " " + str(sentConID) + " " + flag + "\n")

    s.sendto(packet,addr)

def main():
    global seqNum #init seqnum
    global prevSize

    try:
        while(True):
            s.settimeout(2)
            recieve()
            send()
            seqNum = seqNum + prevSize #increase next expected seqNum
            if (seqNum >= 204800): seqNum = abs(204800 - seqNum)

    except timeout:
        #end
        s.close()
        sys.stderr.write("File received, exiting.\n")
        pass

main()