[efi] Disable EFI watchdog timer when shutting down to boot an OS
[ipxe.git] / src / net / peerdisc.c
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 <string.h>
28 #include <ctype.h>
29 #include <errno.h>
30 #include <assert.h>
31 #include <ipxe/xfer.h>
32 #include <ipxe/iobuf.h>
33 #include <ipxe/open.h>
34 #include <ipxe/tcpip.h>
35 #include <ipxe/uuid.h>
36 #include <ipxe/base16.h>
37 #include <ipxe/netdevice.h>
38 #include <ipxe/timer.h>
39 #include <ipxe/fault.h>
40 #include <ipxe/settings.h>
41 #include <ipxe/pccrd.h>
42 #include <ipxe/peerdisc.h>
43
44 /** @file
45 *
46 * Peer Content Caching and Retrieval (PeerDist) protocol peer discovery
47 *
48 */
49
50 /** List of discovery segments */
51 static LIST_HEAD ( peerdisc_segments );
52
53 /** Number of repeated discovery attempts */
54 #define PEERDISC_REPEAT_COUNT 2
55
56 /** Time between repeated discovery attempts */
57 #define PEERDISC_REPEAT_TIMEOUT ( 1 * TICKS_PER_SEC )
58
59 /** Default discovery timeout (in seconds) */
60 #define PEERDISC_DEFAULT_TIMEOUT_SECS 2
61
62 /** Recommended discovery timeout (in seconds)
63 *
64 * We reduce the recommended discovery timeout whenever a segment
65 * fails to discover any peers, and restore the default value whenever
66 * a valid discovery reply is received. We continue to send discovery
67 * requests even if the recommended timeout is reduced to zero.
68 *
69 * This strategy is intended to minimise discovery delays when no
70 * peers are available on the network, while allowing downloads to
71 * quickly switch back to using PeerDist acceleration if new peers
72 * become available.
73 */
74 unsigned int peerdisc_timeout_secs = PEERDISC_DEFAULT_TIMEOUT_SECS;
75
76 /** Most recently discovered peer (for any block) */
77 static char *peerdisc_recent;
78
79 /** Hosted cache server */
80 static char *peerhost;
81
82 static struct peerdisc_segment * peerdisc_find ( const char *id );
83 static int peerdisc_discovered ( struct peerdisc_segment *segment,
84 const char *location );
85
86 /******************************************************************************
87 *
88 * Statistics reporting
89 *
90 ******************************************************************************
91 */
92
93 /**
94 * Report peer discovery statistics
95 *
96 * @v intf Interface
97 * @v peer Selected peer (or NULL)
98 * @v peers List of available peers
99 */
100 void peerdisc_stat ( struct interface *intf, struct peerdisc_peer *peer,
101 struct list_head *peers ) {
102 struct interface *dest;
103 peerdisc_stat_TYPE ( void * ) *op =
104 intf_get_dest_op ( intf, peerdisc_stat, &dest );
105 void *object = intf_object ( dest );
106
107 if ( op ) {
108 op ( object, peer, peers );
109 } else {
110 /* Default is to do nothing */
111 }
112
113 intf_put ( dest );
114 }
115
116 /******************************************************************************
117 *
118 * Discovery sockets
119 *
120 ******************************************************************************
121 */
122
123 /**
124 * Open all PeerDist discovery sockets
125 *
126 * @ret rc Return status code
127 */
128 static int peerdisc_socket_open ( void ) {
129 struct peerdisc_socket *socket;
130 int rc;
131
132 /* Open each socket */
133 for_each_table_entry ( socket, PEERDISC_SOCKETS ) {
134 if ( ( rc = xfer_open_socket ( &socket->xfer, SOCK_DGRAM,
135 &socket->address.sa,
136 NULL ) ) != 0 ) {
137 DBGC ( socket, "PEERDISC %s could not open socket: "
138 "%s\n", socket->name, strerror ( rc ) );
139 goto err;
140 }
141 }
142
143 return 0;
144
145 err:
146 for_each_table_entry_continue_reverse ( socket, PEERDISC_SOCKETS )
147 intf_restart ( &socket->xfer, rc );
148 return rc;
149 }
150
151 /**
152 * Attempt to transmit PeerDist discovery requests on all sockets
153 *
154 * @v uuid Message UUID string
155 * @v id Segment identifier string
156 */
157 static void peerdisc_socket_tx ( const char *uuid, const char *id ) {
158 struct peerdisc_socket *socket;
159 struct net_device *netdev;
160 struct xfer_metadata meta;
161 union {
162 struct sockaddr sa;
163 struct sockaddr_tcpip st;
164 } address;
165 char *request;
166 size_t len;
167 int rc;
168
169 /* Construct discovery request */
170 request = peerdist_discovery_request ( uuid, id );
171 if ( ! request )
172 goto err_request;
173 len = strlen ( request );
174
175 /* Initialise data transfer metadata */
176 memset ( &meta, 0, sizeof ( meta ) );
177 meta.dest = &address.sa;
178
179 /* Send message on each socket */
180 for_each_table_entry ( socket, PEERDISC_SOCKETS ) {
181
182 /* Initialise socket address */
183 memcpy ( &address.sa, &socket->address.sa,
184 sizeof ( address.sa ) );
185
186 /* Send message on each open network device */
187 for_each_netdev ( netdev ) {
188
189 /* Skip unopened network devices */
190 if ( ! netdev_is_open ( netdev ) )
191 continue;
192 address.st.st_scope_id = netdev->index;
193
194 /* Discard request (for test purposes) if applicable */
195 if ( inject_fault ( PEERDISC_DISCARD_RATE ) )
196 continue;
197
198 /* Transmit request */
199 if ( ( rc = xfer_deliver_raw_meta ( &socket->xfer,
200 request, len,
201 &meta ) ) != 0 ) {
202 DBGC ( socket, "PEERDISC %s could not transmit "
203 "via %s: %s\n", socket->name,
204 netdev->name, strerror ( rc ) );
205 /* Contine to try other net devices/sockets */
206 continue;
207 }
208 }
209 }
210
211 free ( request );
212 err_request:
213 return;
214 }
215
216 /**
217 * Handle received PeerDist discovery reply
218 *
219 * @v socket PeerDist discovery socket
220 * @v iobuf I/O buffer
221 * @v meta Data transfer metadata
222 * @ret rc Return status code
223 */
224 static int peerdisc_socket_rx ( struct peerdisc_socket *socket,
225 struct io_buffer *iobuf,
226 struct xfer_metadata *meta __unused ) {
227 struct peerdist_discovery_reply reply;
228 struct peerdisc_segment *segment;
229 char *id;
230 char *location;
231 int rc;
232
233 /* Discard reply (for test purposes) if applicable */
234 if ( ( rc = inject_fault ( PEERDISC_DISCARD_RATE ) ) != 0 )
235 goto err;
236
237 /* Parse reply */
238 if ( ( rc = peerdist_discovery_reply ( iobuf->data, iob_len ( iobuf ),
239 &reply ) ) != 0 ) {
240 DBGC ( socket, "PEERDISC %s could not parse reply: %s\n",
241 socket->name, strerror ( rc ) );
242 DBGC_HDA ( socket, 0, iobuf->data, iob_len ( iobuf ) );
243 goto err;
244 }
245
246 /* Any kind of discovery reply indicates that there are active
247 * peers on a local network, so restore the recommended
248 * discovery timeout to its default value for future requests.
249 */
250 if ( peerdisc_timeout_secs != PEERDISC_DEFAULT_TIMEOUT_SECS ) {
251 DBGC ( socket, "PEERDISC %s restoring timeout to %d seconds\n",
252 socket->name, PEERDISC_DEFAULT_TIMEOUT_SECS );
253 }
254 peerdisc_timeout_secs = PEERDISC_DEFAULT_TIMEOUT_SECS;
255
256 /* Iterate over segment IDs */
257 for ( id = reply.ids ; *id ; id += ( strlen ( id ) + 1 /* NUL */ ) ) {
258
259 /* Find corresponding segment */
260 segment = peerdisc_find ( id );
261 if ( ! segment ) {
262 DBGC ( socket, "PEERDISC %s ignoring reply for %s\n",
263 socket->name, id );
264 continue;
265 }
266
267 /* Report all discovered peer locations */
268 for ( location = reply.locations ; *location ;
269 location += ( strlen ( location ) + 1 /* NUL */ ) ) {
270
271 /* Report discovered peer location */
272 if ( ( rc = peerdisc_discovered ( segment,
273 location ) ) != 0 )
274 goto err;
275 }
276 }
277
278 err:
279 free_iob ( iobuf );
280 return rc;
281 }
282
283 /**
284 * Close all PeerDist discovery sockets
285 *
286 * @v rc Reason for close
287 */
288 static void peerdisc_socket_close ( int rc ) {
289 struct peerdisc_socket *socket;
290
291 /* Close all sockets */
292 for_each_table_entry ( socket, PEERDISC_SOCKETS )
293 intf_restart ( &socket->xfer, rc );
294 }
295
296 /** PeerDist discovery socket interface operations */
297 static struct interface_operation peerdisc_socket_operations[] = {
298 INTF_OP ( xfer_deliver, struct peerdisc_socket *, peerdisc_socket_rx ),
299 };
300
301 /** PeerDist discovery socket interface descriptor */
302 static struct interface_descriptor peerdisc_socket_desc =
303 INTF_DESC ( struct peerdisc_socket, xfer, peerdisc_socket_operations );
304
305 /** PeerDist discovery IPv4 socket */
306 struct peerdisc_socket peerdisc_socket_ipv4 __peerdisc_socket = {
307 .name = "IPv4",
308 .address = {
309 .sin = {
310 .sin_family = AF_INET,
311 .sin_port = htons ( PEERDIST_DISCOVERY_PORT ),
312 .sin_addr.s_addr = htonl ( PEERDIST_DISCOVERY_IPV4 ),
313 },
314 },
315 .xfer = INTF_INIT ( peerdisc_socket_desc ),
316 };
317
318 /** PeerDist discovery IPv6 socket */
319 struct peerdisc_socket peerdisc_socket_ipv6 __peerdisc_socket = {
320 .name = "IPv6",
321 .address = {
322 .sin6 = {
323 .sin6_family = AF_INET6,
324 .sin6_port = htons ( PEERDIST_DISCOVERY_PORT ),
325 .sin6_addr.s6_addr = PEERDIST_DISCOVERY_IPV6,
326 },
327 },
328 .xfer = INTF_INIT ( peerdisc_socket_desc ),
329 };
330
331 /******************************************************************************
332 *
333 * Discovery segments
334 *
335 ******************************************************************************
336 */
337
338 /**
339 * Free PeerDist discovery segment
340 *
341 * @v refcnt Reference count
342 */
343 static void peerdisc_free ( struct refcnt *refcnt ) {
344 struct peerdisc_segment *segment =
345 container_of ( refcnt, struct peerdisc_segment, refcnt );
346 struct peerdisc_peer *peer;
347 struct peerdisc_peer *tmp;
348
349 /* Free all discovered peers */
350 list_for_each_entry_safe ( peer, tmp, &segment->peers, list ) {
351 list_del ( &peer->list );
352 free ( peer );
353 }
354
355 /* Free segment */
356 free ( segment );
357 }
358
359 /**
360 * Find PeerDist discovery segment
361 *
362 * @v id Segment ID
363 * @ret segment PeerDist discovery segment, or NULL if not found
364 */
365 static struct peerdisc_segment * peerdisc_find ( const char *id ) {
366 struct peerdisc_segment *segment;
367
368 /* Look for a matching segment */
369 list_for_each_entry ( segment, &peerdisc_segments, list ) {
370 if ( strcmp ( id, segment->id ) == 0 )
371 return segment;
372 }
373
374 return NULL;
375 }
376
377 /**
378 * Add discovered PeerDist peer
379 *
380 * @v segment PeerDist discovery segment
381 * @v location Peer location
382 * @ret rc Return status code
383 */
384 static int peerdisc_discovered ( struct peerdisc_segment *segment,
385 const char *location ) {
386 struct peerdisc_peer *peer;
387 struct peerdisc_client *peerdisc;
388 struct peerdisc_client *tmp;
389 char *recent;
390
391 /* Ignore duplicate peers */
392 list_for_each_entry ( peer, &segment->peers, list ) {
393 if ( strcmp ( peer->location, location ) == 0 ) {
394 DBGC2 ( segment, "PEERDISC %p duplicate %s\n",
395 segment, location );
396 return 0;
397 }
398 }
399 DBGC2 ( segment, "PEERDISC %p discovered %s\n", segment, location );
400
401 /* Allocate and initialise structure */
402 peer = zalloc ( sizeof ( *peer ) + strlen ( location ) + 1 /* NUL */ );
403 if ( ! peer )
404 return -ENOMEM;
405 strcpy ( peer->location, location );
406
407 /* Add to end of list of peers */
408 list_add_tail ( &peer->list, &segment->peers );
409
410 /* Record as most recently discovered peer */
411 if ( location != peerdisc_recent ) {
412 recent = strdup ( location );
413 if ( recent ) {
414 free ( peerdisc_recent );
415 peerdisc_recent = recent;
416 }
417 }
418
419 /* Notify all clients */
420 list_for_each_entry_safe ( peerdisc, tmp, &segment->clients, list )
421 peerdisc->op->discovered ( peerdisc );
422
423 return 0;
424 }
425
426 /**
427 * Handle discovery timer expiry
428 *
429 * @v timer Discovery timer
430 * @v over Failure indicator
431 */
432 static void peerdisc_expired ( struct retry_timer *timer, int over __unused ) {
433 struct peerdisc_segment *segment =
434 container_of ( timer, struct peerdisc_segment, timer );
435
436 /* Attempt to transmit discovery requests */
437 peerdisc_socket_tx ( segment->uuid, segment->id );
438
439 /* Schedule next transmission, if applicable */
440 if ( timer->count < PEERDISC_REPEAT_COUNT )
441 start_timer_fixed ( &segment->timer, PEERDISC_REPEAT_TIMEOUT );
442 }
443
444 /**
445 * Create PeerDist discovery segment
446 *
447 * @v id Segment ID
448 * @ret segment PeerDist discovery segment, or NULL on error
449 */
450 static struct peerdisc_segment * peerdisc_create ( const char *id ) {
451 struct peerdisc_segment *segment;
452 union {
453 union uuid uuid;
454 uint32_t dword[ sizeof ( union uuid ) / sizeof ( uint32_t ) ];
455 } random_uuid;
456 size_t uuid_len;
457 size_t id_len;
458 const char *uuid;
459 char *uuid_copy;
460 char *id_copy;
461 unsigned int i;
462 int rc;
463
464 /* Generate a random message UUID. This does not require high
465 * quality randomness.
466 */
467 for ( i = 0 ; i < ( sizeof ( random_uuid.dword ) /
468 sizeof ( random_uuid.dword[0] ) ) ; i++ )
469 random_uuid.dword[i] = random();
470 uuid = uuid_ntoa ( &random_uuid.uuid );
471
472 /* Calculate string lengths */
473 id_len = ( strlen ( id ) + 1 /* NUL */ );
474 uuid_len = ( strlen ( uuid ) + 1 /* NUL */ );
475
476 /* Allocate and initialise structure */
477 segment = zalloc ( sizeof ( *segment ) + id_len + uuid_len );
478 if ( ! segment )
479 goto err_alloc;
480 id_copy = ( ( ( void * ) segment ) + sizeof ( *segment ) );
481 memcpy ( id_copy, id, id_len );
482 uuid_copy = ( ( ( void * ) id_copy ) + id_len );
483 memcpy ( uuid_copy, uuid, uuid_len );
484 ref_init ( &segment->refcnt, peerdisc_free );
485 segment->id = id_copy;
486 segment->uuid = uuid_copy;
487 INIT_LIST_HEAD ( &segment->peers );
488 INIT_LIST_HEAD ( &segment->clients );
489 timer_init ( &segment->timer, peerdisc_expired, &segment->refcnt );
490
491 /* Add hosted cache server or initiate discovery */
492 if ( peerhost ) {
493
494 /* Add hosted cache server to list of peers */
495 if ( ( rc = peerdisc_discovered ( segment, peerhost ) ) != 0 )
496 goto err_peerhost;
497
498 } else {
499
500 /* Add most recently discovered peer to list of peers
501 *
502 * This is a performance optimisation: we assume that
503 * the most recently discovered peer for any block has
504 * a high probability of also having a copy of the
505 * next block that we attempt to discover.
506 */
507 if ( peerdisc_recent )
508 peerdisc_discovered ( segment, peerdisc_recent );
509
510 /* Start discovery timer */
511 start_timer_nodelay ( &segment->timer );
512 DBGC2 ( segment, "PEERDISC %p discovering %s\n",
513 segment, segment->id );
514 }
515
516 /* Add to list of segments, transfer reference to list, and return */
517 list_add_tail ( &segment->list, &peerdisc_segments );
518 return segment;
519
520 err_peerhost:
521 ref_put ( &segment->refcnt );
522 err_alloc:
523 return NULL;
524 }
525
526 /**
527 * Destroy PeerDist discovery segment
528 *
529 * @v segment PeerDist discovery segment
530 */
531 static void peerdisc_destroy ( struct peerdisc_segment *segment ) {
532
533 /* Sanity check */
534 assert ( list_empty ( &segment->clients ) );
535
536 /* Stop timer */
537 stop_timer ( &segment->timer );
538
539 /* Remove from list of segments and drop list's reference */
540 list_del ( &segment->list );
541 ref_put ( &segment->refcnt );
542 }
543
544 /******************************************************************************
545 *
546 * Discovery clients
547 *
548 ******************************************************************************
549 */
550
551 /**
552 * Open PeerDist discovery client
553 *
554 * @v peerdisc PeerDist discovery client
555 * @v id Segment ID
556 * @v len Length of segment ID
557 * @ret rc Return status code
558 */
559 int peerdisc_open ( struct peerdisc_client *peerdisc, const void *id,
560 size_t len ) {
561 struct peerdisc_segment *segment;
562 char id_string[ base16_encoded_len ( len ) + 1 /* NUL */ ];
563 char *id_chr;
564 int rc;
565
566 /* Construct ID string */
567 base16_encode ( id, len, id_string, sizeof ( id_string ) );
568 for ( id_chr = id_string ; *id_chr ; id_chr++ )
569 *id_chr = toupper ( *id_chr );
570
571 /* Sanity check */
572 assert ( peerdisc->segment == NULL );
573
574 /* Open socket if this is the first segment */
575 if ( list_empty ( &peerdisc_segments ) &&
576 ( ( rc = peerdisc_socket_open() ) != 0 ) )
577 return rc;
578
579 /* Find or create segment */
580 if ( ! ( ( segment = peerdisc_find ( id_string ) ) ||
581 ( segment = peerdisc_create ( id_string ) ) ) )
582 return -ENOMEM;
583
584 /* Add to list of clients */
585 ref_get ( &segment->refcnt );
586 peerdisc->segment = segment;
587 list_add_tail ( &peerdisc->list, &segment->clients );
588
589 return 0;
590 }
591
592 /**
593 * Close PeerDist discovery client
594 *
595 * @v peerdisc PeerDist discovery client
596 */
597 void peerdisc_close ( struct peerdisc_client *peerdisc ) {
598 struct peerdisc_segment *segment = peerdisc->segment;
599
600 /* Ignore if discovery is already closed */
601 if ( ! segment )
602 return;
603
604 /* If no peers were discovered, reduce the recommended
605 * discovery timeout to minimise delays on future requests.
606 */
607 if ( list_empty ( &segment->peers ) && peerdisc_timeout_secs ) {
608 peerdisc_timeout_secs--;
609 DBGC ( segment, "PEERDISC %p reducing timeout to %d "
610 "seconds\n", peerdisc, peerdisc_timeout_secs );
611 }
612
613 /* Remove from list of clients */
614 peerdisc->segment = NULL;
615 list_del ( &peerdisc->list );
616 ref_put ( &segment->refcnt );
617
618 /* If this was the last clients, destroy the segment */
619 if ( list_empty ( &segment->clients ) )
620 peerdisc_destroy ( segment );
621
622 /* If there are no more segments, close the socket */
623 if ( list_empty ( &peerdisc_segments ) )
624 peerdisc_socket_close ( 0 );
625 }
626
627 /******************************************************************************
628 *
629 * Settings
630 *
631 ******************************************************************************
632 */
633
634 /** PeerDist hosted cache server setting */
635 const struct setting peerhost_setting __setting ( SETTING_MISC, peerhost ) = {
636 .name = "peerhost",
637 .description = "PeerDist hosted cache",
638 .type = &setting_type_string,
639 };
640
641 /**
642 * Apply PeerDist discovery settings
643 *
644 * @ret rc Return status code
645 */
646 static int apply_peerdisc_settings ( void ) {
647
648 /* Free any existing hosted cache server */
649 free ( peerhost );
650 peerhost = NULL;
651
652 /* Fetch hosted cache server */
653 fetch_string_setting_copy ( NULL, &peerhost_setting, &peerhost );
654 if ( peerhost ) {
655 DBGC ( &peerhost, "PEERDISC using hosted cache %s\n",
656 peerhost );
657 }
658
659 return 0;
660 }
661
662 /** PeerDist discovery settings applicator */
663 struct settings_applicator peerdisc_applicator __settings_applicator = {
664 .apply = apply_peerdisc_settings,
665 };