1 /*
2 * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301, USA.
18 *
19 * You can also choose to distribute this program under the terms of
20 * the Unmodified Binary Distribution Licence (as given in the file
21 * COPYING.UBDL), provided that you have satisfied its requirements.
22 */
23
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <errno.h>
29 #include <ipxe/http.h>
30 #include <ipxe/iobuf.h>
31 #include <ipxe/xfer.h>
32 #include <ipxe/uri.h>
33 #include <ipxe/timer.h>
34 #include <ipxe/profile.h>
35 #include <ipxe/fault.h>
36 #include <ipxe/pccrr.h>
37 #include <ipxe/peerblk.h>
38
39 /** @file
40 *
41 * Peer Content Caching and Retrieval (PeerDist) protocol block downloads
42 *
43 */
44
45 /** PeerDist decryption chunksize
46 *
47 * This is a policy decision.
48 */
49 #define PEERBLK_DECRYPT_CHUNKSIZE 2048
50
51 /** PeerDist maximum number of concurrent raw block downloads
52 *
53 * Raw block downloads are expensive if the origin server uses HTTPS,
54 * since each concurrent download will require local TLS resources
55 * (including potentially large received encrypted data buffers).
56 *
57 * Raw block downloads may also be prohibitively slow to initiate when
58 * the origin server is using HTTPS and client certificates. Origin
59 * servers for PeerDist downloads are likely to be running IIS, which
60 * has a bug that breaks session resumption and requires each
61 * connection to go through the full client certificate verification.
62 *
63 * Limit the total number of concurrent raw block downloads to
64 * ameliorate these problems.
65 *
66 * This is a policy decision.
67 */
68 #define PEERBLK_RAW_MAX 2
69
70 /** PeerDist raw block download attempt initial progress timeout
71 *
72 * This is a policy decision.
73 */
74 #define PEERBLK_RAW_OPEN_TIMEOUT ( 10 * TICKS_PER_SEC )
75
76 /** PeerDist raw block download attempt ongoing progress timeout
77 *
78 * This is a policy decision.
79 */
80 #define PEERBLK_RAW_RX_TIMEOUT ( 15 * TICKS_PER_SEC )
81
82 /** PeerDist retrieval protocol block download attempt initial progress timeout
83 *
84 * This is a policy decision.
85 */
86 #define PEERBLK_RETRIEVAL_OPEN_TIMEOUT ( 3 * TICKS_PER_SEC )
87
88 /** PeerDist retrieval protocol block download attempt ongoing progress timeout
89 *
90 * This is a policy decision.
91 */
92 #define PEERBLK_RETRIEVAL_RX_TIMEOUT ( 5 * TICKS_PER_SEC )
93
94 /** PeerDist maximum number of full download attempt cycles
95 *
96 * This is the maximum number of times that we will try a full cycle
97 * of download attempts (i.e. a retrieval protocol download attempt
98 * from each discovered peer plus a raw download attempt from the
99 * origin server).
100 *
101 * This is a policy decision.
102 */
103 #define PEERBLK_MAX_ATTEMPT_CYCLES 4
104
105 /** PeerDist block download profiler */
106 static struct profiler peerblk_download_profiler __profiler =
107 { .name = "peerblk.download" };
108
109 /** PeerDist block download attempt success profiler */
110 static struct profiler peerblk_attempt_success_profiler __profiler =
111 { .name = "peerblk.attempt.success" };
112
113 /** PeerDist block download attempt failure profiler */
114 static struct profiler peerblk_attempt_failure_profiler __profiler =
115 { .name = "peerblk.attempt.failure" };
116
117 /** PeerDist block download attempt timeout profiler */
118 static struct profiler peerblk_attempt_timeout_profiler __profiler =
119 { .name = "peerblk.attempt.timeout" };
120
121 /** PeerDist block download discovery success profiler */
122 static struct profiler peerblk_discovery_success_profiler __profiler =
123 { .name = "peerblk.discovery.success" };
124
125 /** PeerDist block download discovery timeout profiler */
126 static struct profiler peerblk_discovery_timeout_profiler __profiler =
127 { .name = "peerblk.discovery.timeout" };
128
129 static void peerblk_dequeue ( struct peerdist_block *peerblk );
130
131 /**
132 * Get profiling timestamp
133 *
134 * @ret timestamp Timestamp
135 */
136 static inline __attribute__ (( always_inline )) unsigned long
peerblk_timestamp(void)137 peerblk_timestamp ( void ) {
138
139 if ( PROFILING ) {
140 return currticks();
141 } else {
142 return 0;
143 }
144 }
145
146 /**
147 * Free PeerDist block download
148 *
149 * @v refcnt Reference count
150 */
peerblk_free(struct refcnt * refcnt)151 static void peerblk_free ( struct refcnt *refcnt ) {
152 struct peerdist_block *peerblk =
153 container_of ( refcnt, struct peerdist_block, refcnt );
154
155 uri_put ( peerblk->uri );
156 free ( peerblk->cipherctx );
157 free ( peerblk );
158 }
159
160 /**
161 * Reset PeerDist block download attempt
162 *
163 * @v peerblk PeerDist block download
164 * @v rc Reason for reset
165 */
peerblk_reset(struct peerdist_block * peerblk,int rc)166 static void peerblk_reset ( struct peerdist_block *peerblk, int rc ) {
167
168 /* Stop decryption process */
169 process_del ( &peerblk->process );
170
171 /* Stop timer */
172 stop_timer ( &peerblk->timer );
173
174 /* Abort any current download attempt */
175 intf_restart ( &peerblk->raw, rc );
176 intf_restart ( &peerblk->retrieval, rc );
177
178 /* Remove from download queue, if applicable */
179 if ( peerblk->queue )
180 peerblk_dequeue ( peerblk );
181
182 /* Empty received data buffer */
183 xferbuf_free ( &peerblk->buffer );
184 peerblk->pos = 0;
185
186 /* Reset digest and free cipher context */
187 digest_init ( peerblk->digest, peerblk->digestctx );
188 free ( peerblk->cipherctx );
189 peerblk->cipherctx = NULL;
190 peerblk->cipher = NULL;
191
192 /* Reset trim thresholds */
193 peerblk->start = ( peerblk->trim.start - peerblk->range.start );
194 peerblk->end = ( peerblk->trim.end - peerblk->range.start );
195 assert ( peerblk->start <= peerblk->end );
196 }
197
198 /**
199 * Close PeerDist block download
200 *
201 * @v peerblk PeerDist block download
202 * @v rc Reason for close
203 */
peerblk_close(struct peerdist_block * peerblk,int rc)204 static void peerblk_close ( struct peerdist_block *peerblk, int rc ) {
205 unsigned long now = peerblk_timestamp();
206
207 /* Profile overall block download */
208 profile_custom ( &peerblk_download_profiler,
209 ( now - peerblk->started ) );
210
211 /* Reset download attempt */
212 peerblk_reset ( peerblk, rc );
213
214 /* Close discovery */
215 peerdisc_close ( &peerblk->discovery );
216
217 /* Shut down all interfaces */
218 intf_shutdown ( &peerblk->retrieval, rc );
219 intf_shutdown ( &peerblk->raw, rc );
220 intf_shutdown ( &peerblk->xfer, rc );
221 }
222
223 /**
224 * Calculate offset within overall download
225 *
226 * @v peerblk PeerDist block download
227 * @v pos Position within incoming data stream
228 * @ret offset Offset within overall download
229 */
230 static inline __attribute__ (( always_inline )) size_t
peerblk_offset(struct peerdist_block * peerblk,size_t pos)231 peerblk_offset ( struct peerdist_block *peerblk, size_t pos ) {
232
233 return ( ( pos - peerblk->start ) + peerblk->offset );
234 }
235
236 /**
237 * Deliver download attempt data block
238 *
239 * @v peerblk PeerDist block download
240 * @v iobuf I/O buffer
241 * @v meta Original data transfer metadata
242 * @v pos Position within incoming data stream
243 * @ret rc Return status code
244 */
peerblk_deliver(struct peerdist_block * peerblk,struct io_buffer * iobuf,struct xfer_metadata * meta,size_t pos)245 static int peerblk_deliver ( struct peerdist_block *peerblk,
246 struct io_buffer *iobuf,
247 struct xfer_metadata *meta, size_t pos ) {
248 struct xfer_metadata xfer_meta;
249 size_t len = iob_len ( iobuf );
250 size_t start = pos;
251 size_t end = ( pos + len );
252 int rc;
253
254 /* Discard zero-length packets and packets which lie entirely
255 * outside the trimmed range.
256 */
257 if ( ( start >= peerblk->end ) || ( end <= peerblk->start ) ||
258 ( len == 0 ) ) {
259 free_iob ( iobuf );
260 return 0;
261 }
262
263 /* Truncate data to within trimmed range */
264 if ( start < peerblk->start ) {
265 iob_pull ( iobuf, ( peerblk->start - start ) );
266 start = peerblk->start;
267 }
268 if ( end > peerblk->end ) {
269 iob_unput ( iobuf, ( end - peerblk->end ) );
270 end = peerblk->end;
271 }
272
273 /* Construct metadata */
274 memcpy ( &xfer_meta, meta, sizeof ( xfer_meta ) );
275 xfer_meta.flags |= XFER_FL_ABS_OFFSET;
276 xfer_meta.offset = peerblk_offset ( peerblk, start );
277
278 /* Deliver data */
279 if ( ( rc = xfer_deliver ( &peerblk->xfer, iob_disown ( iobuf ),
280 &xfer_meta ) ) != 0 ) {
281 DBGC ( peerblk, "PEERBLK %p %d.%d could not deliver data: %s\n",
282 peerblk, peerblk->segment, peerblk->block,
283 strerror ( rc ) );
284 return rc;
285 }
286
287 return 0;
288 }
289
290 /**
291 * Finish PeerDist block download attempt
292 *
293 * @v peerblk PeerDist block download
294 * @v rc Reason for close
295 */
peerblk_done(struct peerdist_block * peerblk,int rc)296 static void peerblk_done ( struct peerdist_block *peerblk, int rc ) {
297 struct digest_algorithm *digest = peerblk->digest;
298 struct peerdisc_segment *segment = peerblk->discovery.segment;
299 struct peerdisc_peer *head;
300 struct peerdisc_peer *peer;
301 uint8_t hash[digest->digestsize];
302 unsigned long now = peerblk_timestamp();
303
304 /* Check for errors on completion */
305 if ( rc != 0 ) {
306 DBGC ( peerblk, "PEERBLK %p %d.%d attempt failed: %s\n",
307 peerblk, peerblk->segment, peerblk->block,
308 strerror ( rc ) );
309 goto err;
310 }
311
312 /* Check digest */
313 digest_final ( digest, peerblk->digestctx, hash );
314 if ( memcmp ( hash, peerblk->hash, peerblk->digestsize ) != 0 ) {
315 DBGC ( peerblk, "PEERBLK %p %d.%d digest mismatch:\n",
316 peerblk, peerblk->segment, peerblk->block );
317 DBGC_HDA ( peerblk, 0, hash, peerblk->digestsize );
318 DBGC_HDA ( peerblk, 0, peerblk->hash, peerblk->digestsize );
319 rc = -EIO;
320 goto err;
321 }
322
323 /* Profile successful attempt */
324 profile_custom ( &peerblk_attempt_success_profiler,
325 ( now - peerblk->attempted ) );
326
327 /* Report peer statistics */
328 head = list_entry ( &segment->peers, struct peerdisc_peer, list );
329 peer = ( ( peerblk->peer == head ) ? NULL : peerblk->peer );
330 peerdisc_stat ( &peerblk->xfer, peer, &segment->peers );
331
332 /* Close download */
333 peerblk_close ( peerblk, 0 );
334 return;
335
336 err:
337 /* Record failure reason and schedule a retry attempt */
338 profile_custom ( &peerblk_attempt_failure_profiler,
339 ( now - peerblk->attempted ) );
340 peerblk_reset ( peerblk, rc );
341 peerblk->rc = rc;
342 start_timer_nodelay ( &peerblk->timer );
343 }
344
345 /******************************************************************************
346 *
347 * Raw block download attempts (using an HTTP range request)
348 *
349 ******************************************************************************
350 */
351
352 /**
353 * Open PeerDist raw block download attempt
354 *
355 * @v peerblk PeerDist block download
356 * @ret rc Return status code
357 */
peerblk_raw_open(struct peerdist_block * peerblk)358 static int peerblk_raw_open ( struct peerdist_block *peerblk ) {
359 struct http_request_range range;
360 int rc;
361
362 DBGC2 ( peerblk, "PEERBLK %p %d.%d attempting raw range request\n",
363 peerblk, peerblk->segment, peerblk->block );
364
365 /* Construct HTTP range */
366 memset ( &range, 0, sizeof ( range ) );
367 range.start = peerblk->range.start;
368 range.len = ( peerblk->range.end - peerblk->range.start );
369
370 /* Initiate range request to retrieve block */
371 if ( ( rc = http_open ( &peerblk->raw, &http_get, peerblk->uri,
372 &range, NULL ) ) != 0 ) {
373 DBGC ( peerblk, "PEERBLK %p %d.%d could not create range "
374 "request: %s\n", peerblk, peerblk->segment,
375 peerblk->block, strerror ( rc ) );
376 return rc;
377 }
378
379 /* Annul HTTP connection (for testing) if applicable. Do not
380 * report as an immediate error, in order to test our ability
381 * to recover from a totally unresponsive HTTP server.
382 */
383 if ( inject_fault ( PEERBLK_ANNUL_RATE ) )
384 intf_restart ( &peerblk->raw, 0 );
385
386 /* Start download attempt timer */
387 peerblk->rc = -ETIMEDOUT;
388 start_timer_fixed ( &peerblk->timer, PEERBLK_RAW_OPEN_TIMEOUT );
389
390 return 0;
391 }
392
393 /**
394 * Receive PeerDist raw data
395 *
396 * @v peerblk PeerDist block download
397 * @v iobuf I/O buffer
398 * @v meta Data transfer metadata
399 * @ret rc Return status code
400 */
peerblk_raw_rx(struct peerdist_block * peerblk,struct io_buffer * iobuf,struct xfer_metadata * meta)401 static int peerblk_raw_rx ( struct peerdist_block *peerblk,
402 struct io_buffer *iobuf,
403 struct xfer_metadata *meta ) {
404 size_t len = iob_len ( iobuf );
405 size_t pos = peerblk->pos;
406 size_t mid = ( ( peerblk->range.end - peerblk->range.start ) / 2 );
407 int rc;
408
409 /* Corrupt received data (for testing) if applicable */
410 inject_corruption ( PEERBLK_CORRUPT_RATE, iobuf->data, len );
411
412 /* Fail if data is delivered out of order, since the streaming
413 * digest requires strict ordering.
414 */
415 if ( ( rc = xfer_check_order ( meta, &peerblk->pos, len ) ) != 0 )
416 goto err;
417
418 /* Add data to digest */
419 digest_update ( peerblk->digest, peerblk->digestctx, iobuf->data, len );
420
421 /* Deliver data */
422 if ( ( rc = peerblk_deliver ( peerblk, iob_disown ( iobuf ), meta,
423 pos ) ) != 0 )
424 goto err;
425
426 /* Extend download attempt timer */
427 start_timer_fixed ( &peerblk->timer, PEERBLK_RAW_RX_TIMEOUT );
428
429 /* Stall download attempt (for testing) if applicable */
430 if ( ( pos < mid ) && ( ( pos + len ) >= mid ) &&
431 ( ( rc = inject_fault ( PEERBLK_STALL_RATE ) ) != 0 ) ) {
432 intf_restart ( &peerblk->raw, rc );
433 }
434
435 return 0;
436
437 err:
438 free_iob ( iobuf );
439 peerblk_done ( peerblk, rc );
440 return rc;
441 }
442
443 /**
444 * Close PeerDist raw block download attempt
445 *
446 * @v peerblk PeerDist block download
447 * @v rc Reason for close
448 */
peerblk_raw_close(struct peerdist_block * peerblk,int rc)449 static void peerblk_raw_close ( struct peerdist_block *peerblk, int rc ) {
450
451 /* Restart interface */
452 intf_restart ( &peerblk->raw, rc );
453
454 /* Fail immediately if we have an error */
455 if ( rc != 0 )
456 goto done;
457
458 /* Abort download attempt (for testing) if applicable */
459 if ( ( rc = inject_fault ( PEERBLK_ABORT_RATE ) ) != 0 )
460 goto done;
461
462 done:
463 /* Complete download attempt */
464 peerblk_done ( peerblk, rc );
465 }
466
467 /******************************************************************************
468 *
469 * Block download queue
470 *
471 ******************************************************************************
472 */
473
474 /**
475 * PeerDist block download queue process
476 *
477 * @v queue Block download queue
478 */
peerblk_step(struct peerdist_block_queue * queue)479 static void peerblk_step ( struct peerdist_block_queue *queue ) {
480 struct peerdist_block *peerblk;
481 int rc;
482
483 /* Do nothing yet if we have too many open block downloads */
484 if ( queue->count >= queue->max )
485 return;
486
487 /* Do nothing unless there are queued block downloads */
488 peerblk = list_first_entry ( &queue->list, struct peerdist_block,
489 queued );
490 if ( ! peerblk )
491 return;
492
493 /* Reschedule queue process */
494 process_add ( &queue->process );
495
496 /* Remove block from queue */
497 list_del ( &peerblk->queued );
498 INIT_LIST_HEAD ( &peerblk->queued );
499
500 /* Attempt download */
501 if ( ( rc = queue->open ( peerblk ) ) != 0 ) {
502 peerblk_close ( peerblk, rc );
503 return;
504 }
505
506 /* Increment open block download count */
507 queue->count++;
508 }
509
510 /**
511 * Add block to download queue
512 *
513 * @v peerblk PeerDist block download
514 * @v queue Block download queue
515 */
peerblk_enqueue(struct peerdist_block * peerblk,struct peerdist_block_queue * queue)516 static void peerblk_enqueue ( struct peerdist_block *peerblk,
517 struct peerdist_block_queue *queue ) {
518
519 /* Sanity checks */
520 assert ( peerblk->queue == NULL );
521 assert ( list_empty ( &peerblk->queued ) );
522
523 /* Add block to queue */
524 peerblk->queue = queue;
525 list_add_tail ( &peerblk->queued, &queue->list );
526
527 /* Schedule queue process */
528 process_add ( &queue->process );
529 }
530
531 /**
532 * Remove block from download queue
533 *
534 * @v peerblk PeerDist block download
535 */
peerblk_dequeue(struct peerdist_block * peerblk)536 static void peerblk_dequeue ( struct peerdist_block *peerblk ) {
537 struct peerdist_block_queue *queue = peerblk->queue;
538
539 /* Sanity checks */
540 assert ( queue != NULL );
541
542 /* Remove block from queue */
543 peerblk->queue = NULL;
544 if ( list_empty ( &peerblk->queued ) ) {
545
546 /* Open download: decrement count and reschedule queue */
547 queue->count--;
548 process_add ( &queue->process );
549
550 } else {
551
552 /* Queued download: remove from queue */
553 list_del ( &peerblk->queued );
554 INIT_LIST_HEAD ( &peerblk->queued );
555 }
556 }
557
558 /** PeerDist block download queue process descriptor */
559 static struct process_descriptor peerblk_queue_desc =
560 PROC_DESC_ONCE ( struct peerdist_block_queue, process, peerblk_step );
561
562 /** Raw block download queue */
563 static struct peerdist_block_queue peerblk_raw_queue = {
564 .process = PROC_INIT ( peerblk_raw_queue.process, &peerblk_queue_desc ),
565 .list = LIST_HEAD_INIT ( peerblk_raw_queue.list ),
566 .max = PEERBLK_RAW_MAX,
567 .open = peerblk_raw_open,
568 };
569
570 /******************************************************************************
571 *
572 * Retrieval protocol block download attempts (using HTTP POST)
573 *
574 ******************************************************************************
575 */
576
577 /**
578 * Construct PeerDist retrieval protocol URI
579 *
580 * @v location Peer location
581 * @ret uri Retrieval URI, or NULL on error
582 */
peerblk_retrieval_uri(const char * location)583 static struct uri * peerblk_retrieval_uri ( const char *location ) {
584 char uri_string[ 7 /* "http://" */ + strlen ( location ) +
585 sizeof ( PEERDIST_MAGIC_PATH /* includes NUL */ ) ];
586
587 /* Construct URI string */
588 snprintf ( uri_string, sizeof ( uri_string ),
589 ( "http://%s" PEERDIST_MAGIC_PATH ), location );
590
591 /* Parse URI string */
592 return parse_uri ( uri_string );
593 }
594
595 /**
596 * Open PeerDist retrieval protocol block download attempt
597 *
598 * @v peerblk PeerDist block download
599 * @v location Peer location
600 * @ret rc Return status code
601 */
peerblk_retrieval_open(struct peerdist_block * peerblk,const char * location)602 static int peerblk_retrieval_open ( struct peerdist_block *peerblk,
603 const char *location ) {
604 size_t digestsize = peerblk->digestsize;
605 peerdist_msg_getblks_t ( digestsize, 1, 0 ) req;
606 peerblk_msg_blk_t ( digestsize, 0, 0, 0 ) *rsp;
607 struct http_request_content content;
608 struct uri *uri;
609 int rc;
610
611 DBGC2 ( peerblk, "PEERBLK %p %d.%d attempting retrieval from %s\n",
612 peerblk, peerblk->segment, peerblk->block, location );
613
614 /* Construct block fetch request */
615 memset ( &req, 0, sizeof ( req ) );
616 req.getblks.hdr.version.raw = htonl ( PEERDIST_MSG_GETBLKS_VERSION );
617 req.getblks.hdr.type = htonl ( PEERDIST_MSG_GETBLKS_TYPE );
618 req.getblks.hdr.len = htonl ( sizeof ( req ) );
619 req.getblks.hdr.algorithm = htonl ( PEERDIST_MSG_AES_128_CBC );
620 req.segment.segment.digestsize = htonl ( digestsize );
621 memcpy ( req.segment.id, peerblk->id, digestsize );
622 req.ranges.ranges.count = htonl ( 1 );
623 req.ranges.range[0].first = htonl ( peerblk->block );
624 req.ranges.range[0].count = htonl ( 1 );
625
626 /* Construct POST request content */
627 memset ( &content, 0, sizeof ( content ) );
628 content.data = &req;
629 content.len = sizeof ( req );
630
631 /* Construct URI */
632 if ( ( uri = peerblk_retrieval_uri ( location ) ) == NULL ) {
633 rc = -ENOMEM;
634 goto err_uri;
635 }
636
637 /* Update trim thresholds */
638 peerblk->start += offsetof ( typeof ( *rsp ), msg.vrf );
639 peerblk->end += offsetof ( typeof ( *rsp ), msg.vrf );
640
641 /* Initiate HTTP POST to retrieve block */
642 if ( ( rc = http_open ( &peerblk->retrieval, &http_post, uri,
643 NULL, &content ) ) != 0 ) {
644 DBGC ( peerblk, "PEERBLK %p %d.%d could not create retrieval "
645 "request: %s\n", peerblk, peerblk->segment,
646 peerblk->block, strerror ( rc ) );
647 goto err_open;
648 }
649
650 /* Annul HTTP connection (for testing) if applicable. Do not
651 * report as an immediate error, in order to test our ability
652 * to recover from a totally unresponsive HTTP server.
653 */
654 if ( inject_fault ( PEERBLK_ANNUL_RATE ) )
655 intf_restart ( &peerblk->retrieval, 0 );
656
657 /* Start download attempt timer */
658 peerblk->rc = -ETIMEDOUT;
659 start_timer_fixed ( &peerblk->timer, PEERBLK_RETRIEVAL_OPEN_TIMEOUT );
660
661 err_open:
662 uri_put ( uri );
663 err_uri:
664 return rc;
665 }
666
667 /**
668 * Receive PeerDist retrieval protocol data
669 *
670 * @v peerblk PeerDist block download
671 * @v iobuf I/O buffer
672 * @v meta Data transfer metadata
673 * @ret rc Return status code
674 */
peerblk_retrieval_rx(struct peerdist_block * peerblk,struct io_buffer * iobuf,struct xfer_metadata * meta)675 static int peerblk_retrieval_rx ( struct peerdist_block *peerblk,
676 struct io_buffer *iobuf,
677 struct xfer_metadata *meta ) {
678 size_t len = iob_len ( iobuf );
679 size_t start;
680 size_t end;
681 size_t before;
682 size_t after;
683 size_t cut;
684 int rc;
685
686 /* Some genius at Microsoft thought it would be a great idea
687 * to place the AES-CBC initialisation vector *after* the
688 * encrypted data, thereby making it logically impossible to
689 * decrypt each packet as it arrives.
690 *
691 * To work around this mindless stupidity, we deliver the
692 * ciphertext as-is and later use xfer_buffer() to obtain
693 * access to the underlying data transfer buffer in order to
694 * perform the decryption.
695 *
696 * There will be some data both before and after the bytes
697 * corresponding to the trimmed plaintext: a MSG_BLK
698 * header/footer, some block padding for the AES-CBC cipher,
699 * and a possibly large quantity of unwanted ciphertext which
700 * is excluded from the trimmed content range. We store this
701 * data in a local data transfer buffer. If the amount of
702 * data to be stored is too large, we will fail allocation and
703 * so eventually fall back to using a range request (which
704 * does not require this kind of temporary storage
705 * allocation).
706 */
707
708 /* Corrupt received data (for testing) if applicable */
709 inject_corruption ( PEERBLK_CORRUPT_RATE, iobuf->data, len );
710
711 /* Calculate start and end positions of this buffer */
712 start = peerblk->pos;
713 if ( meta->flags & XFER_FL_ABS_OFFSET )
714 start = 0;
715 start += meta->offset;
716 end = ( start + len );
717
718 /* Buffer any data before the trimmed content */
719 if ( ( start < peerblk->start ) && ( len > 0 ) ) {
720
721 /* Calculate length of data before the trimmed content */
722 before = ( peerblk->start - start );
723 if ( before > len )
724 before = len;
725
726 /* Buffer data before the trimmed content */
727 if ( ( rc = xferbuf_write ( &peerblk->buffer, start,
728 iobuf->data, before ) ) != 0 ) {
729 DBGC ( peerblk, "PEERBLK %p %d.%d could not buffer "
730 "data: %s\n", peerblk, peerblk->segment,
731 peerblk->block, strerror ( rc ) );
732 goto err;
733 }
734 }
735
736 /* Buffer any data after the trimmed content */
737 if ( ( end > peerblk->end ) && ( len > 0 ) ) {
738
739 /* Calculate length of data after the trimmed content */
740 after = ( end - peerblk->end );
741 if ( after > len )
742 after = len;
743
744 /* Buffer data after the trimmed content */
745 cut = ( peerblk->end - peerblk->start );
746 if ( ( rc = xferbuf_write ( &peerblk->buffer,
747 ( end - after - cut ),
748 ( iobuf->data + len - after ),
749 after ) ) != 0 ) {
750 DBGC ( peerblk, "PEERBLK %p %d.%d could not buffer "
751 "data: %s\n", peerblk, peerblk->segment,
752 peerblk->block, strerror ( rc ) );
753 goto err;
754 }
755 }
756
757 /* Deliver any remaining data */
758 if ( ( rc = peerblk_deliver ( peerblk, iob_disown ( iobuf ), meta,
759 start ) ) != 0 )
760 goto err;
761
762 /* Update position */
763 peerblk->pos = end;
764
765 /* Extend download attempt timer */
766 start_timer_fixed ( &peerblk->timer, PEERBLK_RETRIEVAL_RX_TIMEOUT );
767
768 /* Stall download attempt (for testing) if applicable */
769 if ( ( start < peerblk->end ) && ( end >= peerblk->end ) &&
770 ( ( rc = inject_fault ( PEERBLK_STALL_RATE ) ) != 0 ) ) {
771 intf_restart ( &peerblk->retrieval, rc );
772 }
773
774 return 0;
775
776 err:
777 free_iob ( iobuf );
778 peerblk_done ( peerblk, rc );
779 return rc;
780 }
781
782 /**
783 * Parse retrieval protocol message header
784 *
785 * @v peerblk PeerDist block download
786 * @ret rc Return status code
787 */
peerblk_parse_header(struct peerdist_block * peerblk)788 static int peerblk_parse_header ( struct peerdist_block *peerblk ) {
789 struct {
790 struct peerdist_msg_transport_header hdr;
791 struct peerdist_msg_header msg;
792 } __attribute__ (( packed )) *msg = peerblk->buffer.data;
793 struct cipher_algorithm *cipher;
794 size_t len = peerblk->buffer.len;
795 size_t keylen = 0;
796 int rc;
797
798 /* Check message length */
799 if ( len < sizeof ( *msg ) ) {
800 DBGC ( peerblk, "PEERBLK %p %d.%d message too short for header "
801 "(%zd bytes)\n", peerblk, peerblk->segment,
802 peerblk->block, len );
803 return -ERANGE;
804 }
805
806 /* Check message type */
807 if ( msg->msg.type != htonl ( PEERDIST_MSG_BLK_TYPE ) ) {
808 DBGC ( peerblk, "PEERBLK %p %d.%d unexpected message type "
809 "%#08x\n", peerblk, peerblk->segment, peerblk->block,
810 ntohl ( msg->msg.type ) );
811 return -EPROTO;
812 }
813
814 /* Determine cipher algorithm and key length */
815 cipher = &aes_cbc_algorithm;
816 switch ( msg->msg.algorithm ) {
817 case htonl ( PEERDIST_MSG_PLAINTEXT ) :
818 cipher = NULL;
819 break;
820 case htonl ( PEERDIST_MSG_AES_128_CBC ) :
821 keylen = ( 128 / 8 );
822 break;
823 case htonl ( PEERDIST_MSG_AES_192_CBC ) :
824 keylen = ( 192 / 8 );
825 break;
826 case htonl ( PEERDIST_MSG_AES_256_CBC ) :
827 keylen = ( 256 / 8 );
828 break;
829 default:
830 DBGC ( peerblk, "PEERBLK %p %d.%d unrecognised algorithm "
831 "%#08x\n", peerblk, peerblk->segment, peerblk->block,
832 ntohl ( msg->msg.algorithm ) );
833 return -ENOTSUP;
834 }
835 DBGC2 ( peerblk, "PEERBLK %p %d.%d using %s with %zd-bit key\n",
836 peerblk, peerblk->segment, peerblk->block,
837 ( cipher ? cipher->name : "plaintext" ), ( 8 * keylen ) );
838
839 /* Sanity check key length against maximum secret length */
840 if ( keylen > peerblk->digestsize ) {
841 DBGC ( peerblk, "PEERBLK %p %d.%d %zd-byte secret too short "
842 "for %zd-bit key\n", peerblk, peerblk->segment,
843 peerblk->block, peerblk->digestsize, ( 8 * keylen ) );
844 return -EPROTO;
845 }
846
847 /* Allocate cipher context, if applicable. Freeing the cipher
848 * context (on error or otherwise) is handled by peerblk_reset().
849 */
850 peerblk->cipher = cipher;
851 assert ( peerblk->cipherctx == NULL );
852 if ( cipher ) {
853 peerblk->cipherctx = malloc ( cipher->ctxsize );
854 if ( ! peerblk->cipherctx )
855 return -ENOMEM;
856 }
857
858 /* Initialise cipher, if applicable */
859 if ( cipher &&
860 ( rc = cipher_setkey ( cipher, peerblk->cipherctx, peerblk->secret,
861 keylen ) ) != 0 ) {
862 DBGC ( peerblk, "PEERBLK %p %d.%d could not set key: %s\n",
863 peerblk, peerblk->segment, peerblk->block,
864 strerror ( rc ) );
865 return rc;
866 }
867
868 return 0;
869 }
870
871 /**
872 * Parse retrieval protocol message segment and block details
873 *
874 * @v peerblk PeerDist block download
875 * @v buf_len Length of buffered data to fill in
876 * @ret rc Return status code
877 */
peerblk_parse_block(struct peerdist_block * peerblk,size_t * buf_len)878 static int peerblk_parse_block ( struct peerdist_block *peerblk,
879 size_t *buf_len ) {
880 size_t digestsize = peerblk->digestsize;
881 peerblk_msg_blk_t ( digestsize, 0, 0, 0 ) *msg = peerblk->buffer.data;
882 size_t len = peerblk->buffer.len;
883 size_t data_len;
884 size_t total;
885
886 /* Check message length */
887 if ( len < offsetof ( typeof ( *msg ), msg.block.data ) ) {
888 DBGC ( peerblk, "PEERBLK %p %d.%d message too short for "
889 "zero-length data (%zd bytes)\n", peerblk,
890 peerblk->segment, peerblk->block, len );
891 return -ERANGE;
892 }
893
894 /* Check digest size */
895 if ( ntohl ( msg->msg.segment.segment.digestsize ) != digestsize ) {
896 DBGC ( peerblk, "PEERBLK %p %d.%d incorrect digest size %d\n",
897 peerblk, peerblk->segment, peerblk->block,
898 ntohl ( msg->msg.segment.segment.digestsize ) );
899 return -EPROTO;
900 }
901
902 /* Check segment ID */
903 if ( memcmp ( msg->msg.segment.id, peerblk->id, digestsize ) != 0 ) {
904 DBGC ( peerblk, "PEERBLK %p %d.%d segment ID mismatch\n",
905 peerblk, peerblk->segment, peerblk->block );
906 return -EPROTO;
907 }
908
909 /* Check block ID */
910 if ( ntohl ( msg->msg.index ) != peerblk->block ) {
911 DBGC ( peerblk, "PEERBLK %p %d.%d block ID mismatch (got %d)\n",
912 peerblk, peerblk->segment, peerblk->block,
913 ntohl ( msg->msg.index ) );
914 return -EPROTO;
915 }
916
917 /* Check for missing blocks */
918 data_len = be32_to_cpu ( msg->msg.block.block.len );
919 if ( ! data_len ) {
920 DBGC ( peerblk, "PEERBLK %p %d.%d block not found\n",
921 peerblk, peerblk->segment, peerblk->block );
922 return -ENOENT;
923 }
924
925 /* Check for underlength blocks */
926 if ( data_len < ( peerblk->range.end - peerblk->range.start ) ) {
927 DBGC ( peerblk, "PEERBLK %p %d.%d underlength block (%zd "
928 "bytes)\n", peerblk, peerblk->segment, peerblk->block,
929 data_len );
930 return -ERANGE;
931 }
932
933 /* Calculate buffered data length (i.e. excluding data which
934 * was delivered to the final data transfer buffer).
935 */
936 *buf_len = ( data_len - ( peerblk->end - peerblk->start ) );
937
938 /* Describe data before the trimmed content */
939 peerblk->decrypt[PEERBLK_BEFORE].xferbuf = &peerblk->buffer;
940 peerblk->decrypt[PEERBLK_BEFORE].offset =
941 offsetof ( typeof ( *msg ), msg.block.data );
942 peerblk->decrypt[PEERBLK_BEFORE].len =
943 ( peerblk->start -
944 offsetof ( typeof ( *msg ), msg.block.data ) );
945 total = peerblk->decrypt[PEERBLK_BEFORE].len;
946
947 /* Describe data within the trimmed content */
948 peerblk->decrypt[PEERBLK_DURING].offset =
949 peerblk_offset ( peerblk, peerblk->start );
950 peerblk->decrypt[PEERBLK_DURING].len =
951 ( peerblk->end - peerblk->start );
952 total += peerblk->decrypt[PEERBLK_DURING].len;
953
954 /* Describe data after the trimmed content */
955 peerblk->decrypt[PEERBLK_AFTER].xferbuf = &peerblk->buffer;
956 peerblk->decrypt[PEERBLK_AFTER].offset = peerblk->start;
957 peerblk->decrypt[PEERBLK_AFTER].len =
958 ( offsetof ( typeof ( *msg ), msg.block.data )
959 + *buf_len - peerblk->start );
960 total += peerblk->decrypt[PEERBLK_AFTER].len;
961
962 /* Sanity check */
963 assert ( total == be32_to_cpu ( msg->msg.block.block.len ) );
964
965 /* Initialise cipher and digest lengths */
966 peerblk->cipher_remaining = total;
967 peerblk->digest_remaining =
968 ( peerblk->range.end - peerblk->range.start );
969 assert ( peerblk->cipher_remaining >= peerblk->digest_remaining );
970
971 return 0;
972 }
973
974 /**
975 * Parse retrieval protocol message useless details
976 *
977 * @v peerblk PeerDist block download
978 * @v buf_len Length of buffered data
979 * @v vrf_len Length of uselessness to fill in
980 * @ret rc Return status code
981 */
peerblk_parse_useless(struct peerdist_block * peerblk,size_t buf_len,size_t * vrf_len)982 static int peerblk_parse_useless ( struct peerdist_block *peerblk,
983 size_t buf_len, size_t *vrf_len ) {
984 size_t digestsize = peerblk->digestsize;
985 peerblk_msg_blk_t ( digestsize, buf_len, 0, 0 ) *msg =
986 peerblk->buffer.data;
987 size_t len = peerblk->buffer.len;
988
989 /* Check message length */
990 if ( len < offsetof ( typeof ( *msg ), msg.vrf.data ) ) {
991 DBGC ( peerblk, "PEERBLK %p %d.%d message too short for "
992 "zero-length uselessness (%zd bytes)\n", peerblk,
993 peerblk->segment, peerblk->block, len );
994 return -ERANGE;
995 }
996
997 /* Extract length of uselessness */
998 *vrf_len = be32_to_cpu ( msg->msg.vrf.vrf.len );
999
1000 return 0;
1001 }
1002
1003 /**
1004 * Parse retrieval protocol message initialisation vector details
1005 *
1006 * @v peerblk PeerDist block download
1007 * @v buf_len Length of buffered data
1008 * @v vrf_len Length of uselessness
1009 * @ret rc Return status code
1010 */
peerblk_parse_iv(struct peerdist_block * peerblk,size_t buf_len,size_t vrf_len)1011 static int peerblk_parse_iv ( struct peerdist_block *peerblk, size_t buf_len,
1012 size_t vrf_len ) {
1013 size_t digestsize = peerblk->digestsize;
1014 size_t blksize = peerblk->cipher->blocksize;
1015 peerblk_msg_blk_t ( digestsize, buf_len, vrf_len, blksize ) *msg =
1016 peerblk->buffer.data;
1017 size_t len = peerblk->buffer.len;
1018
1019 /* Check message length */
1020 if ( len < sizeof ( *msg ) ) {
1021 DBGC ( peerblk, "PEERBLK %p %d.%d message too short for "
1022 "initialisation vector (%zd bytes)\n", peerblk,
1023 peerblk->segment, peerblk->block, len );
1024 return -ERANGE;
1025 }
1026
1027 /* Check initialisation vector size */
1028 if ( ntohl ( msg->msg.iv.iv.blksize ) != blksize ) {
1029 DBGC ( peerblk, "PEERBLK %p %d.%d incorrect IV size %d\n",
1030 peerblk, peerblk->segment, peerblk->block,
1031 ntohl ( msg->msg.iv.iv.blksize ) );
1032 return -EPROTO;
1033 }
1034
1035 /* Set initialisation vector */
1036 cipher_setiv ( peerblk->cipher, peerblk->cipherctx, msg->msg.iv.data );
1037
1038 return 0;
1039 }
1040
1041 /**
1042 * Read from decryption buffers
1043 *
1044 * @v peerblk PeerDist block download
1045 * @v data Data buffer
1046 * @v len Length to read
1047 * @ret rc Return status code
1048 */
peerblk_decrypt_read(struct peerdist_block * peerblk,void * data,size_t len)1049 static int peerblk_decrypt_read ( struct peerdist_block *peerblk,
1050 void *data, size_t len ) {
1051 struct peerdist_block_decrypt *decrypt = peerblk->decrypt;
1052 size_t frag_len;
1053 int rc;
1054
1055 /* Read from each decryption buffer in turn */
1056 for ( ; len ; decrypt++, data += frag_len, len -= frag_len ) {
1057
1058 /* Calculate length to use from this buffer */
1059 frag_len = decrypt->len;
1060 if ( frag_len > len )
1061 frag_len = len;
1062 if ( ! frag_len )
1063 continue;
1064
1065 /* Read from this buffer */
1066 if ( ( rc = xferbuf_read ( decrypt->xferbuf, decrypt->offset,
1067 data, frag_len ) ) != 0 )
1068 return rc;
1069 }
1070
1071 return 0;
1072 }
1073
1074 /**
1075 * Write to decryption buffers and update offsets and lengths
1076 *
1077 * @v peerblk PeerDist block download
1078 * @v data Data buffer
1079 * @v len Length to read
1080 * @ret rc Return status code
1081 */
peerblk_decrypt_write(struct peerdist_block * peerblk,const void * data,size_t len)1082 static int peerblk_decrypt_write ( struct peerdist_block *peerblk,
1083 const void *data, size_t len ) {
1084 struct peerdist_block_decrypt *decrypt = peerblk->decrypt;
1085 size_t frag_len;
1086 int rc;
1087
1088 /* Write to each decryption buffer in turn */
1089 for ( ; len ; decrypt++, data += frag_len, len -= frag_len ) {
1090
1091 /* Calculate length to use from this buffer */
1092 frag_len = decrypt->len;
1093 if ( frag_len > len )
1094 frag_len = len;
1095 if ( ! frag_len )
1096 continue;
1097
1098 /* Write to this buffer */
1099 if ( ( rc = xferbuf_write ( decrypt->xferbuf, decrypt->offset,
1100 data, frag_len ) ) != 0 )
1101 return rc;
1102
1103 /* Update offset and length */
1104 decrypt->offset += frag_len;
1105 decrypt->len -= frag_len;
1106 }
1107
1108 return 0;
1109 }
1110
1111 /**
1112 * Decrypt one chunk of PeerDist retrieval protocol data
1113 *
1114 * @v peerblk PeerDist block download
1115 */
peerblk_decrypt(struct peerdist_block * peerblk)1116 static void peerblk_decrypt ( struct peerdist_block *peerblk ) {
1117 struct cipher_algorithm *cipher = peerblk->cipher;
1118 struct digest_algorithm *digest = peerblk->digest;
1119 struct xfer_buffer *xferbuf;
1120 size_t cipher_len;
1121 size_t digest_len;
1122 void *data;
1123 int rc;
1124
1125 /* Sanity check */
1126 assert ( ( PEERBLK_DECRYPT_CHUNKSIZE % cipher->blocksize ) == 0 );
1127
1128 /* Get the underlying data transfer buffer */
1129 xferbuf = xfer_buffer ( &peerblk->xfer );
1130 if ( ! xferbuf ) {
1131 DBGC ( peerblk, "PEERBLK %p %d.%d has no underlying data "
1132 "transfer buffer\n", peerblk, peerblk->segment,
1133 peerblk->block );
1134 rc = -ENOTSUP;
1135 goto err_xfer_buffer;
1136 }
1137 peerblk->decrypt[PEERBLK_DURING].xferbuf = xferbuf;
1138
1139 /* Calculate cipher and digest lengths */
1140 cipher_len = PEERBLK_DECRYPT_CHUNKSIZE;
1141 if ( cipher_len > peerblk->cipher_remaining )
1142 cipher_len = peerblk->cipher_remaining;
1143 digest_len = cipher_len;
1144 if ( digest_len > peerblk->digest_remaining )
1145 digest_len = peerblk->digest_remaining;
1146 assert ( ( cipher_len & ( cipher->blocksize - 1 ) ) == 0 );
1147
1148 /* Allocate temporary data buffer */
1149 data = malloc ( cipher_len );
1150 if ( ! data ) {
1151 rc = -ENOMEM;
1152 goto err_alloc_data;
1153 }
1154
1155 /* Read ciphertext */
1156 if ( ( rc = peerblk_decrypt_read ( peerblk, data, cipher_len ) ) != 0 ){
1157 DBGC ( peerblk, "PEERBLK %p %d.%d could not read ciphertext: "
1158 "%s\n", peerblk, peerblk->segment, peerblk->block,
1159 strerror ( rc ) );
1160 goto err_read;
1161 }
1162
1163 /* Decrypt data */
1164 cipher_decrypt ( cipher, peerblk->cipherctx, data, data, cipher_len );
1165
1166 /* Add data to digest */
1167 digest_update ( digest, peerblk->digestctx, data, digest_len );
1168
1169 /* Write plaintext */
1170 if ( ( rc = peerblk_decrypt_write ( peerblk, data, cipher_len ) ) != 0){
1171 DBGC ( peerblk, "PEERBLK %p %d.%d could not write plaintext: "
1172 "%s\n", peerblk, peerblk->segment, peerblk->block,
1173 strerror ( rc ) );
1174 goto err_write;
1175 }
1176
1177 /* Consume input */
1178 peerblk->cipher_remaining -= cipher_len;
1179 peerblk->digest_remaining -= digest_len;
1180
1181 /* Free temporary data buffer */
1182 free ( data );
1183
1184 /* Continue processing until all input is consumed */
1185 if ( peerblk->cipher_remaining )
1186 return;
1187
1188 /* Complete download attempt */
1189 peerblk_done ( peerblk, 0 );
1190 return;
1191
1192 err_write:
1193 err_read:
1194 free ( data );
1195 err_alloc_data:
1196 err_xfer_buffer:
1197 peerblk_done ( peerblk, rc );
1198 }
1199
1200 /**
1201 * Close PeerDist retrieval protocol block download attempt
1202 *
1203 * @v peerblk PeerDist block download
1204 * @v rc Reason for close
1205 */
peerblk_retrieval_close(struct peerdist_block * peerblk,int rc)1206 static void peerblk_retrieval_close ( struct peerdist_block *peerblk, int rc ) {
1207 size_t buf_len;
1208 size_t vrf_len;
1209
1210 /* Restart interface */
1211 intf_restart ( &peerblk->retrieval, rc );
1212
1213 /* Fail immediately if we have an error */
1214 if ( rc != 0 )
1215 goto done;
1216
1217 /* Abort download attempt (for testing) if applicable */
1218 if ( ( rc = inject_fault ( PEERBLK_ABORT_RATE ) ) != 0 )
1219 goto done;
1220
1221 /* Parse message header */
1222 if ( ( rc = peerblk_parse_header ( peerblk ) ) != 0 )
1223 goto done;
1224
1225 /* Parse message segment and block details */
1226 if ( ( rc = peerblk_parse_block ( peerblk, &buf_len ) ) != 0 )
1227 goto done;
1228
1229 /* If the block was plaintext, then there is nothing more to do */
1230 if ( ! peerblk->cipher )
1231 goto done;
1232
1233 /* Parse message useless details */
1234 if ( ( rc = peerblk_parse_useless ( peerblk, buf_len, &vrf_len ) ) != 0)
1235 goto done;
1236
1237 /* Parse message initialisation vector details */
1238 if ( ( rc = peerblk_parse_iv ( peerblk, buf_len, vrf_len ) ) != 0 )
1239 goto done;
1240
1241 /* Fail if decryption length is not aligned to the cipher block size */
1242 if ( peerblk->cipher_remaining & ( peerblk->cipher->blocksize - 1 ) ) {
1243 DBGC ( peerblk, "PEERBLK %p %d.%d unaligned data length %zd\n",
1244 peerblk, peerblk->segment, peerblk->block,
1245 peerblk->cipher_remaining );
1246 rc = -EPROTO;
1247 goto done;
1248 }
1249
1250 /* Stop the download attempt timer: there is no point in
1251 * timing out while decrypting.
1252 */
1253 stop_timer ( &peerblk->timer );
1254
1255 /* Start decryption process */
1256 process_add ( &peerblk->process );
1257 return;
1258
1259 done:
1260 /* Complete download attempt */
1261 peerblk_done ( peerblk, rc );
1262 }
1263
1264 /******************************************************************************
1265 *
1266 * Retry policy
1267 *
1268 ******************************************************************************
1269 */
1270
1271 /**
1272 * Handle PeerDist retry timer expiry
1273 *
1274 * @v timer Retry timer
1275 * @v over Failure indicator
1276 */
peerblk_expired(struct retry_timer * timer,int over __unused)1277 static void peerblk_expired ( struct retry_timer *timer, int over __unused ) {
1278 struct peerdist_block *peerblk =
1279 container_of ( timer, struct peerdist_block, timer );
1280 struct peerdisc_segment *segment = peerblk->discovery.segment;
1281 struct peerdisc_peer *head;
1282 unsigned long now = peerblk_timestamp();
1283 const char *location;
1284 int rc;
1285
1286 /* Profile discovery timeout, if applicable */
1287 if ( ( peerblk->peer == NULL ) && ( timer->timeout != 0 ) ) {
1288 profile_custom ( &peerblk_discovery_timeout_profiler,
1289 ( now - peerblk->started ) );
1290 DBGC ( peerblk, "PEERBLK %p %d.%d discovery timed out after "
1291 "%ld ticks\n", peerblk, peerblk->segment,
1292 peerblk->block, timer->timeout );
1293 }
1294
1295 /* Profile download timeout, if applicable */
1296 if ( ( peerblk->peer != NULL ) && ( timer->timeout != 0 ) ) {
1297 profile_custom ( &peerblk_attempt_timeout_profiler,
1298 ( now - peerblk->attempted ) );
1299 DBGC ( peerblk, "PEERBLK %p %d.%d timed out after %ld ticks\n",
1300 peerblk, peerblk->segment, peerblk->block,
1301 timer->timeout );
1302 }
1303
1304 /* Abort any current download attempt */
1305 peerblk_reset ( peerblk, -ETIMEDOUT );
1306
1307 /* Record attempt start time */
1308 peerblk->attempted = now;
1309
1310 /* If we have exceeded our maximum number of attempt cycles
1311 * (each cycle comprising a retrieval protocol download from
1312 * each peer in the list followed by a raw download from the
1313 * origin server), then abort the overall download.
1314 */
1315 head = list_entry ( &segment->peers, struct peerdisc_peer, list );
1316 if ( ( peerblk->peer == head ) &&
1317 ( ++peerblk->cycles >= PEERBLK_MAX_ATTEMPT_CYCLES ) ) {
1318 rc = peerblk->rc;
1319 assert ( rc != 0 );
1320 goto err;
1321 }
1322
1323 /* If we have not yet made any download attempts, then move to
1324 * the start of the peer list.
1325 */
1326 if ( peerblk->peer == NULL )
1327 peerblk->peer = head;
1328
1329 /* Attempt retrieval protocol download from next usable peer */
1330 list_for_each_entry_continue ( peerblk->peer, &segment->peers, list ) {
1331
1332 /* Attempt retrieval protocol download from this peer */
1333 location = peerblk->peer->location;
1334 if ( ( rc = peerblk_retrieval_open ( peerblk,
1335 location ) ) != 0 ) {
1336 /* Non-fatal: continue to try next peer */
1337 continue;
1338 }
1339
1340 /* Peer download started */
1341 return;
1342 }
1343
1344 /* Add to raw download queue */
1345 peerblk_enqueue ( peerblk, &peerblk_raw_queue );
1346
1347 return;
1348
1349 err:
1350 peerblk_close ( peerblk, rc );
1351 }
1352
1353 /**
1354 * Handle PeerDist peer discovery
1355 *
1356 * @v discovery PeerDist discovery client
1357 */
peerblk_discovered(struct peerdisc_client * discovery)1358 static void peerblk_discovered ( struct peerdisc_client *discovery ) {
1359 struct peerdist_block *peerblk =
1360 container_of ( discovery, struct peerdist_block, discovery );
1361 unsigned long now = peerblk_timestamp();
1362
1363 /* Do nothing unless we are still waiting for the initial
1364 * discovery timeout.
1365 */
1366 if ( ( peerblk->peer != NULL ) || ( peerblk->timer.timeout == 0 ) )
1367 return;
1368
1369 /* Schedule an immediate retry */
1370 start_timer_nodelay ( &peerblk->timer );
1371
1372 /* Profile discovery success */
1373 profile_custom ( &peerblk_discovery_success_profiler,
1374 ( now - peerblk->started ) );
1375 }
1376
1377 /******************************************************************************
1378 *
1379 * Opener
1380 *
1381 ******************************************************************************
1382 */
1383
1384 /** PeerDist block download data transfer interface operations */
1385 static struct interface_operation peerblk_xfer_operations[] = {
1386 INTF_OP ( intf_close, struct peerdist_block *, peerblk_close ),
1387 };
1388
1389 /** PeerDist block download data transfer interface descriptor */
1390 static struct interface_descriptor peerblk_xfer_desc =
1391 INTF_DESC ( struct peerdist_block, xfer, peerblk_xfer_operations );
1392
1393 /** PeerDist block download raw data interface operations */
1394 static struct interface_operation peerblk_raw_operations[] = {
1395 INTF_OP ( xfer_deliver, struct peerdist_block *, peerblk_raw_rx ),
1396 INTF_OP ( intf_close, struct peerdist_block *, peerblk_raw_close ),
1397 };
1398
1399 /** PeerDist block download raw data interface descriptor */
1400 static struct interface_descriptor peerblk_raw_desc =
1401 INTF_DESC ( struct peerdist_block, raw, peerblk_raw_operations );
1402
1403 /** PeerDist block download retrieval protocol interface operations */
1404 static struct interface_operation peerblk_retrieval_operations[] = {
1405 INTF_OP ( xfer_deliver, struct peerdist_block *, peerblk_retrieval_rx ),
1406 INTF_OP ( intf_close, struct peerdist_block *, peerblk_retrieval_close),
1407 };
1408
1409 /** PeerDist block download retrieval protocol interface descriptor */
1410 static struct interface_descriptor peerblk_retrieval_desc =
1411 INTF_DESC ( struct peerdist_block, retrieval,
1412 peerblk_retrieval_operations );
1413
1414 /** PeerDist block download decryption process descriptor */
1415 static struct process_descriptor peerblk_process_desc =
1416 PROC_DESC ( struct peerdist_block, process, peerblk_decrypt );
1417
1418 /** PeerDist block download discovery operations */
1419 static struct peerdisc_client_operations peerblk_discovery_operations = {
1420 .discovered = peerblk_discovered,
1421 };
1422
1423 /**
1424 * Open PeerDist block download
1425 *
1426 * @v xfer Data transfer interface
1427 * @v uri Original URI
1428 * @v info Content information block
1429 * @ret rc Return status code
1430 */
peerblk_open(struct interface * xfer,struct uri * uri,struct peerdist_info_block * block)1431 int peerblk_open ( struct interface *xfer, struct uri *uri,
1432 struct peerdist_info_block *block ) {
1433 const struct peerdist_info_segment *segment = block->segment;
1434 const struct peerdist_info *info = segment->info;
1435 struct digest_algorithm *digest = info->digest;
1436 struct peerdist_block *peerblk;
1437 unsigned long timeout;
1438 size_t digestsize;
1439 int rc;
1440
1441 /* Allocate and initialise structure */
1442 peerblk = zalloc ( sizeof ( *peerblk ) + digest->ctxsize );
1443 if ( ! peerblk ) {
1444 rc = -ENOMEM;
1445 goto err_alloc;
1446 }
1447 ref_init ( &peerblk->refcnt, peerblk_free );
1448 intf_init ( &peerblk->xfer, &peerblk_xfer_desc, &peerblk->refcnt );
1449 intf_init ( &peerblk->raw, &peerblk_raw_desc, &peerblk->refcnt );
1450 intf_init ( &peerblk->retrieval, &peerblk_retrieval_desc,
1451 &peerblk->refcnt );
1452 peerblk->uri = uri_get ( uri );
1453 memcpy ( &peerblk->range, &block->range, sizeof ( peerblk->range ) );
1454 memcpy ( &peerblk->trim, &block->trim, sizeof ( peerblk->trim ) );
1455 peerblk->offset = ( block->trim.start - info->trim.start );
1456 peerblk->digest = info->digest;
1457 peerblk->digestsize = digestsize = info->digestsize;
1458 peerblk->digestctx = ( ( ( void * ) peerblk ) + sizeof ( *peerblk ) );
1459 peerblk->segment = segment->index;
1460 memcpy ( peerblk->id, segment->id, sizeof ( peerblk->id ) );
1461 memcpy ( peerblk->secret, segment->secret, sizeof ( peerblk->secret ) );
1462 peerblk->block = block->index;
1463 memcpy ( peerblk->hash, block->hash, sizeof ( peerblk->hash ) );
1464 xferbuf_malloc_init ( &peerblk->buffer );
1465 process_init_stopped ( &peerblk->process, &peerblk_process_desc,
1466 &peerblk->refcnt );
1467 peerdisc_init ( &peerblk->discovery, &peerblk_discovery_operations );
1468 INIT_LIST_HEAD ( &peerblk->queued );
1469 timer_init ( &peerblk->timer, peerblk_expired, &peerblk->refcnt );
1470 DBGC2 ( peerblk, "PEERBLK %p %d.%d id %02x%02x%02x%02x%02x..."
1471 "%02x%02x%02x [%08zx,%08zx)", peerblk, peerblk->segment,
1472 peerblk->block, peerblk->id[0], peerblk->id[1], peerblk->id[2],
1473 peerblk->id[3], peerblk->id[4], peerblk->id[ digestsize - 3 ],
1474 peerblk->id[ digestsize - 2 ], peerblk->id[ digestsize - 1 ],
1475 peerblk->range.start, peerblk->range.end );
1476 if ( ( peerblk->trim.start != peerblk->range.start ) ||
1477 ( peerblk->trim.end != peerblk->range.end ) ) {
1478 DBGC2 ( peerblk, " covers [%08zx,%08zx)",
1479 peerblk->trim.start, peerblk->trim.end );
1480 }
1481 DBGC2 ( peerblk, "\n" );
1482
1483 /* Open discovery */
1484 if ( ( rc = peerdisc_open ( &peerblk->discovery, peerblk->id,
1485 peerblk->digestsize ) ) != 0 )
1486 goto err_open_discovery;
1487
1488 /* Schedule a retry attempt either immediately (if we already
1489 * have some peers) or after the discovery timeout.
1490 */
1491 timeout = ( list_empty ( &peerblk->discovery.segment->peers ) ?
1492 ( peerdisc_timeout_secs * TICKS_PER_SEC ) : 0 );
1493 start_timer_fixed ( &peerblk->timer, timeout );
1494
1495 /* Record start time */
1496 peerblk->started = peerblk_timestamp();
1497
1498 /* Attach to parent interface, mortalise self, and return */
1499 intf_plug_plug ( xfer, &peerblk->xfer );
1500 ref_put ( &peerblk->refcnt );
1501 return 0;
1502
1503 err_open_discovery:
1504 peerblk_close ( peerblk, rc );
1505 err_alloc:
1506 return rc;
1507 }
1508