| /* | |
| * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. | |
| * | |
| * 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 2 of the | |
| * License, or 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, write to the Free Software | |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| * 02110-1301, USA. | |
| * | |
| * You can also choose to distribute this program under the terms of | |
| * the Unmodified Binary Distribution Licence (as given in the file | |
| * COPYING.UBDL), provided that you have satisfied its requirements. | |
| */ | |
| FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| /** | |
| * @file | |
| * | |
| * Hyper Text Transfer Protocol (HTTP) connection management | |
| * | |
| */ | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <strings.h> | |
| #include <errno.h> | |
| #include <byteswap.h> | |
| #include <ipxe/tcpip.h> | |
| #include <ipxe/uri.h> | |
| #include <ipxe/timer.h> | |
| #include <ipxe/xfer.h> | |
| #include <ipxe/open.h> | |
| #include <ipxe/pool.h> | |
| #include <ipxe/fault.h> | |
| #include <ipxe/http.h> | |
| /** HTTP pooled connection expiry time */ | |
| #define HTTP_CONN_EXPIRY ( 10 * TICKS_PER_SEC ) | |
| /** HTTP connection pool */ | |
| static LIST_HEAD ( http_connection_pool ); | |
| /** | |
| * Identify HTTP scheme | |
| * | |
| * @v uri URI | |
| * @ret scheme HTTP scheme, or NULL | |
| */ | |
| static struct http_scheme * http_scheme ( struct uri *uri ) { | |
| struct http_scheme *scheme; | |
| /* Sanity check */ | |
| if ( ! uri->scheme ) | |
| return NULL; | |
| /* Identify scheme */ | |
| for_each_table_entry ( scheme, HTTP_SCHEMES ) { | |
| if ( strcasecmp ( uri->scheme, scheme->name ) == 0 ) | |
| return scheme; | |
| } | |
| return NULL; | |
| } | |
| /** | |
| * Free HTTP connection | |
| * | |
| * @v refcnt Reference count | |
| */ | |
| static void http_conn_free ( struct refcnt *refcnt ) { | |
| struct http_connection *conn = | |
| container_of ( refcnt, struct http_connection, refcnt ); | |
| /* Free connection */ | |
| uri_put ( conn->uri ); | |
| free ( conn ); | |
| } | |
| /** | |
| * Close HTTP connection | |
| * | |
| * @v conn HTTP connection | |
| * @v rc Reason for close | |
| */ | |
| static void http_conn_close ( struct http_connection *conn, int rc ) { | |
| /* Remove from connection pool, if applicable */ | |
| pool_del ( &conn->pool ); | |
| /* Shut down interfaces */ | |
| intf_shutdown ( &conn->socket, rc ); | |
| intf_shutdown ( &conn->xfer, rc ); | |
| if ( rc == 0 ) { | |
| DBGC2 ( conn, "HTTPCONN %p closed %s://%s\n", | |
| conn, conn->scheme->name, conn->uri->host ); | |
| } else { | |
| DBGC ( conn, "HTTPCONN %p closed %s://%s: %s\n", | |
| conn, conn->scheme->name, conn->uri->host, | |
| strerror ( rc ) ); | |
| } | |
| } | |
| /** | |
| * Disconnect idle HTTP connection | |
| * | |
| * @v pool Pooled connection | |
| */ | |
| static void http_conn_expired ( struct pooled_connection *pool ) { | |
| struct http_connection *conn = | |
| container_of ( pool, struct http_connection, pool ); | |
| /* Close connection */ | |
| http_conn_close ( conn, 0 /* Not an error to close idle connection */ ); | |
| } | |
| /** | |
| * Receive data from transport layer interface | |
| * | |
| * @v http HTTP connection | |
| * @v iobuf I/O buffer | |
| * @v meta Transfer metadata | |
| * @ret rc Return status code | |
| */ | |
| static int http_conn_socket_deliver ( struct http_connection *conn, | |
| struct io_buffer *iobuf, | |
| struct xfer_metadata *meta ) { | |
| /* Mark connection as alive */ | |
| pool_alive ( &conn->pool ); | |
| /* Pass on to data transfer interface */ | |
| return xfer_deliver ( &conn->xfer, iobuf, meta ); | |
| } | |
| /** | |
| * Close HTTP connection transport layer interface | |
| * | |
| * @v http HTTP connection | |
| * @v rc Reason for close | |
| */ | |
| static void http_conn_socket_close ( struct http_connection *conn, int rc ) { | |
| /* If we are reopenable (i.e. we are a recycled connection | |
| * from the connection pool, and we have received no data from | |
| * the underlying socket since we were pooled), then suggest | |
| * that the client should reopen the connection. | |
| */ | |
| if ( pool_is_reopenable ( &conn->pool ) ) | |
| pool_reopen ( &conn->xfer ); | |
| /* Close the connection */ | |
| http_conn_close ( conn, rc ); | |
| } | |
| /** | |
| * Recycle this connection after closing | |
| * | |
| * @v http HTTP connection | |
| */ | |
| static void http_conn_xfer_recycle ( struct http_connection *conn ) { | |
| /* Mark connection as recyclable */ | |
| pool_recyclable ( &conn->pool ); | |
| DBGC2 ( conn, "HTTPCONN %p keepalive enabled\n", conn ); | |
| } | |
| /** | |
| * Close HTTP connection data transfer interface | |
| * | |
| * @v conn HTTP connection | |
| * @v rc Reason for close | |
| */ | |
| static void http_conn_xfer_close ( struct http_connection *conn, int rc ) { | |
| /* Add to the connection pool if keepalive is enabled and no | |
| * error occurred. | |
| */ | |
| if ( ( rc == 0 ) && pool_is_recyclable ( &conn->pool ) && | |
| ( ( rc = inject_fault ( HTTP_FAIL_KEEPALIVE_RATE ) ) == 0 ) ) { | |
| intf_restart ( &conn->xfer, rc ); | |
| pool_add ( &conn->pool, &http_connection_pool, | |
| HTTP_CONN_EXPIRY ); | |
| DBGC2 ( conn, "HTTPCONN %p pooled %s://%s\n", | |
| conn, conn->scheme->name, conn->uri->host ); | |
| return; | |
| } | |
| /* Otherwise, close the connection */ | |
| http_conn_close ( conn, rc ); | |
| } | |
| /** HTTP connection socket interface operations */ | |
| static struct interface_operation http_conn_socket_operations[] = { | |
| INTF_OP ( xfer_deliver, struct http_connection *, | |
| http_conn_socket_deliver ), | |
| INTF_OP ( intf_close, struct http_connection *, | |
| http_conn_socket_close ), | |
| }; | |
| /** HTTP connection socket interface descriptor */ | |
| static struct interface_descriptor http_conn_socket_desc = | |
| INTF_DESC_PASSTHRU ( struct http_connection, socket, | |
| http_conn_socket_operations, xfer ); | |
| /** HTTP connection data transfer interface operations */ | |
| static struct interface_operation http_conn_xfer_operations[] = { | |
| INTF_OP ( pool_recycle, struct http_connection *, | |
| http_conn_xfer_recycle ), | |
| INTF_OP ( intf_close, struct http_connection *, | |
| http_conn_xfer_close ), | |
| }; | |
| /** HTTP connection data transfer interface descriptor */ | |
| static struct interface_descriptor http_conn_xfer_desc = | |
| INTF_DESC_PASSTHRU ( struct http_connection, xfer, | |
| http_conn_xfer_operations, socket ); | |
| /** | |
| * Connect to an HTTP server | |
| * | |
| * @v xfer Data transfer interface | |
| * @v uri Connection URI | |
| * @ret rc Return status code | |
| * | |
| * HTTP connections are pooled. The caller should be prepared to | |
| * receive a pool_reopen() message. | |
| */ | |
| int http_connect ( struct interface *xfer, struct uri *uri ) { | |
| struct http_connection *conn; | |
| struct http_scheme *scheme; | |
| struct sockaddr_tcpip server; | |
| unsigned int port; | |
| int rc; | |
| /* Identify scheme */ | |
| scheme = http_scheme ( uri ); | |
| if ( ! scheme ) | |
| return -ENOTSUP; | |
| /* Sanity check */ | |
| if ( ! uri->host ) | |
| return -EINVAL; | |
| /* Identify port */ | |
| port = uri_port ( uri, scheme->port ); | |
| /* Look for a reusable connection in the pool. Reuse the most | |
| * recent connection in order to accommodate authentication | |
| * schemes that break the stateless nature of HTTP and rely on | |
| * the same connection being reused for authentication | |
| * responses. | |
| */ | |
| list_for_each_entry_reverse ( conn, &http_connection_pool, pool.list ) { | |
| /* Sanity checks */ | |
| assert ( conn->uri != NULL ); | |
| assert ( conn->uri->host != NULL ); | |
| /* Reuse connection, if possible */ | |
| if ( ( scheme == conn->scheme ) && | |
| ( strcmp ( uri->host, conn->uri->host ) == 0 ) && | |
| ( port == uri_port ( conn->uri, scheme->port ) ) ) { | |
| /* Remove from connection pool, stop timer, | |
| * attach to parent interface, and return. | |
| */ | |
| pool_del ( &conn->pool ); | |
| intf_plug_plug ( &conn->xfer, xfer ); | |
| DBGC2 ( conn, "HTTPCONN %p reused %s://%s:%d\n", conn, | |
| conn->scheme->name, conn->uri->host, port ); | |
| return 0; | |
| } | |
| } | |
| /* Allocate and initialise structure */ | |
| conn = zalloc ( sizeof ( *conn ) ); | |
| if ( ! conn ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| ref_init ( &conn->refcnt, http_conn_free ); | |
| conn->uri = uri_get ( uri ); | |
| conn->scheme = scheme; | |
| intf_init ( &conn->socket, &http_conn_socket_desc, &conn->refcnt ); | |
| intf_init ( &conn->xfer, &http_conn_xfer_desc, &conn->refcnt ); | |
| pool_init ( &conn->pool, http_conn_expired, &conn->refcnt ); | |
| /* Open socket */ | |
| memset ( &server, 0, sizeof ( server ) ); | |
| server.st_port = htons ( port ); | |
| if ( ( rc = xfer_open_named_socket ( &conn->socket, SOCK_STREAM, | |
| ( struct sockaddr * ) &server, | |
| uri->host, NULL ) ) != 0 ) | |
| goto err_open; | |
| /* Add filter, if any */ | |
| if ( scheme->filter && ( ( rc = scheme->filter ( conn ) ) != 0 ) ) | |
| goto err_filter; | |
| /* Attach to parent interface, mortalise self, and return */ | |
| intf_plug_plug ( &conn->xfer, xfer ); | |
| ref_put ( &conn->refcnt ); | |
| DBGC2 ( conn, "HTTPCONN %p created %s://%s:%d\n", conn, | |
| conn->scheme->name, conn->uri->host, port ); | |
| return 0; | |
| err_filter: | |
| err_open: | |
| DBGC2 ( conn, "HTTPCONN %p could not create %s://%s:%d: %s\n", conn, | |
| conn->scheme->name, conn->uri->host, port, strerror ( rc ) ); | |
| http_conn_close ( conn, rc ); | |
| ref_put ( &conn->refcnt ); | |
| err_alloc: | |
| return rc; | |
| } |