cpp-httplib icon indicating copy to clipboard operation
cpp-httplib copied to clipboard

Unable to talk to docker daemon through sock file

Open bialasjaroslaw opened this issue 1 year ago • 8 comments

I was trying to use cpp-httplib to communicate with my local docker daemon using sock file. There is a possibility to do that according to this

After testing my connection with curl I was pretty sure that it will be easy to do however problem (IMO) is caused by the fact that docker engine is expecting to receive request on endpoint http://localhost/v1.39/images/json and not /v1.39/images/json. I can not see how to do that using httplib::Client object because its only argument is a path to sock file.

Also curl is working with that socket curl --unix-socket /run/docker.sock /v1.39/images/json

I am attaching a short example comparing httplib and raw socket usage

#if defined(HTTP_LIB)
#include <httplib.h>
#include <iostream>
#include <sys/socket.h>
#else
#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>
#endif

int main(){
    std::string dockerSocket("/run/docker.sock");
    std::string endpoint("http://localhost/v1.39/images/json");
    #if defined(HTTP_LIB)
        httplib::Client cli(dockerSocket);
        cli.set_address_family(AF_UNIX);
        auto res = cli.Get(endpoint);
        if (res && res->status == 200) {
            std::cout << "Response from Docker Engine: "<< res->body << std::endl;
        } else {
            if (res) {
                std::cout << "HTTP Error: " << res->status << std::endl;
            } else {
                std::cout << "Connection failed: " << httplib::to_string(res.error()) << std::endl;
            }
        }
    #else
        int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
        try{
            const char* data = "GET http://localhost/v1.39/images/json HTTP/1.1\r\n"
                "Host: localhost\r\n"
                "Connection: close\r\n\r\n";
            if (sockfd < 0) {
                throw std::runtime_error("Failed to create socket");
            }
            sockaddr_un addr{};
            addr.sun_family = AF_UNIX;
            std::strncpy(addr.sun_path, dockerSocket.c_str(), sizeof(addr.sun_path) - 1);
            if (connect(sockfd, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) < 0) {
                throw std::runtime_error("Failed to connect to the socket");
            }
            if (send(sockfd, data, strlen(data), 0) < 0) {
                throw std::runtime_error("Failed to send data");
            }
            char buffer[4096] = {};
            while(true)
            {   
                ssize_t bytesReceived = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
                if (bytesReceived <= 0) {
                    break;
                }
                buffer[bytesReceived] = '\0';
                std::cout << buffer << std::endl;
            }
        }
        catch(const std::exception &ex){
            std::cout << "Error: " << ex.what() << std::endl;
        }
        if (sockfd >= 0){
            close(sockfd);
        }
    #endif

    return 0;
}

g++ example.cpp && ./a.out

HTTP/1.1 200 OK
Api-Version: 1.46
Content-Type: application/json
Docker-Experimental: false
Ostype: linux
Server: Docker/27.1.2 (linux)
Date: Tue, 14 Jan 2025 17:08:12 GMT
Connection: close
Transfer-Encoding: chunked

f6b

g++ example.cpp -DHTTP_LIB && ./a.out

HTTP Error: 400

I am using httplib 0.18.3 from vcpkg, but I also tried with trunk version. My GCC is 14.2.1 and Docker 27.1.2

bialasjaroslaw avatar Jan 14 '25 17:01 bialasjaroslaw

@bialasjaroslaw thanks for the report. What is the request line of the curl request? You can check with curl --unix-socket /run/docker.sock /v1.39/images/json -v.

Also what HTTP status code will be returned with auto res = cli.Get("/v1.39/images/json");?

yhirose avatar Jan 17 '25 00:01 yhirose

@yhirose thanks for response. Here are the responses for curl for both with and without localhost

curl --unix-socket /run/docker.sock /v1.39/images/json -v
* URL rejected: No host part in the URL
* closing connection #-1
curl: (3) URL rejected: No host part in the URL
curl --unix-socket /run/docker.sock localhost/v1.39/images/json -v
*   Trying /run/docker.sock:0...
* Connected to localhost (/run/docker.sock) port 0
> GET /v1.39/images/json HTTP/1.1
> Host: localhost
> User-Agent: curl/8.9.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< Api-Version: 1.46
< Content-Type: application/json
< Docker-Experimental: false
< Ostype: linux
< Server: Docker/27.1.2 (linux)
< Date: Fri, 17 Jan 2025 07:53:32 GMT
< Transfer-Encoding: chunked
<
...

Running c++ app with auto res = cli.Get("/v1.39/images/json");

HTTP Error: 400

I tried various things and I could omit localhost/ for curl as long as it is not starting from /

