0

I apologize if this question seems trivial, but I'm a beginner at both C and programming with sockets. Also, english is not my native language.

I recently failed an uni assignment that asked me to implement a TCP server-client in C that accepts both ipv4 and ipv6, in which the client would send messages to add, remove, list and query pairs of ints from a linked list stored in the server. So, I made the server and the client with the codes below (not included are the code for the linked lists and for the function strsplit):

server (servidor.c)

void func(int sockfd)
{
    struct Node* posts = NULL;
    char client_message[MAX];
    char server_message[MAX];
    char* str;
    char **p;
    int X, Y, i, j, k;
    int n = 0;
    char sX[5];
    char sY[5];

    for (;;)
    {
        recv(sockfd, client_message, sizeof(client_message), 0);

        printf("> %s", client_message);
        strcpy(str, client_message);
        p = strsplit(str, " ");

        memset(server_message, 0, MAX);

        if(strncmp("kill", p[0], 4) == 0)
        {
            break;
        }
        else if(strncmp("add", p[0], 3) == 0)
        {
            if(n == 50)
            {
                strncat(server_message, "limit exceeded", 14);
            }
            else
            {
                strncat(server_message, p[1], 4);
                strncat(server_message, " ", 1);
                X = atoi(p[1]);

                strtok(p[2], "\n");
                strncat(server_message, p[2], 4);
                strncat(server_message, " ", 1);
                Y = atoi(p[2]);

                i = push(&posts, X, Y);

                if(i == 0)
                {
                    strncat(server_message, "added", 5);
                    n++;
                }
                else
                {
                    strncat(server_message, "already exists", 14);
                }

                free(p);
            }
        }
        else if(strncmp("rm", p[0], 2) == 0)
        {
            strncat(server_message, p[1], 4);
            strncat(server_message, " ", 1);
            X = atoi(p[1]);

            strtok(p[2], "\n");
            strncat(server_message, p[2], 4);
            strncat(server_message, " ", 1);
            Y = atoi(p[2]);

            i = deleteNode(&posts, X, Y);

            if(i == 0)
            {
                strncat(server_message, "removed", 7);
                n--;
            }
            else
            {
                strncat(server_message, "does not exist", 14);
            }

            free(p);
        }
        else if(strncmp("list", p[0], 4) == 0)
        {
            if(posts == NULL)
            {
                strncat(server_message, "none", 4);
            }
            else
            {
                struct Node *temp = posts;

                while(temp != NULL)
                {
                    bzero(sX, 4);
                    bzero(sY, 4);

                    snprintf(sX, sizeof(sX), "%d",temp->X);
                    snprintf(sY, sizeof(sY), "%d",temp->Y);

                    strncat(server_message, sX, 4);
                    strncat(server_message, " ", 1);
                    strncat(server_message, sY, 4);
                    strncat(server_message, " ", 1);

                    temp = temp->next;
                }

                free(temp);
            }
        }
        else if(strncmp("query", p[0], 5) == 0)
        {
            if(posts == NULL)
            {
                strncat(server_message, "none", 4);
            }
            else
            {
                struct Node *temp1 = posts;
                k = 19998;

                X = atoi(p[1]);
                strtok(p[2], "\n");
                Y = atoi(p[2]);

                while(temp1 != NULL)
                {
                    i = abs(temp1->X - X);
                    j = abs(temp1->Y - Y);
                    i = i + j;

                    if(i <= k)
                    {
                        snprintf(sX, sizeof(sX), "%d",temp1->X);
                        snprintf(sY, sizeof(sY), "%d",temp1->Y);
                        k = i;
                    }

                    temp1 = temp1->next;
                }

                strncat(server_message, sX, 4);
                strncat(server_message, " ", 1);
                strncat(server_message, sY, 4);
                strncat(server_message, " ", 1);

                free(temp1);
            }
        }

        strncat(server_message, "\n", 1);
        printf("%s", server_message);

        send(sockfd, server_message, sizeof(server_message), 0);
    }
 }

