| /* |
| * 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 ); |
| |
| #include <stdlib.h> |
| #include <stdio.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; |
| |
| /* 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; |
| } |
| |
| /* Check initialisation vector size */ |
| if ( ntohl ( msg->msg.iv.iv.blksize ) != blksize ) { |
| DBGC ( peerblk, "PEERBLK %p %d.%d incorrect IV size %d\n", |
| peerblk, peerblk->segment, peerblk->block, |
| ntohl ( msg->msg.iv.iv.blksize ) ); |
| return -EPROTO; |
| } |
| |
| /* Set initialisation vector */ |
| cipher_setiv ( peerblk->cipher, peerblk->cipherctx, msg->msg.iv.data, |
| blksize ); |
| |
| 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 check */ |
| assert ( ( PEERBLK_DECRYPT_CHUNKSIZE % cipher->blocksize ) == 0 ); |
| |
| /* 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: |
| 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; |
| } |