| /* |
| * 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 |
| * |
| * Hyper Text Transfer Protocol (HTTP) connection management |
| * |
| */ |
| |
| #include <stdlib.h> |
| #include <string.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/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 ( strcmp ( 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 ) ) { |
| 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; |
| struct interface *socket; |
| 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 ); |
| socket = &conn->socket; |
| if ( scheme->filter && |
| ( ( rc = scheme->filter ( socket, uri->host, &socket ) ) != 0 ) ) |
| goto err_filter; |
| if ( ( rc = xfer_open_named_socket ( socket, SOCK_STREAM, |
| ( struct sockaddr * ) &server, |
| uri->host, NULL ) ) != 0 ) |
| goto err_open; |
| |
| /* 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_open: |
| err_filter: |
| 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; |
| } |