int main(int argc, char *argv[])
{
    if(strncmp("v4", argv[1], 2) == 0)
    {
        int sockfd, connfd, len;
        struct sockaddr_in servaddr, cli;

        sockfd = socket(AF_INET, SOCK_STREAM, 0);

        if (sockfd == -1)
        {
            exit(0);
        }

        bzero(&servaddr, sizeof(servaddr));

        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(atoi(argv[2]));

        if ((bind(sockfd, (SA*)&servaddr, sizeof(servaddr))) != 0)
        {
            exit(0);
        }

        if ((listen(sockfd, 5)) != 0)
        {
            exit(0);
        }

        len = sizeof(cli);

        connfd = accept(sockfd, (SA*)&cli, &len);
        if (connfd < 0)
        {
            exit(0);
        }

        func(connfd);

        close(sockfd);
    }
    else if(strncmp("v6", argv[1], 2) == 0)
    {
        int sockfd, connfd, len;
        struct sockaddr_in6 servaddr, cli;

        sockfd = socket(AF_INET6, SOCK_STREAM, 0);

        if(sockfd == -1)
        {
            exit(0);
        }

        bzero(&servaddr, sizeof(servaddr));

        servaddr.sin6_family = AF_INET6;
        servaddr.sin6_addr = in6addr_any;
        servaddr.sin6_port = htons(atoi(argv[2]));

        if ((bind(sockfd, (SA*)&servaddr, sizeof(servaddr))) != 0)
        {
            exit(0);
        }

        if ((listen(sockfd, 5)) != 0)
        {
            exit(0);
        }

        len = sizeof(cli);

        connfd = accept(sockfd, (SA*)&cli, &len);
        if (connfd < 0)
        {
            exit(0);
        }

        func(connfd);

        close(sockfd);
    }

    return 0;
}

client (cliente.c)

void func(int sockfd)
{
    char buff[MAX];
    int n;
    for (;;)
    {
        bzero(buff, sizeof(buff));
        n = 0;
        while ((buff[n++] = getchar()) != '\n')
            ;
        send(sockfd, buff, sizeof(buff), 0);
        if ((strncmp(buff, "kill", 4)) == 0)
        {
            break;
        }
        bzero(buff, sizeof(buff));
        recv(sockfd, buff, sizeof(buff), 0);
        printf("> %s", buff);
    }
}

int main(int argc, char *argv[])
{
    if(strchr(argv[1], ':') != NULL)
    {
        int sockfd, connfd;
        struct sockaddr_in6 servaddr, cli;
        struct in6_addr result;

        sockfd = socket(AF_INET6, SOCK_STREAM, 0);

        if (sockfd == -1)
        {
            exit(0);
        }

        bzero(&servaddr, sizeof(servaddr));

        servaddr.sin6_family = AF_INET6;
        inet_pton(AF_INET6, argv[1], &result);
        servaddr.sin6_addr = result;
        servaddr.sin6_port = htons(atoi(argv[2]));

        if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0)
        {
            exit(0);
        }

        func(sockfd);

        close(sockfd);
    }
    else
    {
        int sockfd, connfd;
        struct sockaddr_in servaddr, cli;

        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd == -1)
        {
            exit(0);
        }

        bzero(&servaddr, sizeof(servaddr));

        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = inet_addr(argv[1]);
        servaddr.sin_port = htons(atoi(argv[2]));

        if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0)
        {
            exit(0);
        }

        func(sockfd);

        close(sockfd);
    }

    return 0;
}

Testing on my own, by opening the server and the client in separate terminals and sending messages one by one, the server seemed to be working as intended. However, my professor says otherwise. After failing the assignment, he provided me with the python code of the software used to grade my server, which is as follows:

run_tests.py

