How to properly use buffer while exchanging data via recv/send in UNIX?

1

I am making a simple chat that has to send text message made by one member to all other members. The format of the message that everybody has to receive is "[IP]: hello!". Also the server has to notify everybody when somebody connects or disconnects: "[IP] has connected" and "[IP] is doosconnected" respectively.

Here is a piece of code that implements this function of the server. You can start looking since the line that has "THE PROBLEM IS HERE" comment:

while (true)
{

// select() for reading

static constexpr int bufferSize = 1024;
static char buffer[bufferSize];

// this is for getting IP-address of sender
static sockaddr_in sockAddr;
static socklen_t sockAddrSize;

if (FD_ISSET(masterSocket, &set)) // masterSocket is a socket that establishes connections
{
    sockAddrSize = sizeof(sockAddr);
    int slaveSocket = accept(masketSocket, &sockAddr, &sockAddrSize); // slaveSocket is a client socket

    // setting a slaveSocket non-blocking

    sprintf(buffer, "[%d.%d.%d.%d] has connected\n", 
        (sockAddr.sin_addr.s_addr & 0x000000FF), 
        (sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8, 
        (sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16, 
        (sockAddr.sin_addr.s_addr & 0xFF000000) >> 24);

    for (const auto &socket : slaveSockets)
        send(socket, buffer, strlen(buffer), MSG_NOSIGNAL);

    slaveSockets.insert(slaveSocket);
}

for (const auto &socket : slaveSockets)
{
    if (FD_ISSET(socket, &set))
        continue;

    static int recvSize = recv(socket, buffer, bufferSize, MSG_NOSIGNAL);

    if (recvSize == 0 && errno != EAGAIN)
    {
        sockAddrSize = sizeof(sockAddr);
        getsockname(socket, (sockaddr *) &sockAddr, &sockAddrSize);
        sprintf(buffer, "[%d.%d.%d.%d] has disconnected\n", 
                    (sockAddr.sin_addr.s_addr & 0x000000FF), 
                    (sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8, 
                    (sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16, 
                    (sockAddr.sin_addr.s_addr & 0xFF000000) >> 24);

        shutdown(socket, SHUT_RDWR);
        close(socket);
        slaveSockets.erase(socket);

        for (const auto &socket : slaveSockets)
            send(socket, buffer, strlen(buffer), MSG_NOSIGNAL);
    }
    else if (recvSize > 0) // THE PROBLEM IS HERE
    {
        static char reply[bufferSize];
        sockAddrSize = sizeof(&sockAddr);
        getsocklen(socket, (sockaddr *) &sockAddr, &sockAddrSize);
        sprintf(reply, "[%d.%d.%d.%d]: %s\n",
            (sockAddr.sin_addr.s_addr & 0x000000FF),
            (sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8,
            (sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16,
            (sockAddr.sin_addr.s_addr & 0xFF000000) >> 24,
            buffer);

        int senderSocket = socket;
        for (const auto &socket : slaveSockets)
        {
            if (socket == senderSocket)
                continue;

            send(socket, reply, strlen(reply), MSG_NOSIGNAL); // even tried the "strlen(reply) + 1"
        }
    }
}

}

The problem is that receivers have each message incorrectly outputed: it is outputed completely but also has the end of old values of buffer in the end. For example:

Client A has connected.

Client B has connected. Client A has received "[127.0.0.1] has connected".

Client A has sent "hello". Client B received "[127.0.0.1]: hello\n0.1] has connected\n".

Client B has sent "what's up?". Client A has received "[127.0.0.1]: what's up?\nhas connected\n".

Client A has disconnected. Client B has received "[127.0.0.1] has disconnected".

As you can see connection/disconnection information is always outputed correctly but chatting is works wrong: it contains parts of connection/disonnection information in its end.

I sincerely beleive that I use buffers properly and can not understand what I am doing wrong.

c++
sockets
unix
networking

2 Answers

3

The buffer is not a null-terminated C string after recv returns. This is logical - what if you transfer binary data? Then you would want to recv exactly (message-length) bytes and don't append any zero bytes.

Note that sending terminating null byte in send is the wrong thing to do - your receiver relies on sender to append this zero byte, but if sender is malicious, then he may not append zero byte and cause all sorts of bugs and vulnerabilities - including DoS attacks and remote code execution.

You may still rely on sender appending zero byte, but then you should pass bufferSize-1 as buffer length to recv, and after a call to recv set the reply[bufferSize-1]=0. But maybe it is still not the best thing to do: one of multitude of other options is to pass a "message length" as a 32-bit unsinged integer, check for maximum length (say, no message is bigger than 1024 chars, and if is, don't receive anything and just close the socket), and recv exactly the passed "message length" bytes to buffer. You'll still need to append the terminating null byte if you intend to use the buffer as a C-style string.

Edit: IMPORTANT! If you use TCP (SOCK_STREAM), always use message length: the message might (and someday will) be read by recv in fragments. You absolutely should concatenate them into whole message by yourself.

answered on Stack Overflow Nov 3, 2019 by smitsyn • edited Nov 3, 2019 by smitsyn
2

Just to add to smitsyn's answer, you should use the recvSize (that's grater than 0 in the branch with the issue - since you've received something from one of the clients) to set the '\0' inside buffer.

Your static variables got initialized to 0 (default) all over so your buffer did contain a '\0' immediately after the longest message that you got (since the rest was overwritten) and you got lucky that sprintf found it and did not go search for it somewhere else in your program's memory (out of bounds even).

Something along these lines should make it work (for a simple case):

else if (recvSize > 0) // THE PROBLEM IS HERE
    {
     ...

     buffer[recvSize] = '\0'; // of course make sure it fits! could use a std::min(recvSize, bufferSize - 1)
     sprintf(reply, "[%d.%d.%d.%d]: %s\n",
            (sockAddr.sin_addr.s_addr & 0x000000FF),
            (sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8,
            (sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16,
            (sockAddr.sin_addr.s_addr & 0xFF000000) >> 24,
            buffer); // now this print should work as expected 

Edit: because I can't post comments :(

answered on Stack Overflow Nov 3, 2019 by ben10

User contributions licensed under CC BY-SA 3.0