0

I wrote a simple TFTP server that only handles read requests (RRQ) and it is working fine. The server is supposed to re-transmit the current data packet again, if no ACK is received within 5 seconds. The server should also re-transmit the packet three times before giving up. I tried to suspend the client in the middle of a transmission session to see if the server would re-transmit the data packet again and it didn't. The problem seems to be that the server doesn't continue in the while loop. I tried to test if it escapes the loop and it didn't. I really can't figure out why doesn't it iterates through the loop again.

Here's the code I've written so far...

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#define TIMEOUT 5000
#define RETRIES 3

void sendFile (char *Filename, char *mode, struct sockaddr_in client, int tid)
{
    struct timeval tv;
    tv.tv_sec = 5;
    char path[70] = "tmp/";
    char filebuf [1024];
    int count = 0, i;  // Number of data portions sent 
    unsigned char packetbuf[1024];
    char recvbuf[1024];
    socklen_t recv_size;

    int sock = socket(PF_INET, SOCK_DGRAM, 0);
    socklen_t optionslength = sizeof(tv);
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, optionslength);

    FILE *fp;
    char fullpath[200];
    strcpy(fullpath, path);
    strncat(fullpath, Filename, sizeof(fullpath) -1);
    fp = fopen(fullpath, "r");
    if (fp == NULL)
        perror("");

    memset(filebuf, 0, sizeof(filebuf));
    while (1)
    {   
        int acked = 0;
        int ssize = fread(filebuf, 1 , 512, fp);
        count++;        
        sprintf((char *) packetbuf, "%c%c%c%c", 0x00, 0x03, 0x00, 0x00);
        memcpy((char *) packetbuf + 4, filebuf, ssize);
        packetbuf[2] = (count & 0xFF00) >> 8;
        packetbuf[3] = (count & 0x00FF);

        int len = 4 + ssize;        

        memset(recvbuf, 0, 1024);
        printf("\nSending Packet #%d", count);
        sendto(sock, packetbuf, len, 0, (struct sockaddr *) &client, sizeof(client));

        for (i=0; i<3; i++)
        {
            int result = recvfrom(sock, recvbuf, 1024, 0, (struct sockaddr *) &client, &recv_size);

            if ((result == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
            {
                printf("\nRetransmitting Packet #%d",count);
                sendto(sock, packetbuf, len, 0, (struct sockaddr *) &client, sizeof(client));
            }

            else if (result == -1)
            {

            }

            else 
            {
                if (tid == ntohs(client.sin_port))
                {
                    printf("\nReceived Ack #%d",count);
                    acked++;
                    break;
                }

                else
                    continue;
            }
        }

        if (acked!=1)
        {
            puts("\nGave Up Transmission");
            break;
        }

        if (ssize != 512)
        {
            break;

        }
    }
}


int main()
{
    int udpSocket, nBytes, tid, pid, status;
    char buffer[1024], filename[200], mode[20], *bufindex, opcode;
    struct sockaddr_in serverAddr, client;
    struct sockaddr_storage serverStorage;
    socklen_t addr_size;

    udpSocket = socket(AF_INET, SOCK_DGRAM, 0);

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(69);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero); 

    bind(udpSocket, (struct sockaddr *) &serverAddr, sizeof(serverAddr));
    pid = fork();

    while(1)
    {
        int client_len = sizeof(client);
        memset (buffer, 0, 1024);
        nBytes = 0;
        while (errno == EAGAIN || nBytes == 0)
        {
            waitpid(-1, &status, WNOHANG);
            nBytes = recvfrom(udpSocket,buffer,1024,0,(struct sockaddr *)&client, &client_len);

        }

        bufindex = buffer;
        bufindex++;

        // Record the client port...
        tid = ntohs(client.sin_port); 

        // Extracting the opcode from the packet...     
        opcode = *bufindex++;

        // Extracting the filename from the packet...
        strncpy(filename, bufindex, sizeof(filename)-1);

        bufindex += strlen(filename) + 1;

        // Extracting the mode from the packet...       
        strncpy(mode, bufindex, sizeof(mode)-1);

        // If we received an RRQ...
        if (opcode == 1)
        {
            puts("Received RRQ Packet");
            pid = fork();
            if (pid == 0)
            {
                sendFile(filename, mode, client, tid);
                exit(1);
            }
        }
    }   

    return 0;
}

Note: You can use the standard TFTP client that comes with linux to test the server.

Thanks in advance :)

user3490561
  • 446
  • 3
  • 8
  • 20

2 Answers2

2

