linuxsocketstcpmss

Reduce TCP maximum segment size (MSS) in Linux on a socket


In a special application in which our server needs to update firmware of low-on-resource sensor/tracking devices we encountered a problem in which sometimes data is lost in the remote devices (clients) receiving packets of the new firmware. The connection is TCP/IP over GPRS network. The devices use SIM900 GSM chip as a network interface.

The problems possibly come because of the device receiving too much data. We tried reducing the traffic by sending packages more rarely but sometimes the error still occured.

We contacted the local retailer of the SIM900 chip who is also responsible for giving technical support and possibly contacting the chinese manufacturer (simcom) of the chip. They said that at first we should try to reduce the TCP MSS (Maximum Segment Size) of our connection.

In our server I did the following:

static int
create_master_socket(unsigned short master_port) {

    static struct sockaddr_in master_address;
    int master_socket = socket(AF_INET,SOCK_STREAM,0);
    if(!master_socket) {
            perror("socket");
            throw runtime_error("Failed to create master socket.");
    }

    int tr=1;
    if(setsockopt(master_socket,SOL_SOCKET,SO_REUSEADDR,&tr,sizeof(int))==-1) {
            perror("setsockopt");
            throw runtime_error("Failed to set SO_REUSEADDR on master socket");
    }

    master_address.sin_family = AF_INET;
    master_address.sin_addr.s_addr = INADDR_ANY;
    master_address.sin_port = htons(master_port);
    uint16_t tcp_maxseg;
    socklen_t tcp_maxseg_len = sizeof(tcp_maxseg);
    if(getsockopt(master_socket, IPPROTO_TCP, TCP_MAXSEG, &tcp_maxseg, &tcp_maxseg_len)) {
            log_error << "Failed to get TCP_MAXSEG for master socket. Reason: " << errno;
            perror("getsockopt");
    } else {
            log_info << "TCP_MAXSEG: " << tcp_maxseg;
    }
    tcp_maxseg = 256;
    if(setsockopt(master_socket, IPPROTO_TCP, TCP_MAXSEG, &tcp_maxseg, tcp_maxseg_len)) {
            log_error << "Failed to set TCP_MAXSEG for master socket. Reason: " << errno;
            perror("setsockopt");
    } else {
            log_info << "TCP_MAXSEG: " << tcp_maxseg;
    }
    if(getsockopt(master_socket, IPPROTO_TCP, TCP_MAXSEG, &tcp_maxseg, &tcp_maxseg_len)) {
            log_error << "Failed to get TCP_MAXSEG for master socket. Reason: " << errno;
            perror("getsockopt");
    } else {
            log_info << "TCP_MAXSEG: " << tcp_maxseg;
    }
    if(bind(master_socket, (struct sockaddr*)&master_address,
                            sizeof(master_address))) {
            perror("bind");
            close(master_socket);
            throw runtime_error("Failed to bind master_socket to port");

    }

    return master_socket;
}

Running the above code results in:

I0807 ... main.cpp:267] TCP_MAXSEG: 536
E0807 ... main.cpp:271] Failed to set TCP_MAXSEG for master socket. Reason: 22 setsockopt: Invalid argument
I0807 ... main.cpp:280] TCP_MAXSEG: 536

As you may see, the problem in the second line of the output: setsockopt returns "Invalid argument".

Why does this happen? I read about some constraints in setting TCP_MAXSEG but I did not encounter any report on such a behaviour as this.

Thanks, Dennis


Solution

  • Unless otherwise noted, optval is a pointer to an int.

    but you're using a u_int16. I don't see anything saying that this parameter isn't an int.

    edit: Yeah, here is the source code and you can see:

    637         if (optlen < sizeof(int))
    638                 return -EINVAL;