def run_basic_tests(exec_path, version, port, msg_type):
    address = '127.0.0.1' if version == 'v4' else '::1'
    list_dir = os.listdir('tests/in')
    list_dir = sorted(list_dir)
    for filename in list_dir:
        filename = filename.split('.')[0]
        _, result_file_path = tempfile.mkstemp()

        server = sp.Popen(
            [f'exec {exec_path} {version} {port}'], shell=True, stdout=sp.DEVNULL, stderr=sp.DEVNULL)
        ret = os.system(
            f'python3 client.py {address} {port} {msg_type} < tests/in/{filename}.in > {result_file_path}')
        ret = os.WEXITSTATUS(ret)

        if ret == 0:
            if filecmp.cmp(f'tests/out/{filename}.out', result_file_path, shallow=False):
                print(f'{filename}\t[OK]')
            else:
                print(f'{filename}\t[FAILED] (diff output)')
        else:
            print(f'{filename}\t[FAILED] (client exited with non-zero code {ret})')
        os.remove(result_file_path)
        server.kill()


if __name__ == '__main__':
    if len(sys.argv) != 3:
        print(f'usage: python3 {sys.argv[0]} <server> <port>')
        sys.exit(0)
    exec_path = sys.argv[1]
    port = int(sys.argv[2])

    if not exec_path.startswith('/'):
        print('provide the full path to the executable')
        sys.exit(0)

    for address_family in ['v4', 'v6']:
        for msg_type in ['single_msg_single_pkg', 'single_msg_multiple_pkg', 'multiple_msg_single_pkg']:
            print('Testing IP' + address_family, msg_type)
            run_basic_tests(exec_path, address_family, port, msg_type)

client.py

BUFFER = ''


def get_address_family(address):
    try:
        socket.inet_pton(socket.AF_INET, address)
        return socket.AF_INET
    except:
        pass

    try:
        socket.inet_pton(socket.AF_INET6, address)
        return socket.AF_INET6
    except:
        pass

    sys.exit(1)


def create_socket(address, port):
    address_family = get_address_family(address)
    attempts = 0
    client_socket = None
    while attempts < 5:
        try:
            client_socket = socket.socket(address_family, socket.SOCK_STREAM)
            client_socket.connect((address, port))
            break
        except socket.error:
            client_socket = None
            time.sleep(0.05)
        attempts += 1
    if not client_socket:
        sys.exit(2)
    return client_socket


# send one message
def send(client_socket, msg):
    msg = msg.encode('ascii')
    total_sent = 0
    while total_sent != len(msg):
        sent = client_socket.send(msg[total_sent:])
        if sent == 0:
            sys.exit(3)
        total_sent += sent


# receive one complete message
def receive(client_socket):
    global BUFFER
    while True:
        if '\n' in BUFFER:
            msg = BUFFER[:BUFFER.index('\n')]
            BUFFER = BUFFER[BUFFER.index('\n') + 1:]
            return msg
        try:
            data = client_socket.recv(500 - len(BUFFER)).decode()
        except socket.timeout:
            sys.exit(7)
    
        if not data:
            sys.exit(4)

        BUFFER += data
        if len(BUFFER) >= 500:
            sys.exit(5)


def run_multiple_msg_single_pkg(client_socket):
    msg_buffer = []
    for msg in sys.stdin:
        msg_buffer.append(msg)

    for i in range(0, len(msg_buffer), 2):
        if i == len(msg_buffer) - 1:
            send(client_socket, msg_buffer[i])
            if msg_buffer[i] == 'kill\n':
                client_socket.close()
                sys.exit(0)
            ret = receive(client_socket)
            print(ret)
        else:
            msg = msg_buffer[i] + msg_buffer[i + 1]
            send(client_socket, msg)
            if msg_buffer[i] == 'kill\n':
                client_socket.close()
                sys.exit(0)
            ret = receive(client_socket)
            print(ret)
            if msg_buffer[i + 1] == 'kill\n':
                client_socket.close()
                sys.exit(0)
            ret = receive(client_socket)
            print(ret)


def run_single_msg_multiple_pkg(client_socket):
    for msg in sys.stdin:
        send(client_socket, msg[:3])
        time.sleep(0.1)
        send(client_socket, msg[3:])
        if msg == 'kill\n':
            client_socket.close()
            sys.exit(0)
        ret = receive(client_socket)
        print(ret)


