Annotation of embedaddon/libpdel/http/http_connection_cache.c, revision 1.1
1.1 ! misho 1:
! 2: /*
! 3: * Copyright (c) 2001-2002 Packet Design, LLC.
! 4: * All rights reserved.
! 5: *
! 6: * Subject to the following obligations and disclaimer of warranty,
! 7: * use and redistribution of this software, in source or object code
! 8: * forms, with or without modifications are expressly permitted by
! 9: * Packet Design; provided, however, that:
! 10: *
! 11: * (i) Any and all reproductions of the source or object code
! 12: * must include the copyright notice above and the following
! 13: * disclaimer of warranties; and
! 14: * (ii) No rights are granted, in any manner or form, to use
! 15: * Packet Design trademarks, including the mark "PACKET DESIGN"
! 16: * on advertising, endorsements, or otherwise except as such
! 17: * appears in the above copyright notice or in the software.
! 18: *
! 19: * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
! 20: * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
! 21: * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
! 22: * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
! 23: * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
! 24: * OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
! 25: * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
! 26: * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
! 27: * RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE
! 28: * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
! 29: * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
! 30: * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
! 31: * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
! 32: * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF
! 33: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
! 34: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
! 35: * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF
! 36: * THE POSSIBILITY OF SUCH DAMAGE.
! 37: *
! 38: * Author: Archie Cobbs <archie@freebsd.org>
! 39: */
! 40:
! 41: #include <sys/types.h>
! 42: #include <sys/queue.h>
! 43:
! 44: #include <netinet/in.h>
! 45: #include <arpa/inet.h>
! 46:
! 47: #include <stdio.h>
! 48: #include <stdlib.h>
! 49: #include <stdarg.h>
! 50: #include <string.h>
! 51: #include <unistd.h>
! 52: #include <assert.h>
! 53: #include <syslog.h>
! 54: #include <errno.h>
! 55: #include <pthread.h>
! 56: #include <poll.h>
! 57:
! 58: #include <openssl/ssl.h>
! 59:
! 60: #include "structs/structs.h"
! 61: #include "structs/type/array.h"
! 62: #include "sys/alog.h"
! 63: #include "util/pevent.h"
! 64: #include "util/typed_mem.h"
! 65:
! 66: #include "http/http_server.h"
! 67: #include "http/http_internal.h"
! 68:
! 69: #define MEM_TYPE_CACHE "http_connection_cache"
! 70: #define MEM_TYPE_CONN "http_connection_cache.connection"
! 71:
! 72: /* Connection cache structure */
! 73: struct http_connection_cache {
! 74: struct pevent_ctx *ctx; /* event context */
! 75: u_int max_num; /* max # in cache */
! 76: u_int max_idle; /* max socket idle */
! 77: u_int num; /* # in the cache now */
! 78: struct pevent *timer; /* exipiration timer */
! 79: pthread_mutex_t mutex; /* mutex */
! 80: TAILQ_HEAD(, cached_connection) list; /* connection list */
! 81: };
! 82:
! 83: /* Cached connection structure */
! 84: struct cached_connection {
! 85: struct sockaddr_in peer; /* peer ip and port */
! 86: const SSL_CTX *ssl; /* ssl context */
! 87: time_t expiry; /* when connection expires */
! 88: int sock; /* connected tcp socket */
! 89: FILE *fp; /* connected tcp stream */
! 90: TAILQ_ENTRY(cached_connection) next; /* next in list */
! 91: };
! 92:
! 93: /* Internal functions */
! 94: static void http_connection_cache_extract(
! 95: struct http_connection_cache *cache,
! 96: struct cached_connection *conn, FILE **fpp, int *sockp);
! 97: static void http_connection_cache_start_timer(
! 98: struct http_connection_cache *cache);
! 99: static void http_connection_cache_timeout(void *arg);
! 100: static int http_connection_cache_check(int sock);
! 101:
! 102: /*********************************************************************
! 103: PUBLIC API FUNCTIONS
! 104: *********************************************************************/
! 105:
! 106: /*
! 107: * Create a new connection cache.
! 108: */
! 109: struct http_connection_cache *
! 110: _http_connection_cache_create(struct pevent_ctx *ctx,
! 111: u_int max_num, u_int max_idle)
! 112: {
! 113: struct http_connection_cache *cache;
! 114:
! 115: /* Sanity check */
! 116: if (max_num == 0 || max_idle == 0) {
! 117: errno = EINVAL;
! 118: return (NULL);
! 119: }
! 120:
! 121: /* Create new cache */
! 122: if ((cache = MALLOC(MEM_TYPE_CACHE, sizeof(*cache))) == NULL) {
! 123: alogf(LOG_ERR, "%s: %m", "malloc");
! 124: return (NULL);
! 125: }
! 126:
! 127: /* Initialize it */
! 128: memset(cache, 0, sizeof(*cache));
! 129: TAILQ_INIT(&cache->list);
! 130: cache->ctx = ctx;
! 131: cache->max_num = max_num;
! 132: cache->max_idle = max_idle;
! 133:
! 134: /* Initialize mutex */
! 135: if ((errno = pthread_mutex_init(&cache->mutex, NULL)) != 0) {
! 136: alogf(LOG_ERR, "%s: %m", "pthread_mutex_init");
! 137: FREE(MEM_TYPE_CACHE, cache);
! 138: return (NULL);
! 139: }
! 140:
! 141: /* Done */
! 142: return (cache);
! 143: }
! 144:
! 145: /*
! 146: * Destroy a connection cache.
! 147: */
! 148: void
! 149: _http_connection_cache_destroy(struct http_connection_cache **cachep)
! 150: {
! 151: struct http_connection_cache *const cache = *cachep;
! 152: int r;
! 153:
! 154: /* Sanity */
! 155: if (cache == NULL)
! 156: return;
! 157: *cachep = NULL;
! 158:
! 159: /* Lock cache */
! 160: r = pthread_mutex_lock(&cache->mutex);
! 161: assert(r == 0);
! 162:
! 163: /* Close all cached connections */
! 164: while (!TAILQ_EMPTY(&cache->list)) {
! 165: http_connection_cache_extract(cache,
! 166: TAILQ_FIRST(&cache->list), NULL, NULL);
! 167: }
! 168:
! 169: /* Cleanup */
! 170: r = pthread_mutex_unlock(&cache->mutex);
! 171: assert(r == 0);
! 172: pthread_mutex_destroy(&cache->mutex);
! 173: FREE(MEM_TYPE_CACHE, cache);
! 174: }
! 175:
! 176: /*
! 177: * Get a connection item from the connection cache.
! 178: *
! 179: * Returns 0 and sets *fp and *sock if found (and the connection
! 180: * is removed from the cache), else -1 and errno will be ENOENT.
! 181: */
! 182: int
! 183: _http_connection_cache_get(struct http_connection_cache *cache,
! 184: const struct sockaddr_in *peer, const SSL_CTX *ssl,
! 185: FILE **fpp, int *sockp)
! 186: {
! 187: struct cached_connection *conn;
! 188: struct cached_connection *next;
! 189: int r;
! 190:
! 191: /* Cache enabled? */
! 192: if (cache == NULL)
! 193: goto not_found;
! 194: DBG(HTTP_CONNECTION_CACHE, "looking in cache (%d entries)"
! 195: " for %s:%u, ssl=%p", cache->num, inet_ntoa(peer->sin_addr),
! 196: ntohs(peer->sin_port), ssl);
! 197:
! 198: /* Lock cache */
! 199: r = pthread_mutex_lock(&cache->mutex);
! 200: assert(r == 0);
! 201:
! 202: /* Find oldest matching connection */
! 203: for (conn = TAILQ_FIRST(&cache->list); conn != NULL; conn = next) {
! 204: FILE *fp;
! 205: int sock;
! 206:
! 207: /* Get next element in list */
! 208: next = TAILQ_NEXT(conn, next);
! 209:
! 210: /* See if this element matches */
! 211: if (conn->peer.sin_addr.s_addr != peer->sin_addr.s_addr
! 212: || conn->peer.sin_port != peer->sin_port
! 213: || conn->ssl != ssl)
! 214: continue;
! 215:
! 216: /* Remove element from list */
! 217: http_connection_cache_extract(cache, conn, &fp, &sock);
! 218:
! 219: /* If connection is no longer valid, close and skip it */
! 220: if (!http_connection_cache_check(sock)) {
! 221: DBG(HTTP_CONNECTION_CACHE, "old connection fp=%p"
! 222: " for %s:%u is no longer valid", fp,
! 223: inet_ntoa(peer->sin_addr), ntohs(peer->sin_port));
! 224: fclose(fp);
! 225: continue;
! 226: }
! 227: DBG(HTTP_CONNECTION_CACHE, "found connection fp=%p for %s:%u",
! 228: fp, inet_ntoa(peer->sin_addr), ntohs(peer->sin_port));
! 229:
! 230: /* Return it */
! 231: *fpp = fp;
! 232: *sockp = sock;
! 233:
! 234: /* Done */
! 235: r = pthread_mutex_unlock(&cache->mutex);
! 236: assert(r == 0);
! 237: return (0);
! 238: }
! 239:
! 240: /* Unlock cache */
! 241: r = pthread_mutex_unlock(&cache->mutex);
! 242: assert(r == 0);
! 243:
! 244: not_found:
! 245: DBG(HTTP_CONNECTION_CACHE, "nothing found for %s:%u, ssl=%p",
! 246: inet_ntoa(peer->sin_addr), ntohs(peer->sin_port), ssl);
! 247: errno = ENOENT;
! 248: return (-1);
! 249: }
! 250:
! 251: /*
! 252: * Store a connection in the cache. It is assumed that
! 253: * calling 'fclose(fp)' implicitly closes 'sock' as well.
! 254: *
! 255: * Returns zero if stored, else -1 and sets errno (in which case
! 256: * caller is responsible for dealing with 'fp' and 'sock').
! 257: */
! 258: int
! 259: _http_connection_cache_put(struct http_connection_cache *cache,
! 260: const struct sockaddr_in *peer, const SSL_CTX *ssl, FILE *fp, int sock)
! 261: {
! 262: struct cached_connection *conn;
! 263: int r;
! 264:
! 265: /* Is cache enabled? */
! 266: if (cache == NULL) {
! 267: errno = ENXIO;
! 268: return (-1);
! 269: }
! 270:
! 271: /* Get a new connection holder */
! 272: if ((conn = MALLOC(MEM_TYPE_CONN, sizeof(*conn))) == NULL)
! 273: return (-1);
! 274: memset(conn, 0, sizeof(*conn));
! 275: conn->peer = *peer;
! 276: conn->ssl = ssl;
! 277: conn->fp = fp;
! 278: conn->sock = sock;
! 279:
! 280: /* Set expiration time */
! 281: conn->expiry = time(NULL) + cache->max_idle;
! 282:
! 283: /* Lock cache */
! 284: r = pthread_mutex_lock(&cache->mutex);
! 285: assert(r == 0);
! 286:
! 287: /* Is there room in the cache? If not, drop oldest one. */
! 288: if (cache->num >= cache->max_num) {
! 289: http_connection_cache_extract(cache,
! 290: TAILQ_FIRST(&cache->list), NULL, NULL);
! 291: }
! 292:
! 293: /* Add connection to the cache */
! 294: TAILQ_INSERT_TAIL(&cache->list, conn, next);
! 295: cache->num++;
! 296: DBG(HTTP_CONNECTION_CACHE, "connection fp=%p for %s:%u ssl=%p"
! 297: " cached (%d total)", fp, inet_ntoa(conn->peer.sin_addr),
! 298: ntohs(conn->peer.sin_port), ssl, cache->num);
! 299:
! 300: /* Make sure the timer is running */
! 301: if (cache->num == 1)
! 302: http_connection_cache_start_timer(cache);
! 303:
! 304: /* Unlock cache */
! 305: r = pthread_mutex_unlock(&cache->mutex);
! 306: assert(r == 0);
! 307:
! 308: /* Done */
! 309: return (0);
! 310: }
! 311:
! 312: /*********************************************************************
! 313: INTERNAL FUNCTIONS
! 314: *********************************************************************/
! 315:
! 316: /*
! 317: * Remove a cached item from the cache.
! 318: *
! 319: * This assumes the cache is locked.
! 320: */
! 321: static void
! 322: http_connection_cache_extract(struct http_connection_cache *cache,
! 323: struct cached_connection *conn, FILE **fpp, int *sockp)
! 324: {
! 325: struct cached_connection *const oldest = TAILQ_FIRST(&cache->list);
! 326:
! 327: /* Remove item and decrement count */
! 328: TAILQ_REMOVE(&cache->list, conn, next);
! 329: cache->num--;
! 330:
! 331: /* If we are the oldest, kill timer in order to restart later */
! 332: if (conn == oldest)
! 333: pevent_unregister(&cache->timer);
! 334:
! 335: /* Restart timer if any connections left */
! 336: if (cache->num > 0)
! 337: http_connection_cache_start_timer(cache);
! 338:
! 339: /* Return or close connection */
! 340: if (fpp != NULL) {
! 341: *fpp = conn->fp;
! 342: *sockp = conn->sock;
! 343: } else
! 344: fclose(conn->fp);
! 345:
! 346: /* Free connection */
! 347: FREE(MEM_TYPE_CONN, conn);
! 348: }
! 349:
! 350: /*
! 351: * Start the idle timer based on the oldest item in the cache.
! 352: *
! 353: * This assumes the cache is locked.
! 354: */
! 355: static void
! 356: http_connection_cache_start_timer(struct http_connection_cache *cache)
! 357: {
! 358: struct cached_connection *const oldest = TAILQ_FIRST(&cache->list);
! 359: const time_t now = time(NULL);
! 360: int timeout;
! 361:
! 362: /* Don't start timer when not appropriate */
! 363: if (cache->num == 0
! 364: || cache->max_idle == 0
! 365: || cache->timer != NULL)
! 366: return;
! 367:
! 368: /* Get time until oldest connection expires */
! 369: timeout = (now < oldest->expiry) ? (oldest->expiry - now) * 1000 : 0;
! 370:
! 371: /* Start timer */
! 372: if (pevent_register(cache->ctx, &cache->timer, 0,
! 373: &cache->mutex, http_connection_cache_timeout, cache,
! 374: PEVENT_TIME, timeout) == -1)
! 375: alogf(LOG_ERR, "%s: %m", "pevent_register");
! 376: }
! 377:
! 378: /*
! 379: * Expire a cached connection from the cache.
! 380: *
! 381: * This assumes the cache is locked.
! 382: */
! 383: static void
! 384: http_connection_cache_timeout(void *arg)
! 385: {
! 386: struct http_connection_cache *cache = arg;
! 387: const time_t now = time(NULL);
! 388:
! 389: /* Remove exipred entries */
! 390: while (!TAILQ_EMPTY(&cache->list)) {
! 391: struct cached_connection *const oldest
! 392: = TAILQ_FIRST(&cache->list);
! 393:
! 394: if (oldest->expiry < now)
! 395: break;
! 396: http_connection_cache_extract(cache, oldest, NULL, NULL);
! 397: }
! 398:
! 399: /* Restart timer if any are left */
! 400: if (cache->num > 0)
! 401: http_connection_cache_start_timer(cache);
! 402: }
! 403:
! 404: /*
! 405: * http_connection_cache_check() checks whether a socket is invalid.
! 406: *
! 407: * NOTE: we are not checking if the socket is valid. All we can tell is we
! 408: * think the socket is supposed to be valid because the server said keep-alive.
! 409: * The connection should not be closed, but if it gracefully closed then the
! 410: * server would have sent a FIN, which we will translate in to an EOF. This
! 411: * will mean the socket is readable (because the EOF) is in the pipe. In all
! 412: * other cases, the socket should not be readable because we are not expecting
! 413: * data from the server. If the server sent us anything we should declare the
! 414: * socket invalid. We still don't know if the socket is valid or not because if
! 415: * the server crashed then nothing will be there for us, same as if the
! 416: * connection is still working.
! 417: */
! 418: static int
! 419: http_connection_cache_check(int sock)
! 420: {
! 421: struct pollfd myfd;
! 422:
! 423: /* Poll for readability */
! 424: memset(&myfd, 0, sizeof(myfd));
! 425: myfd.fd = sock;
! 426: myfd.events = POLLRDNORM;
! 427:
! 428: /* Return invalid if readable or other error */
! 429: return (poll(&myfd, 1, 0) == 0);
! 430: }
! 431:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>