| /* |
| This file is part of libmicrospdy |
| Copyright Copyright (C) 2012 Andrey Uzunov |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program 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 General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| /** |
| * @file microspdy/daemon.c |
| * @brief daemon functionality |
| * @author Andrey Uzunov |
| */ |
| |
| #include "platform.h" |
| #include "structures.h" |
| #include "internal.h" |
| #include "session.h" |
| #include "io.h" |
| |
| |
| /** |
| * Default implementation of the panic function, |
| * prints an error message and aborts. |
| * |
| * @param cls unused |
| * @param file name of the file with the problem |
| * @param line line number with the problem |
| * @param reason error message with details |
| */ |
| static void |
| spdyf_panic_std (void *cls, |
| const char *file, |
| unsigned int line, |
| const char *reason) |
| { |
| (void)cls; |
| fprintf (stdout, "Fatal error in libmicrospdy %s:%u: %s\n", |
| file, line, reason); |
| //raise(SIGINT); //used for gdb |
| abort (); |
| } |
| |
| |
| /** |
| * Global handler for fatal errors. |
| */ |
| SPDY_PanicCallback spdyf_panic = &spdyf_panic_std; |
| |
| |
| /** |
| * Global closure argument for "spdyf_panic". |
| */ |
| void *spdyf_panic_cls; |
| |
| |
| /** |
| * Free resources associated with all closed connections. |
| * (destroy responses, free buffers, etc.). |
| * |
| * @param daemon daemon to clean up |
| */ |
| static void |
| spdyf_cleanup_sessions (struct SPDY_Daemon *daemon) |
| { |
| struct SPDY_Session *session; |
| |
| while (NULL != (session = daemon->cleanup_head)) |
| { |
| DLL_remove (daemon->cleanup_head, |
| daemon->cleanup_tail, |
| session); |
| |
| SPDYF_session_destroy(session); |
| } |
| } |
| |
| |
| /** |
| * Closing of all connections handled by the daemon. |
| * |
| * @param daemon SPDY daemon |
| */ |
| static void |
| spdyf_close_all_sessions (struct SPDY_Daemon *daemon) |
| { |
| struct SPDY_Session *session; |
| |
| while (NULL != (session = daemon->sessions_head)) |
| { |
| //prepare GOAWAY frame |
| SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_OK, true); |
| //try to send the frame (it is best effort, so it will maybe sent) |
| SPDYF_session_write(session,true); |
| SPDYF_session_close(session); |
| } |
| |
| spdyf_cleanup_sessions(daemon); |
| } |
| |
| |
| /** |
| * Parse a list of options given as varargs. |
| * |
| * @param daemon the daemon to initialize |
| * @param valist the options |
| * @return SPDY_YES on success, SPDY_NO on error |
| */ |
| static int |
| spdyf_parse_options_va (struct SPDY_Daemon *daemon, |
| va_list valist) |
| { |
| enum SPDY_DAEMON_OPTION opt; |
| |
| while (SPDY_DAEMON_OPTION_END != (opt = (enum SPDY_DAEMON_OPTION) va_arg (valist, int))) |
| { |
| if(opt & daemon->options) |
| { |
| SPDYF_DEBUG("Daemon option %i used twice",opt); |
| return SPDY_NO; |
| } |
| daemon->options |= opt; |
| |
| switch (opt) |
| { |
| case SPDY_DAEMON_OPTION_SESSION_TIMEOUT: |
| daemon->session_timeout = va_arg (valist, unsigned int) * 1000; |
| break; |
| case SPDY_DAEMON_OPTION_SOCK_ADDR: |
| daemon->address = va_arg (valist, struct sockaddr *); |
| break; |
| case SPDY_DAEMON_OPTION_FLAGS: |
| daemon->flags = va_arg (valist, enum SPDY_DAEMON_FLAG); |
| break; |
| case SPDY_DAEMON_OPTION_IO_SUBSYSTEM: |
| daemon->io_subsystem = va_arg (valist, enum SPDY_IO_SUBSYSTEM); |
| break; |
| case SPDY_DAEMON_OPTION_MAX_NUM_FRAMES: |
| daemon->max_num_frames = va_arg (valist, uint32_t); |
| break; |
| default: |
| SPDYF_DEBUG("Wrong option for the daemon %i",opt); |
| return SPDY_NO; |
| } |
| } |
| return SPDY_YES; |
| } |
| |
| |
| void |
| SPDY_set_panic_func (SPDY_PanicCallback cb, |
| void *cls) |
| { |
| spdyf_panic = cb; |
| spdyf_panic_cls = cls; |
| } |
| |
| |
| struct SPDY_Daemon * |
| SPDYF_start_daemon_va (uint16_t port, |
| const char *certfile, |
| const char *keyfile, |
| SPDY_NewSessionCallback nscb, |
| SPDY_SessionClosedCallback sccb, |
| SPDY_NewRequestCallback nrcb, |
| SPDY_NewDataCallback npdcb, |
| SPDYF_NewStreamCallback fnscb, |
| SPDYF_NewDataCallback fndcb, |
| void * cls, |
| void * fcls, |
| va_list valist) |
| { |
| struct SPDY_Daemon *daemon = NULL; |
| int afamily; |
| int option_on = 1; |
| int ret; |
| struct sockaddr_in* servaddr4 = NULL; |
| #if HAVE_INET6 |
| struct sockaddr_in6* servaddr6 = NULL; |
| #endif |
| socklen_t addrlen; |
| |
| if (NULL == (daemon = malloc (sizeof (struct SPDY_Daemon)))) |
| { |
| SPDYF_DEBUG("malloc"); |
| return NULL; |
| } |
| memset (daemon, 0, sizeof (struct SPDY_Daemon)); |
| daemon->socket_fd = -1; |
| daemon->port = port; |
| |
| if(SPDY_YES != spdyf_parse_options_va (daemon, valist)) |
| { |
| SPDYF_DEBUG("parse"); |
| goto free_and_fail; |
| } |
| |
| if(0 == daemon->max_num_frames) |
| daemon->max_num_frames = SPDYF_NUM_SENT_FRAMES_AT_ONCE; |
| |
| if(!port && NULL == daemon->address) |
| { |
| SPDYF_DEBUG("Port is 0"); |
| goto free_and_fail; |
| } |
| if(0 == daemon->io_subsystem) |
| daemon->io_subsystem = SPDY_IO_SUBSYSTEM_OPENSSL; |
| |
| if(SPDY_YES != SPDYF_io_set_daemon(daemon, daemon->io_subsystem)) |
| goto free_and_fail; |
| |
| if(SPDY_IO_SUBSYSTEM_RAW != daemon->io_subsystem) |
| { |
| if (NULL == certfile |
| || NULL == (daemon->certfile = strdup (certfile))) |
| { |
| SPDYF_DEBUG("strdup (certfile)"); |
| goto free_and_fail; |
| } |
| if (NULL == keyfile |
| || NULL == (daemon->keyfile = strdup (keyfile))) |
| { |
| SPDYF_DEBUG("strdup (keyfile)"); |
| goto free_and_fail; |
| } |
| } |
| |
| daemon->new_session_cb = nscb; |
| daemon->session_closed_cb = sccb; |
| daemon->new_request_cb = nrcb; |
| daemon->received_data_cb = npdcb; |
| daemon->cls = cls; |
| daemon->fcls = fcls; |
| daemon->fnew_stream_cb = fnscb; |
| daemon->freceived_data_cb = fndcb; |
| |
| #if HAVE_INET6 |
| //handling IPv6 |
| if((daemon->flags & SPDY_DAEMON_FLAG_ONLY_IPV6) |
| && NULL != daemon->address && AF_INET6 != daemon->address->sa_family) |
| { |
| SPDYF_DEBUG("SPDY_DAEMON_FLAG_ONLY_IPV6 set but IPv4 address provided"); |
| goto free_and_fail; |
| } |
| |
| addrlen = sizeof (struct sockaddr_in6); |
| |
| if(NULL == daemon->address) |
| { |
| if (NULL == (servaddr6 = malloc (addrlen))) |
| { |
| SPDYF_DEBUG("malloc"); |
| goto free_and_fail; |
| } |
| memset (servaddr6, 0, addrlen); |
| servaddr6->sin6_family = AF_INET6; |
| servaddr6->sin6_addr = in6addr_any; |
| servaddr6->sin6_port = htons (port); |
| daemon->address = (struct sockaddr *) servaddr6; |
| } |
| |
| if(AF_INET6 == daemon->address->sa_family) |
| { |
| afamily = PF_INET6; |
| } |
| else |
| { |
| afamily = PF_INET; |
| } |
| #else |
| //handling IPv4 |
| if(daemon->flags & SPDY_DAEMON_FLAG_ONLY_IPV6) |
| { |
| SPDYF_DEBUG("SPDY_DAEMON_FLAG_ONLY_IPV6 set but no support"); |
| goto free_and_fail; |
| } |
| |
| addrlen = sizeof (struct sockaddr_in); |
| |
| if(NULL == daemon->address) |
| { |
| if (NULL == (servaddr4 = malloc (addrlen))) |
| { |
| SPDYF_DEBUG("malloc"); |
| goto free_and_fail; |
| } |
| memset (servaddr4, 0, addrlen); |
| servaddr4->sin_family = AF_INET; |
| servaddr4->sin_addr = INADDR_ANY; |
| servaddr4->sin_port = htons (port); |
| daemon->address = (struct sockaddr *) servaddr4; |
| } |
| |
| afamily = PF_INET; |
| #endif |
| |
| daemon->socket_fd = socket (afamily, SOCK_STREAM, 0); |
| if (-1 == daemon->socket_fd) |
| { |
| SPDYF_DEBUG("sock"); |
| goto free_and_fail; |
| } |
| |
| //setting option for the socket to reuse address |
| ret = setsockopt(daemon->socket_fd, SOL_SOCKET, SO_REUSEADDR, &option_on, sizeof(option_on)); |
| if(ret) |
| { |
| SPDYF_DEBUG("WARNING: SO_REUSEADDR was not set for the server"); |
| } |
| |
| #if HAVE_INET6 |
| if(daemon->flags & SPDY_DAEMON_FLAG_ONLY_IPV6) |
| { |
| ret = setsockopt(daemon->socket_fd, IPPROTO_IPV6, IPV6_V6ONLY, &option_on, sizeof(option_on)); |
| if(ret) |
| { |
| SPDYF_DEBUG("setsockopt with IPPROTO_IPV6 failed"); |
| goto free_and_fail; |
| } |
| } |
| #endif |
| |
| if (-1 == bind (daemon->socket_fd, daemon->address, addrlen)) |
| { |
| SPDYF_DEBUG("bind %i",errno); |
| goto free_and_fail; |
| } |
| |
| if (listen (daemon->socket_fd, 20) < 0) |
| { |
| SPDYF_DEBUG("listen %i",errno); |
| goto free_and_fail; |
| } |
| |
| if(SPDY_YES != daemon->fio_init(daemon)) |
| { |
| SPDYF_DEBUG("tls"); |
| goto free_and_fail; |
| } |
| |
| return daemon; |
| |
| //for GOTO |
| free_and_fail: |
| if(daemon->socket_fd > 0) |
| (void)close (daemon->socket_fd); |
| |
| free(servaddr4); |
| #if HAVE_INET6 |
| free(servaddr6); |
| #endif |
| if(NULL != daemon->certfile) |
| free(daemon->certfile); |
| if(NULL != daemon->keyfile) |
| free(daemon->keyfile); |
| free (daemon); |
| |
| return NULL; |
| } |
| |
| |
| void |
| SPDYF_stop_daemon (struct SPDY_Daemon *daemon) |
| { |
| daemon->fio_deinit(daemon); |
| |
| shutdown (daemon->socket_fd, SHUT_RDWR); |
| spdyf_close_all_sessions (daemon); |
| (void)close (daemon->socket_fd); |
| |
| if(!(SPDY_DAEMON_OPTION_SOCK_ADDR & daemon->options)) |
| free(daemon->address); |
| |
| free(daemon->certfile); |
| free(daemon->keyfile); |
| |
| free(daemon); |
| } |
| |
| |
| int |
| SPDYF_get_timeout (struct SPDY_Daemon *daemon, |
| unsigned long long *timeout) |
| { |
| unsigned long long earliest_deadline = 0; |
| unsigned long long now; |
| struct SPDY_Session *pos; |
| bool have_timeout; |
| |
| if(0 == daemon->session_timeout) |
| return SPDY_NO; |
| |
| now = SPDYF_monotonic_time(); |
| have_timeout = false; |
| for (pos = daemon->sessions_head; NULL != pos; pos = pos->next) |
| { |
| if ( (! have_timeout) || |
| (earliest_deadline > pos->last_activity + daemon->session_timeout) ) |
| earliest_deadline = pos->last_activity + daemon->session_timeout; |
| |
| have_timeout = true; |
| |
| if (SPDY_YES == pos->fio_is_pending(pos)) |
| { |
| earliest_deadline = 0; |
| break; |
| } |
| } |
| |
| if (!have_timeout) |
| return SPDY_NO; |
| if (earliest_deadline <= now) |
| *timeout = 0; |
| else |
| *timeout = earliest_deadline - now; |
| |
| return SPDY_YES; |
| } |
| |
| |
| int |
| SPDYF_get_fdset (struct SPDY_Daemon *daemon, |
| fd_set *read_fd_set, |
| fd_set *write_fd_set, |
| fd_set *except_fd_set, |
| bool all) |
| { |
| (void)except_fd_set; |
| struct SPDY_Session *pos; |
| int fd; |
| int max_fd = -1; |
| |
| fd = daemon->socket_fd; |
| if (-1 != fd) |
| { |
| FD_SET (fd, read_fd_set); |
| /* update max file descriptor */ |
| max_fd = fd; |
| } |
| |
| for (pos = daemon->sessions_head; NULL != pos; pos = pos->next) |
| { |
| fd = pos->socket_fd; |
| FD_SET(fd, read_fd_set); |
| if (all |
| || (NULL != pos->response_queue_head) //frames pending |
| || (NULL != pos->write_buffer) //part of last frame pending |
| || (SPDY_SESSION_STATUS_CLOSING == pos->status) //the session is about to be closed |
| || (daemon->session_timeout //timeout passed for the session |
| && (pos->last_activity + daemon->session_timeout < SPDYF_monotonic_time())) |
| || (SPDY_YES == pos->fio_is_pending(pos)) //data in TLS' read buffer pending |
| || ((pos->read_buffer_offset - pos->read_buffer_beginning) > 0) // data in lib's read buffer pending |
| ) |
| FD_SET(fd, write_fd_set); |
| if(fd > max_fd) |
| max_fd = fd; |
| } |
| |
| return max_fd; |
| } |
| |
| |
| void |
| SPDYF_run (struct SPDY_Daemon *daemon) |
| { |
| struct SPDY_Session *pos; |
| struct SPDY_Session *next; |
| int num_ready; |
| fd_set rs; |
| fd_set ws; |
| fd_set es; |
| int max; |
| struct timeval timeout; |
| int ds; |
| |
| timeout.tv_sec = 0; |
| timeout.tv_usec = 0; |
| FD_ZERO (&rs); |
| FD_ZERO (&ws); |
| FD_ZERO (&es); |
| //here we need really all descriptors to see later which are ready |
| max = SPDYF_get_fdset(daemon,&rs,&ws,&es, true); |
| |
| num_ready = select (max + 1, &rs, &ws, &es, &timeout); |
| |
| if(num_ready < 1) |
| return; |
| |
| if ( (-1 != (ds = daemon->socket_fd)) && |
| (FD_ISSET (ds, &rs)) ){ |
| SPDYF_session_accept(daemon); |
| } |
| |
| next = daemon->sessions_head; |
| while (NULL != (pos = next)) |
| { |
| next = pos->next; |
| ds = pos->socket_fd; |
| if (ds != -1) |
| { |
| //fill the read buffer |
| if (FD_ISSET (ds, &rs) || pos->fio_is_pending(pos)){ |
| SPDYF_session_read(pos); |
| } |
| |
| //do something with the data in read buffer |
| if(SPDY_NO == SPDYF_session_idle(pos)) |
| { |
| //the session was closed, cannot write anymore |
| //continue; |
| } |
| |
| //write whatever has been put to the response queue |
| //during read or idle operation, something might be put |
| //on the response queue, thus call write operation |
| if (FD_ISSET (ds, &ws)){ |
| if(SPDY_NO == SPDYF_session_write(pos, false)) |
| { |
| //SPDYF_session_close(pos); |
| //continue; |
| } |
| } |
| |
| /* the response queue has been flushed for half closed |
| * connections, so let close them */ |
| /*if(pos->read_closed) |
| { |
| SPDYF_session_close(pos); |
| }*/ |
| } |
| } |
| |
| spdyf_cleanup_sessions(daemon); |
| } |