~ne02ptzero/pistache

44e40478d604746963b898efd463a541ebbea824 — Louis Solofrizzo 2 years ago 1c9d71a
Transport: Fix static file serving from SSL connections

This patch fixes the behavior of serveFile with SSL enabled servers.
Before, the sendfile(2) syscall was used, but cannot work if our current
version of OpenSSL, because the transfer happens kernel-side, and the
SSL encryption does not. So I've implemented a _very_ basic sendfile
utility for SSL connections, which only stream 4096 bytes of the file
per call, in order not to block the whole pipeline.

It's worth noting that this patch will be useless when the upstream
moves to OpenSSL 3.0, which uses Kernel TLS, and implement a zero-copy
version of SSL_sendfile, which is far more superior than the one
implemented in this patch.

I've tested the basic use cases with this patch, whis is file
downloading. I've used small and big (45G) files, and it appears to be
working.

I've launched kip's tests[1] on my code, and it seems to be passing
everything:

          Start 25: https_server_test
    25/25 Test #25: https_server_test ................   Passed    0.07 sec

[1] https://github.com/kiplingw/pistache/tree/kip-ssl-servefile-test

Here is my example file:

    #include <pistache/endpoint.h>
    #include <iostream>

    using namespace Pistache;

    struct HelloHandler : public Http::Handler {
        HTTP_PROTOTYPE(HelloHandler)

        void onRequest(const Http::Request& request, Http::ResponseWriter writer) {
            Http::serveFile(writer, "main.cpp");
        }
    };

    int main(void) {
        std::cout << "Starting server\n";
        Pistache::Address addr;
        auto opts = Http::Endpoint::options().threads(5);

        addr = Pistache::Address(Pistache::Ipv4::any(), Pistache::Port(9999));

        Http::Endpoint server(addr);
        server.init(opts);
        server.setHandler(std::make_shared<HelloHandler>());
        server.useSSL("./cas/server/server.crt", "./cas/server/server.key", true);
        std::cout << "SSL enabled\n";

        server.serve();
        return 0;
    }

    $> curl https://localhost:9999 -k > test
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100   770  100   770    0     0  30800      0 --:--:-- --:--:-- --:--:-- 30800
    $> diff test main.cpp
    $>

Pull Request: https://github.com/oktal/pistache/pull/620
Fork: https://git.mobley.ne02ptzero.me/~louis/pistache

Signed-off-by: Louis Solofrizzo <lsolofrizzo@online.net>
3 files changed, 90 insertions(+), 1 deletions(-)

A include/pistache/utils.h
M src/common/transport.cc
A src/common/utils.cc
A include/pistache/utils.h => include/pistache/utils.h +39 -0
@@ 0,0 1,39 @@
/* utils.h
   Louis Solofrizzo 2019-10-17

   Utilities for pistache
*/

#pragma once

#ifdef PISTACHE_USE_SSL

#include <openssl/ssl.h>

/*!
 * \brief sendfile(2) like utility for OpenSSL contexts
 *
 * \param[out] out The SSL context
 * \param[in] in File descriptor to stream
 * \param[out] offset See below.
 * \param[in] count Number of bytes to write
 *
 * Unlike the system call, this function will bufferise data in user-space,
 * thus making it blocking and memory hungry.
 *
 * If offset is  not NULL, then it points to a variable holding the file offset
 * from which SSL_sendfile() will start reading data from in. When
 * SSL_sendfile() returns, this  variable will be set to the offset of the byte
 * following the last byte that was read.
 *
 * \note This function exists in OpenSSL3[1]. It uses KTLS features which are
 * far more superior that this function. We'll need to do the switch when
 * OpenSSL3 becomes the SSL mainline.
 *
 * \return The number of bytes written to the SSL context
 *
 * [1] https://www.openssl.org/docs/manmaster/man3/SSL_sendfile.html
 */
ssize_t SSL_sendfile(SSL *out, int in, off_t *offset, size_t count);

#endif /* PISTACHE_USE_SSL */

M src/common/transport.cc => src/common/transport.cc +16 -1
@@ 12,6 12,7 @@
#include <pistache/peer.h>
#include <pistache/tcp.h>
#include <pistache/os.h>
#include <pistache/utils.h>


namespace Pistache {


@@ 257,7 258,21 @@ Transport::asyncWriteImpl(Fd fd)
            } else {
                auto file = buffer.fd();
                off_t offset = totalWritten;
                bytesWritten = ::sendfile(fd, file, &offset, len);

#ifdef PISTACHE_USE_SSL
                auto it = peers.find(fd);

                if (it == std::end(peers))
                    throw std::runtime_error("No peer found for fd: " + std::to_string(fd));

                if (it->second->ssl() != NULL) {
                    bytesWritten = SSL_sendfile((SSL *)it->second->ssl(), file, &offset, len);
                } else {
#endif /* PISTACHE_USE_SSL */
                    bytesWritten = ::sendfile(fd, file, &offset, len);
#ifdef PISTACHE_USE_SSL
                }
#endif /* PISTACHE_USE_SSL */
            }
            if (bytesWritten < 0) {
                if (errno == EAGAIN || errno == EWOULDBLOCK) {

A src/common/utils.cc => src/common/utils.cc +35 -0
@@ 0,0 1,35 @@
/* utils.cc
   Louis Solofrizzo 2019-10-17

   Utilities for pistache
*/

#include <pistache/peer.h>
#include <unistd.h>

ssize_t SSL_sendfile(SSL *out, int in, off_t *offset, size_t count)
{
    unsigned char       buffer[4096] = { 0 };
    ssize_t             ret;
    ssize_t             written;
    size_t              to_read;

    if (in == -1)
        return -1;

    to_read = sizeof(buffer) > count ? count : sizeof(buffer);

    if (offset != NULL)
        ret = pread(in, buffer, to_read, *offset);
    else
        ret = read(in, buffer, to_read);

    if (ret == -1)
        return -1;

    written = SSL_write(out, buffer, ret);
    if (offset != NULL)
        *offset += written;

    return written;
}