Problem with openssl connection
  • Caterpillar
    Caterpillar
    jarokuczi
    Posts: 22 from 2016/12/6
    Hi guys,

    I'm having problems connecting to the SSL socket in my C++ application. I'm trying to write a simple HTTPS client for accessing Ollama openai-compatible API.

    When I compile my app on Linux I can connect without issues. But when I build for morphos it fails to SSL_connect without any error.

    SSL_connect returns -1 and that's it. Do I need to call OpenLibrary or do some special initialization not required in linux?

    Below is my program (I added some ifdefs to build for linux) and the build script:
    Code:

    #ifdef __MORPHOS__
    #include <proto/exec.h>
    #include <proto/socket.h>
    #endif
    #include <sys/socket.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <openssl/ssl.h>
    #include <openssl/err.h>
    #include <string>
    #include <netdb.h>

    #ifndef __MORPHOS__
    #define UBYTE char
    #endif

    #define OLLAMA_API_HOST "some-host-with-ssl.com"

    #define OLLAMA_API_PORT 443
    #define OLLAMA_API_ENDPOINT "/ollama/v1/chat/completions"
    #define OLLAMA_API_KEY "Bearer <base64 encoded credentials>"
    #ifdef __MORPHOS__
    struct Library *SocketBase;
    #endif
    SSL_CTX *ssl_ctx;
    SSL *ssl;
    const SSL_METHOD *method;
    int sockfd;

    bool init_network() {
    #ifdef __MORPHOS__
    SocketBase = OpenLibrary("bsdsocket.library", 4);
    if (!SocketBase) {
    printf("Can't open bsdsocket.libraryn");
    return false;
    }
    #endif

    SSL_library_init();
    SSL_load_error_strings();
    ERR_load_crypto_strings();
    method = TLS_client_method();
    ssl_ctx = SSL_CTX_new(method);
    SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
    SSL_CTX_set_cipher_list(ssl_ctx, "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256");
    if (!ssl_ctx) {
    printf("Error initializing OpenSSLn");
    return false;
    }

    return true;
    }

    bool connect_to_openai() {
    struct hostent *server;
    struct sockaddr_in serv_addr;
    std::string host = OLLAMA_API_HOST;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
    printf("Can't create socketn");
    return false;
    }

    server = gethostbyname((UBYTE*)host.c_str());
    if (!server) {
    printf("DNS Errorn");
    return false;
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    memcpy(&serv_addr.sin_addr.s_addr, server->h_addr_list[0], server->h_length);
    serv_addr.sin_port = htons(OLLAMA_API_PORT);

    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
    printf("Connection errorn");
    return false;
    }

    ssl = SSL_new(ssl_ctx);
    SSL_set_fd(ssl, sockfd);
    int ret,err;
    if ((ret = SSL_connect(ssl)) < 0) {
    printf("SSL_connect: %dn", ret);

    ret = SSL_get_error(ssl, ret);
    ERR_print_errors_fp(stdout);

    if((err = ERR_get_error())) {

    printf("SSL connect err code:[%lu](%s)n", err, ERR_error_string(err, NULL));
    printf("Error is %s n",ERR_reason_error_string(err));
    }
    printf("SSL Error: %dn", err);
    return false;
    }

    return true;
    }

    bool send_request() {
    const char *request_body =
    "{"
    ""model": "llama3.1:8b","
    ""messages": [{"role": "user", "content": "Hello, Ollama!"}],"
    ""temperature": 0.7"
    "}";

    char request[2048];
    snprintf(request, sizeof(request),
    "POST %s HTTP/1.1rn"
    "Host: %srn"
    "Authorization: %srn"
    "Content-Type: application/jsonrn"
    "Content-Length: %lurn"
    "Connection: closern"
    "rn"
    "%s",
    OLLAMA_API_ENDPOINT, OLLAMA_API_HOST, OLLAMA_API_KEY, strlen(request_body), request_body);

    if (SSL_write(ssl, request, strlen(request)) <= 0) {
    printf("Error sending datan");
    return false;
    }

    return true;
    }

    void receive_response() {
    char response[8192];
    int bytes_received;
    while ((bytes_received = SSL_read(ssl, response, sizeof(response) - 1)) > 0) {
    response[bytes_received] = '';
    printf("%s", response);
    }
    }

    void cleanup() {
    if (ssl) SSL_free(ssl);

    #ifdef __MORPHOS__
    if (sockfd) CloseSocket(sockfd);
    if (SocketBase) CloseLibrary(SocketBase);
    #else
    if (sockfd) close(sockfd);
    #endif
    if (ssl_ctx) SSL_CTX_free(ssl_ctx);

    }

    int main(void) {
    if (!init_network()) {
    return 1;
    }

    if (!connect_to_openai()) {
    cleanup();
    return 1;
    }

    if (send_request()) {
    receive_response();
    }

    cleanup();
    return 0;
    }


    And here is the CMakeLists.txt I use to build it:
    Code:

    cmake_minimum_required(VERSION 3.9.0)
    project(ollama-client VERSION 0.1.0 LANGUAGES C CXX)

    add_executable(ollama-client main.cpp)


    set(MORPHOS_BUILD ON)


    set(LINK_LIBS ssl crypto pthread)
    if(${MORPHOS_BUILD})
    target_include_directories(ollama-client PUBLIC /gg/os-include)
    target_compile_options(ollama-client PUBLIC -noixemul -D__MORPHOS__)
    list(APPEND LINK_LIBS atomic)
    endif()

    target_link_libraries(ollama-client PUBLIC ${LINK_LIBS})


    Thanks
    Jarek
  • »22.02.25 - 11:54
    Profile
  • MorphOS Developer
    jacadcaps
    Posts: 3163 from 2003/3/5
    From: Canada
    Does SSL_get_error() return anything useful?
  • »22.02.25 - 17:52
    Profile Visit Website
  • Caterpillar
    Caterpillar
    jarokuczi
    Posts: 22 from 2016/12/6
    Quote:

    jacadcaps wrote:
    Does SSL_get_error() return anything useful?


    It returns 5, which is defined in the ssl.h as SSL_ERROR_SYSCALL.

    I found this in OpenSSL docs:
    Code:

    Some TLS implementations do not send a close_notify alert on shutdown.

    On an unexpected EOF, versions before OpenSSL 3.0 returned SSL_ERROR_SYSCALL, nothing was added to the error stack, and errno was 0.
    Since OpenSSL 3.0 the returned error is SSL_ERROR_SSL with a meaningful error on the error stack (SSL_R_UNEXPECTED_EOF_WHILE_READING).
    This error reason code may be used for control flow decisions (see the man page for ERR_GET_REASON(3) for further details on this).


    This is strange because we have OpenSSL 3.x right?
  • »22.02.25 - 18:39
    Profile
  • MorphOS Developer
    Piru
    Posts: 589 from 2003/2/24
    From: finland, the l...
    The program works when I connect to my own server, at least.

    Did you try disabling setting the min protocol version and cipher list (SSL_CTX_set_min_proto_version and SSL_CTX_set_cipher_list calls)?

    In addition there are couple of issues:

    - You must set the host name of the host you connect to. This is done with SSL_set_tlsext_host_name(). Without this call the server hosting multiple servers might not know where to forward the connection. See https://en.wikipedia.org/wiki/Server_Name_Indication for details.

    - SSL_Write() might return 0 and SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE error in which case you need to call the function again with same parameters.

    - Currently there is no host name validation of any kind in the code. That is, the client will accept any valid certificate, even ones assigned to completely different host. This is insecure and will alloc man in the middle attacks. Example implementation of the validation can be found from https://github.com/iSECPartners/ssl-conservatory/tree/master/openssl

    Obviously the last point can be worried about once the app works otherwise.

    Finally, doing HTTP connections manually is quite cumbersome. I would highly recommend using libcurl to avoid most of these issues.

    [ Edited by Piru 22.02.2025 - 21:43 ]
  • »22.02.25 - 19:30
    Profile