Уменьшите максимальный размер сегмента TCP (MSS) в Linux на сокете

В специальном приложении, в котором нашему серверу необходимо обновить микропрограмму датчиков / отслеживающих устройств с низким уровнем ресурсов, мы столкнулись с проблемой, при которой иногда данные теряются на удаленных устройствах (клиентах), получающих пакеты новой микропрограммы. Соединение TCP/IP по сети GPRS. В качестве сетевого интерфейса устройства используют GSM-чип SIM900.

Проблемы могут возникнуть из-за того, что устройство получает слишком много данных. Мы пытались уменьшить трафик, отправляя пакеты реже, но иногда ошибка все же возникала.

Мы связались с местным продавцом чипа SIM900, который также отвечает за техническую поддержку и, возможно, связался с китайским производителем (simcom) чипа. Они сказали, что сначала мы должны попытаться уменьшить TCP MSS (максимальный размер сегмента) нашего соединения.

На нашем сервере я сделал следующее:

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;
}

Запуск приведенного выше кода приводит к:

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

Как видите, проблема во второй строке вывода: setsockopt возвращает "Неверный аргумент".

Почему это происходит? Я читал о некоторых ограничениях в настройке TCP_MAXSEG, но я не встречал сообщений о таком поведении, как это.

Спасибо Деннис

2 ответа

Решение

Если не указано иное, optval является указателем на int.

но вы используете u_int16. Я не вижу ничего, говорящего, что этот параметр не является int.

редактировать: да, вот исходный код, и вы можете увидеть:

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

В дополнение к ответу xaxxon, просто хочу отметить мой опыт с попыткой заставить мой Linux посылать только максимальные сегменты TCP определенного размера (ниже, чем обычно):

  • Самым простым способом, который я нашел, было использование iptables:

sudo iptables -A INPUT -p tcp --tcp-flags SYN,RST SYN --destination 1.1.1.1 -j TCPMSS --set-mss 200

Это перезаписывает удаленный входящий пакет SYN/ACK на исходящем соединении и заставляет MSS к определенному значению.

Примечание 1: Вы не видите этого в wireshark, так как wireshark перехватывает до того, как это произойдет.

Примечание 2: Iptables не позволяет вам увеличить MSS, просто понизьте его

  • В качестве альтернативы я также попытался установить опцию сокета TCP_MAXSEG, как это сделал Деннис. После получения исправления от xaxxon это тоже сработало.

Примечание: вы должны прочитать значение MSS после того, как соединение было установлено. В противном случае он возвращает значение по умолчанию, которое поставило меня (и Дениса) на неверную дорожку.

Теперь, наконец, я столкнулся с рядом других вещей:

  • Я столкнулся с проблемами разгрузки TCP, когда, несмотря на то, что мой MSS был настроен правильно, отправляемые кадры все еще показывались Wireshark как слишком большие. Вы можете отключить эту функцию: sudo ethtool -K eth0 tx off sg off tso off, Это заняло у меня много времени, чтобы понять.

  • В TCP есть много интересных вещей, таких как обнаружение пути MTU, которые на самом деле пытаются динамически увеличить MSS. Весело и круто, но запутанно очевидно. У меня не было проблем с этим, хотя в моих тестах

Надеюсь, что это поможет кому-то попытаться сделать то же самое в один прекрасный день.

Другие вопросы по тегам