Talking HTTP to an abstract unix socket

We’re writing an HTTP REST API for snappy, first and foremost for local consumption, so it’s exposed over an abstract AF_UNIX socket.

Turns out, talking HTTP to an abstract unix socket is not trivial.

One quick hack is to use socat to proxy things for you:

socat TCP4-LISTEN:8080,fork,reuseaddr ABSTRACT-CLIENT:snapd

but you’d have to scp socat onto the device, because snappy core systems don’t have wget, let alone fancy socat.

So what then? The default HTTP library for many is the curl one, but it doesn’t (yet) support abstract unix sockets. You can easily tell it to do HTTP over a regular one,

if (curl_easy_setopt(curl, CURLOPT_UNIX_SOCKET_PATH, "/run/snapd.sock") != CURLE_OK)
  die("_unix_socket_path not supported?");
// note it's not a double slash:
if (curl_easy_setopt(curl, CURLOPT_URL, "http:/1.0/packages") != CURLE_OK)
  die("curlopt_url failed");

but, as I hinted in that code, many curl libraries are compiled without unix socket support (including Ubuntu’s at the time of writing), or are older than the introduction of that option (in which case it wouldn’t even compile).

However, even when you get that working (say, if you were to apt-get build-dep curl && apt-get source curl && cd curl-*, edit debian/rules to --enable-unix-sockets, and dpkg-buildpackage), if you change the socket path to "@snapd" (an initial @ is used in many places to flag an abstract socket) it’ll try to look for a socket with that name on the filesystem, and if you change it to "\0snapd" it’ll do a strlen on that and think it’s the empty string and laugh in your face.

Do not despair. Do not dig into the curl code trying to fix this, then give up and go looking for smaller HTTP libraries that mostly do what you want and just need abstract UNIX socket support. The small ones are rather poor (not supporting chunked responses, for example), and the big ones are, as usual, untractable to the uninitiated.

Do this instead:

#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <curl/curl.h>
#include <sys/un.h>

void die(const char *msg) {
    perror(msg);
    exit(1);
}

curl_socket_t opensocket_callback(void *clientp,
                                  curlsocktype purpose,
                                  struct curl_sockaddr *address) {
    int sock;
    struct sockaddr_un server;

    if ((sock= socket(AF_UNIX, SOCK_STREAM, 0))<0) die("socket");

    memset(&server, 0, sizeof(struct sockaddr_un));
    server.sun_family = AF_UNIX;
    strcpy(&server.sun_path[1], "snapd");

    address->family = AF_UNIX;
    address->socktype = SOCK_STREAM;
    address->protocol = 0;
    address->addrlen = sizeof(sa_family_t) + strlen("snapd") + 1;
    address->addr = *((struct sockaddr *) &server);

    return sock;
}

int main(void) {
    CURL *curl;
    CURLcode res;

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();
    if (!curl) die("init");

    // note it's not a double slash
    if (curl_easy_setopt(curl, CURLOPT_URL, "http:/1.0/packages") != CURLE_OK)
      die("curlopt_url failed");
    if (curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback) != CURLE_OK)
      die("opensocketfunction failed");

    res = curl_easy_perform(curl);
    if (res != CURLE_OK) die(curl_easy_strerror(res));

    curl_easy_cleanup(curl);
    curl_global_cleanup();

    return 0;
}

edited: tumblr mangled some of that once already. If it happens again, http://pastebin.ubuntu.com/12537401/.