/*
- * Copyright 2016 IoT.bzh
+ * Copyright (C) 2015-2020 "IoT.bzh"
* Author: José Bollo <jose.bollo@iot.bzh>
*
* Licensed under the Apache License, Version 2.0 (the "License");
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
+#include <fcntl.h>
#include "afb-wsj1.h"
+#include "fdev-systemd.h"
/**************** WebSocket handshake ****************************/
"MHiEc+Qc8w/SJ3zMHEM8pA==", "FVCxLBmoil3gY0jSX3aNJ6kR/t4="
};
-static const char websocket_s[] = "websocket";
-static const char sec_websocket_key_s[] = "Sec-WebSocket-Key";
-static const char sec_websocket_version_s[] = "Sec-WebSocket-Version";
-static const char sec_websocket_accept_s[] = "Sec-WebSocket-Accept";
-static const char sec_websocket_protocol_s[] = "Sec-WebSocket-Protocol";
-
-static const char vseparators[] = " \t,";
-
/* get randomly a pair of key/accept value */
static void getkeypair(const char **key, const char **ack)
{
for(count = 0 ; strings[count] != NULL ; count++);
/* compute the length of the result */
- length = 0;
- if (count != 0) {
+ if (count == 0)
+ length = 0;
+ else {
+ length = (unsigned)(count - 1) * strlen(separ);
+ for (idx = 0 ; idx < count ; idx ++)
+ length += strlen(strings[idx]);
}
/* allocates the result */
}
/* creates the http message for the request */
-static int make_request(char **request, const char *path, const char *key, const char *protocols)
+static int make_request(char **request, const char *path, const char *host, const char *key, const char *protocols)
{
- int rc = asprintf(request,
- "GET %s HTTP1.1\r\n"
+ int rc = asprintf(request,
+ "GET %s HTTP/1.1\r\n"
+ "Host: %s\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Content-Length: 0\r\n"
"\r\n"
, path
+ , host
, key
, protocols
);
}
/* create the request and send it to fd, returns the expected accept string */
-static const char *send_request(int fd, const char **protocols, const char *path)
+static const char *send_request(int fd, const char **protocols, const char *path, const char *host)
{
const char *key, *ack;
char *protolist, *request;
/* create the request */
getkeypair(&key, &ack);
- length = make_request(&request, path, key, protolist);
+ length = make_request(&request, path, host, key, protolist);
free(protolist);
if (length < 0)
return NULL;
goto error;
len = strcspn(line, " ");
if (len != 8 || 0 != strncmp(line, "HTTP/1.1", 8))
- goto error;
+ goto abort;
it = line + len;
- len = strspn(line, " ");
+ len = strspn(it, " ");
if (len == 0)
- goto error;
+ goto abort;
it += len;
- len = strcspn(line, " ");
- if (len != 3 || 0 != strncmp(line, "101", 3))
- goto error;
+ len = strcspn(it, " ");
+ if (len != 3 || 0 != strncmp(it, "101", 3))
+ goto abort;
/* reads the rest of the response until empty line */
clen = 0;
if (clen > 0) {
while (read(fd, line, len) < 0 && errno == EINTR);
}
- if (haserr != 0)
- result = -1;
- else if (result < 0) {
- result = 0;
- while(protocols[result] != NULL)
- result++;
- }
-error:
+ if (haserr != 0 || result < 0)
+ goto abort;
return result;
+abort:
+ errno = ECONNABORTED;
+error:
+ return -1;
}
-static int negociate(int fd, const char **protocols, const char *path)
+static int negociate(int fd, const char **protocols, const char *path, const char *host)
{
- const char *ack = send_request(fd, protocols, path);
+ const char *ack = send_request(fd, protocols, path, host);
return ack == NULL ? -1 : receive_response(fd, protocols, ack);
}
/* make the result */
*host = strndup(h, hlen);
if (*host != NULL) {
- return -1;
- *service = plen ? strndup(h, hlen) : strdup("http");
+ *service = plen ? strndup(p, plen) : strdup("http");
if (*service != NULL) {
*path = uri;
return 0;
errno = EINVAL;
error:
return -1;
-
}
static const char *proto_json1[2] = { "x-afb-ws-json1", NULL };
-struct afb_wsj1 *afb_ws_client_connect_wsj1(const char *uri, struct afb_wsj1_itf *itf, void *closure)
+struct afb_wsj1 *afb_ws_client_connect_wsj1(struct sd_event *eloop, const char *uri, struct afb_wsj1_itf *itf, void *closure)
{
int rc, fd;
- char *host, *service;
+ char *host, *service, xhost[32];
const char *path;
struct addrinfo hint, *rai, *iai;
struct afb_wsj1 *result;
+ struct fdev *fdev;
/* scan the uri */
rc = parse_uri(uri, &host, &service, &path);
result = NULL;
iai = rai;
while (iai != NULL) {
+ struct sockaddr_in *a = (struct sockaddr_in*)(iai->ai_addr);
+ unsigned char *ipv4 = (unsigned char*)&(a->sin_addr.s_addr);
+ unsigned char *port = (unsigned char*)&(a->sin_port);
+ sprintf(xhost, "%d.%d.%d.%d:%d",
+ (int)ipv4[0], (int)ipv4[1], (int)ipv4[2], (int)ipv4[3],
+ (((int)port[0]) << 8)|(int)port[1]);
fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol);
if (fd >= 0) {
rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
if (rc == 0) {
- rc = negociate(fd, proto_json1, path);
+ rc = negociate(fd, proto_json1, path, xhost);
if (rc == 0) {
- result = afb_wsj1_create(fd, itf, closure);
- if (result != NULL)
- break;
+ fdev = fdev_systemd_create(eloop, fd);
+ if (fdev) {
+ result = afb_wsj1_create(fdev, itf, closure);
+ if (result != NULL) {
+ fcntl(fd, F_SETFL, O_NONBLOCK);
+ break;
+ }
+ }
}
}
close(fd);
}
#endif
+/*****************************************************************************************************************************/
+
+#include <sys/un.h>
+#include "afb-proto-ws.h"
+
+static int get_socket_unix(const char *uri)
+{
+ int fd, rc;
+ struct sockaddr_un addr;
+ size_t length;
+
+ length = strlen(uri);
+ if (length >= 108) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ return fd;
+
+ memset(&addr, 0, sizeof addr);
+ addr.sun_family = AF_UNIX;
+ strcpy(addr.sun_path, uri);
+ if (addr.sun_path[0] == '@')
+ addr.sun_path[0] = 0; /* implement abstract sockets */
+ rc = connect(fd, (struct sockaddr *) &addr, (socklen_t)(sizeof addr));
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ return fd;
+}
+
+static int get_socket_inet(const char *uri)
+{
+ int rc, fd;
+ const char *service, *host, *api;
+ struct addrinfo hint, *rai, *iai;
+
+ /* scan the uri */
+ service = strrchr(uri, ':');
+ if (service == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+ api = strchrnul(service, '/');
+ host = strndupa(uri, service++ - uri);
+ service = strndupa(service, api - service);
+
+ /* get addr */
+ memset(&hint, 0, sizeof hint);
+ hint.ai_family = AF_INET;
+ hint.ai_socktype = SOCK_STREAM;
+ rc = getaddrinfo(host, service, &hint, &rai);
+ if (rc != 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* get the socket */
+ iai = rai;
+ while (iai != NULL) {
+ fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol);
+ if (fd >= 0) {
+ rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
+ if (rc == 0) {
+ freeaddrinfo(rai);
+ return fd;
+ }
+ close(fd);
+ }
+ iai = iai->ai_next;
+ }
+ freeaddrinfo(rai);
+ return -1;
+}
+
+static int get_socket(const char *uri)
+{
+ int fd;
+
+ /* check for unix socket */
+ if (0 == strncmp(uri, "unix:", 5))
+ /* unix socket */
+ fd = get_socket_unix(uri + 5);
+ else if (0 == strncmp(uri, "tcp:", 4))
+ /* unix socket */
+ fd = get_socket_inet(uri + 4);
+ else
+ /* inet socket */
+ fd = get_socket_inet(uri);
+
+ /* configure the socket */
+ if (fd >= 0) {
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ fcntl(fd, F_SETFL, O_NONBLOCK);
+ }
+ return fd;
+}
+
+/*
+ * Establish a websocket-like client connection to the API of 'uri' and if successful
+ * instantiate a client afb_proto_ws websocket for this API using 'itf' and 'closure'.
+ * (see afb_proto_ws_create_client).
+ * The systemd event loop 'eloop' is used to handle the websocket.
+ * Returns NULL in case of failure with errno set appropriately.
+ */
+struct afb_proto_ws *afb_ws_client_connect_api(struct sd_event *eloop, const char *uri, struct afb_proto_ws_client_itf *itf, void *closure)
+{
+ int fd;
+ struct afb_proto_ws *pws;
+ struct fdev *fdev;
+
+ fd = get_socket(uri);
+ if (fd >= 0) {
+ fdev = fdev_systemd_create(eloop, fd);
+ if (fdev) {
+ pws = afb_proto_ws_create_client(fdev, itf, closure);
+ if (pws)
+ return pws;
+ }
+ close(fd);
+ }
+ return NULL;
+}