Do not give IPv4 any special treatment in httpd code
[deb_shairplay.git] / src / lib / httpd.c
index 5779c0646e6501c8b012ba815bf547c891bf99d0..7cb6fdea61d4f322778f3407ba9f056f6afc54b7 100644 (file)
@@ -1,3 +1,17 @@
+/**
+ *  Copyright (C) 2011-2012  Juho Vähä-Herttua
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ */
+
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
@@ -22,9 +36,8 @@ struct httpd_s {
        logger_t *logger;
        httpd_callbacks_t callbacks;
 
-       int use_rtsp;
-
        int max_connections;
+       int open_connections;
        http_connection_t *connections;
 
        /* These variables only edited mutex locked */
@@ -33,12 +46,13 @@ struct httpd_s {
        thread_handle_t thread;
        mutex_handle_t run_mutex;
 
-       /* Server fd for accepting connections */
-       int server_fd;
+       /* Server fds for accepting connections */
+       int server_fd4;
+       int server_fd6;
 };
 
 httpd_t *
-httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int max_connections, int use_rtsp)
+httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int max_connections)
 {
        httpd_t *httpd;
 
@@ -52,7 +66,6 @@ httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int max_connections,
                return NULL;
        }
 
-       httpd->use_rtsp = !!use_rtsp;
        httpd->max_connections = max_connections;
        httpd->connections = calloc(max_connections, sizeof(http_connection_t));
        if (!httpd->connections) {
@@ -95,17 +108,53 @@ httpd_add_connection(httpd_t *httpd, int fd, unsigned char *local, int local_len
                }
        }
        if (i == httpd->max_connections) {
-               logger_log(httpd->logger, LOGGER_INFO, "Max connections reached\n");
+               /* This code should never be reached, we do not select server_fds when full */
+               logger_log(httpd->logger, LOGGER_INFO, "Max connections reached");
                shutdown(fd, SHUT_RDWR);
                closesocket(fd);
                return;
        }
 
+       httpd->open_connections++;
        httpd->connections[i].socket_fd = fd;
        httpd->connections[i].connected = 1;
        httpd->connections[i].user_data = httpd->callbacks.conn_init(httpd->callbacks.opaque, local, local_len, remote, remote_len);
 }
 
+static int
+httpd_accept_connection(httpd_t *httpd, int server_fd, int is_ipv6)
+{
+       struct sockaddr_storage remote_saddr;
+       socklen_t remote_saddrlen;
+       struct sockaddr_storage local_saddr;
+       socklen_t local_saddrlen;
+       unsigned char *local, *remote;
+       int local_len, remote_len;
+       int ret, fd;
+
+       remote_saddrlen = sizeof(remote_saddr);
+       fd = accept(server_fd, (struct sockaddr *)&remote_saddr, &remote_saddrlen);
+       if (fd == -1) {
+               /* FIXME: Error happened */
+               return -1;
+       }
+
+       local_saddrlen = sizeof(local_saddr);
+       ret = getsockname(fd, (struct sockaddr *)&local_saddr, &local_saddrlen);
+       if (ret == -1) {
+               closesocket(fd);
+               return 0;
+       }
+
+       logger_log(httpd->logger, LOGGER_INFO, "Accepted %s client on socket %d",
+                  (is_ipv6 ? "IPv6"  : "IPv4"), fd);
+       local = netutils_get_address(&local_saddr, &local_len);
+       remote = netutils_get_address(&remote_saddr, &remote_len);
+
+       httpd_add_connection(httpd, fd, local, local_len, remote, remote_len);
+       return 1;
+}
+
 static void
 httpd_remove_connection(httpd_t *httpd, http_connection_t *connection)
 {
@@ -117,6 +166,7 @@ httpd_remove_connection(httpd_t *httpd, http_connection_t *connection)
        shutdown(connection->socket_fd, SHUT_WR);
        closesocket(connection->socket_fd);
        connection->connected = 0;
+       httpd->open_connections--;
 }
 
 static THREAD_RETVAL
@@ -147,8 +197,20 @@ httpd_thread(void *arg)
 
                /* Get the correct nfds value and set rfds */
                FD_ZERO(&rfds);
