socket.c
changeset 26 6d615203d963
child 27 1e79b4cc8f1b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/socket.c	Sun Jul 06 23:33:24 2008 +0300
@@ -0,0 +1,184 @@
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+
+
+#include "socket.h"
+#include "common.h"
+
+#define _SOCKOP_SOCKET 0x01
+#define _SOCKOP_ENDPOINT_BIND 0x02
+#define _SOCKOP_ENDPOINT_CONNECT 0x04
+#define _SOCKOP_LISTEN 0x08
+#define _SOCKOP_FLAGS_PASSIVE 0x10
+#define _SOCKOP_FCNTL_NONBLOCK 0x20
+
+enum socket_op {
+    /*
+     * Just resolve the address, don't create a socket
+     */
+    SOCKOP_RESOLVE = _SOCKOP_FLAGS_PASSIVE,
+
+    /*
+     * Create a socket whose local end is bound to the given endpoint, and listening
+     */
+    SOCKOP_LISTEN = _SOCKOP_FLAGS_PASSIVE | _SOCKOP_SOCKET | _SOCKOP_ENDPOINT_BIND | _SOCKOP_LISTEN,
+
+    /*
+     * Create a socket whose remote end is connected to the given endpoint, and connected
+     */
+    SOCKOP_CONNECT = _SOCKOP_SOCKET | _SOCKOP_ENDPOINT_CONNECT,
+
+    /*
+     * Create a socket and initiate a nonblocking connect to the given endpoint
+     */
+    SOCKOP_CONNECT_ASYNC = _SOCKOP_SOCKET | _SOCKOP_FCNTL_NONBLOCK | _SOCKOP_ENDPOINT_CONNECT,
+};
+
+static int _socket_do (struct config_endpoint *endpoint, int *sock, int sock_type, struct sockaddr_storage *addr, enum socket_op sockop);
+
+/*
+ * Create a socket of the given type that's listening on the given endpoint
+ */
+int socket_listen (struct config_endpoint *endpoint, int sock_type) {
+    int sock = -1;
+    struct sockaddr_storage addr;
+    
+    // and then _socket_do
+    if (_socket_do(endpoint, &sock, sock_type, &addr, SOCKOP_LISTEN))
+        return -1;
+    
+    // just return the socket, discard addr
+    return sock;
+}
+
+/*
+ * Initiate an async connect to the given endpoint for the given socket type. This should work for
+ * PF_INET and PF_LOCAL sockets, and in both cases, socket writeability should indicate that the
+ * connect succeeded.
+ *
+ * XXX: Currently it looks up the endpoint each time - the working addrinfo should be cached
+ */
+int socket_connect_async (struct config_endpoint *endpoint, int sock_type) {
+    int sock = -1;
+    struct sockaddr_storage addr;
+    
+    // and then _socket_do
+    if (_socket_do(endpoint, &sock, sock_type, &addr, SOCKOP_CONNECT_ASYNC))
+        return -1;
+    
+    // return the socket
+    return sock;
+}
+
+
+/*
+ * Do something to apply an endpoint to a socket
+ */
+static int _socket_do (struct config_endpoint *endpoint, int *sock, int sock_type, struct sockaddr_storage *addr, enum socket_op sockop) {
+    struct addrinfo *res = NULL, *info, _fake_res;
+    struct sockaddr_un _fake_addr_un;
+
+    if (endpoint->family == PF_UNIX) {
+        // getaddrinfo doesn't handle PF_UNIX, so we need to build a fake result
+        
+        // build the sockaddr_un
+        _fake_addr_un.sun_family = endpoint->family;
+        memcpy(_fake_addr_un.sun_path, endpoint->af.local.path, sizeof(_fake_addr_un.sun_path));
+        
+        // build the fake addrinfo res
+        _fake_res.ai_flags = 0;
+        _fake_res.ai_family = PF_UNIX;
+        _fake_res.ai_socktype = sock_type;
+        _fake_res.ai_protocol = 0;
+        _fake_res.ai_addrlen = SUN_LEN(&_fake_addr_un);
+        _fake_res.ai_addr = (struct sockaddr *) &_fake_addr_un;
+        _fake_res.ai_canonname = (char *) endpoint->af.local.path;  /* XXX: not const */
+        _fake_res.ai_next = NULL;
+
+        res = &_fake_res;
+
+    } else if (endpoint->family == PF_INET) {
+        // use the real getaddrinfo
+
+        struct addrinfo hints;
+        memset(&hints, 0, sizeof(hints));
+        
+        hints.ai_socktype = sock_type;
+        hints.ai_protocol = 0;
+        hints.ai_flags = (sockop & _SOCKOP_FLAGS_PASSIVE ? AI_PASSIVE : 0) | AI_CANONNAME;
+
+        int err;
+
+        // check that we have a service, doesn't make any sense without
+        if (!endpoint->af.inet.port)
+            ERROR("no service specified");
+
+        if ((err = getaddrinfo(endpoint->af.inet.addr, endpoint->af.inet.port, &hints, &res)) != 0)
+            ERROR("getaddrinfo: %s#%s: %s", endpoint->af.inet.addr, endpoint->af.inet.port, gai_strerror(err));
+
+    } else {
+        ERROR("invalid endpoint");
+    }
+
+    for (info = res; info; info = info->ai_next) {
+        if (sockop & _SOCKOP_SOCKET && (*sock = socket(info->ai_family, info->ai_socktype, info->ai_protocol)) == -1) {
+            PWARNING("socket(%d, %s)", info->ai_family, info->ai_canonname);
+            continue;
+        }
+
+        if (sockop & _SOCKOP_FCNTL_NONBLOCK && fcntl(*sock, F_SETFL, O_NONBLOCK) == -1) {
+            PWARNING("fcntl(%d, %s, F_SETFL O_NONBLOCK)", info->ai_family, info->ai_canonname);
+            close(*sock); *sock = -1;
+            continue;
+        }
+        
+        if (sockop & _SOCKOP_ENDPOINT_BIND && bind(*sock, info->ai_addr, info->ai_addrlen) == -1) {
+            PWARNING("bind(%d, %s)", info->ai_family, info->ai_canonname);
+            close(*sock); *sock = -1;
+            continue;
+        }
+
+        if (sockop & _SOCKOP_LISTEN && listen(*sock, SOCKET_LISTEN_BACKLOG) == -1) {
+            PWARNING("listen(%d, %s)", info->ai_family, info->ai_canonname);
+            close(*sock); *sock = -1;
+            continue;
+        }
+
+        if (sockop & _SOCKOP_ENDPOINT_CONNECT && connect(*sock, info->ai_addr, info->ai_addrlen) == -1) {
+            if (sockop & _SOCKOP_FCNTL_NONBLOCK && errno == EINPROGRESS) {
+                /* to be expected */
+
+            } else {
+                PWARNING("listen(%d, %s)", info->ai_family, info->ai_canonname);
+                close(*sock); *sock = -1;
+                continue;
+            }
+        }
+
+        // copy the succesfull address over
+        memcpy(addr, info->ai_addr, info->ai_addrlen);
+        
+        // exit the loop with a valid socket
+        assert(*sock != -1);
+        break;
+    }
+
+    if (*sock == -1)
+        ERROR("no working results from getaddrinfo: %s#%s", endpoint->af.inet.addr, endpoint->af.inet.port);
+
+    return 0;
+
+error:
+    if (res != 0 && res != &_fake_res)
+        freeaddrinfo(res);
+
+    return -1;
+}   
+