iPXE
httpconn.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 /**
00027  * @file
00028  *
00029  * Hyper Text Transfer Protocol (HTTP) connection management
00030  *
00031  */
00032 
00033 #include <stdlib.h>
00034 #include <string.h>
00035 #include <errno.h>
00036 #include <byteswap.h>
00037 #include <ipxe/tcpip.h>
00038 #include <ipxe/uri.h>
00039 #include <ipxe/timer.h>
00040 #include <ipxe/xfer.h>
00041 #include <ipxe/open.h>
00042 #include <ipxe/pool.h>
00043 #include <ipxe/http.h>
00044 
00045 /** HTTP pooled connection expiry time */
00046 #define HTTP_CONN_EXPIRY ( 10 * TICKS_PER_SEC )
00047 
00048 /** HTTP connection pool */
00049 static LIST_HEAD ( http_connection_pool );
00050 
00051 /**
00052  * Identify HTTP scheme
00053  *
00054  * @v uri               URI
00055  * @ret scheme          HTTP scheme, or NULL
00056  */
00057 static struct http_scheme * http_scheme ( struct uri *uri ) {
00058         struct http_scheme *scheme;
00059 
00060         /* Sanity check */
00061         if ( ! uri->scheme )
00062                 return NULL;
00063 
00064         /* Identify scheme */
00065         for_each_table_entry ( scheme, HTTP_SCHEMES ) {
00066                 if ( strcmp ( uri->scheme, scheme->name ) == 0 )
00067                         return scheme;
00068         }
00069 
00070         return NULL;
00071 }
00072 
00073 /**
00074  * Free HTTP connection
00075  *
00076  * @v refcnt            Reference count
00077  */
00078 static void http_conn_free ( struct refcnt *refcnt ) {
00079         struct http_connection *conn =
00080                 container_of ( refcnt, struct http_connection, refcnt );
00081 
00082         /* Free connection */
00083         uri_put ( conn->uri );
00084         free ( conn );
00085 }
00086 
00087 /**
00088  * Close HTTP connection
00089  *
00090  * @v conn              HTTP connection
00091  * @v rc                Reason for close
00092  */
00093 static void http_conn_close ( struct http_connection *conn, int rc ) {
00094 
00095         /* Remove from connection pool, if applicable */
00096         pool_del ( &conn->pool );
00097 
00098         /* Shut down interfaces */
00099         intf_shutdown ( &conn->socket, rc );
00100         intf_shutdown ( &conn->xfer, rc );
00101         if ( rc == 0 ) {
00102                 DBGC2 ( conn, "HTTPCONN %p closed %s://%s\n",
00103                         conn, conn->scheme->name, conn->uri->host );
00104         } else {
00105                 DBGC ( conn, "HTTPCONN %p closed %s://%s: %s\n",
00106                        conn, conn->scheme->name, conn->uri->host,
00107                        strerror ( rc ) );
00108         }
00109 }
00110 
00111 /**
00112  * Disconnect idle HTTP connection
00113  *
00114  * @v pool              Pooled connection
00115  */
00116 static void http_conn_expired ( struct pooled_connection *pool ) {
00117         struct http_connection *conn =
00118                 container_of ( pool, struct http_connection, pool );
00119 
00120         /* Close connection */
00121         http_conn_close ( conn, 0 /* Not an error to close idle connection */ );
00122 }
00123 
00124 /**
00125  * Receive data from transport layer interface
00126  *
00127  * @v http              HTTP connection
00128  * @v iobuf             I/O buffer
00129  * @v meta              Transfer metadata
00130  * @ret rc              Return status code
00131  */
00132 static int http_conn_socket_deliver ( struct http_connection *conn,
00133                                       struct io_buffer *iobuf,
00134                                       struct xfer_metadata *meta ) {
00135 
00136         /* Mark connection as alive */
00137         pool_alive ( &conn->pool );
00138 
00139         /* Pass on to data transfer interface */
00140         return xfer_deliver ( &conn->xfer, iobuf, meta );
00141 }
00142 
00143 /**
00144  * Close HTTP connection transport layer interface
00145  *
00146  * @v http              HTTP connection
00147  * @v rc                Reason for close
00148  */
00149 static void http_conn_socket_close ( struct http_connection *conn, int rc ) {
00150 
00151         /* If we are reopenable (i.e. we are a recycled connection
00152          * from the connection pool, and we have received no data from
00153          * the underlying socket since we were pooled), then suggest
00154          * that the client should reopen the connection.
00155          */
00156         if ( pool_is_reopenable ( &conn->pool ) )
00157                 pool_reopen ( &conn->xfer );
00158 
00159         /* Close the connection */
00160         http_conn_close ( conn, rc );
00161 }
00162 
00163 /**
00164  * Recycle this connection after closing
00165  *
00166  * @v http              HTTP connection
00167  */
00168 static void http_conn_xfer_recycle ( struct http_connection *conn ) {
00169 
00170         /* Mark connection as recyclable */
00171         pool_recyclable ( &conn->pool );
00172         DBGC2 ( conn, "HTTPCONN %p keepalive enabled\n", conn );
00173 }
00174 
00175 /**
00176  * Close HTTP connection data transfer interface
00177  *
00178  * @v conn              HTTP connection
00179  * @v rc                Reason for close
00180  */
00181 static void http_conn_xfer_close ( struct http_connection *conn, int rc ) {
00182 
00183         /* Add to the connection pool if keepalive is enabled and no
00184          * error occurred.
00185          */
00186         if ( ( rc == 0 ) && pool_is_recyclable ( &conn->pool ) ) {
00187                 intf_restart ( &conn->xfer, rc );
00188                 pool_add ( &conn->pool, &http_connection_pool,
00189                            HTTP_CONN_EXPIRY );
00190                 DBGC2 ( conn, "HTTPCONN %p pooled %s://%s\n",
00191                         conn, conn->scheme->name, conn->uri->host );
00192                 return;
00193         }
00194 
00195         /* Otherwise, close the connection */
00196         http_conn_close ( conn, rc );
00197 }
00198 
00199 /** HTTP connection socket interface operations */
00200 static struct interface_operation http_conn_socket_operations[] = {
00201         INTF_OP ( xfer_deliver, struct http_connection *,
00202                   http_conn_socket_deliver ),
00203         INTF_OP ( intf_close, struct http_connection *,
00204                   http_conn_socket_close ),
00205 };
00206 
00207 /** HTTP connection socket interface descriptor */
00208 static struct interface_descriptor http_conn_socket_desc =
00209         INTF_DESC_PASSTHRU ( struct http_connection, socket,
00210                              http_conn_socket_operations, xfer );
00211 
00212 /** HTTP connection data transfer interface operations */
00213 static struct interface_operation http_conn_xfer_operations[] = {
00214         INTF_OP ( pool_recycle, struct http_connection *,
00215                   http_conn_xfer_recycle ),
00216         INTF_OP ( intf_close, struct http_connection *,
00217                   http_conn_xfer_close ),
00218 };
00219 
00220 /** HTTP connection data transfer interface descriptor */
00221 static struct interface_descriptor http_conn_xfer_desc =
00222         INTF_DESC_PASSTHRU ( struct http_connection, xfer,
00223                              http_conn_xfer_operations, socket );
00224 
00225 /**
00226  * Connect to an HTTP server
00227  *
00228  * @v xfer              Data transfer interface
00229  * @v uri               Connection URI
00230  * @ret rc              Return status code
00231  *
00232  * HTTP connections are pooled.  The caller should be prepared to
00233  * receive a pool_reopen() message.
00234  */
00235 int http_connect ( struct interface *xfer, struct uri *uri ) {
00236         struct http_connection *conn;
00237         struct http_scheme *scheme;
00238         struct sockaddr_tcpip server;
00239         struct interface *socket;
00240         unsigned int port;
00241         int rc;
00242 
00243         /* Identify scheme */
00244         scheme = http_scheme ( uri );
00245         if ( ! scheme )
00246                 return -ENOTSUP;
00247 
00248         /* Sanity check */
00249         if ( ! uri->host )
00250                 return -EINVAL;
00251 
00252         /* Identify port */
00253         port = uri_port ( uri, scheme->port );
00254 
00255         /* Look for a reusable connection in the pool.  Reuse the most
00256          * recent connection in order to accommodate authentication
00257          * schemes that break the stateless nature of HTTP and rely on
00258          * the same connection being reused for authentication
00259          * responses.
00260          */
00261         list_for_each_entry_reverse ( conn, &http_connection_pool, pool.list ) {
00262 
00263                 /* Sanity checks */
00264                 assert ( conn->uri != NULL );
00265                 assert ( conn->uri->host != NULL );
00266 
00267                 /* Reuse connection, if possible */
00268                 if ( ( scheme == conn->scheme ) &&
00269                      ( strcmp ( uri->host, conn->uri->host ) == 0 ) &&
00270                      ( port == uri_port ( conn->uri, scheme->port ) ) ) {
00271 
00272                         /* Remove from connection pool, stop timer,
00273                          * attach to parent interface, and return.
00274                          */
00275                         pool_del ( &conn->pool );
00276                         intf_plug_plug ( &conn->xfer, xfer );
00277                         DBGC2 ( conn, "HTTPCONN %p reused %s://%s:%d\n", conn,
00278                                 conn->scheme->name, conn->uri->host, port );
00279                         return 0;
00280                 }
00281         }
00282 
00283         /* Allocate and initialise structure */
00284         conn = zalloc ( sizeof ( *conn ) );
00285         if ( ! conn ) {
00286                 rc = -ENOMEM;
00287                 goto err_alloc;
00288         }
00289         ref_init ( &conn->refcnt, http_conn_free );
00290         conn->uri = uri_get ( uri );
00291         conn->scheme = scheme;
00292         intf_init ( &conn->socket, &http_conn_socket_desc, &conn->refcnt );
00293         intf_init ( &conn->xfer, &http_conn_xfer_desc, &conn->refcnt );
00294         pool_init ( &conn->pool, http_conn_expired, &conn->refcnt );
00295 
00296         /* Open socket */
00297         memset ( &server, 0, sizeof ( server ) );
00298         server.st_port = htons ( port );
00299         socket = &conn->socket;
00300         if ( scheme->filter &&
00301              ( ( rc = scheme->filter ( socket, uri->host, &socket ) ) != 0 ) )
00302                 goto err_filter;
00303         if ( ( rc = xfer_open_named_socket ( socket, SOCK_STREAM,
00304                                              ( struct sockaddr * ) &server,
00305                                              uri->host, NULL ) ) != 0 )
00306                 goto err_open;
00307 
00308         /* Attach to parent interface, mortalise self, and return */
00309         intf_plug_plug ( &conn->xfer, xfer );
00310         ref_put ( &conn->refcnt );
00311 
00312         DBGC2 ( conn, "HTTPCONN %p created %s://%s:%d\n", conn,
00313                 conn->scheme->name, conn->uri->host, port );
00314         return 0;
00315 
00316  err_open:
00317  err_filter:
00318         DBGC2 ( conn, "HTTPCONN %p could not create %s://%s:%d: %s\n", conn,
00319                 conn->scheme->name, conn->uri->host, port, strerror ( rc ) );
00320         http_conn_close ( conn, rc );
00321         ref_put ( &conn->refcnt );
00322  err_alloc:
00323         return rc;
00324 }