-               FD_SET(httpd->server_fd, &rfds);
-               nfds = httpd->server_fd+1;
+               if (httpd->open_connections < httpd->max_connections) {
+                       if (httpd->server_fd4 != -1) {
+                               FD_SET(httpd->server_fd4, &rfds);
+                               if (nfds <= httpd->server_fd4) {
+                                       nfds = httpd->server_fd4+1;
+                               }
+                       }
+                       if (httpd->server_fd6 != -1) {
+                               FD_SET(httpd->server_fd6, &rfds);
+                               if (nfds <= httpd->server_fd6) {
+                                       nfds = httpd->server_fd6+1;
+                               }
+                       }
+               }
                for (i=0; i<httpd->max_connections; i++) {
                        int socket_fd;
                        if (!httpd->connections[i].connected) {
@@ -167,38 +229,27 @@ httpd_thread(void *arg)
                        continue;
                } else if (ret == -1) {
                        /* FIXME: Error happened */
-                       logger_log(httpd->logger, LOGGER_INFO, "Error in select\n");
+                       logger_log(httpd->logger, LOGGER_INFO, "Error in select");
                        break;
                }
 
-               if (FD_ISSET(httpd->server_fd, &rfds)) {
-                       struct sockaddr_storage remote_saddr;
-                       socklen_t remote_saddrlen;
-                       struct sockaddr_storage local_saddr;
-                       socklen_t local_saddrlen;
-                       unsigned char *local, *remote;
-                       int local_len, remote_len;
-                       int fd;
-
-                       remote_saddrlen = sizeof(remote_saddr);
-                       fd = accept(httpd->server_fd, (struct sockaddr *)&remote_saddr, &remote_saddrlen);
-                       if (fd == -1) {
-                               /* FIXME: Error happened */
+               if (httpd->open_connections < httpd->max_connections &&
+                   httpd->server_fd4 != -1 && FD_ISSET(httpd->server_fd4, &rfds)) {
+                       ret = httpd_accept_connection(httpd, httpd->server_fd4, 0);
+                       if (ret == -1) {
                                break;
+                       } else if (ret == 0) {
+                               continue;
                        }
-
-                       local_saddrlen = sizeof(local_saddr);
-                       ret = getsockname(fd, (struct sockaddr *)&local_saddr, &local_saddrlen);
+               }
+               if (httpd->open_connections < httpd->max_connections &&
+                   httpd->server_fd6 != -1 && FD_ISSET(httpd->server_fd6, &rfds)) {
+                       ret = httpd_accept_connection(httpd, httpd->server_fd6, 1);
                        if (ret == -1) {
-                               closesocket(fd);
+                               break;
+                       } else if (ret == 0) {
                                continue;
                        }
-
-                       logger_log(httpd->logger, LOGGER_INFO, "Accepted client on socket %d\n", fd);
-                       local = netutils_get_address(&local_saddr, &local_len);
-                       remote = netutils_get_address(&remote_saddr, &remote_len);
-
-                       httpd_add_connection(httpd, fd, local, local_len, remote, remote_len);
                }
                for (i=0; i<httpd->max_connections; i++) {
                        http_connection_t *connection = &httpd->connections[i];
@@ -212,14 +263,14 @@ httpd_thread(void *arg)
 
                        /* If not in the middle of request, allocate one */
                        if (!connection->request) {
-                               connection->request = http_request_init(httpd->use_rtsp);
+                               connection->request = http_request_init();
                                assert(connection->request);
                        }
 
-                       logger_log(httpd->logger, LOGGER_DEBUG, "Receiving on socket %d\n", httpd->connections[i].socket_fd);
+                       logger_log(httpd->logger, LOGGER_DEBUG, "Receiving on socket %d", connection->socket_fd);
                        ret = recv(connection->socket_fd, buffer, sizeof(buffer), 0);
                        if (ret == 0) {
-                               logger_log(httpd->logger, LOGGER_INFO, "Connection closed\n");
+                               logger_log(httpd->logger, LOGGER_INFO, "Connection closed for socket %d", connection->socket_fd);
                                httpd_remove_connection(httpd, connection);
                                continue;
                        }
@@ -227,7 +278,7 @@ httpd_thread(void *arg)
                        /* Parse HTTP request from data read from connection */
                        http_request_add_data(connection->request, buffer, ret);
                        if (http_request_has_error(connection->request)) {
-                               logger_log(httpd->logger, LOGGER_INFO, "Error in parsing: %s\n", http_request_get_error_name(connection->request));
+                               logger_log(httpd->logger, LOGGER_INFO, "Error in parsing: %s", http_request_get_error_name(connection->request));
                                httpd_remove_connection(httpd, connection);
                                continue;
                        }
@@ -254,15 +305,22 @@ httpd_thread(void *arg)
                                                ret = send(connection->socket_fd, data+written, datalen-written, 0);
                                                if (ret == -1) {
                                                        /* FIXME: Error happened */
-                                                       logger_log(httpd->logger, LOGGER_INFO, "Error in sending data\n");
+                                                       logger_log(httpd->logger, LOGGER_INFO, "Error in sending data");
                                                        break;
                                                }
                                                written += ret;
                                        }
+
+                                       if (http_response_get_disconnect(response)) {
+                                               logger_log(httpd->logger, LOGGER_INFO, "Disconnecting on software request");
+                                               httpd_remove_connection(httpd, connection);
+                                       }
                                } else {
-                                       logger_log(httpd->logger, LOGGER_INFO, "Didn't get response\n");
+                                       logger_log(httpd->logger, LOGGER_INFO, "Didn't get response");
                                }
                                http_response_destroy(response);
+                       } else {
+                               logger_log(httpd->logger, LOGGER_DEBUG, "Request not complete, waiting for more data...");
                        }
                }
        }