def run_single_msg_single_pkg(client_socket):
    for msg in sys.stdin:
        send(client_socket, msg)
        if msg == 'kill\n':
            client_socket.close()
            sys.exit(0)
        ret = receive(client_socket)
        print(ret)


if __name__ == '__main__':
    if len(sys.argv) != 4:
        print(
            f'Usage: python3 {sys.argv[0]} <address> <port> [single_msg_single_pkg | single_msg_multiple_pkg | multiple_msg_single_pkg]')
        sys.exit(6)
    client_socket = create_socket(sys.argv[1], int(sys.argv[2]))
    client_socket.settimeout(5)

    if sys.argv[3] == 'single_msg_single_pkg':
        run_single_msg_single_pkg(client_socket)
    elif sys.argv[3] == 'single_msg_multiple_pkg':
        run_single_msg_multiple_pkg(client_socket)
    elif sys.argv[3] == 'multiple_msg_single_pkg':
        run_multiple_msg_single_pkg(client_socket)

    client_socket.close()
    sys.exit(0)

Running my code with this software, none of the tests work. Basically, after receiving and sending the first message, my server halts communication with the client, failing every single test. This has probably something to do with how I'm treating the strings on my code, but honestly I'm at a loss here. Can someone at least give me a nudge in the right direction?

  • Duplicate. You assume that `recv` will return the full message and only this. This is not how `recv` in TCP works - since there is no such thing as a message in TCP in the first place. The test client deliberately tries if this part is properly handled by your code by sending a "message" with multiple send (see `run_single_msg_multiple_pkg`) and by sending multiple messages within a single send (see `run_multiple_msg_single_pkg`). – Steffen Ullrich Jul 05 '21 at 15:53
  • @Afshin: I don't think the teacher is wrong (see my comment to your answer). – Steffen Ullrich Jul 05 '21 at 16:00

1 Answers1

0

The main problem is that string that is send from python test application is not null-terminated, but you assume it is.

Here in python:

msg = msg.encode('ascii')

msg is encoded in ASCII format, but encode() method returns a bytes object which is not null-terminated, but when you receive it in your server:

recv(sockfd, client_message, sizeof(client_message), 0);

printf("> %s", client_message);
strcpy(str, client_message);

You have codes in your code that has assumed that what you have received in client_message is a null-terminated string. If you have zeroed client_message before recv(), you probably will not see this problem.

Bugs that I see in your code:

  • You assumed that messages are null-terminated. But it seems that they are supposed to be \n separated.
  • always check return of recv. you need to handle return values correctly. you may get error or you may get part of whole data.

In addition, run recv loop in a thread. although a suggestion, you normally always want to do this.

Afshin
  • 8,839
  • 1
  • 18
  • 53
  • *"I think that your teacher's python client code (or better said protocol) is incorrect."* - One does not need to prefix each message with a length to distinguish the messages. Another way is to have a clear separator, like done within HTTP, SMTP ... and many text based protocols. In this case the protocol has a clear separator between messages - the `\n`. – Steffen Ullrich Jul 05 '21 at 15:58
  • @SteffenUllrich yea, but it is need to be defined. anyway, I persoanlly think the code that teacher provides for the testing is incorrect too. so student is not completely to blame. – Afshin Jul 05 '21 at 15:59
  • *"but it is need to be defined"* - and it probably is defined. The OP did not provide the full task, only the parts assumed to be relevant. – Steffen Ullrich Jul 05 '21 at 16:01
  • @SteffenUllrich I don't see `\n` in send method of python code. I'm not python expert. so maybe I;m missing it anyway. – Afshin Jul 05 '21 at 16:01
  • `for msg in sys.stdin: ...` - each `msg` will have an `\n` at the end. And there are multiple places in the code where `\n` is used which clearly shows that it is expected as separator. See for example `receive`. – Steffen Ullrich Jul 05 '21 at 16:03
  • @SteffenUllrich Ok, I didn't know that. updating answer. – Afshin Jul 05 '21 at 16:05