Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to talk to docker daemon through sock file #2021

Open
bialasjaroslaw opened this issue Jan 14, 2025 · 8 comments
Open

Unable to talk to docker daemon through sock file #2021

bialasjaroslaw opened this issue Jan 14, 2025 · 8 comments

Comments

@bialasjaroslaw
Copy link

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

@yhirose
Copy link
Owner

yhirose commented Jan 17, 2025

@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");?

@bialasjaroslaw
Copy link
Author

@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.

@yhirose
Copy link
Owner

yhirose commented Jan 17, 2025

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

@yhirose
Copy link
Owner

yhirose commented Jan 17, 2025

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

@yhirose
Copy link
Owner

yhirose commented Jan 17, 2025

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
Copy link
Owner

yhirose commented Jan 17, 2025

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

@bialasjaroslaw
Copy link
Author

@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.

@yhirose
Copy link
Owner

yhirose commented Jan 17, 2025

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants