wufp
wufp

Reputation: 101

How to send and receive an empty tcp segment?

I try to understand and document how tcp works in some corner-cases.

Here I’m talking about an IP packet, containing a TCP segment that have a zero-length data part. Zero-length udp datagram seems simpler, recv(2) mention them but nothing about their TCP equivalent.

Does empty tcp packet exist?

I only found unsourced forum answers here. Some say empty tcp segments cannot exist, other that they are used as cheap keepalive mechanism. It seems the theory have no problem with empty segments since ack have no data. But are they TCP segment without any useful information other than being there and empty? Are they really used and for what purpose?

Can I send an empty packet?

I found this c++ code that send a string then an empty segment. When I run it, according to tcpdump, the "hello" string is sent (and ack) but the empty packet is never sent (although the console says it was). I believe it is not a "too small data" issue since it is not sent event when the socket is closed both ends (or maybe it is sent with the FIN ack, but I can’t see it... Because it is empty...). In any ways, the server never receive it as empty data.

Are they tcp implementations that allow applications to send empty tcp segment ?

#include <iostream>    
#include <sys/socket.h>    
#include <arpa/inet.h>    
#include <unistd.h>    
#include <cstring>    
    
int main() {    
    int sock = socket(AF_INET, SOCK_STREAM, 0);    
    if (sock < 0) {    
        std::cerr << "Socket creation error: " << strerror(errno) << "\n";    
        return -1;    
    }    
    
    sockaddr_in server_address;    
    server_address.sin_family = AF_INET;    
    server_address.sin_port = htons(8080);    
    server_address.sin_addr.s_addr = inet_addr("127.0.0.1");    
    
    if (connect(sock, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) {    
        std::cerr << "Connection failed: " << strerror(errno) << "\n";    
        close(sock);    
        return -1;    
    }    
    
    send(sock, "hello", 6, 0);                                                                                                                                                                
    sleep(1);                                                                                                                                                                                  
    // Sending zero-length data                                                                                                                                                                
    if (send(sock, "", 0, 0) < 0) {                                                                                                                                                            
        std::cerr << "Send failed: " << strerror(errno) << "\n";                                                                                                                               
    } else {                                                                                                                                                                                   
        std::cout << "Zero-length data sent successfully\n";                                                                                                                                   
    }                                                                                                                                                                                          
    sleep(1);                                                                                                                                                                                  
                                                                                                                                                                                               
    close(sock);                                                                                                                                                                               
    return 0;                                                                                                                                                                                  
}

Can it trigger POLLIN event?

I have a bug in a piece of soft where POLLIN is triggered on a socket and the read call returns 0 BUT both ends are convinced the socket is up (and I don’t see why it would have closed). Is it possible that my server sent an "empty segment" responsible for this?

Note: I have no keepalive mechanism so I don’t see any reason for the socket to close. Even when I disable the connection between the two ends, nobody ever close the socket until it tries to write and fail.

I believe it is not possible to read from an empty segment from the receiving end (even if it was inserted by a middle-man pirate), since read would return 0 (the size of the data payload) which is the reserved value for closed connection. But who knows what exists out in the wild?

Upvotes: 4

Views: 96

Answers (2)

Shelton Liu
Shelton Liu

Reputation: 601

It seems the theory have no problem with empty segments since ack have no data. But are they TCP segment without any useful information other than being there and empty? Are they really used and for what purpose?

While empty TCP segments carry no data, they still contain important control information in their headers. This is quite common in TCP communication, empty TCP segments serve important control purposes beyond transferring useful application data, e.g ACK Segments, SYN and SYN-ACK, FIN Segments, RST Segments ...

Are they tcp implementations that allow applications to send empty tcp segment ?

No, for example, POSIX send() function does not guarantee the sending of zero-length segments.

I have a bug in a piece of soft where POLLIN is triggered on a socket and the read call returns 0 BUT both ends are convinced the socket is up (and I don’t see why it would have closed). Is it possible that my server sent an "empty segment" responsible for this?

It probably indicates that the remote side has closed its writing end, and the TCP connection is in a half-closed state. There are several possible reasons for read() returning 0. E.g, when one side sends a FIN segment to indicate it has finished sending data...., or the remote peer may have called shutdown() on the socket..., or while TCP does send empty segments ...

Upvotes: 0

Remy Lebeau
Remy Lebeau

Reputation: 598114

Does empty tcp packet exist?

TCP is a byte stream. A 0-length packet sent by user code doesn't add anything to the stream. So TCP uses 0-length packets internally for keep-alives.

Can I send an empty packet?

No. Unless you use a RAW socket and form the TCP packet manually. Otherwise, you can configure the socket to send keep-alives at fixed intervals when the socket is idle.

Are they tcp implementations that allow applications to send empty tcp segment ?

Not that I've ever seen.

Can it trigger POLLIN event?

No.

I have a bug in a piece of soft where POLLIN is triggered on a socket and the read call returns 0

That usually means the remote peer closed the connection gracefully (ie, sent a TCP packet with the FIN flag on it). The only other way that read() (or recv()) can return 0 is if you give it a 0-length buffer to read into, but that is very rare and would be a bug in the code if it happens.

BUT both ends are convinced the socket is up (and I don’t see why it would have closed). Is it possible that my server sent an "empty segment" responsible for this?

No.

Note: I have no keepalive mechanism so I don’t see any reason for the socket to close.

You might not, but the underling socket provider may. Check with getsockopt() to see whether the SO_KEEPALIVE option is enabled on the socket.

Even when I disable the connection between the two ends, nobody ever close the socket until it tries to write and fail.

Abruptly severing the connection typically won't report a lost connection right away. TCP is designed to survive temporary network outages, so it can takes awhile to detect a truly lost connection. In the meantime, reads will report no data received, and writes will be buffered internally until the buffer fills up, blocking further writes until the buffer is flushed.

If you need more timely detection, that is where keep-alives and read/write timeouts can come into play.

Upvotes: 4

Related Questions