/*
* Copyright (c) 2001-2002 Packet Design, LLC.
* All rights reserved.
*
* Subject to the following obligations and disclaimer of warranty,
* use and redistribution of this software, in source or object code
* forms, with or without modifications are expressly permitted by
* Packet Design; provided, however, that:
*
* (i) Any and all reproductions of the source or object code
* must include the copyright notice above and the following
* disclaimer of warranties; and
* (ii) No rights are granted, in any manner or form, to use
* Packet Design trademarks, including the mark "PACKET DESIGN"
* on advertising, endorsements, or otherwise except as such
* appears in the above copyright notice or in the software.
*
* THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
* TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
* REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
* THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
* OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
* OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
* OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
* RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE
* LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
* OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
* DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
* USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
* Author: Archie Cobbs <archie@freebsd.org>
*/
#include <sys/types.h>
#include <sys/queue.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <syslog.h>
#include <errno.h>
#include <pthread.h>
#include <poll.h>
#include <openssl/ssl.h>
#include "structs/structs.h"
#include "structs/type/array.h"
#include "sys/alog.h"
#include "util/pevent.h"
#include "util/typed_mem.h"
#include "http/http_server.h"
#include "http/http_internal.h"
#define MEM_TYPE_CACHE "http_connection_cache"
#define MEM_TYPE_CONN "http_connection_cache.connection"
/* Connection cache structure */
struct http_connection_cache {
struct pevent_ctx *ctx; /* event context */
u_int max_num; /* max # in cache */
u_int max_idle; /* max socket idle */
u_int num; /* # in the cache now */
struct pevent *timer; /* exipiration timer */
pthread_mutex_t mutex; /* mutex */
TAILQ_HEAD(, cached_connection) list; /* connection list */
};
/* Cached connection structure */
struct cached_connection {
struct sockaddr_in peer; /* peer ip and port */
const SSL_CTX *ssl; /* ssl context */
time_t expiry; /* when connection expires */
int sock; /* connected tcp socket */
FILE *fp; /* connected tcp stream */
TAILQ_ENTRY(cached_connection) next; /* next in list */
};
/* Internal functions */
static void http_connection_cache_extract(
struct http_connection_cache *cache,
struct cached_connection *conn, FILE **fpp, int *sockp);
static void http_connection_cache_start_timer(
struct http_connection_cache *cache);
static void http_connection_cache_timeout(void *arg);
static int http_connection_cache_check(int sock);
/*********************************************************************
PUBLIC API FUNCTIONS
*********************************************************************/
/*
* Create a new connection cache.
*/
struct http_connection_cache *
_http_connection_cache_create(struct pevent_ctx *ctx,
u_int max_num, u_int max_idle)
{
struct http_connection_cache *cache;
/* Sanity check */
if (max_num == 0 || max_idle == 0) {
errno = EINVAL;
return (NULL);
}
/* Create new cache */
if ((cache = MALLOC(MEM_TYPE_CACHE, sizeof(*cache))) == NULL) {
alogf(LOG_ERR, "%s: %m", "malloc");
return (NULL);
}
/* Initialize it */
memset(cache, 0, sizeof(*cache));
TAILQ_INIT(&cache->list);
cache->ctx = ctx;
cache->max_num = max_num;
cache->max_idle = max_idle;
/* Initialize mutex */
if ((errno = pthread_mutex_init(&cache->mutex, NULL)) != 0) {
alogf(LOG_ERR, "%s: %m", "pthread_mutex_init");
FREE(MEM_TYPE_CACHE, cache);
return (NULL);
}
/* Done */
return (cache);
}
/*
* Destroy a connection cache.
*/
void
_http_connection_cache_destroy(struct http_connection_cache **cachep)
{
struct http_connection_cache *const cache = *cachep;
int r;
/* Sanity */
if (cache == NULL)
return;
*cachep = NULL;
/* Lock cache */
r = pthread_mutex_lock(&cache->mutex);
assert(r == 0);
/* Close all cached connections */
while (!TAILQ_EMPTY(&cache->list)) {
http_connection_cache_extract(cache,
TAILQ_FIRST(&cache->list), NULL, NULL);
}
/* Cleanup */
r = pthread_mutex_unlock(&cache->mutex);
assert(r == 0);
pthread_mutex_destroy(&cache->mutex);
FREE(MEM_TYPE_CACHE, cache);
}
/*
* Get a connection item from the connection cache.
*
* Returns 0 and sets *fp and *sock if found (and the connection
* is removed from the cache), else -1 and errno will be ENOENT.
*/
int
_http_connection_cache_get(struct http_connection_cache *cache,
const struct sockaddr_in *peer, const SSL_CTX *ssl,
FILE **fpp, int *sockp)
{
struct cached_connection *conn;
struct cached_connection *next;
int r;
/* Cache enabled? */
if (cache == NULL)
goto not_found;
DBG(HTTP_CONNECTION_CACHE, "looking in cache (%d entries)"
" for %s:%u, ssl=%p", cache->num, inet_ntoa(peer->sin_addr),
ntohs(peer->sin_port), ssl);
/* Lock cache */
r = pthread_mutex_lock(&cache->mutex);
assert(r == 0);
/* Find oldest matching connection */
for (conn = TAILQ_FIRST(&cache->list); conn != NULL; conn = next) {
FILE *fp;
int sock;
/* Get next element in list */
next = TAILQ_NEXT(conn, next);
/* See if this element matches */
if (conn->peer.sin_addr.s_addr != peer->sin_addr.s_addr
|| conn->peer.sin_port != peer->sin_port
|| conn->ssl != ssl)
continue;
/* Remove element from list */
http_connection_cache_extract(cache, conn, &fp, &sock);
/* If connection is no longer valid, close and skip it */
if (!http_connection_cache_check(sock)) {
DBG(HTTP_CONNECTION_CACHE, "old connection fp=%p"
" for %s:%u is no longer valid", fp,
inet_ntoa(peer->sin_addr), ntohs(peer->sin_port));
fclose(fp);
continue;
}
DBG(HTTP_CONNECTION_CACHE, "found connection fp=%p for %s:%u",
fp, inet_ntoa(peer->sin_addr), ntohs(peer->sin_port));
/* Return it */
*fpp = fp;
*sockp = sock;
/* Done */
r = pthread_mutex_unlock(&cache->mutex);
assert(r == 0);
return (0);
}
/* Unlock cache */
r = pthread_mutex_unlock(&cache->mutex);
assert(r == 0);
not_found:
DBG(HTTP_CONNECTION_CACHE, "nothing found for %s:%u, ssl=%p",
inet_ntoa(peer->sin_addr), ntohs(peer->sin_port), ssl);
errno = ENOENT;
return (-1);
}
/*
* Store a connection in the cache. It is assumed that
* calling 'fclose(fp)' implicitly closes 'sock' as well.
*
* Returns zero if stored, else -1 and sets errno (in which case
* caller is responsible for dealing with 'fp' and 'sock').
*/
int
_http_connection_cache_put(struct http_connection_cache *cache,
const struct sockaddr_in *peer, const SSL_CTX *ssl, FILE *fp, int sock)
{
struct cached_connection *conn;
int r;
/* Is cache enabled? */
if (cache == NULL) {
errno = ENXIO;
return (-1);
}
/* Get a new connection holder */
if ((conn = MALLOC(MEM_TYPE_CONN, sizeof(*conn))) == NULL)
return (-1);
memset(conn, 0, sizeof(*conn));
conn->peer = *peer;
conn->ssl = ssl;
conn->fp = fp;
conn->sock = sock;
/* Set expiration time */
conn->expiry = time(NULL) + cache->max_idle;
/* Lock cache */
r = pthread_mutex_lock(&cache->mutex);
assert(r == 0);
/* Is there room in the cache? If not, drop oldest one. */
if (cache->num >= cache->max_num) {
http_connection_cache_extract(cache,
TAILQ_FIRST(&cache->list), NULL, NULL);
}
/* Add connection to the cache */
TAILQ_INSERT_TAIL(&cache->list, conn, next);
cache->num++;
DBG(HTTP_CONNECTION_CACHE, "connection fp=%p for %s:%u ssl=%p"
" cached (%d total)", fp, inet_ntoa(conn->peer.sin_addr),
ntohs(conn->peer.sin_port), ssl, cache->num);
/* Make sure the timer is running */
if (cache->num == 1)
http_connection_cache_start_timer(cache);
/* Unlock cache */
r = pthread_mutex_unlock(&cache->mutex);
assert(r == 0);
/* Done */
return (0);
}
/*********************************************************************
INTERNAL FUNCTIONS
*********************************************************************/
/*
* Remove a cached item from the cache.
*
* This assumes the cache is locked.
*/
static void
http_connection_cache_extract(struct http_connection_cache *cache,
struct cached_connection *conn, FILE **fpp, int *sockp)
{
struct cached_connection *const oldest = TAILQ_FIRST(&cache->list);
/* Remove item and decrement count */
TAILQ_REMOVE(&cache->list, conn, next);
cache->num--;
/* If we are the oldest, kill timer in order to restart later */
if (conn == oldest)
pevent_unregister(&cache->timer);
/* Restart timer if any connections left */
if (cache->num > 0)
http_connection_cache_start_timer(cache);
/* Return or close connection */
if (fpp != NULL) {
*fpp = conn->fp;
*sockp = conn->sock;
} else
fclose(conn->fp);
/* Free connection */
FREE(MEM_TYPE_CONN, conn);
}
/*
* Start the idle timer based on the oldest item in the cache.
*
* This assumes the cache is locked.
*/
static void
http_connection_cache_start_timer(struct http_connection_cache *cache)
{
struct cached_connection *const oldest = TAILQ_FIRST(&cache->list);
const time_t now = time(NULL);
int timeout;
/* Don't start timer when not appropriate */
if (cache->num == 0
|| cache->max_idle == 0
|| cache->timer != NULL)
return;
/* Get time until oldest connection expires */
timeout = (now < oldest->expiry) ? (oldest->expiry - now) * 1000 : 0;
/* Start timer */
if (pevent_register(cache->ctx, &cache->timer, 0,
&cache->mutex, http_connection_cache_timeout, cache,
PEVENT_TIME, timeout) == -1)
alogf(LOG_ERR, "%s: %m", "pevent_register");
}
/*
* Expire a cached connection from the cache.
*
* This assumes the cache is locked.
*/
static void
http_connection_cache_timeout(void *arg)
{
struct http_connection_cache *cache = arg;
const time_t now = time(NULL);
/* Remove exipred entries */
while (!TAILQ_EMPTY(&cache->list)) {
struct cached_connection *const oldest
= TAILQ_FIRST(&cache->list);
if (oldest->expiry < now)
break;
http_connection_cache_extract(cache, oldest, NULL, NULL);
}
/* Restart timer if any are left */
if (cache->num > 0)
http_connection_cache_start_timer(cache);
}
/*
* http_connection_cache_check() checks whether a socket is invalid.
*
* NOTE: we are not checking if the socket is valid. All we can tell is we
* think the socket is supposed to be valid because the server said keep-alive.
* The connection should not be closed, but if it gracefully closed then the
* server would have sent a FIN, which we will translate in to an EOF. This
* will mean the socket is readable (because the EOF) is in the pipe. In all
* other cases, the socket should not be readable because we are not expecting
* data from the server. If the server sent us anything we should declare the
* socket invalid. We still don't know if the socket is valid or not because if
* the server crashed then nothing will be there for us, same as if the
* connection is still working.
*/
static int
http_connection_cache_check(int sock)
{
struct pollfd myfd;
/* Poll for readability */
memset(&myfd, 0, sizeof(myfd));
myfd.fd = sock;
myfd.events = POLLRDNORM;
/* Return invalid if readable or other error */
return (poll(&myfd, 1, 0) == 0);
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>