Как узнать, что SSL_read получил и обработал все записи из одного сообщения - PullRequest
0 голосов
/ 17 июня 2020

Далее следует дилемма, SSL_read, в случае успеха возвращает количество прочитанных байтов, SSL_pending используется, чтобы определить, есть ли у обработанной записи больше того, что нужно прочитать, это означает, что, вероятно, предоставленного буфера недостаточно для хранения записи.

SSL_read может возвращать n> 0, но что, если это произойдет, когда первые записи были обработаны и сообщение фактически представляет собой обмен данными с несколькими записями.

Вопрос: я использую epoll для отправки / получения сообщений, что означает, что я нужно поставить в очередь событие, если я ожидаю больше данных. Какая проверка гарантирует, что все записи были прочитаны из одного сообщения, и что пора удалить это событие и поставить в очередь событие ответа, которое запишет ответ обратно клиенту?

PS: Этот код не был протестировано, поэтому оно может быть неверным. Цель кода - поделиться идеей, которую я пытаюсь реализовать.

Ниже приведен фрагмент кода для чтения -

        //read whatever is available.
        while (1)
        {
            auto n = SSL_read(ssl_, ptr_ + tail_, sz_ - tail_);

            if (n <= 0)
            {
                int ssle = SSL_get_error(ch->ssl_, rd);
                auto old_ev = evt_.events;
                if (ssle == SSL_ERROR_WANT_READ)
                {
                    //need more data to process, wait for epoll notification again
                    evt_.events = EPOLLIN | EPOLLERR;
                }
                else if (err == SSL_ERROR_WANT_WRITE)
                {
                    evt_.events = EPOLLOUT | EPOLLERR;
                }
                else
                {
                    /*  connection closed by peer, or
                        some irrecoverable error */
                    done_ = true;
                    tail_ = 0; //invalidate the data
                    break;
                }

                if (old_ev != evt_.events)
                    if (epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, socket_fd_, &evt_) < 0)
                    {
                        perror("handshake failed at EPOLL_CTL_MOD");
                        SSL_free(ssl_);
                        ssl_ = nullptr;
                        return false;
                    }
            }
            else //some data has been read
            {
                tail_ = n;
                if (SSL_pending(ssl_) > 0)
                //buffer wasn't enough to hold the content. resize and reread
                    resize();
                else
                    break;
            }
        }
    ```
    enter code here

Ответы [ 2 ]

0 голосов
/ 17 июня 2020

SSL_read() возвращает количество дешифрованных байтов, возвращенных в буфер вызывающего абонента, а не количество байтов, полученных при соединении. Это имитирует возвращаемое значение recv() и read().

SSL_pending() возвращает количество расшифрованных байтов, которые все еще находятся в буфере SSL и еще не были прочитаны вызывающей стороной . Это было бы эквивалентно вызову ioctl(FIONREAD) на сокете.

Невозможно узнать, сколько записей SSL / TLS составляет «сообщение приложения», которое должны определять расшифрованные данные протокола. Протокол должен указать, где заканчивается сообщение и начинается новое сообщение. Например, включив длину сообщения в данные сообщения. Или разделение сообщений терминаторами.

В любом случае, уровень SSL / TLS не имеет понятия «сообщения», а только произвольный поток байтов, который он шифрует и расшифровывает по мере необходимости и передает в «записях» своих выбор. Подобно тому, как TCP разбивает поток произвольных байтов на IP-кадры, et c.

Итак, пока ваш l oop читает произвольные байты из OpenSSL, ему необходимо обработать эти байты для обнаружения разделений. между протокольными сообщениями, чтобы затем он мог действовать соответственно для каждого сообщения.

0 голосов
/ 17 июня 2020

Какая проверка гарантирует, что все записи были прочитаны из одного сообщения, и пришло время удалить это событие и поставить в очередь событие ответа, которое запишет ответ обратно клиенту?

Я бы надеялся, что у вашего сообщения есть заголовок с количеством записей в нем. В противном случае протокол, который у вас есть, вероятно, не поддается анализу.

Что вам нужно, так это иметь анализатор с отслеживанием состояния, который потребляет все доступные байты и выводит записи после их завершения. Такой синтаксический анализатор должен приостановить свое состояние, как только он достигнет последнего байта расшифрованного ввода, а затем должен быть вызван снова, когда станет доступно больше данных для чтения. Но во всех случаях, если вы не можете заранее предсказать, сколько данных ожидается, вы не сможете определить, когда сообщение будет завершено - это если вы не используете протокол самосинхронизации. Что-то вроде заголовков банкоматов могло бы стать отправной точкой. Но в таком усложнении нет необходимости, когда все, что вам нужно, это просто правильно разграничить ваши данные, чтобы парсер пакетов мог точно знать, есть ли у него все необходимое или нет.

Это проблема с отправкой сообщений: это очень легко отправлять материал, который не может быть декодирован получателем, поскольку отправитель прекрасно переносит потерю данных - ему просто все равно. Но получателю обязательно нужно будет каким-то образом знать, сколько байтов или записей ожидается. Об этом можно сообщить априори, отправив заголовки, которые включают счетчики байтов или счетчики записей фиксированного размера (это одна и та же информация о размере только в разных единицах), или апостериори, используя уникальные разделители записей. Например, при отправке печатаемого текста, разбитого на строки, такими разделителями могут быть разделители абзацев Unicode (U + 2029).

Очень важно убедиться, что разделители записей не могут встречаться в самих данных записи. Таким образом, вам нужен какой-то механизм «наполнения», при котором, если последовательность разделителей появляется в полезной нагрузке, вы можете изменить ее так, чтобы она больше не была допустимым разделителем. Вам также нужен механизм «распаковки», чтобы такие измененные последовательности разделителей можно было обнаружить и преобразовать обратно в их исходную форму, конечно, без интерпретации как разделитель. Очень простой пример такого процесса разделения - кадрирование с заполнением октетами в протоколе PPP . Это форма обрамления HDL C. Разделитель записей - 0x7E. Каждый раз, когда этот байт обнаруживается в полезной нагрузке, он экранируется - заменяется последовательностью 0x7D 0x5E. На принимающей стороне 0x7D интерпретируется как означающее «следующий символ был подвергнут XOR с 0x20». Таким образом, получатель сначала преобразует 0x7D 0x5E в 0x5E (удаляет escape-байт), а затем выполняет XOR с 0x20, давая исходный 0x7E. Такое кадрирование легко реализовать, но потенциально оно связано с большими накладными расходами, чем кадрирование с более длинной последовательностью разделителей или даже последовательностью разделителей динамического c, форма которой отличается для каждой позиции в потоке. Это можно использовать для предотвращения атак типа «отказ в обслуживании», когда злоумышленник может злонамеренно предоставить полезную нагрузку, которая повлечет за собой большие накладные расходы на побег. Последовательность разделителей динамических c - особенно если она непредсказуема, например, путем согласования новой последовательности для каждого соединения - предотвращает такое ухудшение качества обслуживания.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...