Annotation of embedaddon/libpdel/http/http_connection_cache.c, revision 1.1.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>