/*
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);
}