gounix-socketgo-ginfirecracker

How to use unix domain socket to make a request when using Gin?


I am creating a wrapper for firecracker.

To start a VM with firecracker on command line, you have to pass a socket file to the firecracker executable. Something like this:

firecracker --api-sock /path/to/file.socket

Then from another terminal, you can make requests to this server/socket something like this:

curl --unix-socket /tmp/firecracker.socket -XPUT 'http://localhost/actions' -d '{"action_type": "SendCtrlAltDel"}'

I am trying to replicate the same thing from within a Gin server.

I have an endpoint which does the first work, which is to start a server. A minimal code looks like this:

cmd := exec.Command("firecracker", "--api-sock", "/path/to/file.socket")

err := cmd.Start()

This endpoint starts the server and listens for any command. The problem is, I don't know how to use the socket file to make a PUT request to this server. I have found this on the web, but it does not makes much sense to me.

Here is a starter code which does not use any socket file.

func BootSource(c *gin.Context) {
    var body models.BootSource
    c.BindJSON(&body)
    bodyJson, _ := json.Marshal(body)

    // initialize http client
    client := &http.Client{}

    // set the HTTP method, url, and request body
    req, err := http.NewRequest(http.MethodPut, "http://localhost/boot-source", bytes.NewBuffer(bodyJson))
    if err != nil {
        panic(err)
    }

    // set the request header Content-Type for json
    _, err = client.Do(req)
    if err != nil {
        panic(err)
    }
}

How do I make this PUT request use the socket file?

Please also note that I'm using Gin framework.


Solution

  • To do this, you'll need to override the Transport used by your http.Client to configure a function for it to use to create a connection to the socket:

    client := http.Client{
      Transport: &http.Transport{
        DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
          return net.Dial("unix", "/path/to/socket") 
        },
      },
    }
    

    You can then use that client, and all requests made by it will use that connection. Usually for HTTP services exposed over a socket, the host used in the request is not important, so you can just use any value that makes sense to you e.g

    client.Get("http://firecracker/some/api/path")
    

    However, as you are trying to use the Firecracker API, why not just use their SDK: https://github.com/firecracker-microvm/firecracker-go-sdk

    This will handle the set up of the connection for you, and prevent you needing to manually craft all of the requests.