@@ -274,11 +332,21 @@ httpd_thread(void *arg)
                if (!connection->connected) {
                        continue;
                }
-               logger_log(httpd->logger, LOGGER_INFO, "Removing connection\n");
+               logger_log(httpd->logger, LOGGER_INFO, "Removing connection for socket %d", connection->socket_fd);
                httpd_remove_connection(httpd, connection);
        }
 
-       logger_log(httpd->logger, LOGGER_INFO, "Exiting thread\n");
+       /* Close server sockets since they are not used any more */
+       if (httpd->server_fd4 != -1) {
+               closesocket(httpd->server_fd4);
+               httpd->server_fd4 = -1;
+       }
+       if (httpd->server_fd6 != -1) {
+               closesocket(httpd->server_fd6);
+               httpd->server_fd6 = -1;
+       }
+
+       logger_log(httpd->logger, LOGGER_INFO, "Exiting HTTP thread");
 
        return 0;
 }
@@ -286,6 +354,9 @@ httpd_thread(void *arg)
 int
 httpd_start(httpd_t *httpd, unsigned short *port)
 {
+       /* How many connection attempts are kept in queue */
+       int backlog = 5;
+
        assert(httpd);
        assert(port);
 
@@ -295,18 +366,33 @@ httpd_start(httpd_t *httpd, unsigned short *port)
                return 0;
        }
 
-       httpd->server_fd = netutils_init_socket(port, 1, 0);
-       if (httpd->server_fd == -1) {
-               logger_log(httpd->logger, LOGGER_INFO, "Error initialising socket %d\n", SOCKET_GET_ERROR());
+       httpd->server_fd4 = netutils_init_socket(port, 0, 0);
+       if (httpd->server_fd4 == -1) {
+               logger_log(httpd->logger, LOGGER_ERR, "Error initialising socket %d", SOCKET_GET_ERROR());
                MUTEX_UNLOCK(httpd->run_mutex);
                return -1;
        }
-       if (listen(httpd->server_fd, 5) == -1) {
-               logger_log(httpd->logger, LOGGER_INFO, "Error listening to socket\n");
+       httpd->server_fd6 = netutils_init_socket(port, 1, 0);
+       if (httpd->server_fd6 == -1) {
+               logger_log(httpd->logger, LOGGER_WARNING, "Error initialising IPv6 socket %d", SOCKET_GET_ERROR());
+               logger_log(httpd->logger, LOGGER_WARNING, "Continuing without IPv6 support");
+       }
+
+       if (httpd->server_fd4 != -1 && listen(httpd->server_fd4, backlog) == -1) {
+               logger_log(httpd->logger, LOGGER_ERR, "Error listening to IPv4 socket");
+               closesocket(httpd->server_fd4);
+               closesocket(httpd->server_fd6);
+               MUTEX_UNLOCK(httpd->run_mutex);
+               return -2;
+       }
+       if (httpd->server_fd6 != -1 && listen(httpd->server_fd6, backlog) == -1) {
+               logger_log(httpd->logger, LOGGER_ERR, "Error listening to IPv6 socket");
+               closesocket(httpd->server_fd4);
+               closesocket(httpd->server_fd6);
                MUTEX_UNLOCK(httpd->run_mutex);
                return -2;
        }
-       logger_log(httpd->logger, LOGGER_INFO, "Initialized server socket\n");
+       logger_log(httpd->logger, LOGGER_INFO, "Initialized server socket(s)");
 
        /* Set values correctly and create new thread */
        httpd->running = 1;
@@ -317,6 +403,20 @@ httpd_start(httpd_t *httpd, unsigned short *port)
        return 1;
 }
 
+int
+httpd_is_running(httpd_t *httpd)
+{
+       int running;
+
+       assert(httpd);
+
+       MUTEX_LOCK(httpd->run_mutex);
+       running = httpd->running || !httpd->joined;
+       MUTEX_UNLOCK(httpd->run_mutex);
+
+       return running;
+}
+
 void
 httpd_stop(httpd_t *httpd)
 {