curl --unix-socket /run/docker.sock v1.39/images/json -v
*   Trying /run/docker.sock:0...
* Connected to v1.39 (/run/docker.sock) port 0
> GET /images/json HTTP/1.1
> Host: v1.39
> User-Agent: curl/8.9.1
> Accept: */*
> 
...

I can even type whatever I want for host. The only requirement, that seems to be necessary, is to have something hostlike before /images/json(seems that protocol version is also not mandatory)

curl --unix-socket /run/docker.sock whatever/v1.39/images/json -v
*   Trying /run/docker.sock:0...
* Connected to whatever (/run/docker.sock) port 0
> GET /v1.39/images/json HTTP/1.1
> Host: whatever
> User-Agent: curl/8.9.1
> Accept: */*
> 
...

I tried the same things with raw sockets and it is not working the same way curl does (protocol is required)

# These 2 are not working
"GET localhost/v1.39/images/json HTTP/1.1\r\n"
"GET v1.39/images/json HTTP/1.1\r\n"
# This one still works as expected
"GET http://whatever/images/json HTTP/1.1\r\n"

Result from first two examples

HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Connection: close

400 Bad Request

Result from last example

HTTP/1.1 200 OK
Api-Version: 1.46
Content-Type: application/json
Docker-Experimental: false
Ostype: linux
Server: Docker/27.1.2 (linux)
Date: Fri, 17 Jan 2025 08:13:35 GMT
Connection: close
Transfer-Encoding: chunked

...

This is so confusing because I thought that there is some kind of host validation on the docker side.

bialasjaroslaw avatar Jan 17 '25 08:01 bialasjaroslaw

Thanks. How about curl --unix-socket /run/docker.sock http://localhost/v1.39/images/json -v?

yhirose avatar Jan 17 '25 12:01 yhirose

Also if you call curl --unix-socket /run/docker.sock whatever/v1.39/images/json -v, did you receive a 200 response?

yhirose avatar Jan 17 '25 12:01 yhirose

Also what if the following change in your code?

            const char* data = "GET /v1.39/images/json HTTP/1.1\r\n"
                "Host: localhost\r\n"
                "Connection: close\r\n\r\n";

yhirose avatar Jan 17 '25 12:01 yhirose

One more. 😄 How about curl --unix-socket /run/docker.sock /v1.39/images/json -H 'Host: localhost' -v?

yhirose avatar Jan 17 '25 13:01 yhirose

@yhirose thank you for response and all of your suggestions. I found a solution, but first I will put an output of all the things you asked for.

curl --unix-socket /run/docker.sock http://localhost/v1.39/images/json -v
*   Trying /run/docker.sock:0...
* Connected to localhost (/run/docker.sock) port 0
> GET /v1.39/images/json HTTP/1.1
> Host: localhost
> User-Agent: curl/8.9.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< Api-Version: 1.46
< Content-Type: application/json
< Docker-Experimental: false
< Ostype: linux
< Server: Docker/27.1.2 (linux)
< Date: Fri, 17 Jan 2025 18:20:20 GMT
< Transfer-Encoding: chunked
<
curl --unix-socket /run/docker.sock whatever/v1.39/images/json -v
*   Trying /run/docker.sock:0...
* Connected to whatever (/run/docker.sock) port 0
> GET /v1.39/images/json HTTP/1.1
> Host: whatever
> User-Agent: curl/8.9.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< Api-Version: 1.46
< Content-Type: application/json
< Docker-Experimental: false
< Ostype: linux
< Server: Docker/27.1.2 (linux)
< Date: Fri, 17 Jan 2025 18:21:18 GMT
< Transfer-Encoding: chunked
<

C++ code with header Host

HTTP/1.1 200 OK
Api-Version: 1.46
Content-Type: application/json
Docker-Experimental: false
Ostype: linux
Server: Docker/27.1.2 (linux)
Date: Fri, 17 Jan 2025 18:22:01 GMT
Connection: close
Transfer-Encoding: chunked
curl --unix-socket /run/docker.sock /v1.39/images/json -H 'Host: localhost' -v
* URL rejected: No host part in the URL
* closing connection #-1
curl: (3) URL rejected: No host part in the URL

Solution: Last suggestion of yours give me something to think about, so I tried this:

httplib::Client cli("/run/docker.sock");
cli.set_address_family(AF_UNIX);
httplib::Headers headers{
    {"Host", "localhost"},
    {"Connection", "close"},
};
auto res = cli.Get("/images/json", headers);

This is it. Host header make it work. It is good enough for me and my personal project. I could provide more information if you need anything. However if this is not something that requires code changes I would be happy to finish out small investigation. Thank you for your help and patience. If there is nothing else to clarify, I believe that thread can be closed.

bialasjaroslaw avatar Jan 17 '25 18:01 bialasjaroslaw

Fantastic! I'll put 'information' tag, so that others could benefit from the solution. Thanks!

yhirose avatar Jan 17 '25 18:01 yhirose