Likely caused by this:

struct timeval tv;
tv.tv_sec = 5;

tv is allocated on the stack. Stack memory is uninitialised and thus has random values in it. So you need to explicitly set tv.tv_usec to 0.

kaylum
  • 13,833
  • 2
  • 22
  • 31
  • Sir, do you have any idea why prints `Retransmitting Packet #` 2 times in the second iteration and then stops? I mean it only waits 10 seconds not 15... – user3490561 Apr 15 '15 at 23:06
  • Sir, please check my comment – user3490561 Apr 15 '15 at 23:14
  • Can't see anything obvious. Please put a printf inside the "else if (result==-1)" case to check whether a non-timeout error is occuring. – kaylum Apr 15 '15 at 23:28
  • I mean it should print `Retransmitting Packet #` 3 times in 15 seconds. It seems like the first 2 `recvfrom` take their 5 seconds, while the third doesn't. – user3490561 Apr 15 '15 at 23:30
  • Yes I understand. That's why I ask you to put a printf in the "else if" case. Because if a non-timeout error occurs then it will go into that case and not print "Retransmitting Packet". – kaylum Apr 15 '15 at 23:31
  • Sir, it is printed three times, but the second and third are almost printed together. I did put something in the else if case but it didnot even got there. – user3490561 Apr 15 '15 at 23:33
  • it seems like the RCVTIMEO is reset to zero, but how – user3490561 Apr 15 '15 at 23:35
  • Ok, I thought you said it was only printed twice. Did you actually time it? To see whether it is really 10 seconds or 15 seconds? Because printfs can be buffered and do not necessarily show as soon as they are called. – kaylum Apr 15 '15 at 23:35
  • Yes, sir. almost 10 seconds – user3490561 Apr 15 '15 at 23:36
  • anything new? @Alan Au – user3490561 Apr 16 '15 at 00:13
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/75339/discussion-between-alan-au-and-user3490561). – kaylum Apr 16 '15 at 00:29
  • 1
    @user3490561, The reason of double printing at the end is you begin the print message with `\n` instead of at the end of printed message, and as such, no flushing of buffer at the end of printing is done (it's accumulated to the next printing command). STDIO, by default, works flushing buffers only on `\n`, when output is to a tty, so better put the `\n` at the end of message (or use **fflush(3)** behind **printf(3)** to force the printing) There is an automatic `fflush()` call at the end of the server (on exit), so the reason of the two prints in sequence (the buffered one and the final). – Luis Colorado Apr 16 '15 at 06:10
  • 1
    If you want to synchronize your timeout messages to the time they occur, just finish your printing in a `\n` instead of putting it in the beginning. Other way, you are synchronizing only the end of line (you get an initial `\n` with the first timeout, without message) – Luis Colorado Apr 16 '15 at 06:16
  • 1
    @LuisColorado I already asked about this a few comments up. The OP said that all the print msgs came within 10 seconds (expected 15). That is, they came early, not late. So this means it can't have waited for 15 seconds. So it would seem buffered prints are not the issue. – kaylum Apr 16 '15 at 06:19
  • 1
    @AlanAu, I tried to answer the question of having two simultaneous timeout messages at the end of the server program. And this is the right answer to that question. The messages where buffered in stdio stomach. (The server marks the final timeout and dies, causing the buffered message and this final one to be emitted.) I have not checked in depth this code, so I cannot see the issue you comment. – Luis Colorado Apr 16 '15 at 06:23
  • @LuisColorado Thanks sir, it solved part of the problem (The two prints). The other part was that it doesn't wait 5 seconds before the printing "Gave up transmission", and that was caused by not executing the recvfrom if that was the third iteration; since it is in the beginning of the loop. This was solved by getting it out before the loop and repeating it in the first if condition after the sendto call. – user3490561 Apr 16 '15 at 10:58
  • @AlanAu Check the solution and thanks sir for your time. – user3490561 Apr 16 '15 at 10:58
1

the OP;''s method of testing is to kill the client. However, that kills the socket. Not a good method of testing.

the linux man page for getaddrinfo contains an example for the server code and for the client code for an echo service. The basic logic can be applied to tftp service.

Do remember that tftp has several states. Those states should be reflected in the code logic.

user3629249
  • 16,402
  • 1
  • 16
  • 17
  • 1
    TFTP uses UDP, not TCP, so there's no connection to close and kill the server. Your answer is invalid for this case. Killing the client does nothing on the server side. Receiving ICMP unreachable port might not be enough to kill the server. – Luis Colorado Apr 16 '15 at 06:18