| /* | |
| * 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 (at your option) 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 ); | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <ipxe/http.h> | |
| #include <ipxe/iobuf.h> | |
| #include <ipxe/xfer.h> | |
| #include <ipxe/uri.h> | |
| #include <ipxe/timer.h> | |
| #include <ipxe/profile.h> | |
| #include <ipxe/fault.h> | |
| #include <ipxe/pccrr.h> | |
| #include <ipxe/peerblk.h> | |
| /** @file | |
| * | |
| * Peer Content Caching and Retrieval (PeerDist) protocol block downloads | |
| * | |
| */ | |
| /** PeerDist decryption chunksize | |
| * | |
| * This is a policy decision. | |
| */ | |
| #define PEERBLK_DECRYPT_CHUNKSIZE 2048 | |
| /** PeerDist maximum number of concurrent raw block downloads | |
| * | |
| * Raw block downloads are expensive if the origin server uses HTTPS, | |
| * since each concurrent download will require local TLS resources | |
| * (including potentially large received encrypted data buffers). | |
| * | |
| * Raw block downloads may also be prohibitively slow to initiate when | |
| * the origin server is using HTTPS and client certificates. Origin | |
| * servers for PeerDist downloads are likely to be running IIS, which | |
| * has a bug that breaks session resumption and requires each | |
| * connection to go through the full client certificate verification. | |
| * | |
| * Limit the total number of concurrent raw block downloads to | |
| * ameliorate these problems. | |
| * | |
| * This is a policy decision. | |
| */ | |
| #define PEERBLK_RAW_MAX 2 | |
| /** PeerDist raw block download attempt initial progress timeout | |
| * | |
| * This is a policy decision. | |
| */ | |
| #define PEERBLK_RAW_OPEN_TIMEOUT ( 10 * TICKS_PER_SEC ) | |
| /** PeerDist raw block download attempt ongoing progress timeout | |
| * | |
| * This is a policy decision. | |
| */ | |
| #define PEERBLK_RAW_RX_TIMEOUT ( 15 * TICKS_PER_SEC ) | |
| /** PeerDist retrieval protocol block download attempt initial progress timeout | |
| * | |
| * This is a policy decision. | |
| */ | |
| #define PEERBLK_RETRIEVAL_OPEN_TIMEOUT ( 3 * TICKS_PER_SEC ) | |
| /** PeerDist retrieval protocol block download attempt ongoing progress timeout | |
| * | |
| * This is a policy decision. | |
| */ | |
| #define PEERBLK_RETRIEVAL_RX_TIMEOUT ( 5 * TICKS_PER_SEC ) | |
| /** PeerDist maximum number of full download attempt cycles | |
| * | |
| * This is the maximum number of times that we will try a full cycle | |
| * of download attempts (i.e. a retrieval protocol download attempt | |
| * from each discovered peer plus a raw download attempt from the | |
| * origin server). | |
| * | |
| * This is a policy decision. | |
| */ | |
| #define PEERBLK_MAX_ATTEMPT_CYCLES 4 | |
| /** PeerDist block download profiler */ | |
| static struct profiler peerblk_download_profiler __profiler = | |
| { .name = "peerblk.download" }; | |
| /** PeerDist block download attempt success profiler */ | |
| static struct profiler peerblk_attempt_success_profiler __profiler = | |
| { .name = "peerblk.attempt.success" }; | |
| /** PeerDist block download attempt failure profiler */ | |
| static struct profiler peerblk_attempt_failure_profiler __profiler = | |
| { .name = "peerblk.attempt.failure" }; | |
| /** PeerDist block download attempt timeout profiler */ | |
| static struct profiler peerblk_attempt_timeout_profiler __profiler = | |
| { .name = "peerblk.attempt.timeout" }; | |
| /** PeerDist block download discovery success profiler */ | |
| static struct profiler peerblk_discovery_success_profiler __profiler = | |
| { .name = "peerblk.discovery.success" }; | |
| /** PeerDist block download discovery timeout profiler */ | |
| static struct profiler peerblk_discovery_timeout_profiler __profiler = | |
| { .name = "peerblk.discovery.timeout" }; | |
| static void peerblk_dequeue ( struct peerdist_block *peerblk ); | |
| /** | |
| * Get profiling timestamp | |
| * | |
| * @ret timestamp Timestamp | |
| */ | |
| static inline __attribute__ (( always_inline )) unsigned long | |
| peerblk_timestamp ( void ) { | |
| if ( PROFILING ) { | |
| return currticks(); | |
| } else { | |
| return 0; | |
| } | |
| } | |
| /** | |
| * Free PeerDist block download | |
| * | |
| * @v refcnt Reference count | |
| */ | |
| static void peerblk_free ( struct refcnt *refcnt ) { | |
| struct peerdist_block *peerblk = | |
| container_of ( refcnt, struct peerdist_block, refcnt ); | |
| uri_put ( peerblk->uri ); | |
| free ( peerblk->cipherctx ); | |
| free ( peerblk ); | |
| } | |
| /** | |
| * Reset PeerDist block download attempt | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v rc Reason for reset | |
| */ | |
| static void peerblk_reset ( struct peerdist_block *peerblk, int rc ) { | |
| /* Stop decryption process */ | |
| process_del ( &peerblk->process ); | |
| /* Stop timer */ | |
| stop_timer ( &peerblk->timer ); | |
| /* Abort any current download attempt */ | |
| intf_restart ( &peerblk->raw, rc ); | |
| intf_restart ( &peerblk->retrieval, rc ); | |
| /* Remove from download queue, if applicable */ | |
| if ( peerblk->queue ) | |
| peerblk_dequeue ( peerblk ); | |
| /* Empty received data buffer */ | |
| xferbuf_free ( &peerblk->buffer ); | |
| peerblk->pos = 0; | |
| /* Reset digest and free cipher context */ | |
| digest_init ( peerblk->digest, peerblk->digestctx ); | |
| free ( peerblk->cipherctx ); | |
| peerblk->cipherctx = NULL; | |
| peerblk->cipher = NULL; | |
| /* Reset trim thresholds */ | |
| peerblk->start = ( peerblk->trim.start - peerblk->range.start ); | |
| peerblk->end = ( peerblk->trim.end - peerblk->range.start ); | |
| assert ( peerblk->start <= peerblk->end ); | |
| } | |
| /** | |
| * Close PeerDist block download | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v rc Reason for close | |
| */ | |
| static void peerblk_close ( struct peerdist_block *peerblk, int rc ) { | |
| unsigned long now = peerblk_timestamp(); | |
| /* Profile overall block download */ | |
| profile_custom ( &peerblk_download_profiler, | |
| ( now - peerblk->started ) ); | |
| /* Reset download attempt */ | |
| peerblk_reset ( peerblk, rc ); | |
| /* Close discovery */ | |
| peerdisc_close ( &peerblk->discovery ); | |
| /* Shut down all interfaces */ | |
| intf_shutdown ( &peerblk->retrieval, rc ); | |
| intf_shutdown ( &peerblk->raw, rc ); | |
| intf_shutdown ( &peerblk->xfer, rc ); | |
| } | |
| /** | |
| * Calculate offset within overall download | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v pos Position within incoming data stream | |
| * @ret offset Offset within overall download | |
| */ | |
| static inline __attribute__ (( always_inline )) size_t | |
| peerblk_offset ( struct peerdist_block *peerblk, size_t pos ) { | |
| return ( ( pos - peerblk->start ) + peerblk->offset ); | |
| } | |
| /** | |
| * Deliver download attempt data block | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v iobuf I/O buffer | |
| * @v meta Original data transfer metadata | |
| * @v pos Position within incoming data stream | |
| * @ret rc Return status code | |
| */ | |
| static int peerblk_deliver ( struct peerdist_block *peerblk, | |
| struct io_buffer *iobuf, | |
| struct xfer_metadata *meta, size_t pos ) { | |
| struct xfer_metadata xfer_meta; | |
| size_t len = iob_len ( iobuf ); | |
| size_t start = pos; | |
| size_t end = ( pos + len ); | |
| int rc; | |
| /* Discard zero-length packets and packets which lie entirely | |
| * outside the trimmed range. | |
| */ | |
| if ( ( start >= peerblk->end ) || ( end <= peerblk->start ) || | |
| ( len == 0 ) ) { | |
| free_iob ( iobuf ); | |
| return 0; | |
| } | |
| /* Truncate data to within trimmed range */ | |
| if ( start < peerblk->start ) { | |
| iob_pull ( iobuf, ( peerblk->start - start ) ); | |
| start = peerblk->start; | |
| } | |
| if ( end > peerblk->end ) { | |
| iob_unput ( iobuf, ( end - peerblk->end ) ); | |
| end = peerblk->end; | |
| } | |
| /* Construct metadata */ | |
| memcpy ( &xfer_meta, meta, sizeof ( xfer_meta ) ); | |
| xfer_meta.flags |= XFER_FL_ABS_OFFSET; | |
| xfer_meta.offset = peerblk_offset ( peerblk, start ); | |
| /* Deliver data */ | |
| if ( ( rc = xfer_deliver ( &peerblk->xfer, iob_disown ( iobuf ), | |
| &xfer_meta ) ) != 0 ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d could not deliver data: %s\n", | |
| peerblk, peerblk->segment, peerblk->block, | |
| strerror ( rc ) ); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Finish PeerDist block download attempt | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v rc Reason for close | |
| */ | |
| static void peerblk_done ( struct peerdist_block *peerblk, int rc ) { | |
| struct digest_algorithm *digest = peerblk->digest; | |
| struct peerdisc_segment *segment = peerblk->discovery.segment; | |
| struct peerdisc_peer *head; | |
| struct peerdisc_peer *peer; | |
| uint8_t hash[digest->digestsize]; | |
| unsigned long now = peerblk_timestamp(); | |
| /* Check for errors on completion */ | |
| if ( rc != 0 ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d attempt failed: %s\n", | |
| peerblk, peerblk->segment, peerblk->block, | |
| strerror ( rc ) ); | |
| goto err; | |
| } | |
| /* Check digest */ | |
| digest_final ( digest, peerblk->digestctx, hash ); | |
| if ( memcmp ( hash, peerblk->hash, peerblk->digestsize ) != 0 ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d digest mismatch:\n", | |
| peerblk, peerblk->segment, peerblk->block ); | |
| DBGC_HDA ( peerblk, 0, hash, peerblk->digestsize ); | |
| DBGC_HDA ( peerblk, 0, peerblk->hash, peerblk->digestsize ); | |
| rc = -EIO; | |
| goto err; | |
| } | |
| /* Profile successful attempt */ | |
| profile_custom ( &peerblk_attempt_success_profiler, | |
| ( now - peerblk->attempted ) ); | |
| /* Report peer statistics */ | |
| head = list_entry ( &segment->peers, struct peerdisc_peer, list ); | |
| peer = ( ( peerblk->peer == head ) ? NULL : peerblk->peer ); | |
| peerdisc_stat ( &peerblk->xfer, peer, &segment->peers ); | |
| /* Close download */ | |
| peerblk_close ( peerblk, 0 ); | |
| return; | |
| err: | |
| /* Record failure reason and schedule a retry attempt */ | |
| profile_custom ( &peerblk_attempt_failure_profiler, | |
| ( now - peerblk->attempted ) ); | |
| peerblk_reset ( peerblk, rc ); | |
| peerblk->rc = rc; | |
| start_timer_nodelay ( &peerblk->timer ); | |
| } | |
| /****************************************************************************** | |
| * | |
| * Raw block download attempts (using an HTTP range request) | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** | |
| * Open PeerDist raw block download attempt | |
| * | |
| * @v peerblk PeerDist block download | |
| * @ret rc Return status code | |
| */ | |
| static int peerblk_raw_open ( struct peerdist_block *peerblk ) { | |
| struct http_request_range range; | |
| int rc; | |
| DBGC2 ( peerblk, "PEERBLK %p %d.%d attempting raw range request\n", | |
| peerblk, peerblk->segment, peerblk->block ); | |
| /* Construct HTTP range */ | |
| memset ( &range, 0, sizeof ( range ) ); | |
| range.start = peerblk->range.start; | |
| range.len = ( peerblk->range.end - peerblk->range.start ); | |
| /* Initiate range request to retrieve block */ | |
| if ( ( rc = http_open ( &peerblk->raw, &http_get, peerblk->uri, | |
| &range, NULL ) ) != 0 ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d could not create range " | |
| "request: %s\n", peerblk, peerblk->segment, | |
| peerblk->block, strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Annul HTTP connection (for testing) if applicable. Do not | |
| * report as an immediate error, in order to test our ability | |
| * to recover from a totally unresponsive HTTP server. | |
| */ | |
| if ( inject_fault ( PEERBLK_ANNUL_RATE ) ) | |
| intf_restart ( &peerblk->raw, 0 ); | |
| /* Start download attempt timer */ | |
| peerblk->rc = -ETIMEDOUT; | |
| start_timer_fixed ( &peerblk->timer, PEERBLK_RAW_OPEN_TIMEOUT ); | |
| return 0; | |
| } | |
| /** | |
| * Receive PeerDist raw data | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v iobuf I/O buffer | |
| * @v meta Data transfer metadata | |
| * @ret rc Return status code | |
| */ | |
| static int peerblk_raw_rx ( struct peerdist_block *peerblk, | |
| struct io_buffer *iobuf, | |
| struct xfer_metadata *meta ) { | |
| size_t len = iob_len ( iobuf ); | |
| size_t pos = peerblk->pos; | |
| size_t mid = ( ( peerblk->range.end - peerblk->range.start ) / 2 ); | |
| int rc; | |
| /* Corrupt received data (for testing) if applicable */ | |
| inject_corruption ( PEERBLK_CORRUPT_RATE, iobuf->data, len ); | |
| /* Fail if data is delivered out of order, since the streaming | |
| * digest requires strict ordering. | |
| */ | |
| if ( ( rc = xfer_check_order ( meta, &peerblk->pos, len ) ) != 0 ) | |
| goto err; | |
| /* Add data to digest */ | |
| digest_update ( peerblk->digest, peerblk->digestctx, iobuf->data, len ); | |
| /* Deliver data */ | |
| if ( ( rc = peerblk_deliver ( peerblk, iob_disown ( iobuf ), meta, | |
| pos ) ) != 0 ) | |
| goto err; | |
| /* Extend download attempt timer */ | |
| start_timer_fixed ( &peerblk->timer, PEERBLK_RAW_RX_TIMEOUT ); | |
| /* Stall download attempt (for testing) if applicable */ | |
| if ( ( pos < mid ) && ( ( pos + len ) >= mid ) && | |
| ( ( rc = inject_fault ( PEERBLK_STALL_RATE ) ) != 0 ) ) { | |
| intf_restart ( &peerblk->raw, rc ); | |
| } | |
| return 0; | |
| err: | |
| free_iob ( iobuf ); | |
| peerblk_done ( peerblk, rc ); | |
| return rc; | |
| } | |
| /** | |
| * Close PeerDist raw block download attempt | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v rc Reason for close | |
| */ | |
| static void peerblk_raw_close ( struct peerdist_block *peerblk, int rc ) { | |
| /* Restart interface */ | |
| intf_restart ( &peerblk->raw, rc ); | |
| /* Fail immediately if we have an error */ | |
| if ( rc != 0 ) | |
| goto done; | |
| /* Abort download attempt (for testing) if applicable */ | |
| if ( ( rc = inject_fault ( PEERBLK_ABORT_RATE ) ) != 0 ) | |
| goto done; | |
| done: | |
| /* Complete download attempt */ | |
| peerblk_done ( peerblk, rc ); | |
| } | |
| /****************************************************************************** | |
| * | |
| * Block download queue | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** | |
| * PeerDist block download queue process | |
| * | |
| * @v queue Block download queue | |
| */ | |
| static void peerblk_step ( struct peerdist_block_queue *queue ) { | |
| struct peerdist_block *peerblk; | |
| int rc; | |
| /* Do nothing yet if we have too many open block downloads */ | |
| if ( queue->count >= queue->max ) | |
| return; | |
| /* Do nothing unless there are queued block downloads */ | |
| peerblk = list_first_entry ( &queue->list, struct peerdist_block, | |
| queued ); | |
| if ( ! peerblk ) | |
| return; | |
| /* Reschedule queue process */ | |
| process_add ( &queue->process ); | |
| /* Remove block from queue */ | |
| list_del ( &peerblk->queued ); | |
| INIT_LIST_HEAD ( &peerblk->queued ); | |
| /* Attempt download */ | |
| if ( ( rc = queue->open ( peerblk ) ) != 0 ) { | |
| peerblk_close ( peerblk, rc ); | |
| return; | |
| } | |
| /* Increment open block download count */ | |
| queue->count++; | |
| } | |
| /** | |
| * Add block to download queue | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v queue Block download queue | |
| */ | |
| static void peerblk_enqueue ( struct peerdist_block *peerblk, | |
| struct peerdist_block_queue *queue ) { | |
| /* Sanity checks */ | |
| assert ( peerblk->queue == NULL ); | |
| assert ( list_empty ( &peerblk->queued ) ); | |
| /* Add block to queue */ | |
| peerblk->queue = queue; | |
| list_add_tail ( &peerblk->queued, &queue->list ); | |
| /* Schedule queue process */ | |
| process_add ( &queue->process ); | |
| } | |
| /** | |
| * Remove block from download queue | |
| * | |
| * @v peerblk PeerDist block download | |
| */ | |
| static void peerblk_dequeue ( struct peerdist_block *peerblk ) { | |
| struct peerdist_block_queue *queue = peerblk->queue; | |
| /* Sanity checks */ | |
| assert ( queue != NULL ); | |
| /* Remove block from queue */ | |
| peerblk->queue = NULL; | |
| if ( list_empty ( &peerblk->queued ) ) { | |
| /* Open download: decrement count and reschedule queue */ | |
| queue->count--; | |
| process_add ( &queue->process ); | |
| } else { | |
| /* Queued download: remove from queue */ | |
| list_del ( &peerblk->queued ); | |
| INIT_LIST_HEAD ( &peerblk->queued ); | |
| } | |
| } | |
| /** PeerDist block download queue process descriptor */ | |
| static struct process_descriptor peerblk_queue_desc = | |
| PROC_DESC_ONCE ( struct peerdist_block_queue, process, peerblk_step ); | |
| /** Raw block download queue */ | |
| static struct peerdist_block_queue peerblk_raw_queue = { | |
| .process = PROC_INIT ( peerblk_raw_queue.process, &peerblk_queue_desc ), | |
| .list = LIST_HEAD_INIT ( peerblk_raw_queue.list ), | |
| .max = PEERBLK_RAW_MAX, | |
| .open = peerblk_raw_open, | |
| }; | |
| /****************************************************************************** | |
| * | |
| * Retrieval protocol block download attempts (using HTTP POST) | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** | |
| * Construct PeerDist retrieval protocol URI | |
| * | |
| * @v location Peer location | |
| * @ret uri Retrieval URI, or NULL on error | |
| */ | |
| static struct uri * peerblk_retrieval_uri ( const char *location ) { | |
| char uri_string[ 7 /* "http://" */ + strlen ( location ) + | |
| sizeof ( PEERDIST_MAGIC_PATH /* includes NUL */ ) ]; | |
| /* Construct URI string */ | |
| snprintf ( uri_string, sizeof ( uri_string ), | |
| ( "http://%s" PEERDIST_MAGIC_PATH ), location ); | |
| /* Parse URI string */ | |
| return parse_uri ( uri_string ); | |
| } | |
| /** | |
| * Open PeerDist retrieval protocol block download attempt | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v location Peer location | |
| * @ret rc Return status code | |
| */ | |
| static int peerblk_retrieval_open ( struct peerdist_block *peerblk, | |
| const char *location ) { | |
| size_t digestsize = peerblk->digestsize; | |
| peerdist_msg_getblks_t ( digestsize, 1, 0 ) req; | |
| peerblk_msg_blk_t ( digestsize, 0, 0, 0 ) *rsp; | |
| struct http_request_content content; | |
| struct uri *uri; | |
| int rc; | |
| DBGC2 ( peerblk, "PEERBLK %p %d.%d attempting retrieval from %s\n", | |
| peerblk, peerblk->segment, peerblk->block, location ); | |
| /* Construct block fetch request */ | |
| memset ( &req, 0, sizeof ( req ) ); | |
| req.getblks.hdr.version.raw = htonl ( PEERDIST_MSG_GETBLKS_VERSION ); | |
| req.getblks.hdr.type = htonl ( PEERDIST_MSG_GETBLKS_TYPE ); | |
| req.getblks.hdr.len = htonl ( sizeof ( req ) ); | |
| req.getblks.hdr.algorithm = htonl ( PEERDIST_MSG_AES_128_CBC ); | |
| req.segment.segment.digestsize = htonl ( digestsize ); | |
| memcpy ( req.segment.id, peerblk->id, digestsize ); | |
| req.ranges.ranges.count = htonl ( 1 ); | |
| req.ranges.range[0].first = htonl ( peerblk->block ); | |
| req.ranges.range[0].count = htonl ( 1 ); | |
| /* Construct POST request content */ | |
| memset ( &content, 0, sizeof ( content ) ); | |
| content.data = &req; | |
| content.len = sizeof ( req ); | |
| /* Construct URI */ | |
| if ( ( uri = peerblk_retrieval_uri ( location ) ) == NULL ) { | |
| rc = -ENOMEM; | |
| goto err_uri; | |
| } | |
| /* Update trim thresholds */ | |
| peerblk->start += offsetof ( typeof ( *rsp ), msg.vrf ); | |
| peerblk->end += offsetof ( typeof ( *rsp ), msg.vrf ); | |
| /* Initiate HTTP POST to retrieve block */ | |
| if ( ( rc = http_open ( &peerblk->retrieval, &http_post, uri, | |
| NULL, &content ) ) != 0 ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d could not create retrieval " | |
| "request: %s\n", peerblk, peerblk->segment, | |
| peerblk->block, strerror ( rc ) ); | |
| goto err_open; | |
| } | |
| /* Annul HTTP connection (for testing) if applicable. Do not | |
| * report as an immediate error, in order to test our ability | |
| * to recover from a totally unresponsive HTTP server. | |
| */ | |
| if ( inject_fault ( PEERBLK_ANNUL_RATE ) ) | |
| intf_restart ( &peerblk->retrieval, 0 ); | |
| /* Start download attempt timer */ | |
| peerblk->rc = -ETIMEDOUT; | |
| start_timer_fixed ( &peerblk->timer, PEERBLK_RETRIEVAL_OPEN_TIMEOUT ); | |
| err_open: | |
| uri_put ( uri ); | |
| err_uri: | |
| return rc; | |
| } | |
| /** | |
| * Receive PeerDist retrieval protocol data | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v iobuf I/O buffer | |
| * @v meta Data transfer metadata | |
| * @ret rc Return status code | |
| */ | |
| static int peerblk_retrieval_rx ( struct peerdist_block *peerblk, | |
| struct io_buffer *iobuf, | |
| struct xfer_metadata *meta ) { | |
| size_t len = iob_len ( iobuf ); | |
| size_t start; | |
| size_t end; | |
| size_t before; | |
| size_t after; | |
| size_t cut; | |
| int rc; | |
| /* Some genius at Microsoft thought it would be a great idea | |
| * to place the AES-CBC initialisation vector *after* the | |
| * encrypted data, thereby making it logically impossible to | |
| * decrypt each packet as it arrives. | |
| * | |
| * To work around this mindless stupidity, we deliver the | |
| * ciphertext as-is and later use xfer_buffer() to obtain | |
| * access to the underlying data transfer buffer in order to | |
| * perform the decryption. | |
| * | |
| * There will be some data both before and after the bytes | |
| * corresponding to the trimmed plaintext: a MSG_BLK | |
| * header/footer, some block padding for the AES-CBC cipher, | |
| * and a possibly large quantity of unwanted ciphertext which | |
| * is excluded from the trimmed content range. We store this | |
| * data in a local data transfer buffer. If the amount of | |
| * data to be stored is too large, we will fail allocation and | |
| * so eventually fall back to using a range request (which | |
| * does not require this kind of temporary storage | |
| * allocation). | |
| */ | |
| /* Corrupt received data (for testing) if applicable */ | |
| inject_corruption ( PEERBLK_CORRUPT_RATE, iobuf->data, len ); | |
| /* Calculate start and end positions of this buffer */ | |
| start = peerblk->pos; | |
| if ( meta->flags & XFER_FL_ABS_OFFSET ) | |
| start = 0; | |
| start += meta->offset; | |
| end = ( start + len ); | |
| /* Buffer any data before the trimmed content */ | |
| if ( ( start < peerblk->start ) && ( len > 0 ) ) { | |
| /* Calculate length of data before the trimmed content */ | |
| before = ( peerblk->start - start ); | |
| if ( before > len ) | |
| before = len; | |
| /* Buffer data before the trimmed content */ | |
| if ( ( rc = xferbuf_write ( &peerblk->buffer, start, | |
| iobuf->data, before ) ) != 0 ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d could not buffer " | |
| "data: %s\n", peerblk, peerblk->segment, | |
| peerblk->block, strerror ( rc ) ); | |
| goto err; | |
| } | |
| } | |
| /* Buffer any data after the trimmed content */ | |
| if ( ( end > peerblk->end ) && ( len > 0 ) ) { | |
| /* Calculate length of data after the trimmed content */ | |
| after = ( end - peerblk->end ); | |
| if ( after > len ) | |
| after = len; | |
| /* Buffer data after the trimmed content */ | |
| cut = ( peerblk->end - peerblk->start ); | |
| if ( ( rc = xferbuf_write ( &peerblk->buffer, | |
| ( end - after - cut ), | |
| ( iobuf->data + len - after ), | |
| after ) ) != 0 ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d could not buffer " | |
| "data: %s\n", peerblk, peerblk->segment, | |
| peerblk->block, strerror ( rc ) ); | |
| goto err; | |
| } | |
| } | |
| /* Deliver any remaining data */ | |
| if ( ( rc = peerblk_deliver ( peerblk, iob_disown ( iobuf ), meta, | |
| start ) ) != 0 ) | |
| goto err; | |
| /* Update position */ | |
| peerblk->pos = end; | |
| /* Extend download attempt timer */ | |
| start_timer_fixed ( &peerblk->timer, PEERBLK_RETRIEVAL_RX_TIMEOUT ); | |
| /* Stall download attempt (for testing) if applicable */ | |
| if ( ( start < peerblk->end ) && ( end >= peerblk->end ) && | |
| ( ( rc = inject_fault ( PEERBLK_STALL_RATE ) ) != 0 ) ) { | |
| intf_restart ( &peerblk->retrieval, rc ); | |
| } | |
| return 0; | |
| err: | |
| free_iob ( iobuf ); | |
| peerblk_done ( peerblk, rc ); | |
| return rc; | |
| } | |
| /** | |
| * Parse retrieval protocol message header | |
| * | |
| * @v peerblk PeerDist block download | |
| * @ret rc Return status code | |
| */ | |
| static int peerblk_parse_header ( struct peerdist_block *peerblk ) { | |
| struct { | |
| struct peerdist_msg_transport_header hdr; | |
| struct peerdist_msg_header msg; | |
| } __attribute__ (( packed )) *msg = peerblk->buffer.data; | |
| struct cipher_algorithm *cipher; | |
| size_t len = peerblk->buffer.len; | |
| size_t keylen = 0; | |
| int rc; | |
| /* Check message length */ | |
| if ( len < sizeof ( *msg ) ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d message too short for header " | |
| "(%zd bytes)\n", peerblk, peerblk->segment, | |
| peerblk->block, len ); | |
| return -ERANGE; | |
| } | |
| /* Check message type */ | |
| if ( msg->msg.type != htonl ( PEERDIST_MSG_BLK_TYPE ) ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d unexpected message type " | |
| "%#08x\n", peerblk, peerblk->segment, peerblk->block, | |
| ntohl ( msg->msg.type ) ); | |
| return -EPROTO; | |
| } | |
| /* Determine cipher algorithm and key length */ | |
| cipher = &aes_cbc_algorithm; | |
| switch ( msg->msg.algorithm ) { | |
| case htonl ( PEERDIST_MSG_PLAINTEXT ) : | |
| cipher = NULL; | |
| break; | |
| case htonl ( PEERDIST_MSG_AES_128_CBC ) : | |
| keylen = ( 128 / 8 ); | |
| break; | |
| case htonl ( PEERDIST_MSG_AES_192_CBC ) : | |
| keylen = ( 192 / 8 ); | |
| break; | |
| case htonl ( PEERDIST_MSG_AES_256_CBC ) : | |
| keylen = ( 256 / 8 ); | |
| break; | |
| default: | |
| DBGC ( peerblk, "PEERBLK %p %d.%d unrecognised algorithm " | |
| "%#08x\n", peerblk, peerblk->segment, peerblk->block, | |
| ntohl ( msg->msg.algorithm ) ); | |
| return -ENOTSUP; | |
| } | |
| DBGC2 ( peerblk, "PEERBLK %p %d.%d using %s with %zd-bit key\n", | |
| peerblk, peerblk->segment, peerblk->block, | |
| ( cipher ? cipher->name : "plaintext" ), ( 8 * keylen ) ); | |
| /* Sanity check key length against maximum secret length */ | |
| if ( keylen > peerblk->digestsize ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d %zd-byte secret too short " | |
| "for %zd-bit key\n", peerblk, peerblk->segment, | |
| peerblk->block, peerblk->digestsize, ( 8 * keylen ) ); | |
| return -EPROTO; | |
| } | |
| /* Allocate cipher context, if applicable. Freeing the cipher | |
| * context (on error or otherwise) is handled by peerblk_reset(). | |
| */ | |
| peerblk->cipher = cipher; | |
| assert ( peerblk->cipherctx == NULL ); | |
| if ( cipher ) { | |
| peerblk->cipherctx = malloc ( cipher->ctxsize ); | |
| if ( ! peerblk->cipherctx ) | |
| return -ENOMEM; | |
| } | |
| /* Initialise cipher, if applicable */ | |
| if ( cipher && | |
| ( rc = cipher_setkey ( cipher, peerblk->cipherctx, peerblk->secret, | |
| keylen ) ) != 0 ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d could not set key: %s\n", | |
| peerblk, peerblk->segment, peerblk->block, | |
| strerror ( rc ) ); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Parse retrieval protocol message segment and block details | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v buf_len Length of buffered data to fill in | |
| * @ret rc Return status code | |
| */ | |
| static int peerblk_parse_block ( struct peerdist_block *peerblk, | |
| size_t *buf_len ) { | |
| size_t digestsize = peerblk->digestsize; | |
| peerblk_msg_blk_t ( digestsize, 0, 0, 0 ) *msg = peerblk->buffer.data; | |
| size_t len = peerblk->buffer.len; | |
| size_t data_len; | |
| size_t total; | |
| /* Check message length */ | |
| if ( len < offsetof ( typeof ( *msg ), msg.block.data ) ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d message too short for " | |
| "zero-length data (%zd bytes)\n", peerblk, | |
| peerblk->segment, peerblk->block, len ); | |
| return -ERANGE; | |
| } | |
| /* Check digest size */ | |
| if ( ntohl ( msg->msg.segment.segment.digestsize ) != digestsize ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d incorrect digest size %d\n", | |
| peerblk, peerblk->segment, peerblk->block, | |
| ntohl ( msg->msg.segment.segment.digestsize ) ); | |
| return -EPROTO; | |
| } | |
| /* Check segment ID */ | |
| if ( memcmp ( msg->msg.segment.id, peerblk->id, digestsize ) != 0 ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d segment ID mismatch\n", | |
| peerblk, peerblk->segment, peerblk->block ); | |
| return -EPROTO; | |
| } | |
| /* Check block ID */ | |
| if ( ntohl ( msg->msg.index ) != peerblk->block ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d block ID mismatch (got %d)\n", | |
| peerblk, peerblk->segment, peerblk->block, | |
| ntohl ( msg->msg.index ) ); | |
| return -EPROTO; | |
| } | |
| /* Check for missing blocks */ | |
| data_len = be32_to_cpu ( msg->msg.block.block.len ); | |
| if ( ! data_len ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d block not found\n", | |
| peerblk, peerblk->segment, peerblk->block ); | |
| return -ENOENT; | |
| } | |
| /* Check for underlength blocks */ | |
| if ( data_len < ( peerblk->range.end - peerblk->range.start ) ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d underlength block (%zd " | |
| "bytes)\n", peerblk, peerblk->segment, peerblk->block, | |
| data_len ); | |
| return -ERANGE; | |
| } | |
| /* Calculate buffered data length (i.e. excluding data which | |
| * was delivered to the final data transfer buffer). | |
| */ | |
| *buf_len = ( data_len - ( peerblk->end - peerblk->start ) ); | |
| /* Describe data before the trimmed content */ | |
| peerblk->decrypt[PEERBLK_BEFORE].xferbuf = &peerblk->buffer; | |
| peerblk->decrypt[PEERBLK_BEFORE].offset = | |
| offsetof ( typeof ( *msg ), msg.block.data ); | |
| peerblk->decrypt[PEERBLK_BEFORE].len = | |
| ( peerblk->start - | |
| offsetof ( typeof ( *msg ), msg.block.data ) ); | |
| total = peerblk->decrypt[PEERBLK_BEFORE].len; | |
| /* Describe data within the trimmed content */ | |
| peerblk->decrypt[PEERBLK_DURING].offset = | |
| peerblk_offset ( peerblk, peerblk->start ); | |
| peerblk->decrypt[PEERBLK_DURING].len = | |
| ( peerblk->end - peerblk->start ); | |
| total += peerblk->decrypt[PEERBLK_DURING].len; | |
| /* Describe data after the trimmed content */ | |
| peerblk->decrypt[PEERBLK_AFTER].xferbuf = &peerblk->buffer; | |
| peerblk->decrypt[PEERBLK_AFTER].offset = peerblk->start; | |
| peerblk->decrypt[PEERBLK_AFTER].len = | |
| ( offsetof ( typeof ( *msg ), msg.block.data ) | |
| + *buf_len - peerblk->start ); | |
| total += peerblk->decrypt[PEERBLK_AFTER].len; | |
| /* Sanity check */ | |
| assert ( total == be32_to_cpu ( msg->msg.block.block.len ) ); | |
| /* Initialise cipher and digest lengths */ | |
| peerblk->cipher_remaining = total; | |
| peerblk->digest_remaining = | |
| ( peerblk->range.end - peerblk->range.start ); | |
| assert ( peerblk->cipher_remaining >= peerblk->digest_remaining ); | |
| return 0; | |
| } | |
| /** | |
| * Parse retrieval protocol message useless details | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v buf_len Length of buffered data | |
| * @v vrf_len Length of uselessness to fill in | |
| * @ret rc Return status code | |
| */ | |
| static int peerblk_parse_useless ( struct peerdist_block *peerblk, | |
| size_t buf_len, size_t *vrf_len ) { | |
| size_t digestsize = peerblk->digestsize; | |
| peerblk_msg_blk_t ( digestsize, buf_len, 0, 0 ) *msg = | |
| peerblk->buffer.data; | |
| size_t len = peerblk->buffer.len; | |
| /* Check message length */ | |
| if ( len < offsetof ( typeof ( *msg ), msg.vrf.data ) ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d message too short for " | |
| "zero-length uselessness (%zd bytes)\n", peerblk, | |
| peerblk->segment, peerblk->block, len ); | |
| return -ERANGE; | |
| } | |
| /* Extract length of uselessness */ | |
| *vrf_len = be32_to_cpu ( msg->msg.vrf.vrf.len ); | |
| return 0; | |
| } | |
| /** | |
| * Parse retrieval protocol message initialisation vector details | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v buf_len Length of buffered data | |
| * @v vrf_len Length of uselessness | |
| * @ret rc Return status code | |
| */ | |
| static int peerblk_parse_iv ( struct peerdist_block *peerblk, size_t buf_len, | |
| size_t vrf_len ) { | |
| size_t digestsize = peerblk->digestsize; | |
| size_t blksize = peerblk->cipher->blocksize; | |
| peerblk_msg_blk_t ( digestsize, buf_len, vrf_len, blksize ) *msg = | |
| peerblk->buffer.data; | |
| size_t len = peerblk->buffer.len; | |
| size_t ivlen; | |
| int rc; | |
| /* Check message length */ | |
| if ( len < sizeof ( *msg ) ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d message too short for " | |
| "initialisation vector (%zd bytes)\n", peerblk, | |
| peerblk->segment, peerblk->block, len ); | |
| return -ERANGE; | |
| } | |
| ivlen = ntohl ( msg->msg.iv.iv.blksize ); | |
| /* Set initialisation vector */ | |
| if ( ( rc = cipher_setiv ( peerblk->cipher, peerblk->cipherctx, | |
| msg->msg.iv.data, ivlen ) ) != 0 ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d could not set IV: %s\n", | |
| peerblk, peerblk->segment, peerblk->block, | |
| strerror ( rc ) ); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Read from decryption buffers | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v data Data buffer | |
| * @v len Length to read | |
| * @ret rc Return status code | |
| */ | |
| static int peerblk_decrypt_read ( struct peerdist_block *peerblk, | |
| void *data, size_t len ) { | |
| struct peerdist_block_decrypt *decrypt = peerblk->decrypt; | |
| size_t frag_len; | |
| int rc; | |
| /* Read from each decryption buffer in turn */ | |
| for ( ; len ; decrypt++, data += frag_len, len -= frag_len ) { | |
| /* Calculate length to use from this buffer */ | |
| frag_len = decrypt->len; | |
| if ( frag_len > len ) | |
| frag_len = len; | |
| if ( ! frag_len ) | |
| continue; | |
| /* Read from this buffer */ | |
| if ( ( rc = xferbuf_read ( decrypt->xferbuf, decrypt->offset, | |
| data, frag_len ) ) != 0 ) | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Write to decryption buffers and update offsets and lengths | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v data Data buffer | |
| * @v len Length to read | |
| * @ret rc Return status code | |
| */ | |
| static int peerblk_decrypt_write ( struct peerdist_block *peerblk, | |
| const void *data, size_t len ) { | |
| struct peerdist_block_decrypt *decrypt = peerblk->decrypt; | |
| size_t frag_len; | |
| int rc; | |
| /* Write to each decryption buffer in turn */ | |
| for ( ; len ; decrypt++, data += frag_len, len -= frag_len ) { | |
| /* Calculate length to use from this buffer */ | |
| frag_len = decrypt->len; | |
| if ( frag_len > len ) | |
| frag_len = len; | |
| if ( ! frag_len ) | |
| continue; | |
| /* Write to this buffer */ | |
| if ( ( rc = xferbuf_write ( decrypt->xferbuf, decrypt->offset, | |
| data, frag_len ) ) != 0 ) | |
| return rc; | |
| /* Update offset and length */ | |
| decrypt->offset += frag_len; | |
| decrypt->len -= frag_len; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Decrypt one chunk of PeerDist retrieval protocol data | |
| * | |
| * @v peerblk PeerDist block download | |
| */ | |
| static void peerblk_decrypt ( struct peerdist_block *peerblk ) { | |
| struct cipher_algorithm *cipher = peerblk->cipher; | |
| struct digest_algorithm *digest = peerblk->digest; | |
| struct xfer_buffer *xferbuf; | |
| size_t cipher_len; | |
| size_t digest_len; | |
| void *data; | |
| int rc; | |
| /* Sanity checks */ | |
| assert ( ( PEERBLK_DECRYPT_CHUNKSIZE % cipher->blocksize ) == 0 ); | |
| if ( peerblk->cipher_remaining & ( cipher->blocksize - 1 ) ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d has invalid length %zd\n", | |
| peerblk, peerblk->segment, peerblk->block, | |
| peerblk->cipher_remaining ); | |
| rc = -EINVAL; | |
| goto err_blocksize; | |
| } | |
| /* Get the underlying data transfer buffer */ | |
| xferbuf = xfer_buffer ( &peerblk->xfer ); | |
| if ( ! xferbuf ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d has no underlying data " | |
| "transfer buffer\n", peerblk, peerblk->segment, | |
| peerblk->block ); | |
| rc = -ENOTSUP; | |
| goto err_xfer_buffer; | |
| } | |
| peerblk->decrypt[PEERBLK_DURING].xferbuf = xferbuf; | |
| /* Calculate cipher and digest lengths */ | |
| cipher_len = PEERBLK_DECRYPT_CHUNKSIZE; | |
| if ( cipher_len > peerblk->cipher_remaining ) | |
| cipher_len = peerblk->cipher_remaining; | |
| digest_len = cipher_len; | |
| if ( digest_len > peerblk->digest_remaining ) | |
| digest_len = peerblk->digest_remaining; | |
| assert ( ( cipher_len & ( cipher->blocksize - 1 ) ) == 0 ); | |
| /* Allocate temporary data buffer */ | |
| data = malloc ( cipher_len ); | |
| if ( ! data ) { | |
| rc = -ENOMEM; | |
| goto err_alloc_data; | |
| } | |
| /* Read ciphertext */ | |
| if ( ( rc = peerblk_decrypt_read ( peerblk, data, cipher_len ) ) != 0 ){ | |
| DBGC ( peerblk, "PEERBLK %p %d.%d could not read ciphertext: " | |
| "%s\n", peerblk, peerblk->segment, peerblk->block, | |
| strerror ( rc ) ); | |
| goto err_read; | |
| } | |
| /* Decrypt data */ | |
| cipher_decrypt ( cipher, peerblk->cipherctx, data, data, cipher_len ); | |
| /* Add data to digest */ | |
| digest_update ( digest, peerblk->digestctx, data, digest_len ); | |
| /* Write plaintext */ | |
| if ( ( rc = peerblk_decrypt_write ( peerblk, data, cipher_len ) ) != 0){ | |
| DBGC ( peerblk, "PEERBLK %p %d.%d could not write plaintext: " | |
| "%s\n", peerblk, peerblk->segment, peerblk->block, | |
| strerror ( rc ) ); | |
| goto err_write; | |
| } | |
| /* Consume input */ | |
| peerblk->cipher_remaining -= cipher_len; | |
| peerblk->digest_remaining -= digest_len; | |
| /* Free temporary data buffer */ | |
| free ( data ); | |
| /* Continue processing until all input is consumed */ | |
| if ( peerblk->cipher_remaining ) | |
| return; | |
| /* Complete download attempt */ | |
| peerblk_done ( peerblk, 0 ); | |
| return; | |
| err_write: | |
| err_read: | |
| free ( data ); | |
| err_alloc_data: | |
| err_xfer_buffer: | |
| err_blocksize: | |
| peerblk_done ( peerblk, rc ); | |
| } | |
| /** | |
| * Close PeerDist retrieval protocol block download attempt | |
| * | |
| * @v peerblk PeerDist block download | |
| * @v rc Reason for close | |
| */ | |
| static void peerblk_retrieval_close ( struct peerdist_block *peerblk, int rc ) { | |
| size_t buf_len; | |
| size_t vrf_len; | |
| /* Restart interface */ | |
| intf_restart ( &peerblk->retrieval, rc ); | |
| /* Fail immediately if we have an error */ | |
| if ( rc != 0 ) | |
| goto done; | |
| /* Abort download attempt (for testing) if applicable */ | |
| if ( ( rc = inject_fault ( PEERBLK_ABORT_RATE ) ) != 0 ) | |
| goto done; | |
| /* Parse message header */ | |
| if ( ( rc = peerblk_parse_header ( peerblk ) ) != 0 ) | |
| goto done; | |
| /* Parse message segment and block details */ | |
| if ( ( rc = peerblk_parse_block ( peerblk, &buf_len ) ) != 0 ) | |
| goto done; | |
| /* If the block was plaintext, then there is nothing more to do */ | |
| if ( ! peerblk->cipher ) | |
| goto done; | |
| /* Parse message useless details */ | |
| if ( ( rc = peerblk_parse_useless ( peerblk, buf_len, &vrf_len ) ) != 0) | |
| goto done; | |
| /* Parse message initialisation vector details */ | |
| if ( ( rc = peerblk_parse_iv ( peerblk, buf_len, vrf_len ) ) != 0 ) | |
| goto done; | |
| /* Fail if decryption length is not aligned to the cipher block size */ | |
| if ( peerblk->cipher_remaining & ( peerblk->cipher->blocksize - 1 ) ) { | |
| DBGC ( peerblk, "PEERBLK %p %d.%d unaligned data length %zd\n", | |
| peerblk, peerblk->segment, peerblk->block, | |
| peerblk->cipher_remaining ); | |
| rc = -EPROTO; | |
| goto done; | |
| } | |
| /* Stop the download attempt timer: there is no point in | |
| * timing out while decrypting. | |
| */ | |
| stop_timer ( &peerblk->timer ); | |
| /* Start decryption process */ | |
| process_add ( &peerblk->process ); | |
| return; | |
| done: | |
| /* Complete download attempt */ | |
| peerblk_done ( peerblk, rc ); | |
| } | |
| /****************************************************************************** | |
| * | |
| * Retry policy | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** | |
| * Handle PeerDist retry timer expiry | |
| * | |
| * @v timer Retry timer | |
| * @v over Failure indicator | |
| */ | |
| static void peerblk_expired ( struct retry_timer *timer, int over __unused ) { | |
| struct peerdist_block *peerblk = | |
| container_of ( timer, struct peerdist_block, timer ); | |
| struct peerdisc_segment *segment = peerblk->discovery.segment; | |
| struct peerdisc_peer *head; | |
| unsigned long now = peerblk_timestamp(); | |
| const char *location; | |
| int rc; | |
| /* Profile discovery timeout, if applicable */ | |
| if ( ( peerblk->peer == NULL ) && ( timer->timeout != 0 ) ) { | |
| profile_custom ( &peerblk_discovery_timeout_profiler, | |
| ( now - peerblk->started ) ); | |
| DBGC ( peerblk, "PEERBLK %p %d.%d discovery timed out after " | |
| "%ld ticks\n", peerblk, peerblk->segment, | |
| peerblk->block, timer->timeout ); | |
| } | |
| /* Profile download timeout, if applicable */ | |
| if ( ( peerblk->peer != NULL ) && ( timer->timeout != 0 ) ) { | |
| profile_custom ( &peerblk_attempt_timeout_profiler, | |
| ( now - peerblk->attempted ) ); | |
| DBGC ( peerblk, "PEERBLK %p %d.%d timed out after %ld ticks\n", | |
| peerblk, peerblk->segment, peerblk->block, | |
| timer->timeout ); | |
| } | |
| /* Abort any current download attempt */ | |
| peerblk_reset ( peerblk, -ETIMEDOUT ); | |
| /* Record attempt start time */ | |
| peerblk->attempted = now; | |
| /* If we have exceeded our maximum number of attempt cycles | |
| * (each cycle comprising a retrieval protocol download from | |
| * each peer in the list followed by a raw download from the | |
| * origin server), then abort the overall download. | |
| */ | |
| head = list_entry ( &segment->peers, struct peerdisc_peer, list ); | |
| if ( ( peerblk->peer == head ) && | |
| ( ++peerblk->cycles >= PEERBLK_MAX_ATTEMPT_CYCLES ) ) { | |
| rc = peerblk->rc; | |
| assert ( rc != 0 ); | |
| goto err; | |
| } | |
| /* If we have not yet made any download attempts, then move to | |
| * the start of the peer list. | |
| */ | |
| if ( peerblk->peer == NULL ) | |
| peerblk->peer = head; | |
| /* Attempt retrieval protocol download from next usable peer */ | |
| list_for_each_entry_continue ( peerblk->peer, &segment->peers, list ) { | |
| /* Attempt retrieval protocol download from this peer */ | |
| location = peerblk->peer->location; | |
| if ( ( rc = peerblk_retrieval_open ( peerblk, | |
| location ) ) != 0 ) { | |
| /* Non-fatal: continue to try next peer */ | |
| continue; | |
| } | |
| /* Peer download started */ | |
| return; | |
| } | |
| /* Add to raw download queue */ | |
| peerblk_enqueue ( peerblk, &peerblk_raw_queue ); | |
| return; | |
| err: | |
| peerblk_close ( peerblk, rc ); | |
| } | |
| /** | |
| * Handle PeerDist peer discovery | |
| * | |
| * @v discovery PeerDist discovery client | |
| */ | |
| static void peerblk_discovered ( struct peerdisc_client *discovery ) { | |
| struct peerdist_block *peerblk = | |
| container_of ( discovery, struct peerdist_block, discovery ); | |
| unsigned long now = peerblk_timestamp(); | |
| /* Do nothing unless we are still waiting for the initial | |
| * discovery timeout. | |
| */ | |
| if ( ( peerblk->peer != NULL ) || ( peerblk->timer.timeout == 0 ) ) | |
| return; | |
| /* Schedule an immediate retry */ | |
| start_timer_nodelay ( &peerblk->timer ); | |
| /* Profile discovery success */ | |
| profile_custom ( &peerblk_discovery_success_profiler, | |
| ( now - peerblk->started ) ); | |
| } | |
| /****************************************************************************** | |
| * | |
| * Opener | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** PeerDist block download data transfer interface operations */ | |
| static struct interface_operation peerblk_xfer_operations[] = { | |
| INTF_OP ( intf_close, struct peerdist_block *, peerblk_close ), | |
| }; | |
| /** PeerDist block download data transfer interface descriptor */ | |
| static struct interface_descriptor peerblk_xfer_desc = | |
| INTF_DESC ( struct peerdist_block, xfer, peerblk_xfer_operations ); | |
| /** PeerDist block download raw data interface operations */ | |
| static struct interface_operation peerblk_raw_operations[] = { | |
| INTF_OP ( xfer_deliver, struct peerdist_block *, peerblk_raw_rx ), | |
| INTF_OP ( intf_close, struct peerdist_block *, peerblk_raw_close ), | |
| }; | |
| /** PeerDist block download raw data interface descriptor */ | |
| static struct interface_descriptor peerblk_raw_desc = | |
| INTF_DESC ( struct peerdist_block, raw, peerblk_raw_operations ); | |
| /** PeerDist block download retrieval protocol interface operations */ | |
| static struct interface_operation peerblk_retrieval_operations[] = { | |
| INTF_OP ( xfer_deliver, struct peerdist_block *, peerblk_retrieval_rx ), | |
| INTF_OP ( intf_close, struct peerdist_block *, peerblk_retrieval_close), | |
| }; | |
| /** PeerDist block download retrieval protocol interface descriptor */ | |
| static struct interface_descriptor peerblk_retrieval_desc = | |
| INTF_DESC ( struct peerdist_block, retrieval, | |
| peerblk_retrieval_operations ); | |
| /** PeerDist block download decryption process descriptor */ | |
| static struct process_descriptor peerblk_process_desc = | |
| PROC_DESC ( struct peerdist_block, process, peerblk_decrypt ); | |
| /** PeerDist block download discovery operations */ | |
| static struct peerdisc_client_operations peerblk_discovery_operations = { | |
| .discovered = peerblk_discovered, | |
| }; | |
| /** | |
| * Open PeerDist block download | |
| * | |
| * @v xfer Data transfer interface | |
| * @v uri Original URI | |
| * @v info Content information block | |
| * @ret rc Return status code | |
| */ | |
| int peerblk_open ( struct interface *xfer, struct uri *uri, | |
| struct peerdist_info_block *block ) { | |
| const struct peerdist_info_segment *segment = block->segment; | |
| const struct peerdist_info *info = segment->info; | |
| struct digest_algorithm *digest = info->digest; | |
| struct peerdist_block *peerblk; | |
| unsigned long timeout; | |
| size_t digestsize; | |
| int rc; | |
| /* Allocate and initialise structure */ | |
| peerblk = zalloc ( sizeof ( *peerblk ) + digest->ctxsize ); | |
| if ( ! peerblk ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| ref_init ( &peerblk->refcnt, peerblk_free ); | |
| intf_init ( &peerblk->xfer, &peerblk_xfer_desc, &peerblk->refcnt ); | |
| intf_init ( &peerblk->raw, &peerblk_raw_desc, &peerblk->refcnt ); | |
| intf_init ( &peerblk->retrieval, &peerblk_retrieval_desc, | |
| &peerblk->refcnt ); | |
| peerblk->uri = uri_get ( uri ); | |
| memcpy ( &peerblk->range, &block->range, sizeof ( peerblk->range ) ); | |
| memcpy ( &peerblk->trim, &block->trim, sizeof ( peerblk->trim ) ); | |
| peerblk->offset = ( block->trim.start - info->trim.start ); | |
| peerblk->digest = info->digest; | |
| peerblk->digestsize = digestsize = info->digestsize; | |
| peerblk->digestctx = ( ( ( void * ) peerblk ) + sizeof ( *peerblk ) ); | |
| peerblk->segment = segment->index; | |
| memcpy ( peerblk->id, segment->id, sizeof ( peerblk->id ) ); | |
| memcpy ( peerblk->secret, segment->secret, sizeof ( peerblk->secret ) ); | |
| peerblk->block = block->index; | |
| memcpy ( peerblk->hash, block->hash, sizeof ( peerblk->hash ) ); | |
| xferbuf_malloc_init ( &peerblk->buffer ); | |
| process_init_stopped ( &peerblk->process, &peerblk_process_desc, | |
| &peerblk->refcnt ); | |
| peerdisc_init ( &peerblk->discovery, &peerblk_discovery_operations ); | |
| INIT_LIST_HEAD ( &peerblk->queued ); | |
| timer_init ( &peerblk->timer, peerblk_expired, &peerblk->refcnt ); | |
| DBGC2 ( peerblk, "PEERBLK %p %d.%d id %02x%02x%02x%02x%02x..." | |
| "%02x%02x%02x [%08zx,%08zx)", peerblk, peerblk->segment, | |
| peerblk->block, peerblk->id[0], peerblk->id[1], peerblk->id[2], | |
| peerblk->id[3], peerblk->id[4], peerblk->id[ digestsize - 3 ], | |
| peerblk->id[ digestsize - 2 ], peerblk->id[ digestsize - 1 ], | |
| peerblk->range.start, peerblk->range.end ); | |
| if ( ( peerblk->trim.start != peerblk->range.start ) || | |
| ( peerblk->trim.end != peerblk->range.end ) ) { | |
| DBGC2 ( peerblk, " covers [%08zx,%08zx)", | |
| peerblk->trim.start, peerblk->trim.end ); | |
| } | |
| DBGC2 ( peerblk, "\n" ); | |
| /* Open discovery */ | |
| if ( ( rc = peerdisc_open ( &peerblk->discovery, peerblk->id, | |
| peerblk->digestsize ) ) != 0 ) | |
| goto err_open_discovery; | |
| /* Schedule a retry attempt either immediately (if we already | |
| * have some peers) or after the discovery timeout. | |
| */ | |
| timeout = ( list_empty ( &peerblk->discovery.segment->peers ) ? | |
| ( peerdisc_timeout_secs * TICKS_PER_SEC ) : 0 ); | |
| start_timer_fixed ( &peerblk->timer, timeout ); | |
| /* Record start time */ | |
| peerblk->started = peerblk_timestamp(); | |
| /* Attach to parent interface, mortalise self, and return */ | |
| intf_plug_plug ( xfer, &peerblk->xfer ); | |
| ref_put ( &peerblk->refcnt ); | |
| return 0; | |
| err_open_discovery: | |
| peerblk_close ( peerblk, rc ); | |
| err_alloc: | |
| return rc; | |
| } |